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

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

Get ready to derive GENIPrivCredential

  • Property mode set to 100644
File size: 19.8 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 formatted as an abac-type GENI credential.
35 * @author <a href="http://abac.deterlab.net">ISI ABAC team</a>
36 * @version 1.4
37 */
38public class GENICredential extends Credential implements Comparable {
39    /** The signed XML representing this credential */
40    protected Document doc;
41
42    /**
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.
49     */
50    static protected class X509KeySelector extends KeySelector {
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
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.
121     * @return the parsed XML document
122     * @throws IOException if the stream is unparsable
123     */
124    static protected Document read_certificate(InputStream stream) 
125            throws IOException {
126        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
127        DocumentBuilder db = null;
128        Document ldoc = 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            ldoc = db.parse(stream);
140            ldoc.normalizeDocument();
141            return ldoc;
142        }
143        catch (IllegalArgumentException ie) {
144            throw new IOException("null stream", ie);
145        }
146        catch (SAXException se) {
147            throw new IOException(se.getMessage(), se);
148        }
149        catch (Exception e) {
150            throw new IOException(e.getMessage(), e);
151        }
152
153    }
154
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
203     * @throws MissingIssuerException if the Identity cannot be created from the
204     * certificate.
205     */
206    static protected Identity getIdentity(Node n) 
207        throws MissingIssuerException {
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) {
223            throw new MissingIssuerException(e.getMessage(), e);
224        }
225    }
226
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
233     * @throws CertInvalidException if the stream is unparsable
234     * @throws MissingIssuerException if none of the Identities can validate the
235     *                              certificate
236     * @throws BadSignatureException if the signature check fails
237     */
238    protected void init(Collection<Identity> ids) 
239            throws ABACException {
240
241        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
242        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, 
243                "Signature");
244        DOMValidateContext valContext = null;
245        XMLSignature signature = null;
246
247        try {
248
249            if (nl.getLength() == 0) 
250                throw new CertInvalidException("Cannot find Signature element");
251
252            setIDAttrs(doc);
253            valContext = new DOMValidateContext(new X509KeySelector(),
254                    nl.item(0));
255            if ( valContext == null )
256                throw new ABACException("No validation context!?");
257
258            signature = fac.unmarshalXMLSignature(valContext);
259            if (signature == null) 
260                throw new BadSignatureException("Cannot unmarshal signature");
261
262            if (!signature.validate(valContext))
263                throw new BadSignatureException("bad signature");
264
265            load_roles();
266
267            id = getIdentity(nl.item(0));
268
269            if ( !ids.contains(id) ) ids.add(id);
270
271            if (!id.getKeyID().equals(m_head.principal()))
272                throw new MissingIssuerException("Issuer ID and left hand " +
273                        "side principal disagree");
274        }
275        catch (ABACException ae) {
276            throw ae;
277        }
278        catch (Exception e) {
279            throw new BadSignatureException(e.getMessage(), e);
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 CertInvalidException if the stream is unparsable
291     * @throws MissingIssuerException if none of the Identities can validate the
292     *                              certificate
293     * @throws BadSignatureException if the signature check fails
294     */
295    protected void init(InputStream stream, Collection<Identity> ids) 
296            throws ABACException {
297         try {
298            doc = read_certificate(stream);
299         }
300         catch (IOException e) {
301             throw new CertInvalidException("Cannot parse cert", e);
302         }
303        if (doc == null) throw new CertInvalidException("Unknown Format");
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
315     * @throws CertInvalidException if the stream is unparsable
316     * @throws MissingIssuerException if none of the Identities can validate the
317     *                              certificate
318     * @throws BadSignatureException if the signature check fails
319     */
320    GENICredential(String filename, Collection<Identity> ids) 
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    }
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
338     * @throws CertInvalidException if the stream is unparsable
339     * @throws MissingIssuerException if none of the Identities can validate the
340     *                              certificate
341     * @throws BadSignatureException if the signature check fails
342     */
343    GENICredential(File file, Collection<Identity> ids) 
344            throws ABACException {
345        try {
346            init(new FileInputStream(file), ids);
347        }
348        catch (FileNotFoundException e) {
349            throw new CertInvalidException("Bad filename", e);
350        }
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
360     * @throws CertInvalidException if the stream is unparsable
361     * @throws MissingIssuerException if none of the Identities can validate the
362     *                              certificate
363     * @throws BadSignatureException if the signature check fails
364     */
365    GENICredential(InputStream s, Collection<Identity> ids) 
366            throws ABACException { init(s, ids); }
367
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
372     * @throws ABACException if any of the XML construction fails
373     */
374    protected Node make_rt0_content() throws ABACException {
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 ) 
387            throw new ABACException("Cannot get DocumentBuilderFactory!?");
388
389        try {
390            db = dbf.newDocumentBuilder();
391        }
392        catch (ParserConfigurationException pe) {
393            throw new ABACException("Cannot get DocumentBuilder!?" + 
394                    pe.getMessage(), pe);
395        }
396
397        doc = db.newDocument();
398        if ( doc == null ) 
399            throw new ABACException("No Document");
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
440    /**
441     * Create a certificate from this credential issued by the given identity.
442     * This is the signed XML ABAC credential.
443     * @param i the Identity that will issue the certificate
444     * @throws ABACException if xml creation fails
445     * @throws MissingIssuerException if the issuer is bad
446     * @throws BadSignatureException if the signature creation fails
447     */
448    public void make_cert(Identity i) 
449            throws ABACException {
450        X509Certificate cert = i.getCertificate();
451        KeyPair kp = i.getKeyPair();
452        PublicKey pubKey = null;
453        PrivateKey privKey = null;
454        if ( cert == null ) 
455            throw new MissingIssuerException("No credential in identity?");
456        if ( kp == null ) 
457            throw new MissingIssuerException("No keypair in identity?");
458
459        pubKey = kp.getPublic();
460        if ((privKey = kp.getPrivate()) == null ) 
461            throw new MissingIssuerException("No private ket in identity");
462
463        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
464        Reference ref = null;
465        SignedInfo si = null;
466        KeyInfoFactory kif = fac.getKeyInfoFactory();
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>();
474        x509Content.add(cert.getSubjectX500Principal().getName());
475        x509Content.add(cert);
476        X509Data xd = kif.newX509Data(x509Content);
477        KeyValue kv = null;
478
479        try {
480            kv = kif.newKeyValue(pubKey);
481        } 
482        catch (KeyException ke) {
483            throw new ABACException("Unsupported key format " + 
484                    ke.getMessage(), ke);
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", 
494                    fac.newDigestMethod(DigestMethod.SHA1, null),
495                    Collections.singletonList(
496                        fac.newTransform(Transform.ENVELOPED, 
497                            (TransformParameterSpec) null)),
498                    null, null);
499
500            si = fac.newSignedInfo(
501                    fac.newCanonicalizationMethod(
502                        CanonicalizationMethod.INCLUSIVE,
503                        (C14NMethodParameterSpec) null),
504                    fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
505                    Collections.singletonList(ref));
506
507            DOMSignContext dsc = new DOMSignContext(privKey, sig);
508            XMLSignature signature = fac.newXMLSignature(si, ki);
509            signature.sign(dsc);
510        }
511        catch (Exception me) {
512            throw new BadSignatureException(me.getMessage(), me);
513        }
514
515    }
516
517    /**
518     * Load the roles off the attribute cert.
519     * @throws CertInvalidException if the certificate is badly formatted
520     */
521    private void load_roles() throws CertInvalidException {
522        if ( doc == null ) 
523            throw new CertInvalidException("No credential");
524
525        String roles = null;
526        NodeList nodes = doc.getElementsByTagName("rt0");
527
528        if (nodes == null || nodes.getLength() != 1) 
529            throw new CertInvalidException("More than one rt0 element?");
530
531        Node node = nodes.item(0);
532
533        if ( node == null ) 
534            throw new CertInvalidException("bad rt0 element?");
535
536        if ( (roles = node.getTextContent()) == null ) 
537            throw new CertInvalidException("bad rt0 element (no text)?");
538
539        String[] parts = roles.split("\\s*<--?\\s*");
540        if (parts.length != 2)
541            throw new CertInvalidException("Invalid attribute: " + roles);
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) {
566            throw new IOException(e.getMessage(), e);
567        }
568    }
569    /**
570     * Output the signed GENI ABAC certificate associated with this
571     * Credential to the OutputStream.
572     * @param fn a String, the file name on which to write
573     * @throws IOException if there is an error writing.
574     * @throws FileNotFoundExeption if the file path is bogus
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
587    /**
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.
592     */
593    static public CredentialFactorySpecialization
594            getCredentialFactorySpecialization() {
595        return new CredentialFactorySpecialization() {
596            public Credential[] parseCredential(InputStream is, 
597                    Collection<Identity> ids) throws ABACException {
598                return new Credential[] { new GENICredential(is, ids) }; 
599            }
600            public Credential generateCredential(Role head, Role tail) {
601                return new GENICredential(head, tail);
602            }
603        };
604    }
605}
Note: See TracBrowser for help on using the repository browser.