source: java/net/deterlab/abac/GENICredential.java @ 7b33c9b

abac0-leakabac0-meimei-idmei-rt0-nmei_rt0tvf-new-xml
Last change on this file since 7b33c9b was 7b33c9b, checked in by Ted Faber <faber@…>, 11 years ago

More sane class struture

  • Property mode set to 100644
File size: 21.0 KB
Line 
1package net.deterlab.abac;
2
3import java.io.*;
4import java.math.*;
5import java.text.*;
6
7import java.util.*;
8import java.security.*;
9import java.security.cert.*;
10
11import javax.security.auth.x500.*;
12
13import javax.xml.parsers.*;
14import javax.xml.transform.*;
15import javax.xml.transform.dom.*;
16import javax.xml.transform.stream.*;
17
18import javax.xml.crypto.*;
19import javax.xml.crypto.dsig.*;
20import javax.xml.crypto.dsig.dom.*;
21import javax.xml.crypto.dsig.keyinfo.*;
22import javax.xml.crypto.dsig.spec.*;
23
24import org.xml.sax.*;
25import org.w3c.dom.*;
26
27import org.bouncycastle.asn1.*;
28import org.bouncycastle.asn1.x509.*;
29import org.bouncycastle.x509.*;
30import org.bouncycastle.x509.util.*;
31import org.bouncycastle.openssl.*;
32
33/**
34 * An ABAC credential, with or without an underlying certificate that
35 * represents it.  These are edges in proof garphs and can be constructed from
36 * their constituent Roles.
37 * @author <a href="http://abac.deterlab.net">ISI ABAC team</a>
38 * @version 1.3
39 */
40public class GENICredential extends Credential implements Comparable {
41    /** The signed XML representing this credential */
42    protected Document doc;
43
44    /**
45     * X509KeySelector implementation from
46     * http://www.outsourcingi.com/art-271-Programming-With-the-Java-XML-Digital-Signature-API.html
47     * .
48     *
49     * Straight ahead extraction of a key from an X509 cert, but it is their
50     * code and hard to improve on.
51     */
52    protected class X509KeySelector extends KeySelector {
53        public KeySelectorResult select(KeyInfo keyInfo,
54                                        KeySelector.Purpose purpose,
55                                        AlgorithmMethod method,
56                                        XMLCryptoContext context)
57            throws KeySelectorException {
58            Iterator ki = keyInfo.getContent().iterator();
59            while (ki.hasNext()) {
60                XMLStructure info = (XMLStructure) ki.next();
61                if (!(info instanceof X509Data))
62                    continue;
63                X509Data x509Data = (X509Data) info;
64                Iterator xi = x509Data.getContent().iterator();
65                while (xi.hasNext()) {
66                    Object o = xi.next();
67                    if (!(o instanceof X509Certificate))
68                        continue;
69                    final PublicKey key = ((X509Certificate)o).getPublicKey();
70                    // Make sure the algorithm is compatible
71                    // with the method.
72                    if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
73                        return new KeySelectorResult() {
74                            public Key getKey() { return key; }
75                        };
76                    }
77                }
78            }
79            throw new KeySelectorException("No key found!");
80        }
81
82        boolean algEquals(String algURI, String algName) {
83            if ((algName.equalsIgnoreCase("DSA") &&
84                algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
85                (algName.equalsIgnoreCase("RSA") &&
86
87                algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
88                return true;
89            } else {
90                return false;
91            }
92        }
93    }
94
95    /**
96     * Create an empty Credential.
97     */
98    public GENICredential() {
99        m_head = m_tail = null;
100        doc = null;
101        id = null;
102    }
103    /**
104     * Create a credential from a head and tail role.  This credential has no
105     * underlying certificate, and cannot be exported or used in real proofs.
106     * make_cert can create a certificate for a credential initialized this
107     * way.
108     * @param head the Role at the head of the credential
109     * @param tail the Role at the tail of the credential
110     */
111    public GENICredential(Role head, Role tail) {
112        m_head = head;
113        m_tail = tail;
114        doc = null; 
115        id = null;
116    }
117
118    /**
119     * Do the credential extraction from an input stream.  This parses the
120     * certificate from the input stream and saves it. The contents must be
121     * validated and parsed later.
122     * @param stream the InputStream to read the certificate from.
123     * @throws IOException if the stream is unparsable
124     */
125    protected void read_certificate(InputStream stream) 
126            throws IOException {
127        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
128        DocumentBuilder db = null;
129
130        if ( dbf == null ) 
131            throw new IOException("Cannot get DocumentBuilderFactory!?");
132        try {
133            /* Note this setting is required to find the properly
134             * namespace-scoped signature block in the credential.
135             */
136            dbf.setNamespaceAware(true);
137            if ( (db = dbf.newDocumentBuilder()) == null ) 
138                throw new IOException("Cannot get DocumentBuilder!?");
139            doc = db.parse(stream);
140            doc.normalizeDocument();
141        }
142        catch (IllegalArgumentException ie) {
143            throw new IOException("null stream");
144        }
145        catch (SAXException se) {
146            throw new IOException(se.getMessage());
147        }
148        catch (Exception e) {
149            throw new IOException(e.getMessage());
150        }
151
152    }
153
154    /**
155     * Walk the document and set all instances of an xml:id attribute to be ID
156     * attributes in terms of the java libraries.  This is needed for the
157     * signature code to find signed subsections.  The "xml:id" is treated as
158     * both a namespace free ID with a colon and as an "id" identifier in the
159     * "xml" namespace so we don't miss it.
160     * @param n the root of the document to mark
161     */
162    static protected void setIDAttrs(Node n) {
163        if ( n.getNodeType() == Node.ELEMENT_NODE) {
164            Element e = (Element) n;
165            String id = e.getAttribute("xml:id");
166
167            if ( id != null && id.length() > 0 )
168                e.setIdAttribute("xml:id", true);
169
170            id = e.getAttributeNS("xml", "id");
171            if ( id != null && id.length() > 0 )
172                e.setIdAttributeNS("xml", "id", true);
173        }
174
175        for (Node nn = n.getFirstChild(); nn != null; nn = nn.getNextSibling())
176            setIDAttrs(nn);
177    }
178
179    /**
180     * Return the child of Node n  that has the given name, if there is one.
181     * @param n a Node to search
182     * @param name the name to search for
183     * @return a Node with the name, may be null
184     */
185    static protected Node getChildByName(Node n, String name) {
186        if (name == null ) return null;
187
188        for (Node nn = n.getFirstChild(); nn != null; nn = nn.getNextSibling())
189            if ( nn.getNodeType() == Node.ELEMENT_NODE && 
190                    name.equals(nn.getNodeName())) return nn;
191        return null;
192    }
193
194    /**
195     * Find the X509Certificate in the Signature element and convert it to an
196     * ABAC identity.  This assumes a KeyInfo in that section holding an
197     * X509Data section containing the certificate in an X509Certificate
198     * section.  Any of that not being found will cause a failure.  If the
199     * Identity cannot be created a Gignature exception is thrown.
200     * @param n a Node pointing to a Signature section
201     * @return an Identity constructed frm the X509 Certificate
202     * @throws SignatureException if the Identity cannot be created from the
203     * certificate.
204     */
205    static protected Identity getIdentity(Node n) throws SignatureException {
206        Identity rv = null;
207        Node nn = getChildByName(n, "KeyInfo");
208        String certStr = null;
209
210        if ( nn == null ) return null;
211        if ( ( nn = getChildByName(nn, "X509Data")) == null ) return null;
212        if ( ( nn = getChildByName(nn, "X509Certificate")) == null ) return null;
213        if ( ( certStr = nn.getTextContent()) == null ) return null;
214        try {
215            certStr = "-----BEGIN CERTIFICATE-----\n" + 
216                certStr + 
217                "\n-----END CERTIFICATE-----";
218            return new Identity(new StringReader(certStr));
219        } 
220        catch (Exception e) {
221            throw new SignatureException(e.getMessage());
222        }
223    }
224
225    /**
226     * Initialize a credential from parsed certificate.  Validiate it against
227     * the given identities and parse out the roles.  Note that catching
228     * java.security.GeneralSecurityException catches most of the exceptions
229     * this throws.
230     * @param ids a Collection of Identities to use in validating the cert
231     * @throws CertificateException if the certificate is badly formatted
232     * @throws InvalidKeyException if none of the Identities can validate the
233     *                              certificate
234     * @throws NoSuchAlgorithmException if the credential uses an unknown
235     *                              signature algorithm
236     * @throws NoSuchProviderException if the provider of the signature
237     *                              algorithm is unavailable
238     * @throws SignatureException if the signature check fails
239     */
240    protected void init(Collection<Identity> ids) 
241            throws CertificateException, InvalidKeyException, 
242                NoSuchAlgorithmException, NoSuchProviderException,
243                SignatureException {
244
245        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
246        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, 
247                "Signature");
248        DOMValidateContext valContext = null;
249        XMLSignature signature = null;
250
251        try {
252
253            if (nl.getLength() == 0) 
254                throw new SignatureException("Cannot find Signature element");
255
256            setIDAttrs(doc);
257            valContext = new DOMValidateContext(new X509KeySelector(),
258                    nl.item(0));
259            if ( valContext == null )
260                throw new SignatureException("No context");
261
262            signature = fac.unmarshalXMLSignature(valContext);
263            if (signature == null) 
264                throw new SignatureException("No sig");
265
266            if (!signature.validate(valContext))
267                throw new SignatureException("bad signature");
268
269            load_roles();
270
271            id = getIdentity(nl.item(0));
272
273            if ( !ids.contains(id) ) ids.add(id);
274
275            if (!id.getKeyID().equals(m_head.principal()))
276                throw new InvalidKeyException("Unknown identity");
277        }
278        catch (Exception e) {
279            throw new SignatureException(e.getMessage());
280        }
281    }
282
283    /**
284     * Parse a credential from an InputStream and initialize the role from it.
285     * Combine read_credential(stream) and init(ids).  Note that catching
286     * java.security.GeneralSecurityException catches most of the exceptions
287     * this throws.
288     * @param stream the InputStream to read the certificate from.
289     * @param ids a Collection of Identities to use in validating the cert
290     * @throws CertificateException if the certificate is badly formatted
291     * @throws InvalidKeyException if none of the Identities can validate the
292     *                              certificate
293     * @throws NoSuchAlgorithmException if the credential uses an unknown
294     *                              signature algorithm
295     * @throws NoSuchProviderException if the provider of the signature
296     *                              algorithm is unavailable
297     * @throws SignatureException if the signature check fails
298     * @throws IOException if the certificate is unparsable.
299     */
300    protected void init(InputStream stream, Collection<Identity> ids) 
301            throws CertificateException, InvalidKeyException, 
302                NoSuchAlgorithmException, NoSuchProviderException,
303                SignatureException, IOException {
304        read_certificate(stream);
305        if (doc == null) throw new IOException("Unknown Format");
306        init(ids);
307    }
308
309    /**
310     * Create a credential from an attribute cert in a file. Throws an
311     * exception if the cert file can't be opened or if there's a format
312     * problem with the cert.  Note that catching
313     * java.security.GeneralSecurityException catches most of the exceptions
314     * this throws.
315     * @param filename a String containing the filename to read
316     * @param ids a Collection of Identities to use in validating the cert
317     * @throws StreamParsingException if the stream is unparsable
318     * @throws CertificateException if the certificate is badly formatted
319     * @throws InvalidKeyException if none of the Identities can validate the
320     *                              certificate
321     * @throws NoSuchAlgorithmException if the credential uses an unknown
322     *                              signature algorithm
323     * @throws NoSuchProviderException if the provider of the signature
324     *                              algorithm is unavailable
325     * @throws SignatureException if the signature check fails
326     * @throws IOException if the certificate is unparsable.
327     */
328    public GENICredential(String filename, Collection<Identity> ids) 
329        throws Exception { init(new FileInputStream(filename), ids); }
330
331    /**
332     * Create a credential from an attribute cert in a file. Throws an
333     * exception if the cert file can't be opened or if there's a format
334     * problem with the cert.  Note that catching
335     * java.security.GeneralSecurityException catches most of the exceptions
336     * this throws.
337     * @param file the File to read
338     * @param ids a Collection of Identities to use in validating the cert
339     * @throws StreamParsingException if the stream is unparsable
340     * @throws CertificateException if the certificate is badly formatted
341     * @throws InvalidKeyException if none of the Identities can validate the
342     *                              certificate
343     * @throws NoSuchAlgorithmException if the credential uses an unknown
344     *                              signature algorithm
345     * @throws NoSuchProviderException if the provider of the signature
346     *                              algorithm is unavailable
347     * @throws SignatureException if the signature check fails
348     * @throws IOException if the certificate is unparsable.
349     */
350    public GENICredential(File file, Collection<Identity> ids) 
351            throws CertificateException, InvalidKeyException, 
352                NoSuchAlgorithmException, NoSuchProviderException,
353                SignatureException, StreamParsingException, IOException {
354        init(new FileInputStream(file), ids);
355    }
356
357    /**
358     * Create a credential from an InputStream.  Throws an exception if the
359     * stream can't be parsed or if there's a format problem with the cert.
360     * Note that catching java.security.GeneralSecurityException catches most
361     * of the exceptions this throws.
362     * @param s the InputStream to read
363     * @param ids a Collection of Identities to use in validating the cert
364     * @throws StreamParsingException if the stream is unparsable
365     * @throws CertificateException if the certificate is badly formatted
366     * @throws InvalidKeyException if none of the Identities can validate the
367     *                              certificate
368     * @throws NoSuchAlgorithmException if the credential uses an unknown
369     *                              signature algorithm
370     * @throws NoSuchProviderException if the provider of the signature
371     *                              algorithm is unavailable
372     * @throws SignatureException if the signature check fails
373     * @throws IOException if the certificate is unparsable.
374     */
375    public GENICredential(InputStream s, Collection<Identity> ids) 
376            throws CertificateException, InvalidKeyException, 
377                NoSuchAlgorithmException, NoSuchProviderException,
378                SignatureException, StreamParsingException, IOException {
379        init(s, ids);
380    }
381
382    /**
383     * Encode the abac credential's XML.  This is straight line code that
384     * directly builds the credential.
385     * @return a Node, the place to attach signatures
386     * @throws IOException if any of the XML construction fails
387     */
388    protected Node make_rt0_content() throws IOException {
389        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
390        DocumentBuilder db = null;
391        SimpleDateFormat df = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss'Z'");
392        StringBuffer expBuf = new StringBuffer();
393
394        /* This is a weirdness to cope with the GENI format.  They have a naked
395         * xml:id specifier without any namespace declarations in the
396         * credential.  Notice that this is the opposite of the setting used in
397         * init to read a credential. */
398        dbf.setNamespaceAware(false);
399
400        if ( dbf == null ) 
401            throw new IOException("Cannot get DocumentBuilderFactory!?");
402
403        try {
404            db = dbf.newDocumentBuilder();
405        }
406        catch (ParserConfigurationException pe) {
407            throw new IOException("Cannot get DocumentBuilder!?" + 
408                    pe.getMessage());
409        }
410
411        doc = db.newDocument();
412        if ( doc == null ) 
413            throw new IOException("No Document");
414
415        Element root = doc.createElement("signed-credential"); 
416        Element cred = doc.createElement("credential");
417        Element sig = doc.createElement("signatures");
418        Element e = doc.createElement("type");
419        Node text = doc.createTextNode("abac");
420
421        doc.appendChild(root);
422
423        cred.setAttribute("xml:id", "ref0");
424        /* So that the signing code can find the section to sign */
425        cred.setIdAttribute("xml:id", true);
426
427        root.appendChild(cred);
428        e.appendChild(text);
429        cred.appendChild(e);
430
431        /* XXX: pass in valid time */
432        df.format(new Date(System.currentTimeMillis() 
433                    + (3600L * 1000L * 24L * 365L)), expBuf, 
434                new FieldPosition(0));
435        e = doc.createElement("expires");
436        text = doc.createTextNode(expBuf.toString());
437        e.appendChild(text);
438        cred.appendChild(e);
439
440        e = doc.createElement("version");
441        text = doc.createTextNode("1.0");
442        e.appendChild(text);
443        cred.appendChild(e);
444
445        e = doc.createElement("rt0");
446        text = doc.createTextNode(m_head + "<-" + m_tail);
447        e.appendChild(text);
448        cred.appendChild(e);
449
450        root.appendChild(sig);
451        return sig;
452    }
453
454    /**
455     * Create a certificate from this credential issued by the given identity.
456     * Note that catching java.security.GeneralSecurityException catches most
457     * of the exceptions this throws.
458     * @param i the Identity that will issue the certificate
459     * @throws IOException reading or writing problems
460     * @throws CertificateEncodingException Problem creating certificate
461     * @throws InvalidKeyException if none of the Identities can sign the
462     *                              certificate
463     * @throws NoSuchAlgorithmException if the credential uses an unknown
464     *                              signature algorithm
465     * @throws NoSuchProviderException if the provider of the signature
466     *                              algorithm is unavailable
467     * @throws SignatureException if the signature creation fails
468     */
469    public void make_cert(Identity i) 
470            throws IOException, CertificateEncodingException,
471               NoSuchProviderException, NoSuchAlgorithmException,
472               SignatureException, InvalidKeyException {
473        X509Certificate cert = i.getCertificate();
474        KeyPair kp = i.getKeyPair();
475        PublicKey pubKey = null;
476        PrivateKey privKey = null;
477        if ( cert == null ) 
478            throw new IOException("No credential in identity?");
479        if ( kp == null ) 
480            throw new IOException("No keypair in identity?");
481
482        pubKey = kp.getPublic();
483        if ((privKey = kp.getPrivate()) == null ) 
484            throw new IOException("No private ket in identity");
485
486        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
487        Reference ref = null;
488        SignedInfo si = null;
489        KeyInfoFactory kif = fac.getKeyInfoFactory();
490        List x509Content = new ArrayList();
491        List keyInfo = new ArrayList();
492        x509Content.add(cert.getSubjectX500Principal().getName());
493        x509Content.add(cert);
494        X509Data xd = kif.newX509Data(x509Content);
495        KeyValue kv = null;
496
497        try {
498            kv = kif.newKeyValue(pubKey);
499        } 
500        catch (KeyException ke) {
501            throw new InvalidKeyException("Unsupported key format " + 
502                    ke.getMessage());
503        }
504
505        Collections.addAll(keyInfo, kv, xd);
506
507        KeyInfo ki = kif.newKeyInfo(keyInfo);
508        Node sig = make_rt0_content();
509
510        try {
511            ref = fac.newReference("#ref0", 
512                    fac.newDigestMethod(DigestMethod.SHA1, null),
513                    Collections.singletonList(
514                        fac.newTransform(Transform.ENVELOPED, 
515                            (TransformParameterSpec) null)),
516                    null, null);
517
518            si = fac.newSignedInfo(
519                    fac.newCanonicalizationMethod(
520                        CanonicalizationMethod.INCLUSIVE,
521                        (C14NMethodParameterSpec) null),
522                    fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
523                    Collections.singletonList(ref));
524
525            DOMSignContext dsc = new DOMSignContext(privKey, sig);
526            XMLSignature signature = fac.newXMLSignature(si, ki);
527            signature.sign(dsc);
528        }
529        catch (Exception me) {
530            throw new SignatureException(me.getMessage());
531        }
532
533    }
534
535    /**
536     * Load the roles off the attribute cert. Throws a RuntimeException if
537     * there's something wrong with the cert.
538     */
539    private void load_roles() {
540        if ( doc == null ) 
541            throw new RuntimeException("No credential");
542
543        String roles = null;
544        NodeList nodes = doc.getElementsByTagName("rt0");
545
546        if (nodes == null || nodes.getLength() != 1) 
547            throw new RuntimeException("More than one rt0 element?");
548
549        Node node = nodes.item(0);
550
551        if ( node == null ) 
552            throw new RuntimeException("bad rt0 element?");
553
554        if ( (roles = node.getTextContent()) == null ) 
555            throw new RuntimeException("bad rt0 element (no text)?");
556
557        String[] parts = roles.split("\\s*<--?\\s*");
558        if (parts.length != 2)
559            throw new RuntimeException("Invalid attribute: " + roles);
560
561        m_head = new Role(parts[0]);
562        m_tail = new Role(parts[1]);
563    }
564
565    /**
566     * Gets the cert associated with this credential (if any).
567     * @return the attribute cert associated with this credential (if any).
568     */
569    public Document GENIcert() { return doc; }
570
571    /**
572     * Output the signed GENI ABAC certificate associated with this
573     * Credential to the OutputStream.
574     * @param s the OutputStream on which to write
575     * @throws IOException if there is an error writing.
576     */
577    public void write(OutputStream s) throws IOException {
578        if ( doc == null ) 
579            return;
580        try {
581            TransformerFactory tf = TransformerFactory.newInstance();
582            Transformer t = tf.newTransformer();
583            DOMSource source = new DOMSource(doc);
584            StreamResult result = new StreamResult(s);
585
586            t.transform(source, result);
587            s.flush();
588        } 
589        catch (Exception e) {
590            throw new IOException(e.getMessage());
591        }
592    }
593    /**
594     * Output the signed GENI ABAC certificate associated with this
595     * Credential to the OutputStream.
596     * @param fn a String, the file name on which to write
597     * @throws IOException if there is an error writing.
598     */
599    public void write(String fn) throws IOException, FileNotFoundException {
600        write(new FileOutputStream(fn));
601    }
602
603    /**
604     * Return true if this Credential has a certificate associated.  A jabac
605     * extension.
606     * @return true if this Credential has a certificate associated.
607     */
608    public boolean hasCertificate() { return doc != null; }
609
610}
Note: See TracBrowser for help on using the repository browser.