source: java/net/deterlab/abac/GENICredential.java @ 2405adf

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

Check expiration

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