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
Line 
1package net.deterlab.abac;
2
3import java.io.*;
4import java.math.*;
5
6import java.util.*;
7import java.util.zip.*;
8import java.security.*;
9import java.security.cert.*;
10
11import net.deterlab.abac.Identity;
12
13import org.bouncycastle.asn1.*;
14import org.bouncycastle.asn1.x509.*;
15import org.bouncycastle.x509.*;
16import org.bouncycastle.jce.X509Principal;
17import org.bouncycastle.jce.provider.X509AttrCertParser;
18import org.bouncycastle.jce.provider.X509CertificateObject;
19import org.bouncycastle.openssl.PEMReader;
20
21import java.security.PrivateKey;
22
23public class Credential implements Comparable {
24    protected static Vector<Identity> s_ids = new Vector<Identity>();
25    protected static String attrOID = "1.3.6.1.5.5.7.10.4";
26    protected static String authKeyOID = "2.5.29.35";
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    }
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;
44        m_ac = null; 
45        m_id = null;
46    }
47
48    /**
49     * Do the credential initialization from a filename.
50     */
51    protected void init(InputStream stream) throws Exception {
52        X509AttrCertParser parser = new X509AttrCertParser();
53        parser.engineInit(stream);
54        m_ac = (X509V2AttributeCertificate)parser.engineRead();
55        m_id = null;
56
57        if ( m_ac == null ) throw new IOException("Invalid Credential Format");
58
59        for (Identity id: s_ids) {
60            try {
61                m_ac.verify(id.getCertificate().getPublicKey(), "BC");
62                m_id = id;
63                break;
64            }
65            catch (InvalidKeyException e) { }
66        }
67        if (m_id == null) throw new InvalidKeyException("Unknown identity");
68
69        load_roles();
70
71        if (!m_id.getKeyID().equals(m_head.issuer_part()))
72            throw new InvalidKeyException("Unknown identity");
73    }
74
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 {
80        init(new FileInputStream(filename));
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 {
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);
96    }
97
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());
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 { 
124            // Creddy expects an authority key identifier.
125            gen.addExtension(authKeyOID, false, 
126                    new AuthorityKeyIdentifier(pki));
127            // Create the cert.
128            m_ac = (X509V2AttributeCertificate) gen.generate(key, "BC");
129        }
130        catch (Exception e) { 
131            System.err.println(e);
132        }
133    }
134
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
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
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
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
200    /**
201     * Gets the cert associated with this credential (if any).
202     */
203    public X509V2AttributeCertificate cert() {
204        return m_ac;
205    }
206
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
215    public String simpleString() {
216        return m_head.simpleString() + " <- " + m_tail.simpleString();
217    }
218
219    public void write(OutputStream s) throws IOException {
220        s.write(m_ac.getEncoded());
221        s.flush();
222    }
223
224    public void write(String fn) throws IOException, FileNotFoundException {
225        write(new FileOutputStream(fn));
226    }
227
228    public boolean hasCertificate() { return m_ac != null; }
229
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     */
239    static public Collection<Credential> readZipFile(File zf, 
240            Collection<KeyPair> keys, Map<String, Exception> errors) 
241                throws IOException {
242        Vector<Credential> creds = new Vector<Credential>();
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>();
246
247        ZipFile z = new ZipFile(zf);
248
249        for (Enumeration<? extends ZipEntry> ze = z.entries(); 
250                ze.hasMoreElements();) {
251            ZipEntry  f = ze.nextElement();
252            try {
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                    }
285                }
286                else {
287                    // Not a PEM file
288                    derEntries.add(f);
289                    continue;
290                }
291            }
292            catch (Exception e ) {
293                if (errors != null ) errors.put(f.getName(), e);
294            }
295        }
296
297        for ( ZipEntry f : derEntries ) {
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
308    static public Collection<Credential> readZipFile(File d) 
309            throws IOException {
310        return readZipFile(d, null, null);
311    }
312    static public Collection<Credential> readZipFile(File d, 
313            Map<String, Exception> errors) throws IOException {
314        return readZipFile(d, null, errors);
315    }
316    static public Collection<Credential> readZipFile(File d, 
317            Collection<KeyPair> keys) throws IOException {
318        return readZipFile(d, keys, null);
319    }
320
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    }
357
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, 
366            Collection<KeyPair> keys, Map<String, Exception> errors) {
367        Vector<Credential> creds = new Vector<Credential>();
368        Vector<File> derFiles = new Vector<File>();
369        Collection<File> files = new Vector<File>();
370        Map<String, Identity> ids = new TreeMap<String, Identity>();
371        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
372
373        if (d.isDirectory() ) 
374            for (File f : d.listFiles()) 
375                files.add(f);
376        else files.add(d);
377
378        for (File f: files ) {
379            try {
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                    }
411                }
412                else {
413                    // Not a PEM file
414                    derFiles.add(f);
415                    continue;
416                }
417            }
418            catch (Exception e ) {
419                if (errors != null ) errors.put(f.getName(), e);
420            }
421        }
422
423        for ( File f : derFiles ) {
424            try {
425                creds.add(new Credential(f));
426            }
427            catch (Exception e ) {
428                if (errors != null ) errors.put(f.getName(), e);
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, 
438            Map<String, Exception> errors) {
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
446    static public void writeZipFile(Collection<Credential> creds, File f,
447            boolean allIDs, boolean withPrivateKeys) 
448            throws IOException {
449        ZipOutputStream z = new ZipOutputStream(new FileOutputStream(f));
450        Set<Identity> ids = allIDs ? 
451            new TreeSet<Identity>(s_ids) : new TreeSet<Identity>();
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);
463            if (withPrivateKeys)
464                i.writePrivateKey(z);
465            z.closeEntry();
466        }
467        z.close();
468    }
469
470
471private Role m_head, m_tail;
472
473private X509V2AttributeCertificate m_ac;
474private Identity m_id;
475
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     */
483    public static void addIdentity(Identity id) { 
484        s_ids.add(id);
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        }
496    }
497    public static Collection<Identity> identities() { return s_ids; }
498    public static void clearIdentities() {
499        s_ids.clear(); Role.clear_mapping();
500    }
501}
Note: See TracBrowser for help on using the repository browser.