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

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

Document cleanup. Change a few visibilities.

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