source: java/net/deterlab/abac/GENICredential.java @ 797bebe

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

Add Writer & checkpoint

  • Property mode set to 100644
File size: 22.3 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 role at the head */
42    protected Role m_head
43    /** The role at the tail */;
44    protected Role m_tail;
45    /** The signed XML representing this credential */
46    protected Document doc;
47    /** The identity that issued the certificate */
48    protected Identity id;
49
50
51    /**
52     * Keyselector implementation from the web
53     */
54    protected class X509KeySelector extends KeySelector {
55        public KeySelectorResult select(KeyInfo keyInfo,
56                                        KeySelector.Purpose purpose,
57                                        AlgorithmMethod method,
58                                        XMLCryptoContext context)
59            throws KeySelectorException {
60            Iterator ki = keyInfo.getContent().iterator();
61            while (ki.hasNext()) {
62                XMLStructure info = (XMLStructure) ki.next();
63                if (!(info instanceof X509Data))
64                    continue;
65                X509Data x509Data = (X509Data) info;
66                Iterator xi = x509Data.getContent().iterator();
67                while (xi.hasNext()) {
68                    Object o = xi.next();
69                    if (!(o instanceof X509Certificate))
70                        continue;
71                    final PublicKey key = ((X509Certificate)o).getPublicKey();
72                    // Make sure the algorithm is compatible
73                    // with the method.
74                    if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
75                        return new KeySelectorResult() {
76                            public Key getKey() { return key; }
77                        };
78                    }
79                }
80            }
81            throw new KeySelectorException("No key found!");
82        }
83
84        boolean algEquals(String algURI, String algName) {
85            if ((algName.equalsIgnoreCase("DSA") &&
86                algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
87                (algName.equalsIgnoreCase("RSA") &&
88
89                algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
90                return true;
91            } else {
92                return false;
93            }
94        }
95    }
96
97    /**
98     * Create an empty Credential.
99     */
100    public GENICredential() {
101        m_head = m_tail = null;
102        doc = null;
103        id = null;
104    }
105    /**
106     * Create a credential from a head and tail role.  This credential has no
107     * underlying certificate, and cannot be exported or used in real proofs.
108     * make_cert can create a certificate for a credential initialized this
109     * way.
110     * @param head the Role at the head of the credential
111     * @param tail the Role at the tail of the credential
112     */
113    public GENICredential(Role head, Role tail) {
114        m_head = head;
115        m_tail = tail;
116        doc = null; 
117        id = null;
118    }
119
120    /**
121     * Do the credential extraction from an input stream.  This parses the
122     * certificate from the input stream and saves it. The contents must be
123     * validated and parsed later.
124     * @param stream the InputStream to read the certificate from.
125     * @throws IOException if the stream is unparsable
126     */
127    protected void read_certificate(InputStream stream) 
128            throws IOException {
129        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
130        DocumentBuilder db = null;
131
132        if ( dbf == null ) 
133            throw new IOException("Cannot get DocumentBuilderFactory!?");
134        try {
135            dbf.setNamespaceAware(true);
136            if ( (db = dbf.newDocumentBuilder()) == null ) 
137                throw new IOException("Cannot get DocumentBuilder!?");
138            doc = db.parse(stream);
139            doc.normalizeDocument();
140        }
141        catch (IllegalArgumentException ie) {
142            throw new IOException("null stream");
143        }
144        catch (SAXException se) {
145            throw new IOException(se.getMessage());
146        }
147        catch (Exception e) {
148            throw new IOException(e.getMessage());
149        }
150
151    }
152
153    /**
154     * Walk the document and set all instances of an xml:id attribute to be ID
155     * attributes in terms of the java libraries.  This is needed for the
156     * signature code to find signed subsections.  The "xml:id" is treated as
157     * both a namespace free ID with a colon and as an "id" identifier in the
158     * "xml" namespace so we don't miss it.
159     * @param n the root of the document to mark
160     */
161    static protected void setIDAttrs(Node n) {
162        if ( n.getNodeType() == Node.ELEMENT_NODE) {
163            Element e = (Element) n;
164            String id = e.getAttribute("xml:id");
165
166            if ( id != null && id.length() > 0 )
167                e.setIdAttribute("xml:id", true);
168
169            id = e.getAttributeNS("xml", "id");
170            if ( id != null && id.length() > 0 )
171                e.setIdAttributeNS("xml", "id", true);
172        }
173
174        for (Node nn = n.getFirstChild(); nn != null; nn = nn.getNextSibling())
175            setIDAttrs(nn);
176    }
177
178    /**
179     * Return the child of Node n  that has the given name, if there is one.
180     * @param n a Node to search
181     * @param name the name to search for
182     * @return a Node with the name, may be null
183     */
184    static protected Node getChildByName(Node n, String name) {
185        if (name == null ) return null;
186
187        for (Node nn = n.getFirstChild(); nn != null; nn = nn.getNextSibling())
188            if ( nn.getNodeType() == Node.ELEMENT_NODE && 
189                    name.equals(nn.getNodeName())) return nn;
190        return null;
191    }
192
193    /**
194     * Find the X509Certificate in the Signature element and convert it to an
195     * ABAC identity.  This assumes a KeyInfo in that section holding an
196     * X509Data section containing the certificate in an X509Certificate
197     * section.  Any of that not being found will cause a failure.  If the
198     * Identity cannot be created a Gignature exception is thrown.
199     * @param n a Node pointing to a Signature section
200     * @return an Identity constructed frm the X509 Certificate
201     * @throws SignatureException if the Identity cannot be created from the
202     * certificate.
203     */
204    static protected Identity getIdentity(Node n) throws SignatureException {
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 SignatureException(e.getMessage());
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 CertificateException if the certificate is badly formatted
231     * @throws InvalidKeyException if none of the Identities can validate the
232     *                              certificate
233     * @throws NoSuchAlgorithmException if the credential uses an unknown
234     *                              signature algorithm
235     * @throws NoSuchProviderException if the provider of the signature
236     *                              algorithm is unavailable
237     * @throws SignatureException if the signature check fails
238     */
239    protected void init(Collection<Identity> ids) 
240            throws CertificateException, InvalidKeyException, 
241                NoSuchAlgorithmException, NoSuchProviderException,
242                SignatureException {
243
244        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
245        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, 
246                "Signature");
247        DOMValidateContext valContext = null;
248        XMLSignature signature = null;
249
250        try {
251
252            if (nl.getLength() == 0) 
253                throw new SignatureException("Cannot find Signature element");
254
255            setIDAttrs(doc);
256            valContext = new DOMValidateContext(new X509KeySelector(),
257                    nl.item(0));
258            if ( valContext == null )
259                throw new SignatureException("No context");
260
261            signature = fac.unmarshalXMLSignature(valContext);
262            if (signature == null) 
263                throw new SignatureException("No sig");
264
265            if (!signature.validate(valContext))
266                throw new SignatureException("bad signature");
267
268            load_roles();
269
270            id = getIdentity(nl.item(0));
271
272            if ( !ids.contains(id) ) ids.add(id);
273
274            if (!id.getKeyID().equals(m_head.principal()))
275                throw new InvalidKeyException("Unknown identity");
276        }
277        catch (Exception e) {
278            throw new SignatureException(e.getMessage());
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
289     * @throws CertificateException if the certificate is badly formatted
290     * @throws InvalidKeyException if none of the Identities can validate the
291     *                              certificate
292     * @throws NoSuchAlgorithmException if the credential uses an unknown
293     *                              signature algorithm
294     * @throws NoSuchProviderException if the provider of the signature
295     *                              algorithm is unavailable
296     * @throws SignatureException if the signature check fails
297     * @throws IOException if the certificate is unparsable.
298     */
299    protected void init(InputStream stream, Collection<Identity> ids) 
300            throws CertificateException, InvalidKeyException, 
301                NoSuchAlgorithmException, NoSuchProviderException,
302                SignatureException, IOException {
303        read_certificate(stream);
304        if (doc == null) throw new IOException("Unknown Format");
305        init(ids);
306    }
307
308    /**
309     * Create a credential from an attribute cert in a file. Throws an
310     * exception if the cert file can't be opened or if there's a format
311     * problem with the cert.  Note that catching
312     * java.security.GeneralSecurityException catches most of the exceptions
313     * this throws.
314     * @param filename a String containing the filename to read
315     * @param ids a Collection of Identities to use in validating the cert
316     * @throws StreamParsingException if the stream is unparsable
317     * @throws CertificateException if the certificate is badly formatted
318     * @throws InvalidKeyException if none of the Identities can validate the
319     *                              certificate
320     * @throws NoSuchAlgorithmException if the credential uses an unknown
321     *                              signature algorithm
322     * @throws NoSuchProviderException if the provider of the signature
323     *                              algorithm is unavailable
324     * @throws SignatureException if the signature check fails
325     * @throws IOException if the certificate is unparsable.
326     */
327    public GENICredential(String filename, Collection<Identity> ids) 
328        throws Exception { init(new FileInputStream(filename), ids); }
329
330    /**
331     * Create a credential from an attribute cert in a file. Throws an
332     * exception if the cert file can't be opened or if there's a format
333     * problem with the cert.  Note that catching
334     * java.security.GeneralSecurityException catches most of the exceptions
335     * this throws.
336     * @param file the File to read
337     * @param ids a Collection of Identities to use in validating the cert
338     * @throws StreamParsingException if the stream is unparsable
339     * @throws CertificateException if the certificate is badly formatted
340     * @throws InvalidKeyException if none of the Identities can validate the
341     *                              certificate
342     * @throws NoSuchAlgorithmException if the credential uses an unknown
343     *                              signature algorithm
344     * @throws NoSuchProviderException if the provider of the signature
345     *                              algorithm is unavailable
346     * @throws SignatureException if the signature check fails
347     * @throws IOException if the certificate is unparsable.
348     */
349    public GENICredential(File file, Collection<Identity> ids) 
350            throws CertificateException, InvalidKeyException, 
351                NoSuchAlgorithmException, NoSuchProviderException,
352                SignatureException, StreamParsingException, IOException {
353        init(new FileInputStream(file), ids);
354    }
355
356    /**
357     * Create a credential from an InputStream.  Throws an exception if the
358     * stream can't be parsed or if there's a format problem with the cert.
359     * Note that catching java.security.GeneralSecurityException catches most
360     * of the exceptions this throws.
361     * @param s the InputStream to read
362     * @param ids a Collection of Identities to use in validating the cert
363     * @throws StreamParsingException if the stream is unparsable
364     * @throws CertificateException if the certificate is badly formatted
365     * @throws InvalidKeyException if none of the Identities can validate the
366     *                              certificate
367     * @throws NoSuchAlgorithmException if the credential uses an unknown
368     *                              signature algorithm
369     * @throws NoSuchProviderException if the provider of the signature
370     *                              algorithm is unavailable
371     * @throws SignatureException if the signature check fails
372     * @throws IOException if the certificate is unparsable.
373     */
374    public GENICredential(InputStream s, Collection<Identity> ids) 
375            throws CertificateException, InvalidKeyException, 
376                NoSuchAlgorithmException, NoSuchProviderException,
377                SignatureException, StreamParsingException, IOException {
378        init(s, ids);
379    }
380
381    /**
382     * Create a certificate from this credential issued by the given identity.
383     * Note that catching java.security.GeneralSecurityException catches most
384     * of the exceptions this throws.
385     * @param i the Identity that will issue the certificate
386     * @throws IOException reading or writing problems
387     * @throws CertificateEncodingException Problem creating certificate
388     * @throws InvalidKeyException if none of the Identities can sign the
389     *                              certificate
390     * @throws NoSuchAlgorithmException if the credential uses an unknown
391     *                              signature algorithm
392     * @throws NoSuchProviderException if the provider of the signature
393     *                              algorithm is unavailable
394     * @throws SignatureException if the signature creation fails
395     */
396    public void make_cert(Identity i) 
397            throws IOException, CertificateEncodingException,
398               NoSuchProviderException, NoSuchAlgorithmException,
399               SignatureException, InvalidKeyException {
400        try {
401            SimpleDateFormat df = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss'Z'");
402            StringBuffer expBuf = new StringBuffer();
403            X509Certificate cert = i.getCertificate();
404            KeyPair kp = i.getKeyPair();
405            if ( cert == null ) 
406                throw new IOException("No credential in identity?");
407            if ( kp == null ) 
408                throw new IOException("No keypair in identity?");
409
410            XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
411            Reference ref = fac.newReference("#ref0", 
412                    fac.newDigestMethod(DigestMethod.SHA1, null),
413                    Collections.singletonList(
414                        fac.newTransform(Transform.ENVELOPED, 
415                            (TransformParameterSpec) null)),
416                    null, null);
417
418            SignedInfo si = fac.newSignedInfo(
419                    fac.newCanonicalizationMethod(
420                        CanonicalizationMethod.INCLUSIVE,
421                        (C14NMethodParameterSpec) null),
422                    fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
423                    Collections.singletonList(ref));
424            KeyInfoFactory kif = fac.getKeyInfoFactory();
425            List x509Content = new ArrayList();
426            x509Content.add(cert.getSubjectX500Principal().getName());
427            x509Content.add(cert);
428            X509Data xd = kif.newX509Data(x509Content);
429            KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));
430
431
432            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
433            DocumentBuilder db = null;
434
435            if ( dbf == null ) 
436                throw new IOException("Cannot get DocumentBuilderFactory!?");
437            if ( (db = dbf.newDocumentBuilder()) == null ) 
438                throw new IOException("Cannot get DocumentBuilder!?");
439            doc = db.newDocument();
440            if ( doc == null ) 
441                throw new IOException("No Document");
442            Element root = doc.createElement("signed-credential"); 
443            doc.appendChild(root);
444            Element cred = doc.createElement("credential");
445            Element sig = doc.createElement("signatures");
446            Element e = doc.createElement("type");
447            Node text = doc.createTextNode("abac");
448
449            cred.setAttributeNS("xml", "id", "ref0");
450            root.appendChild(cred);
451            e.appendChild(text);
452            cred.appendChild(e);
453                       
454            df.format(new Date(System.currentTimeMillis() 
455                        + (3600L * 1000L * 24L * 365L)), expBuf, 
456                    new FieldPosition(0));
457            e = doc.createElement("expires");
458            text = doc.createTextNode(expBuf.toString());
459            e.appendChild(text);
460            cred.appendChild(e);
461
462            e = doc.createElement("version");
463            text = doc.createTextNode("1.0");
464            e.appendChild(text);
465            cred.appendChild(e);
466
467            e = doc.createElement("rt0");
468            text = doc.createTextNode(m_head + "<-" + m_tail);
469            e.appendChild(text);
470            cred.appendChild(e);
471
472            root.appendChild(sig);
473
474            DOMSignContext dsc = new DOMSignContext(kp.getPrivate(), sig);
475            XMLSignature signature = fac.newXMLSignature(si, ki);
476            signature.sign(dsc);
477        }
478        catch (Exception e) {
479            throw new SignatureException(e.getMessage());
480        }
481
482    }
483
484    /**
485     * Load the roles off the attribute cert. Throws a RuntimeException if
486     * there's something wrong with the cert.
487     */
488    private void load_roles() {
489        if ( doc == null ) 
490            throw new RuntimeException("No credential");
491
492        String roles = null;
493        NodeList nodes = doc.getElementsByTagName("rt0");
494
495        if (nodes == null || nodes.getLength() != 1) 
496            throw new RuntimeException("More than one rt0 element?");
497
498        Node node = nodes.item(0);
499
500        if ( node == null ) 
501            throw new RuntimeException("bad rt0 element?");
502
503        if ( (roles = node.getTextContent()) == null ) 
504            throw new RuntimeException("bad rt0 element (no text)?");
505
506        String[] parts = roles.split("\\s*<--?\\s*");
507        if (parts.length != 2)
508            throw new RuntimeException("Invalid attribute: " + roles);
509
510        m_head = new Role(parts[0]);
511        m_tail = new Role(parts[1]);
512    }
513
514    /**
515     * Two credentials are the same if their roles are the same.
516     * @param o an Object to compare
517     * @return true if the Credentials have the Roles
518     */
519    public boolean equals(Object o) {
520        if ( o instanceof Credential ) {
521            Credential c = (Credential) o;
522
523            if (m_head == null || m_tail == null ) return false;
524            else return (m_head.equals(c.head()) && m_tail.equals(c.tail()));
525        }
526        else return false;
527    }
528
529    /**
530     * Allow credentials to be compared.  They are ordered by their Roles, head
531     * then tail.
532     * @param o an Object to compare
533     * @return -1 if this Credential is before, 0 if they are the same, and 1
534     *              if this Credential is after the given object.
535     */
536    public int compareTo(Object o) {
537        if (o instanceof Credential) {
538            Credential c = (Credential) o;
539
540            if (head().equals(c.head())) return tail().compareTo(c.tail());
541            else return head().compareTo(c.head());
542        }
543        else return 1;
544    }
545
546
547    /**
548     * Get the head role from the credential.
549     * @return the Role in the head
550     */
551    public Role head() { return m_head; }
552
553    /**
554     * Get the tail role from the credential
555     * @return the Role in the tail
556     */
557    public Role tail() { return m_tail; }
558
559    /**
560     * Gets the cert associated with this credential (if any).
561     * @return the attribute cert associated with this credential (if any).
562     */
563    public Document GENIcert() { return doc; }
564
565    /**
566     * Turn the credential into string form. The format is head &lt;- tail. For
567     * example: A.r1 &lt;- B.r2.r3.  Principal names are key identifiers.
568     * @return the string form
569     */
570    public String toString() {
571        return m_head + " <- " + m_tail;
572    }
573
574    /**
575     * Turn the credential into string form. The format is head &lt;- tail. For
576     * example: A.r1 &lt;- B.r2.r3.  Principal names are shortened to menmonics
577     * if the Context knows the identity.
578     * @param c the Context to translate names in
579     * @return the string form
580     */
581    public String simpleString(Context c) {
582        return m_head.simpleString(c) + " <- " + m_tail.simpleString(c);
583    }
584
585    /**
586     * Output the signed GENI ABAC certificate associated with this
587     * Credential to the OutputStream.
588     * @param s the OutputStream on which to write
589     * @throws IOException if there is an error writing.
590     */
591    public void write(OutputStream s) throws IOException {
592        if ( doc == null ) 
593            return;
594        try {
595            TransformerFactory tf = TransformerFactory.newInstance();
596            Transformer t = tf.newTransformer();
597            DOMSource source = new DOMSource(doc);
598            StreamResult result = new StreamResult(s);
599
600            t.transform(source, result);
601            s.flush();
602        } 
603        catch (Exception e) {
604            throw new IOException(e.getMessage());
605        }
606    }
607
608    /**
609     * Output the signed GENI ABAC certificate associated with this
610     * Credential to the filename given.
611     * @param fn a String containing the output filename
612     * @throws IOException if there is an error writing.
613     */
614    public void write(String fn) throws IOException, FileNotFoundException {
615        write(new FileOutputStream(fn));
616    }
617
618    /**
619     * Return true if this Credential has a certificate associated.  A jabac
620     * extension.
621     * @return true if this Credential has a certificate associated.
622     */
623    public boolean hasCertificate() { return doc != null; }
624
625    /**
626     * Return the Identity that issued the underlying certificate.  A jabac
627     * extension.
628     * @return the Identity that issued the underlying certificate.
629     */
630    public Identity issuer() { return id; }
631    /**
632     * Return the X509Certificate that issued the underlying certificate.
633     * @return the X509Certificate that issued the underlying certificate.
634     */
635    public X509Certificate issuerCert() { return id.getCertificate(); }
636    /**
637     * Return the X509V2AttributeCertificate that underlys the Credential
638     * @return the X509V2AttributeCertificate that underlys the Credential.
639    public X509V2AttributeCertificate attributeCert() { return ac; }
640     */
641
642
643}
Note: See TracBrowser for help on using the repository browser.