source: java/net/deterlab/abac/Credential.java @ e9360e2

abac0-leakabac0-meicompt_changesgec13mei-idmei-rt0-nmei_rt0mei_rt2mei_rt2_fix_1meiyap-rt1meiyap1rt2tvf-new-xml
Last change on this file since e9360e2 was e9360e2, checked in by Ted Faber <faber@…>, 13 years ago

Credential compatibility with libcreddy. Creddy expects an X509 extension identifying the key used to sign an attribute credential.

  • Property mode set to 100644
File size: 13.6 KB
RevLine 
[31b67d5]1package net.deterlab.abac;
2
[7ef13e3]3import java.io.*;
[281158a]4import java.math.*;
[7ef13e3]5
6import java.util.*;
[5cf72cc]7import java.util.zip.*;
[7ef13e3]8import java.security.*;
9import java.security.cert.*;
10
[9725efb]11import net.deterlab.abac.Identity;
[90f939f]12
13import org.bouncycastle.asn1.*;
[e9360e2]14import org.bouncycastle.asn1.x509.*;
[90f939f]15import org.bouncycastle.x509.*;
[281158a]16import org.bouncycastle.jce.X509Principal;
[90f939f]17import org.bouncycastle.jce.provider.X509AttrCertParser;
[7ef13e3]18import org.bouncycastle.jce.provider.X509CertificateObject;
19import org.bouncycastle.openssl.PEMReader;
[90f939f]20
[281158a]21import java.security.PrivateKey;
22
[88e139a]23public class Credential implements Comparable {
[9725efb]24    protected static Vector<Identity> s_ids = new Vector<Identity>();
[281158a]25    protected static String attrOID = "1.3.6.1.5.5.7.10.4";
[e9360e2]26    protected static String authKeyOID = "2.5.29.35";
[9394f1f]27
28    /**
29     * A dummy credential.
30     */
31    public Credential() {
32        m_head = m_tail = null;
33        m_ac = null;
34        m_id = null;
35    }
[31b67d5]36    /**
37     * Create a credential from a head and tail role. This is only for testing.
38     * In a real implementation the Credential must be loaded from an X.509
39     * attribute cert.
40     */
41    public Credential(Role head, Role tail) {
42        m_head = head;
43        m_tail = tail;
[9394f1f]44        m_ac = null; 
45        m_id = null;
[31b67d5]46    }
47
[90f939f]48    /**
[7ef13e3]49     * Do the credential initialization from a filename.
[90f939f]50     */
[1a7e6d3]51    protected void init(InputStream stream) throws Exception {
[90f939f]52        X509AttrCertParser parser = new X509AttrCertParser();
[1a7e6d3]53        parser.engineInit(stream);
[90f939f]54        m_ac = (X509V2AttributeCertificate)parser.engineRead();
[7ef13e3]55        m_id = null;
56
[be05757]57        if ( m_ac == null ) throw new IOException("Invalid Credential Format");
58
[9725efb]59        for (Identity id: s_ids) {
[7ef13e3]60            try {
[9725efb]61                m_ac.verify(id.getCertificate().getPublicKey(), "BC");
[7ef13e3]62                m_id = id;
63                break;
64            }
[9725efb]65            catch (InvalidKeyException e) { }
[7ef13e3]66        }
67        if (m_id == null) throw new InvalidKeyException("Unknown identity");
[90f939f]68
69        load_roles();
[281158a]70
71        if (!m_id.getKeyID().equals(m_head.issuer_part()))
72            throw new InvalidKeyException("Unknown identity");
[90f939f]73    }
74
[7ef13e3]75    /**
76     * Create a credential from an attribute cert. Throws an exception if the
77     * cert file can't be opened or if there's a format problem with the cert.
78     */
79    public Credential(String filename) throws Exception {
[1a7e6d3]80        init(new FileInputStream(filename));
[7ef13e3]81    }
82
83    /**
84     * Create a credential from an attribute cert. Throws an exception if the
85     * cert file can't be opened or if there's a format problem with the cert.
86     */
87    public Credential(File file) throws Exception {
[1a7e6d3]88        init(new FileInputStream(file));
89    }
90
91    /**
92     * Create a credential from an InputStream.
93     */
94    public Credential(InputStream s) throws Exception { 
95        init(s);
[7ef13e3]96    }
97
[e9360e2]98    /**
99     * Create a certificate from this credential issued by the given identity.
100     * This is just grungy credential generation work.
101     */
102    public void make_cert(Identity i) {
103        PrivateKey key = i.getKeyPair().getPrivate();
104        SubjectPublicKeyInfo pki = Identity.extractSubjectPublicKeyInfo(
105                i.getKeyPair().getPublic());
[281158a]106        X509V2AttributeCertificateGenerator gen = 
107            new X509V2AttributeCertificateGenerator();
108
109        gen.setIssuer(new AttributeCertificateIssuer(
110                    new X509Principal("CN="+m_head.issuer_part())));
111        gen.setHolder(new AttributeCertificateHolder(
112                    new X509Principal("CN="+m_head.issuer_part())));
113        gen.setNotAfter(new Date(System.currentTimeMillis() 
114                    + 3600 * 1000 * 24 * 365));
115        gen.setNotBefore(new Date(System.currentTimeMillis()));
116        gen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
117        gen.addAttribute(new X509Attribute(attrOID, 
118                    new DERSequence(
119                        new DERSequence(
120                            new DERUTF8String(toString())))));
121        gen.setSignatureAlgorithm("SHA256WithRSAEncryption");
122
123        try { 
[e9360e2]124            // Creddy expects an authority key identifier.
125            gen.addExtension(authKeyOID, false, 
126                    new AuthorityKeyIdentifier(pki));
127            // Create the cert.
[281158a]128            m_ac = (X509V2AttributeCertificate) gen.generate(key, "BC");
129        }
130        catch (Exception e) { 
131            System.err.println(e);
132        }
133    }
[7ef13e3]134
[90f939f]135    /**
136     * Load the roles off the attribute cert. Throws a RuntimeException if
137     * there's something wrong with the cert.
138     */
139    private void load_roles() throws RuntimeException {
140        String roles = null;
141        try {
142            X509Attribute attr = m_ac.getAttributes()[0];
143
144            DERSequence    java     = (DERSequence)attr.getValues()[0];
145            DERSequence    fucking  = (DERSequence)java.getObjectAt(0);
146            DERUTF8String  sucks    = (DERUTF8String)fucking.getObjectAt(0);
147
148            roles = sucks.getString();
149        }
150        catch (Exception e) {
151            throw new RuntimeException("Your attribute certificate is funky and I'm not gonna debug it", e);
152        }
153
154        String[] parts = roles.split("\\s*<--?\\s*");
155        if (parts.length != 2)
156            throw new RuntimeException("Invalid attribute: " + roles);
157
158        m_head = new Role(parts[0]);
159        m_tail = new Role(parts[1]);
160    }
161
[cfcdcb4b]162    /**
163     * Two credentials are the same if their roles are the same.
164     */
165    public boolean equals(Object o) {
166        if ( o instanceof Credential ) {
167            Credential c = (Credential) o;
168
169            if (m_head == null || m_tail == null ) return false;
170            else return (m_head.equals(c.head()) && m_tail.equals(c.tail()));
171        }
172        else return false;
173    }
174
[88e139a]175    public int compareTo(Object o) {
176        if (o instanceof Credential) {
177            Credential c = (Credential) o;
178
179            if (head().equals(c.head())) return tail().compareTo(c.tail());
180            else return head().compareTo(c.head());
181        }
182        else return 1;
183    }
184
185
[31b67d5]186    /**
187     * Get the head role from the credential.
188     */
189    public Role head() {
190        return m_head;
191    }
192
193    /**
194     * Get the tail role from the credential
195     */
196    public Role tail() {
197        return m_tail;
198    }
199
[90f939f]200    /**
201     * Gets the cert associated with this credential (if any).
202     */
203    public X509V2AttributeCertificate cert() {
204        return m_ac;
205    }
206
[31b67d5]207    /**
208     * Turn the credential into string form. The format is head &lt;- tail. For
209     * example: A.r1 &lt;- B.r2.r3.
210     */
211    public String toString() {
212        return m_head + " <- " + m_tail;
213    }
214
[de63a31]215    public String simpleString() {
216        return m_head.simpleString() + " <- " + m_tail.simpleString();
217    }
218
[1a7e6d3]219    public void write(OutputStream s) throws IOException {
220        s.write(m_ac.getEncoded());
[e9360e2]221        s.flush();
[1a7e6d3]222    }
223
224    public void write(String fn) throws IOException, FileNotFoundException {
225        write(new FileOutputStream(fn));
226    }
227
[f6789db]228    public boolean hasCertificate() { return m_ac != null; }
229
[5cf72cc]230    public Identity getID() { return m_id; }
231
232    /**
233     * Import a zip file.  First import all the identities
234     * (pem), then the credentials (der) into the credential graph then any
235     * alias files into the two maps.  If keys is not null, any key pairs in
236     * PEM files are put in there.  If errors is not null, errors reading files
237     * are added indexed by filename.
238     */
[e1c49ce]239    static public Collection<Credential> readZipFile(File zf, 
240            Collection<KeyPair> keys, Map<String, Exception> errors) 
241                throws IOException {
[5cf72cc]242        Vector<Credential> creds = new Vector<Credential>();
[42ca4b8]243        Vector<ZipEntry> derEntries = new Vector<ZipEntry>();
244        Map<String, Identity> ids = new TreeMap<String, Identity>();
245        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
[5cf72cc]246
[e1c49ce]247        ZipFile z = new ZipFile(zf);
248
[5cf72cc]249        for (Enumeration<? extends ZipEntry> ze = z.entries(); 
250                ze.hasMoreElements();) {
251            ZipEntry  f = ze.nextElement();
252            try {
[42ca4b8]253                PEMReader r = new PEMReader(
254                        new InputStreamReader(z.getInputStream(f)));
255                Object o = readPEM(r);
256
257                if ( o != null ) {
258                    if (o instanceof Identity) {
259                        Identity i = (Identity) o;
260                        String kid = i.getKeyID();
261
262                        if (kps.containsKey(kid) ) {
263                            i.setKeyPair(kps.get(kid));
264                            kps.remove(kid);
265                        }
266                        else if (i.getKeyPair() == null ) 
267                            ids.put(i.getKeyID(), i);
268
269                        Credential.addIdentity(i);
270                    }
271                    else if (o instanceof KeyPair ) {
272                        KeyPair kp = (KeyPair) o;
273                        String kid = Identity.extractKeyID(kp.getPublic());
274
275                        if (ids.containsKey(kid)) {
276                            Identity i = ids.get(kid);
277
278                            i.setKeyPair(kp);
279                            ids.remove(kid);
280                        }
281                        else {
282                            kps.put(kid, kp);
283                        }
284                    }
[5cf72cc]285                }
286                else {
[42ca4b8]287                    // Not a PEM file
288                    derEntries.add(f);
289                    continue;
[5cf72cc]290                }
291            }
292            catch (Exception e ) {
293                if (errors != null ) errors.put(f.getName(), e);
294            }
295        }
296
[42ca4b8]297        for ( ZipEntry f : derEntries ) {
[5cf72cc]298            try {
299                creds.add(new Credential(z.getInputStream(f)));
300            }
301            catch (Exception e ) {
302                if (errors != null ) errors.put(f.getName(), e);
303            }
304        }
305        return creds;
306    }
307
[e1c49ce]308    static public Collection<Credential> readZipFile(File d) 
309            throws IOException {
[5cf72cc]310        return readZipFile(d, null, null);
311    }
[e1c49ce]312    static public Collection<Credential> readZipFile(File d, 
313            Map<String, Exception> errors) throws IOException {
[5cf72cc]314        return readZipFile(d, null, errors);
315    }
[e1c49ce]316    static public Collection<Credential> readZipFile(File d, 
317            Collection<KeyPair> keys) throws IOException {
[5cf72cc]318        return readZipFile(d, keys, null);
319    }
320
[42ca4b8]321    protected static Object readPEM(PEMReader r) throws IOException {
322        Identity i = null;
323        KeyPair keys = null;
324        Object o = null;
325
326        while ( (o = r.readObject()) != null ) {
327            if (o instanceof X509CertificateObject) {
328                if ( i == null ) {
329                    try {
330                        i = new Identity((X509CertificateObject)o);
331                    }
332                    catch (Exception e) {
333                        // Translate Idenitiy exceptions to IOException
334                        throw new IOException(e);
335                    }
336                    if (keys != null ) {
337                        i.setKeyPair(keys);
338                        keys = null;
339                    }
340                }
341                else throw new IOException("Two certificates");
342            }
343            else if (o instanceof KeyPair ) {
344                if ( i != null ) i.setKeyPair((KeyPair) o);
345                else keys = (KeyPair) o;
346            }
347            else {
348                throw new IOException("Unexpected PEM object: " + 
349                        o.getClass().getName());
350            }
351        }
352
353        if ( i != null ) return i;
354        else if ( keys != null) return keys;
355        else return null;
356    }
[5cf72cc]357
[be05757]358    /**
359     * Import a directory full of files.  First import all the identities
360     * (pem), then the credentials (der) into the credential graph then any
361     * alias files into the two maps.  If keys is not null, any key pairs in
362     * PEM files are put in there.  If errors is not null, errors reading files
363     * are added indexed by filename.
364     */
365    static public Collection<Credential> readDirectory(File d, 
[5cf72cc]366            Collection<KeyPair> keys, Map<String, Exception> errors) {
[be05757]367        Vector<Credential> creds = new Vector<Credential>();
368        Vector<File> derFiles = new Vector<File>();
[e1c49ce]369        Collection<File> files = new Vector<File>();
[42ca4b8]370        Map<String, Identity> ids = new TreeMap<String, Identity>();
371        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
[e1c49ce]372
373        if (d.isDirectory() ) 
374            for (File f : d.listFiles()) 
375                files.add(f);
376        else files.add(d);
[be05757]377
[e1c49ce]378        for (File f: files ) {
[be05757]379            try {
[42ca4b8]380                PEMReader r = new PEMReader(new FileReader(f));
381                Object o = readPEM(r);
382
383                if ( o != null ) {
384                    if (o instanceof Identity) {
385                        Identity i = (Identity) o;
386                        String kid = i.getKeyID();
387
388                        if (kps.containsKey(kid) ) {
389                            i.setKeyPair(kps.get(kid));
390                            kps.remove(kid);
391                        }
392                        else if (i.getKeyPair() == null ) 
393                            ids.put(i.getKeyID(), i);
394
395                        Credential.addIdentity(i);
396                    }
397                    else if (o instanceof KeyPair ) {
398                        KeyPair kp = (KeyPair) o;
399                        String kid = Identity.extractKeyID(kp.getPublic());
400
401                        if (ids.containsKey(kid)) {
402                            Identity i = ids.get(kid);
403
404                            i.setKeyPair(kp);
405                            ids.remove(kid);
406                        }
407                        else {
408                            kps.put(kid, kp);
409                        }
410                    }
[be05757]411                }
412                else {
[42ca4b8]413                    // Not a PEM file
414                    derFiles.add(f);
415                    continue;
[be05757]416                }
417            }
418            catch (Exception e ) {
[5cf72cc]419                if (errors != null ) errors.put(f.getName(), e);
[be05757]420            }
421        }
422
423        for ( File f : derFiles ) {
424            try {
425                creds.add(new Credential(f));
426            }
427            catch (Exception e ) {
[5cf72cc]428                if (errors != null ) errors.put(f.getName(), e);
[be05757]429            }
430        }
431        return creds;
432    }
433
434    static public Collection<Credential> readDirectory(File d) {
435        return readDirectory(d, null, null);
436    }
437    static public Collection<Credential> readDirectory(File d, 
[5cf72cc]438            Map<String, Exception> errors) {
[be05757]439        return readDirectory(d, null, errors);
440    }
441    static public Collection<Credential> readDirectory(File d, 
442            Collection<KeyPair> keys) {
443        return readDirectory(d, keys, null);
444    }
445
[5cf72cc]446    static public void writeZipFile(Collection<Credential> creds, File f,
[8a93b41]447            boolean allIDs, boolean withPrivateKeys) 
[5cf72cc]448            throws IOException {
449        ZipOutputStream z = new ZipOutputStream(new FileOutputStream(f));
450        Set<Identity> ids = allIDs ? 
[8a6181b]451            new TreeSet<Identity>(s_ids) : new TreeSet<Identity>();
[5cf72cc]452
453        int n = 0;
454        for (Credential c: creds) {
455            z.putNextEntry(new ZipEntry("attr" + n++  + ".der"));
456            c.write(z);
457            z.closeEntry();
458            if ( c.getID() != null && !allIDs) ids.add(c.getID());
459        }
460        for (Identity i: ids) {
461            z.putNextEntry(new ZipEntry(i.getName() + ".pem"));
462            i.write(z);
[8a93b41]463            if (withPrivateKeys)
464                i.writePrivateKey(z);
[5cf72cc]465            z.closeEntry();
466        }
467        z.close();
468    }
469
470
[be05757]471private Role m_head, m_tail;
[90f939f]472
[be05757]473private X509V2AttributeCertificate m_ac;
474private Identity m_id;
[7ef13e3]475
[e1c49ce]476    /**
477     * Put the Identity into the set of ids used to validate certificates.
478     * Also put the keyID and name into the translation mappings used by Roles
479     * to pretty print.  In the role mapping, if multiple ids use the same
480     * common name they are disambiguated.  Only one entry for keyid is
481     * allowed.
482     */
[de63a31]483    public static void addIdentity(Identity id) { 
484        s_ids.add(id);
[e1c49ce]485        if (id.getName() != null && id.getKeyID() != null) {
486            if ( !Role.key_in_mapping(id.getKeyID()) ) {
487                String name = id.getName();
488                int n= 1;
489
490                while (Role.name_in_mapping(name)) {
491                    name = id.getName() + n++;
492                }
493                Role.add_mapping(name, id.getKeyID());
494            }
495        }
[de63a31]496    }
[9725efb]497    public static Collection<Identity> identities() { return s_ids; }
[e1c49ce]498    public static void clearIdentities() {
499        s_ids.clear(); Role.clear_mapping();
500    }
[31b67d5]501}
Note: See TracBrowser for help on using the repository browser.