source: java/net/deterlab/abac/GENICredential.java @ 61f21c5

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

Writing works!

  • Property mode set to 100644
File size: 23.7 KB
Line 
1package net.deterlab.abac;
2
3import java.io.*;
4import java.math.*;
5import java.text.*;
6
7import java.util.*;
8import java.security.*;
9import java.security.cert.*;
10
11import javax.security.auth.x500.*;
12
13import javax.xml.parsers.*;
14import javax.xml.transform.*;
15import javax.xml.transform.dom.*;
16import javax.xml.transform.stream.*;
17
18import javax.xml.crypto.*;
19import javax.xml.crypto.dsig.*;
20import javax.xml.crypto.dsig.dom.*;
21import javax.xml.crypto.dsig.keyinfo.*;
22import javax.xml.crypto.dsig.spec.*;
23
24import org.xml.sax.*;
25import org.w3c.dom.*;
26
27import org.bouncycastle.asn1.*;
28import org.bouncycastle.asn1.x509.*;
29import org.bouncycastle.x509.*;
30import org.bouncycastle.x509.util.*;
31import org.bouncycastle.openssl.*;
32
33/**
34 * An ABAC credential, 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     * X509KeySelector implementation from
53     * http://www.outsourcingi.com/art-271-Programming-With-the-Java-XML-Digital-Signature-API.html
54     * .
55     *
56     * Straight ahead extraction of a key from an X509 cert, but it is their
57     * code and hard to improve on.
58     */
59    protected class X509KeySelector extends KeySelector {
60        public KeySelectorResult select(KeyInfo keyInfo,
61                                        KeySelector.Purpose purpose,
62                                        AlgorithmMethod method,
63                                        XMLCryptoContext context)
64            throws KeySelectorException {
65            Iterator ki = keyInfo.getContent().iterator();
66            while (ki.hasNext()) {
67                XMLStructure info = (XMLStructure) ki.next();
68                if (!(info instanceof X509Data))
69                    continue;
70                X509Data x509Data = (X509Data) info;
71                Iterator xi = x509Data.getContent().iterator();
72                while (xi.hasNext()) {
73                    Object o = xi.next();
74                    if (!(o instanceof X509Certificate))
75                        continue;
76                    final PublicKey key = ((X509Certificate)o).getPublicKey();
77                    // Make sure the algorithm is compatible
78                    // with the method.
79                    if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
80                        return new KeySelectorResult() {
81                            public Key getKey() { return key; }
82                        };
83                    }
84                }
85            }
86            throw new KeySelectorException("No key found!");
87        }
88
89        boolean algEquals(String algURI, String algName) {
90            if ((algName.equalsIgnoreCase("DSA") &&
91                algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
92                (algName.equalsIgnoreCase("RSA") &&
93
94                algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
95                return true;
96            } else {
97                return false;
98            }
99        }
100    }
101
102    /**
103     * Create an empty Credential.
104     */
105    public GENICredential() {
106        m_head = m_tail = null;
107        doc = null;
108        id = null;
109    }
110    /**
111     * Create a credential from a head and tail role.  This credential has no
112     * underlying certificate, and cannot be exported or used in real proofs.
113     * make_cert can create a certificate for a credential initialized this
114     * way.
115     * @param head the Role at the head of the credential
116     * @param tail the Role at the tail of the credential
117     */
118    public GENICredential(Role head, Role tail) {
119        m_head = head;
120        m_tail = tail;
121        doc = null; 
122        id = null;
123    }
124
125    /**
126     * Do the credential extraction from an input stream.  This parses the
127     * certificate from the input stream and saves it. The contents must be
128     * validated and parsed later.
129     * @param stream the InputStream to read the certificate from.
130     * @throws IOException if the stream is unparsable
131     */
132    protected void read_certificate(InputStream stream) 
133            throws IOException {
134        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
135        DocumentBuilder db = null;
136
137        if ( dbf == null ) 
138            throw new IOException("Cannot get DocumentBuilderFactory!?");
139        try {
140            /* Note this setting is required to find the properly
141             * namespace-scoped signature block in the credential.
142             */
143            dbf.setNamespaceAware(true);
144            if ( (db = dbf.newDocumentBuilder()) == null ) 
145                throw new IOException("Cannot get DocumentBuilder!?");
146            doc = db.parse(stream);
147            doc.normalizeDocument();
148        }
149        catch (IllegalArgumentException ie) {
150            throw new IOException("null stream");
151        }
152        catch (SAXException se) {
153            throw new IOException(se.getMessage());
154        }
155        catch (Exception e) {
156            throw new IOException(e.getMessage());
157        }
158
159    }
160
161    /**
162     * Walk the document and set all instances of an xml:id attribute to be ID
163     * attributes in terms of the java libraries.  This is needed for the
164     * signature code to find signed subsections.  The "xml:id" is treated as
165     * both a namespace free ID with a colon and as an "id" identifier in the
166     * "xml" namespace so we don't miss it.
167     * @param n the root of the document to mark
168     */
169    static protected void setIDAttrs(Node n) {
170        if ( n.getNodeType() == Node.ELEMENT_NODE) {
171            Element e = (Element) n;
172            String id = e.getAttribute("xml:id");
173
174            if ( id != null && id.length() > 0 )
175                e.setIdAttribute("xml:id", true);
176
177            id = e.getAttributeNS("xml", "id");
178            if ( id != null && id.length() > 0 )
179                e.setIdAttributeNS("xml", "id", true);
180        }
181
182        for (Node nn = n.getFirstChild(); nn != null; nn = nn.getNextSibling())
183            setIDAttrs(nn);
184    }
185
186    /**
187     * Return the child of Node n  that has the given name, if there is one.
188     * @param n a Node to search
189     * @param name the name to search for
190     * @return a Node with the name, may be null
191     */
192    static protected Node getChildByName(Node n, String name) {
193        if (name == null ) return null;
194
195        for (Node nn = n.getFirstChild(); nn != null; nn = nn.getNextSibling())
196            if ( nn.getNodeType() == Node.ELEMENT_NODE && 
197                    name.equals(nn.getNodeName())) return nn;
198        return null;
199    }
200
201    /**
202     * Find the X509Certificate in the Signature element and convert it to an
203     * ABAC identity.  This assumes a KeyInfo in that section holding an
204     * X509Data section containing the certificate in an X509Certificate
205     * section.  Any of that not being found will cause a failure.  If the
206     * Identity cannot be created a Gignature exception is thrown.
207     * @param n a Node pointing to a Signature section
208     * @return an Identity constructed frm the X509 Certificate
209     * @throws SignatureException if the Identity cannot be created from the
210     * certificate.
211     */
212    static protected Identity getIdentity(Node n) throws SignatureException {
213        Identity rv = null;
214        Node nn = getChildByName(n, "KeyInfo");
215        String certStr = null;
216
217        if ( nn == null ) return null;
218        if ( ( nn = getChildByName(nn, "X509Data")) == null ) return null;
219        if ( ( nn = getChildByName(nn, "X509Certificate")) == null ) return null;
220        if ( ( certStr = nn.getTextContent()) == null ) return null;
221        try {
222            certStr = "-----BEGIN CERTIFICATE-----\n" + 
223                certStr + 
224                "\n-----END CERTIFICATE-----";
225            return new Identity(new StringReader(certStr));
226        } 
227        catch (Exception e) {
228            throw new SignatureException(e.getMessage());
229        }
230    }
231
232    /**
233     * Initialize a credential from parsed certificate.  Validiate it against
234     * the given identities and parse out the roles.  Note that catching
235     * java.security.GeneralSecurityException catches most of the exceptions
236     * this throws.
237     * @param ids a Collection of Identities to use in validating the cert
238     * @throws CertificateException if the certificate is badly formatted
239     * @throws InvalidKeyException if none of the Identities can validate the
240     *                              certificate
241     * @throws NoSuchAlgorithmException if the credential uses an unknown
242     *                              signature algorithm
243     * @throws NoSuchProviderException if the provider of the signature
244     *                              algorithm is unavailable
245     * @throws SignatureException if the signature check fails
246     */
247    protected void init(Collection<Identity> ids) 
248            throws CertificateException, InvalidKeyException, 
249                NoSuchAlgorithmException, NoSuchProviderException,
250                SignatureException {
251
252        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
253        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, 
254                "Signature");
255        DOMValidateContext valContext = null;
256        XMLSignature signature = null;
257
258        try {
259
260            if (nl.getLength() == 0) 
261                throw new SignatureException("Cannot find Signature element");
262
263            setIDAttrs(doc);
264            valContext = new DOMValidateContext(new X509KeySelector(),
265                    nl.item(0));
266            if ( valContext == null )
267                throw new SignatureException("No context");
268
269            signature = fac.unmarshalXMLSignature(valContext);
270            if (signature == null) 
271                throw new SignatureException("No sig");
272
273            if (!signature.validate(valContext))
274                throw new SignatureException("bad signature");
275
276            load_roles();
277
278            id = getIdentity(nl.item(0));
279
280            if ( !ids.contains(id) ) ids.add(id);
281
282            if (!id.getKeyID().equals(m_head.principal()))
283                throw new InvalidKeyException("Unknown identity");
284        }
285        catch (Exception e) {
286            throw new SignatureException(e.getMessage());
287        }
288    }
289
290    /**
291     * Parse a credential from an InputStream and initialize the role from it.
292     * Combine read_credential(stream) and init(ids).  Note that catching
293     * java.security.GeneralSecurityException catches most of the exceptions
294     * this throws.
295     * @param stream the InputStream to read the certificate from.
296     * @param ids a Collection of Identities to use in validating the cert
297     * @throws CertificateException if the certificate is badly formatted
298     * @throws InvalidKeyException if none of the Identities can validate the
299     *                              certificate
300     * @throws NoSuchAlgorithmException if the credential uses an unknown
301     *                              signature algorithm
302     * @throws NoSuchProviderException if the provider of the signature
303     *                              algorithm is unavailable
304     * @throws SignatureException if the signature check fails
305     * @throws IOException if the certificate is unparsable.
306     */
307    protected void init(InputStream stream, Collection<Identity> ids) 
308            throws CertificateException, InvalidKeyException, 
309                NoSuchAlgorithmException, NoSuchProviderException,
310                SignatureException, IOException {
311        read_certificate(stream);
312        if (doc == null) throw new IOException("Unknown Format");
313        init(ids);
314    }
315
316    /**
317     * Create a credential from an attribute cert in a file. Throws an
318     * exception if the cert file can't be opened or if there's a format
319     * problem with the cert.  Note that catching
320     * java.security.GeneralSecurityException catches most of the exceptions
321     * this throws.
322     * @param filename a String containing the filename to read
323     * @param ids a Collection of Identities to use in validating the cert
324     * @throws StreamParsingException if the stream is unparsable
325     * @throws CertificateException if the certificate is badly formatted
326     * @throws InvalidKeyException if none of the Identities can validate the
327     *                              certificate
328     * @throws NoSuchAlgorithmException if the credential uses an unknown
329     *                              signature algorithm
330     * @throws NoSuchProviderException if the provider of the signature
331     *                              algorithm is unavailable
332     * @throws SignatureException if the signature check fails
333     * @throws IOException if the certificate is unparsable.
334     */
335    public GENICredential(String filename, Collection<Identity> ids) 
336        throws Exception { init(new FileInputStream(filename), ids); }
337
338    /**
339     * Create a credential from an attribute cert in a file. Throws an
340     * exception if the cert file can't be opened or if there's a format
341     * problem with the cert.  Note that catching
342     * java.security.GeneralSecurityException catches most of the exceptions
343     * this throws.
344     * @param file the File to read
345     * @param ids a Collection of Identities to use in validating the cert
346     * @throws StreamParsingException if the stream is unparsable
347     * @throws CertificateException if the certificate is badly formatted
348     * @throws InvalidKeyException if none of the Identities can validate the
349     *                              certificate
350     * @throws NoSuchAlgorithmException if the credential uses an unknown
351     *                              signature algorithm
352     * @throws NoSuchProviderException if the provider of the signature
353     *                              algorithm is unavailable
354     * @throws SignatureException if the signature check fails
355     * @throws IOException if the certificate is unparsable.
356     */
357    public GENICredential(File file, Collection<Identity> ids) 
358            throws CertificateException, InvalidKeyException, 
359                NoSuchAlgorithmException, NoSuchProviderException,
360                SignatureException, StreamParsingException, IOException {
361        init(new FileInputStream(file), ids);
362    }
363
364    /**
365     * Create a credential from an InputStream.  Throws an exception if the
366     * stream can't be parsed or if there's a format problem with the cert.
367     * Note that catching java.security.GeneralSecurityException catches most
368     * of the exceptions this throws.
369     * @param s the InputStream to read
370     * @param ids a Collection of Identities to use in validating the cert
371     * @throws StreamParsingException if the stream is unparsable
372     * @throws CertificateException if the certificate is badly formatted
373     * @throws InvalidKeyException if none of the Identities can validate the
374     *                              certificate
375     * @throws NoSuchAlgorithmException if the credential uses an unknown
376     *                              signature algorithm
377     * @throws NoSuchProviderException if the provider of the signature
378     *                              algorithm is unavailable
379     * @throws SignatureException if the signature check fails
380     * @throws IOException if the certificate is unparsable.
381     */
382    public GENICredential(InputStream s, Collection<Identity> ids) 
383            throws CertificateException, InvalidKeyException, 
384                NoSuchAlgorithmException, NoSuchProviderException,
385                SignatureException, StreamParsingException, IOException {
386        init(s, ids);
387    }
388
389    /**
390     * Encode the abac credential's XML.  This is straight line code that
391     * directly builds the credential.
392     * @return a Node, the place to attach signatures
393     * @throws IOException if any of the XML construction fails
394     */
395    protected Node make_rt0_content() throws IOException {
396        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
397        DocumentBuilder db = null;
398        SimpleDateFormat df = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss'Z'");
399        StringBuffer expBuf = new StringBuffer();
400
401        /* This is a weirdness to cope with the GENI format.  They have a naked
402         * xml:id specifier without any namespace declarations in the
403         * credential.  Notice that this is the opposite of the setting used in
404         * init to read a credential. */
405        dbf.setNamespaceAware(false);
406
407        if ( dbf == null ) 
408            throw new IOException("Cannot get DocumentBuilderFactory!?");
409
410        try {
411            db = dbf.newDocumentBuilder();
412        }
413        catch (ParserConfigurationException pe) {
414            throw new IOException("Cannot get DocumentBuilder!?" + 
415                    pe.getMessage());
416        }
417
418        doc = db.newDocument();
419        if ( doc == null ) 
420            throw new IOException("No Document");
421
422        Element root = doc.createElement("signed-credential"); 
423        Element cred = doc.createElement("credential");
424        Element sig = doc.createElement("signatures");
425        Element e = doc.createElement("type");
426        Node text = doc.createTextNode("abac");
427
428        doc.appendChild(root);
429
430        cred.setAttribute("xml:id", "ref0");
431        /* So that the signing code can find the section to sign */
432        cred.setIdAttribute("xml:id", true);
433
434        root.appendChild(cred);
435        e.appendChild(text);
436        cred.appendChild(e);
437
438        /* XXX: pass in valid time */
439        df.format(new Date(System.currentTimeMillis() 
440                    + (3600L * 1000L * 24L * 365L)), expBuf, 
441                new FieldPosition(0));
442        e = doc.createElement("expires");
443        text = doc.createTextNode(expBuf.toString());
444        e.appendChild(text);
445        cred.appendChild(e);
446
447        e = doc.createElement("version");
448        text = doc.createTextNode("1.0");
449        e.appendChild(text);
450        cred.appendChild(e);
451
452        e = doc.createElement("rt0");
453        text = doc.createTextNode(m_head + "<-" + m_tail);
454        e.appendChild(text);
455        cred.appendChild(e);
456
457        root.appendChild(sig);
458        return sig;
459    }
460
461    /**
462     * Create a certificate from this credential issued by the given identity.
463     * Note that catching java.security.GeneralSecurityException catches most
464     * of the exceptions this throws.
465     * @param i the Identity that will issue the certificate
466     * @throws IOException reading or writing problems
467     * @throws CertificateEncodingException Problem creating certificate
468     * @throws InvalidKeyException if none of the Identities can sign the
469     *                              certificate
470     * @throws NoSuchAlgorithmException if the credential uses an unknown
471     *                              signature algorithm
472     * @throws NoSuchProviderException if the provider of the signature
473     *                              algorithm is unavailable
474     * @throws SignatureException if the signature creation fails
475     */
476    public void make_cert(Identity i) 
477            throws IOException, CertificateEncodingException,
478               NoSuchProviderException, NoSuchAlgorithmException,
479               SignatureException, InvalidKeyException {
480        X509Certificate cert = i.getCertificate();
481        KeyPair kp = i.getKeyPair();
482        PublicKey pubKey = null;
483        PrivateKey privKey = null;
484        if ( cert == null ) 
485            throw new IOException("No credential in identity?");
486        if ( kp == null ) 
487            throw new IOException("No keypair in identity?");
488
489        pubKey = kp.getPublic();
490        if ((privKey = kp.getPrivate()) == null ) 
491            throw new IOException("No private ket in identity");
492
493        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
494        Reference ref = null;
495        SignedInfo si = null;
496        KeyInfoFactory kif = fac.getKeyInfoFactory();
497        List x509Content = new ArrayList();
498        List keyInfo = new ArrayList();
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 InvalidKeyException("Unsupported key format " + 
509                    ke.getMessage());
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 SignatureException(me.getMessage());
538        }
539
540    }
541
542    /**
543     * Load the roles off the attribute cert. Throws a RuntimeException if
544     * there's something wrong with the cert.
545     */
546    private void load_roles() {
547        if ( doc == null ) 
548            throw new RuntimeException("No credential");
549
550        String roles = null;
551        NodeList nodes = doc.getElementsByTagName("rt0");
552
553        if (nodes == null || nodes.getLength() != 1) 
554            throw new RuntimeException("More than one rt0 element?");
555
556        Node node = nodes.item(0);
557
558        if ( node == null ) 
559            throw new RuntimeException("bad rt0 element?");
560
561        if ( (roles = node.getTextContent()) == null ) 
562            throw new RuntimeException("bad rt0 element (no text)?");
563
564        String[] parts = roles.split("\\s*<--?\\s*");
565        if (parts.length != 2)
566            throw new RuntimeException("Invalid attribute: " + roles);
567
568        m_head = new Role(parts[0]);
569        m_tail = new Role(parts[1]);
570    }
571
572    /**
573     * Two credentials are the same if their roles are the same.
574     * @param o an Object to compare
575     * @return true if the Credentials have the Roles
576     */
577    public boolean equals(Object o) {
578        if ( o instanceof Credential ) {
579            Credential c = (Credential) o;
580
581            if (m_head == null || m_tail == null ) return false;
582            else return (m_head.equals(c.head()) && m_tail.equals(c.tail()));
583        }
584        else return false;
585    }
586
587    /**
588     * Allow credentials to be compared.  They are ordered by their Roles, head
589     * then tail.
590     * @param o an Object to compare
591     * @return -1 if this Credential is before, 0 if they are the same, and 1
592     *              if this Credential is after the given object.
593     */
594    public int compareTo(Object o) {
595        if (o instanceof Credential) {
596            Credential c = (Credential) o;
597
598            if (head().equals(c.head())) return tail().compareTo(c.tail());
599            else return head().compareTo(c.head());
600        }
601        else return 1;
602    }
603
604
605    /**
606     * Get the head role from the credential.
607     * @return the Role in the head
608     */
609    public Role head() { return m_head; }
610
611    /**
612     * Get the tail role from the credential
613     * @return the Role in the tail
614     */
615    public Role tail() { return m_tail; }
616
617    /**
618     * Gets the cert associated with this credential (if any).
619     * @return the attribute cert associated with this credential (if any).
620     */
621    public Document GENIcert() { return doc; }
622
623    /**
624     * Turn the credential into string form. The format is head &lt;- tail. For
625     * example: A.r1 &lt;- B.r2.r3.  Principal names are key identifiers.
626     * @return the string form
627     */
628    public String toString() {
629        return m_head + " <- " + m_tail;
630    }
631
632    /**
633     * Turn the credential into string form. The format is head &lt;- tail. For
634     * example: A.r1 &lt;- B.r2.r3.  Principal names are shortened to menmonics
635     * if the Context knows the identity.
636     * @param c the Context to translate names in
637     * @return the string form
638     */
639    public String simpleString(Context c) {
640        return m_head.simpleString(c) + " <- " + m_tail.simpleString(c);
641    }
642
643    /**
644     * Output the signed GENI ABAC certificate associated with this
645     * Credential to the OutputStream.
646     * @param s the OutputStream on which to write
647     * @throws IOException if there is an error writing.
648     */
649    public void write(OutputStream s) throws IOException {
650        if ( doc == null ) 
651            return;
652        try {
653            TransformerFactory tf = TransformerFactory.newInstance();
654            Transformer t = tf.newTransformer();
655            DOMSource source = new DOMSource(doc);
656            StreamResult result = new StreamResult(s);
657
658            t.transform(source, result);
659            s.flush();
660        } 
661        catch (Exception e) {
662            throw new IOException(e.getMessage());
663        }
664    }
665
666    /**
667     * Output the signed GENI ABAC certificate associated with this
668     * Credential to the filename given.
669     * @param fn a String containing the output filename
670     * @throws IOException if there is an error writing.
671     */
672    public void write(String fn) throws IOException, FileNotFoundException {
673        write(new FileOutputStream(fn));
674    }
675
676    /**
677     * Return true if this Credential has a certificate associated.  A jabac
678     * extension.
679     * @return true if this Credential has a certificate associated.
680     */
681    public boolean hasCertificate() { return doc != null; }
682
683    /**
684     * Return the Identity that issued the underlying certificate.  A jabac
685     * extension.
686     * @return the Identity that issued the underlying certificate.
687     */
688    public Identity issuer() { return id; }
689    /**
690     * Return the X509Certificate that issued the underlying certificate.
691     * @return the X509Certificate that issued the underlying certificate.
692     */
693    public X509Certificate issuerCert() { return id.getCertificate(); }
694    /**
695     * Return the X509V2AttributeCertificate that underlys the Credential
696     * @return the X509V2AttributeCertificate that underlys the Credential.
697    public X509V2AttributeCertificate attributeCert() { return ac; }
698     */
699
700
701}
Note: See TracBrowser for help on using the repository browser.