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

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

Prune extraneous non-conforming methods

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