source: java/net/deterlab/abac/GENICredential.java @ dd5d4d9

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

Get ready to derive GENIPrivCredential

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