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

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

More reading and writing working. About to revamp CredentialFactory?
again

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