source: java/net/deterlab/abac/Context.java @ 3612811

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

More reading and writing working. About to revamp CredentialFactory?
again

  • Property mode set to 100644
File size: 33.6 KB
Line 
1package net.deterlab.abac;
2
3import edu.uci.ics.jung.graph.*;
4import edu.uci.ics.jung.graph.util.*;
5
6import java.io.*;
7import java.util.*;
8import java.util.regex.*;
9import java.util.zip.*;
10import java.security.*;
11import java.security.cert.*;
12
13import org.bouncycastle.asn1.*;
14import org.bouncycastle.asn1.x509.*;
15import org.bouncycastle.x509.*;
16import org.bouncycastle.openssl.*;
17import org.bouncycastle.jce.provider.BouncyCastleProvider;
18
19/**
20 * Represents a global graph of credentials in the form of principals and
21 * attributes.  Contains the identities and credentials that can be used in a
22 * proof.
23 * @author <a href="http://abac.deterlab.net">ISI ABAC team</a>
24 * @version 1.3
25 */
26public class Context {
27    /** Certificate imported successfully */
28    public static final int ABAC_CERT_SUCCESS = 0;
29    /** Certificate import failed, invalid certificate */
30    public static final int ABAC_CERT_INVALID = -1;
31    /** Certificate import failed, signature filed */
32    public static final int ABAC_CERT_BAD_SIG = -2;
33    /** Certificate import failed, unknown issuer */
34    public static final int ABAC_CERT_MISSING_ISSUER = -3;
35
36    /** Internal graph representation */
37    protected Graph<Role,Credential> g;
38    /** Set of edges in the graph that were added by the logic.  */
39    protected Set<Credential> derived_edges;
40    /** Internal persistent query object */
41    protected Query pq;
42    /** True when the graph has been changed since the last set of implied
43     * edges were calculated. */
44    protected boolean dirty;
45    /** Set of identities known to this Context. */
46    protected Set<Identity> m_identities;
47
48    /** Translation from issuer CN to issuer pubkey identifier */
49    protected Map<String, String> nicknames;
50    /** Translation from issuer pubkey identifier to issuer CN */
51    protected Map<String, String> keys;
52
53    /** True once BouncyCastle has been loaded. */
54    static boolean providerLoaded = false;
55
56    /**
57     * Load the BouncyCastle provider, necessary for ABAC crypto (shouldn't
58     * need to be called directly).  This is called from the Context static
59     * constructor and static constructors in other ABAC classes that use
60     * BouncyCastle crypto (which loads a Context, which calls it as well) to
61     * make sure crypto is available. 
62     */
63    static void loadBouncyCastle() {
64        if ( !providerLoaded ) {
65            AccessController.doPrivileged(new PrivilegedAction<Object>() {
66                public Object run() {
67                    Security.addProvider(new BouncyCastleProvider());
68                    return null;
69                }
70            });
71            providerLoaded = true;
72        }
73    }
74
75    /** Load the BouncyCastle provider. */
76    static { loadBouncyCastle(); };
77
78    /**
79     * The result of a query on this context.  The credentials form a partial
80     * or total proof, and success indicates whether the proof succeeded.
81     * @author <a href="http://abac.deterlab.net">ISI ABAC team</a>
82     * @version 1.3
83     */
84    public class QueryResult {
85        /** Credentials returned */
86        protected Collection<Credential> creds;
87        /** True if the proof succeeded. */
88        protected boolean success;
89
90        /**
91         * Construct a result from components.
92         * @param c the collection of credentials in the proof
93         * @param s a boolean, true if the query succeeded.
94         */
95        QueryResult(Collection<Credential> c, boolean s) {
96            creds = c;
97            success = s;
98        }
99
100        /**
101         * Empty constructor
102         */
103        public QueryResult() { 
104            creds = new TreeSet<Credential>();
105            success = false;
106        }
107
108        /**
109         * Return the credentials in the proof.
110         * @return the collection of credentials
111         */
112        public Collection<Credential> getCredentials() { return creds; }
113        /**
114         * Return the success in the proof.
115         * @return the boolean, true on success
116         */
117        public boolean getSuccess() { return success; }
118    }
119
120
121    /**
122     * Create an empty Context.
123     */
124    public Context() {
125        /* create the graph */
126        g = Graphs.<Role,Credential>synchronizedDirectedGraph(
127                new DirectedSparseGraph<Role,Credential>());
128        derived_edges = new HashSet<Credential>();
129        pq = new Query(g);
130        dirty = false;
131        m_identities = new TreeSet<Identity>();
132        nicknames = new TreeMap<String, String>();
133        keys = new TreeMap<String, String>();
134    }
135
136    /**
137     * Create a context from another context.
138     * @param c the Context to copy
139     */
140    public Context(Context c) {
141        this();
142        for (Identity i: c.m_identities) 
143            load_id_chunk(i);
144        for (Credential cr: c.credentials()) 
145            load_attribute_chunk(cr);
146        derive_implied_edges();
147    }
148
149    /**
150     * Create a Context from a collection of Credentials.  A jabac extension.
151     * @param creds the collection of credentials
152     */
153    public Context(Collection<Credential> creds) {
154        this();
155        for (Credential c: creds) {
156            Identity i = c.issuer();
157
158            if (i != null ) load_id_chunk(i);
159            load_attribute_chunk(c);
160        }
161    }
162
163    /**
164     * Load an Identity from a file.
165     * @param fn a String containing the file name.
166     * @return one of the static int return codes.
167     */
168    public int load_id_file(String fn) { return load_id_chunk(new File(fn)); }
169    /**
170     * Load an Identity from a file.
171     * @param fn a File containing the file name.
172     * @return one of the static int return codes.
173     */
174    public int load_id_file(File fn) { return load_id_chunk(fn); }
175    /**
176     * Load an Identity from an object.  Supported objects are an Identity, a
177     * String, a File, or a java.security.cert.X509Certificate.  A string
178     * creates an new identity, and the others are derived from the contents of
179     * the data or file.
180     * @param c an object convertable to an identity as above.
181     * @return one of the static int return codes.
182     */
183    public int load_id_chunk(Object c) {
184        try {
185            if (c instanceof Identity)
186                addIdentity((Identity) c);
187            else if (c instanceof String) 
188                addIdentity(new Identity((String) c));
189            else if (c instanceof File) 
190                addIdentity(new Identity((File) c));
191            else if (c instanceof X509Certificate) 
192                addIdentity(new Identity((X509Certificate) c));
193            else 
194                return ABAC_CERT_INVALID;
195        }
196        catch (SignatureException sig) {
197            return ABAC_CERT_BAD_SIG;
198        }
199        catch (Exception e) {
200            return ABAC_CERT_INVALID;
201        }
202        return ABAC_CERT_SUCCESS;
203    }
204
205    /**
206     * Load an attribute certificate from a file.
207     * @param fn a String containing the file name.
208     * @return one of the static int return codes.
209     */
210    public int load_attribute_file(String fn) { 
211        return load_attribute_chunk(new File(fn));
212    }
213
214    /**
215     * Load an attribute certificate from a file.
216     * @param fn a File containing the file name.
217     * @return one of the static int return codes.
218     */
219    public int load_attribute_file(File fn) { return load_attribute_chunk(fn); }
220
221    /**
222     * Load an Credential from an object.  Supported objects are a Credential, a
223     * String, a File, or an org.bouncycastle.x509.X509V2AttributeCertificate.
224     * A string creates an new Credential, and the others are derived from the
225     * contents of the data or file.
226     * @param c an object convertable to a Credential as above.
227     * @return one of the static int return codes.
228     */
229    public int load_attribute_chunk(Object c) {
230        try {
231            Credential[] creds = null;
232
233            if (c instanceof Credential) {
234                add_credential((Credential) c);
235                return ABAC_CERT_SUCCESS;
236            } else if (c instanceof String) {
237                creds = CredentialFactory.newInstance((String) c, m_identities);
238            } else if (c instanceof File) {
239                creds = CredentialFactory.newInstance((File) c, m_identities);
240            } else if ( c instanceof X509V2AttributeCertificate)  {
241                add_credential(new X509Credential((X509V2AttributeCertificate)c,
242                            m_identities));
243                return ABAC_CERT_SUCCESS;
244            } else return ABAC_CERT_INVALID;
245
246            //XXX:
247            if ( creds == null ) 
248                return ABAC_CERT_INVALID;
249
250            for (Credential cc: creds ) 
251                add_credential(cc);
252        }
253        catch (MissingIssuerException sig) {
254            return ABAC_CERT_MISSING_ISSUER ;
255        }
256        catch (BadSignatureException sig) {
257            return ABAC_CERT_BAD_SIG;
258        }
259        catch (CertInvalidException e) {
260            return ABAC_CERT_INVALID;
261        }
262        catch (ABACException ae) {
263            return ABAC_CERT_INVALID;
264        }
265        return ABAC_CERT_SUCCESS;
266    }
267
268    /**
269     * Determine if prinicpal possesses role in the current context.  If so,
270     * return a proof of that, otherwise return a partial proof of it.
271     * @param role a String encoding the role to check for.
272     * @param principal a String with the principal ID in it.
273     * @return a Context.QueryResult containing the result.
274     */
275    public QueryResult query(String role, String principal) {
276        derive_implied_edges();
277
278        Query q = new Query(g);
279        Graph<Role, Credential> rg = q.run(role, principal);
280
281        return new QueryResult(rg.getEdges(), q.successful());
282    }
283
284    /**
285     * Return a collection of the credentials in the graph.s
286     * @return a collection of the credentials in the graph.
287     */
288    public Collection<Credential> credentials() {
289        Collection<Credential> creds = new HashSet<Credential>();
290
291        // only non-derived edges
292        for (Credential cred : g.getEdges())
293            if (!derived_edges.contains(cred))
294                creds.add(cred);
295
296        return creds;
297    }
298
299    /**
300     * Return all the Identities known in this context.  A jabac extension.
301     * @return all the Identities known in this context.
302     */
303    public Collection<Identity> identities() {
304        return m_identities;
305    }
306
307    /**
308     * Returns true if the given Identity is known in this Context.  A jabac
309     * extension.
310     * @param i the Identity to look for
311     * @return a boolean, true if the Identity is known.
312     */
313    public boolean knowsIdentity(Identity i) { return m_identities.contains(i);}
314    /**
315     * Returns true if an Identity with the given string representation is
316     * known in this Context.  A jabac extension.
317     * @param k the string representing the Identity to look for
318     * @return a boolean, true if the Identity is known.
319     */
320    public boolean knowsKeyID(String k) {
321        boolean known = false;
322        for (Identity i: m_identities)
323            if (k.equals(i.getKeyID())) return true;
324        return false;
325    }
326
327
328    /**
329     * Add a credential to the graph.
330     * @param cred the Credential to add
331     */
332    protected void add_credential(Credential cred) {
333        Role tail = cred.tail();
334        Role head = cred.head();
335
336        // explicitly add the vertices, to avoid a null pointer exception
337        if ( !g.containsVertex(head)) 
338            g.addVertex(head);
339        if ( !g.containsVertex(tail)) 
340            g.addVertex(tail);
341
342        if (!g.containsEdge(cred))
343            g.addEdge(cred, tail, head);
344
345        // add the prereqs of an intersection to the graph
346        if (tail.is_intersection())
347            for (Role prereq : tail.prereqs())
348                g.addVertex(prereq);
349
350        dirty = true;
351    }
352
353    /**
354     * Remove a credential from the graph.
355     * @param cred the Credential to remove
356     */
357    protected void remove_credential(Credential cred) {
358        if (g.containsEdge(cred))
359            g.removeEdge(cred);
360        dirty = true;
361    }
362
363    /**
364     * Add a role w/o an edge
365     * @param v the Role to add
366     */
367    protected void add_vertex(Role v) {
368        if (!g.containsVertex(v)) {
369            g.addVertex(v);
370            dirty = true;
371        }
372    }
373
374    /**
375     * Remove a role and connected edges.
376     * @param v the Role to remove
377     */
378    protected void remove_vertex(Role v) {
379        if (g.containsVertex(v)) {
380            g.removeVertex(v);
381            dirty = true;
382        }
383    }
384
385    /**
386     * Derive the implied edges in the graph, according to RT0 derivation rules.
387     * They are added to this graph. See "Distributed Credential Chain Discovery
388     * in Trust Management" by Ninghui Li et al. for details. Note that a
389     * derived linking edge can imply a new intersection edge and vice versa.
390     * Therefore we iteratively derive edges, giving up when an iteration
391     * produces 0 new edges.
392     */
393    protected synchronized void derive_implied_edges() {
394        // nothing to do on a clean graph
395        if (!dirty)
396            return;
397
398        clear_old_edges();
399
400        // iteratively derive links. continue as long as new links are added
401        while (derive_links_iter() > 0)
402            ;
403        dirty = false;
404    }
405
406    /**
407     * Single iteration of deriving implied edges. Returns the number of new
408     * links added.
409     * @return the number of new links added.
410     */
411    protected int derive_links_iter() {
412        int count = 0;
413
414        /* for every node in the graph.. */
415        for (Role vertex : g.getVertices()) {
416            if (vertex.is_intersection()) {
417                // for each prereq edge:
418                //     find set of principals that have the prereq
419                // find the intersection of all sets (i.e., principals
420                //     that satisfy all prereqs)
421                // for each principal in intersection:
422                //     add derived edge
423
424                Set<Role> principals = null;
425
426                for (Role prereq : vertex.prereqs()) {
427                    Set<Role> cur_principals = pq.find_principals(prereq);
428
429                    if (principals == null)
430                        principals = cur_principals;
431                    else
432                        // no, they couldn't just call it "intersection"
433                        principals.retainAll(cur_principals);
434
435                    if (principals.size() == 0)
436                        break;
437                }
438
439                // add em
440                for (Role principal : principals)
441                    if (add_derived_edge(vertex, principal))
442                        ++count;
443            }
444
445            else if (vertex.is_linking()) {
446                // make the rest of the code a bit clearer
447                Role A_r1_r2 = vertex;
448
449                Role A_r1 = new Role(A_r1_r2.A_r1());
450                String r2 = A_r1_r2.r2();
451
452                /* locate the node A.r1 */
453                if (!g.containsVertex(A_r1)) continue; 
454
455                /* for each B that satisfies A_r1 */
456                for (Role principal : pq.find_principals(A_r1)) {
457                    Role B_r2 = new Role(principal + "." + r2);
458                    if (!g.containsVertex(B_r2)) continue;
459
460                    if (add_derived_edge(A_r1_r2, B_r2))
461                        ++count;
462                }
463            }
464        }
465
466        return count;
467    }
468
469    /**
470     * Add a derived edge in the graph. Returns true only if the edge does not
471     * exist.
472     * @return a boolean, true if an edge has been added
473     */
474    protected boolean add_derived_edge(Role head, Role tail) {
475        // edge exists: return false
476        if (g.findEdge(tail, head) != null)
477            return false;
478
479        // add the new edge
480        Credential derived_edge = new InternalCredential(head, tail);
481        derived_edges.add(derived_edge);
482        g.addEdge(derived_edge, tail, head);
483
484        return true;
485    }
486
487    /**
488     * Clear the derived edges that currently exist in the graph. This is done
489     * before the edges are rederived. The derived edges in filtered graphs are
490     * also cleared.
491     */
492    protected void clear_old_edges() { 
493        for (Credential i: derived_edges) 
494            g.removeEdge(i);
495        derived_edges = new HashSet<Credential>();
496    }
497    /**
498     * Put the Identity into the set of ids used to validate certificates.
499     * Also put the keyID and name into the translation mappings used by Roles
500     * to pretty print.  In the role mapping, if multiple ids use the same
501     * common name they are disambiguated.  Only one entry for keyid is
502     * allowed.
503     * @param id the Identity to add
504     */
505    protected void addIdentity(Identity id) { 
506        m_identities.add(id);
507        if (id.getName() != null && id.getKeyID() != null) {
508            if ( !keys.containsKey(id.getKeyID()) ) {
509                String name = id.getName();
510                int n= 1;
511
512                while (nicknames.containsKey(name)) {
513                    name = id.getName() + n++;
514                }
515                nicknames.put(name, id.getKeyID());
516                keys.put(id.getKeyID(), name);
517            }
518        }
519    }
520    /**
521     * Translate either keys to nicknames or vice versa.  Break the string into
522     * space separated tokens and then each of them into period separated
523     * strings.  If any of the smallest strings is in the map, replace it with
524     * the value.
525     * @param is the string to manipulate
526     * @param m the Map containing translations
527     * @return the string after modification
528     */
529    protected String replace(String is, Map<String, String> m) {
530        String rv = "";
531        for (String tok: is.split(" ")) {
532            String term = "";
533            for (String s: tok.split("\\.")) {
534                String next = m.containsKey(s) ? m.get(s) : s;
535
536                if (term.isEmpty()) term = next;
537                else term += "." + next;
538            }
539            if (rv.isEmpty()) rv = term;
540            else rv += " " + term;
541        }
542        return rv;
543    }
544
545    /**
546     * Expand menmonic names in a Role string, e.g. the CN of the issuer
547     * certificate, into the full key ID.  Used internally by Roles to provide
548     * transparent use of mnemonics
549     * @param s the string to expand
550     * @return the String after expansion.
551     */
552    String expandKeyID(String s) { return replace(s, nicknames); }
553    /**
554     * Convert key IDs to  menmonic names in a Role string.  The inverse of
555     * expandKeyID.
556     * @param s the string to expand
557     * @return the String after expansion.
558     */
559    String expandNickname(String s) { return replace(s, keys); }
560
561    /**
562     * Read the current ZipEntry's bytes from z.  Tedious because there's no
563     * way to reliably tell how big the entry is, so we have to rely on a
564     * simple expanding array read of the bytes.
565     */
566    protected byte[] readCurrentZipEntry(ZipInputStream z) throws IOException {
567        final int bsize = 4096;
568        byte[] buf = new byte[bsize];
569        byte[] rv = new byte[0];
570        int r = 0;
571
572        // z.read returns -1 at the end of entry
573        while ((r = z.read(buf, 0, bsize)) != -1 ) {
574            byte[] b = new byte[rv.length + r];
575
576            System.arraycopy(rv, 0, b, 0, rv.length);
577            System.arraycopy(buf, 0, b, rv.length, r);
578            rv = b;
579        }
580        return rv;
581    }
582
583    /**
584     * Import a zip file.  First import all the identities
585     * (pem), then the credentials (der) into the credential graph then any
586     * alias files into the two maps.  If keys is not null, any key pairs in
587     * PEM files are put in there.  If errors is not null, errors reading files
588     * are added indexed by filename.  This is a jabac extension.
589     * @param s the InputStream to read
590     * @param keys a Collection into which to insert unmatched keys
591     * @param errors a Map from entry name to generated exception
592     * @throws IOException if the file is unreadable.  Per entry exceptions are
593     *                     returned in the errors parameter.
594     */
595    public void load_zip(InputStream s, Collection<KeyPair> keys, 
596            Map<String, Exception> errors) throws IOException {
597        Map<String, byte[]> derEntries = new HashMap<String, byte[]>();
598        Map<String, Identity> ids = new TreeMap<String, Identity>();
599        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
600        int entries = 0;
601
602        ZipInputStream z = new ZipInputStream(s);
603
604        for (ZipEntry ze = z.getNextEntry(); ze != null; ze = z.getNextEntry()){
605            try {
606                entries++;
607                byte[] buf = readCurrentZipEntry(z);
608                PEMReader r = new PEMReader(
609                        new InputStreamReader(new ByteArrayInputStream(buf)));
610                Object o = readPEM(r);
611
612                if ( o != null ) {
613                    if (o instanceof Identity) {
614                        Identity i = (Identity) o;
615                        String kid = i.getKeyID();
616
617                        if (kps.containsKey(kid) ) {
618                            i.setKeyPair(kps.get(kid));
619                            kps.remove(kid);
620                        }
621                        else if (i.getKeyPair() == null ) 
622                            ids.put(i.getKeyID(), i);
623
624                        load_id_chunk(i);
625                    }
626                    else if (o instanceof KeyPair ) {
627                        KeyPair kp = (KeyPair) o;
628                        String kid = extractKeyID(kp.getPublic());
629
630                        if (ids.containsKey(kid)) {
631                            Identity i = ids.get(kid);
632
633                            i.setKeyPair(kp);
634                            ids.remove(kid);
635                        }
636                        else {
637                            kps.put(kid, kp);
638                        }
639                    }
640                }
641                else {
642                    // Not a PEM file
643                    derEntries.put(ze.getName(),buf);
644                    continue;
645                }
646            }
647            catch (Exception e ) {
648                if (errors != null ) errors.put(ze.getName(), e);
649            }
650        }
651
652        for ( String k: derEntries.keySet() ) {
653            try {
654                Credential[] creds = CredentialFactory.newInstance(
655                            new ByteArrayInputStream(derEntries.get(k)),
656                            m_identities);
657                for (Credential c: creds) 
658                    add_credential(c);
659            }
660            catch (Exception e ) {
661                if (errors != null ) errors.put(k, e);
662            }
663        }
664
665        if (entries == 0) 
666            throw new IOException("Not a ZIP file (or empty ZIP file)");
667    }
668    /**
669     * Equivalent to load_zip(s, null, null).
670     * @param s the InputStream to read
671     * @throws IOException if the file is unreadable. To see per-entry
672     *                      exceptions use a signature with the errors parameter
673     */
674    public void load_zip(InputStream s) 
675            throws IOException {
676        load_zip(s, null, null);
677    }
678    /**
679     * Equivalent to load_zip(s, null, errors).
680     * @param s the InputStream to read
681     * @param errors a Map from entry name to generated exception
682     * @throws IOException if the file is unreadable.  Per entry exceptions are
683     *                     returned in the errors parameter.
684     */
685    public void load_zip(InputStream s, 
686            Map<String, Exception> errors) throws IOException {
687        load_zip(s, null, errors);
688    }
689    /**
690     * Equivalent to load_zip(s, keys, null).
691     * @param s the InputStream to read
692     * @param keys a Collection into which to insert unmatched keys
693     * @throws IOException if the file is unreadable. To see per-entry
694     *                      exceptions use a signature with the errors parameter
695     */
696    public void load_zip(InputStream s, 
697            Collection<KeyPair> keys) throws IOException {
698        load_zip(s, keys, null);
699    }
700
701    /**
702     * Loads a zip file.  Equivalent to
703     * load_zip(new FileInputStream(zf), keys, errors).
704     * @param zf the File to read
705     * @param keys a Collection into which to insert unmatched keys
706     * @param errors a Map from entry name to generated exception
707     * @throws IOException if the file is unreadable.  Per entry exceptions are
708     *                     returned in the errors parameter.
709     */
710    public void load_zip(File zf, Collection<KeyPair> keys, 
711            Map<String, Exception> errors) throws IOException {
712        load_zip(new FileInputStream(zf), keys, errors);
713    }
714    /**
715     * Equivalent to load_zip(d, null, null).
716     * @param d the File to read
717     * @throws IOException if the file is unreadable. To see per-entry
718     *                      exceptions use a signature with the errors parameter
719     */
720    public void load_zip(File d) 
721            throws IOException {
722        load_zip(d, null, null);
723    }
724    /**
725     * Equivalent to load_zip(d, null, errors).
726     * @param d the File to read
727     * @param errors a Map from entry name to generated exception
728     * @throws IOException if the file is unreadable.  Per entry exceptions are
729     *                     returned in the errors parameter.
730     */
731    public void load_zip(File d, 
732            Map<String, Exception> errors) throws IOException {
733        load_zip(d, null, errors);
734    }
735    /**
736     * Equivalent to load_zip(d, keys, null).
737     * @param d the File to read
738     * @param keys a Collection into which to insert unmatched keys
739     * @throws IOException if the file is unreadable. To see per-entry
740     *                      exceptions use a signature with the errors parameter
741     */
742    public void load_zip(File d, 
743            Collection<KeyPair> keys) throws IOException {
744        load_zip(d, keys, null);
745    }
746
747    /**
748     * Read a PEM file that contains an X509 Certificate, a key pair, or both.
749     * If a cert is present it is converted into an Identity.  A key pair is
750     * returned as a java.security.KeyPair and both are returned as an Identity
751     * with an associated key pair.
752     * @param r a PEMReader from which to read
753     * @return an object encoding the contents (as above)
754     * @throws IOException for an unreadable or badly formated input
755     */
756    protected Object readPEM(PEMReader r) throws IOException {
757        Identity i = null;
758        KeyPair keys = null;
759        Object o = null;
760
761        while ( (o = r.readObject()) != null ) {
762            if (o instanceof X509Certificate) {
763                if ( i == null ) {
764                    try {
765                        i = new Identity((X509Certificate)o);
766                    }
767                    catch (Exception e) {
768                        // Translate Idenitiy exceptions to IOException
769                        throw new IOException(e);
770                    }
771                    if (keys != null ) {
772                        i.setKeyPair(keys);
773                        keys = null;
774                    }
775                }
776                else throw new IOException("Two certificates");
777            }
778            else if (o instanceof KeyPair ) {
779                if ( i != null ) i.setKeyPair((KeyPair) o);
780                else keys = (KeyPair) o;
781            }
782            else {
783                throw new IOException("Unexpected PEM object: " + 
784                        o.getClass().getName());
785            }
786        }
787
788        if ( i != null ) return i;
789        else if ( keys != null) return keys;
790        else return null;
791    }
792
793    /**
794     * Import a directory full of files.  First import all the identities
795     * (pem), then the credentials (der) into the credential graph then any
796     * alias files into the two maps.  If keys is not null, any key pairs in
797     * PEM files are put in there.  If errors is not null, errors reading files
798     * are added indexed by filename.  This behaves slightly differently from
799     * the load_directory description in the general libabac documentation.
800     * @param d the File to read.  If it is a directory its contents are read
801     * @param keys a Collection into which to insert unmatched keys
802     * @param errors a Map from entry name to generated exception
803     * @throws IOException if the file is unreadable.  Per file exceptions are
804     *                     returned in the errors parameter.
805     */
806    public void load_directory(File d, Collection<KeyPair> keys, 
807            Map<String, Exception> errors) {
808        Vector<File> derFiles = new Vector<File>();
809        Collection<File> files = new Vector<File>();
810        Map<String, Identity> ids = new TreeMap<String, Identity>();
811        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
812
813        if (d.isDirectory() ) 
814            for (File f : d.listFiles()) 
815                files.add(f);
816        else files.add(d);
817
818        for (File f: files ) {
819            try {
820                PEMReader r = new PEMReader(new FileReader(f));
821                Object o = readPEM(r);
822
823                if ( o != null ) {
824                    if (o instanceof Identity) {
825                        Identity i = (Identity) o;
826                        String kid = i.getKeyID();
827
828                        if (kps.containsKey(kid) ) {
829                            i.setKeyPair(kps.get(kid));
830                            kps.remove(kid);
831                        }
832                        else if (i.getKeyPair() == null ) 
833                            ids.put(i.getKeyID(), i);
834
835                        load_id_chunk(i);
836                    }
837                    else if (o instanceof KeyPair ) {
838                        KeyPair kp = (KeyPair) o;
839                        String kid = extractKeyID(kp.getPublic());
840
841                        if (ids.containsKey(kid)) {
842                            Identity i = ids.get(kid);
843
844                            i.setKeyPair(kp);
845                            ids.remove(kid);
846                        }
847                        else {
848                            kps.put(kid, kp);
849                        }
850                    }
851                }
852                else {
853                    // Not a PEM file
854                    derFiles.add(f);
855                    continue;
856                }
857            }
858            catch (Exception e ) {
859                if (errors != null ) errors.put(f.getName(), e);
860            }
861        }
862
863        for ( File f : derFiles ) {
864            try {
865                Credential[] creds = CredentialFactory.newInstance(f, 
866                        m_identities);
867                for (Credential c: creds) 
868                    add_credential(c);
869            }
870            catch (Exception e ) {
871                if (errors != null ) errors.put(f.getName(), e);
872            }
873        }
874    }
875    /**
876     * Equivalent to load_directory(d, null, null).
877     * @param d the File to read.  If it is a directory its contents are read
878     * @throws IOException if the file is unreadable.  To see per-file
879     *                     exceptions use a signature with the errors parameter.
880     */
881    public void load_directory(File d) {
882        load_directory(d, null, null);
883    }
884    /**
885     * Equivalent to load_directory(d, null, null).
886     * @param d the File to read.  If it is a directory its contents are read
887     * @param errors a Map from entry name to generated exception
888     * @throws IOException if the file is unreadable.  Per file exceptions are
889     *                     returned in the errors parameter.
890     */
891    public void load_directory(File d, Map<String, Exception> errors) {
892        load_directory(d, null, errors);
893    }
894    /**
895     * Equivalent to load_directory(d, null, null).
896     * @param d the File to read.  If it is a directory its contents are read
897     * @param keys a Collection into which to insert unmatched keys
898     * @throws IOException if the file is unreadable.  To see per-file
899     *                     exceptions use a signature with the errors parameter.
900     */
901    public void load_directory(File d, Collection<KeyPair> keys) {
902        load_directory(d, keys, null);
903    }
904
905    /**
906     * Load from a simple rt0 text format.  A jabac extension.  The format is
907     * <br/>
908     * # comments<br/>
909     * role &lt;- role<br/>
910     * <br/>
911     *
912     * Spaces are not significant around the arrow and the tail can be as long
913     * as needed.
914     * @param s the InputStream to load
915     * @throws IOException if there is an error getting the file open or in
916     * format
917     */
918    public void load_rt0(InputStream s) 
919            throws IOException {
920        Pattern comment = Pattern.compile("(^\\s*#|^\\s*$)");
921        Pattern rule = Pattern.compile("([\\w\\.]+)\\s*<-+\\s*(.+)");
922        LineNumberReader r = new LineNumberReader(new InputStreamReader(s));
923        String line = null;
924
925        while ((line = r.readLine()) != null) {
926            Matcher cm = comment.matcher(line);
927            Matcher rm = rule.matcher(line);
928
929            if (cm.find()) continue;
930            if (rm.find()) 
931                add_credential(new InternalCredential(new Role(rm.group(1)), 
932                            new Role(rm.group(2))));
933            else 
934                throw new RuntimeException("Unexpected format: line " + 
935                        r.getLineNumber());
936        }
937    }
938    /**
939     * Equivalent to load_rt0(new FileInputStream(f)
940     * @param f the File to load
941     * @throws IOException if there is an error getting the file open
942     */
943    public void load_rt0(File f) throws IOException {
944        load_rt0(new FileInputStream(f));
945    }
946       
947
948    /**
949     * Write the certificates that make up the context as a zip file, with an
950     * entry for each credential or identity.  The files are all zipped in a
951     * directory derived from the filename.
952     * @param s the OutputStream to write
953     * @param allIDs a boolean, if true write certificates for all Identities,
954     * whether used in signing a credential or not.
955     * @param withPrivateKeys a boolean, if true write the Identities as PEM
956     * file containing both the certificate and the private keys.
957     * @throws IOException if there is a problem writing the file.
958     */
959    public void write_zip(OutputStream s, boolean allIDs, 
960            boolean withPrivateKeys) throws IOException {
961        ZipOutputStream z = new ZipOutputStream(s);
962        Set<Identity> ids = allIDs ?  m_identities : new TreeSet<Identity>();
963        String baseDir = "creds";
964        int idx = baseDir.indexOf('.');
965
966
967        if (idx != -1) 
968            baseDir = baseDir.substring(0, idx);
969
970        int n = 0;
971        for (Credential c: credentials()) {
972            z.putNextEntry(new ZipEntry(baseDir + File.separator + 
973                        "attr" + n++  + ".der"));
974            c.write(z);
975            z.closeEntry();
976            if ( c.issuer() != null && !allIDs) ids.add(c.issuer());
977        }
978        for (Identity i: ids) {
979            z.putNextEntry(new ZipEntry(baseDir + File.separator + 
980                        i.getName() + ".pem"));
981            i.write(z);
982            if (withPrivateKeys)
983                i.writePrivateKey(z);
984            z.closeEntry();
985        }
986        z.close();
987    }
988    /**
989     * Equivalent to
990     * write_zip(new FileOutputStream(f), allIDs, withPrivateKeys).
991     * @param f the File to write
992     * @param allIDs a boolean, if true write certificates for all Identities,
993     * whether used in signing a credential or not.
994     * @param withPrivateKeys a boolean, if true write the Identities as PEM
995     * file containing both the certificate and the private keys.
996     * @throws IOException if there is a problem writing the file.
997     */
998    public void write_zip(File f, boolean allIDs, boolean withPrivateKeys) 
999            throws IOException {
1000        write_zip(new FileOutputStream(f), allIDs, withPrivateKeys);
1001    }
1002
1003    /**
1004     * Write to a simple rt0 text format.  A jabac extension.
1005     * The format is
1006     * <br/>
1007     * role &lt;- role<br/>
1008     * <br/>
1009     *
1010     * @param w a Writer to print on
1011     * @param useKeyIDs a boolean, true to print key IDs not mnemonics
1012     */
1013    public void write_rt0(Writer w, boolean useKeyIDs) {
1014        PrintWriter pw = w instanceof PrintWriter ? 
1015            (PrintWriter) w : new PrintWriter(w);
1016
1017        for (Credential c: credentials()) 
1018            pw.println(useKeyIDs ? c.toString() : c.simpleString(this));
1019        pw.flush();
1020    }
1021
1022    /**
1023     * Call write_rt0 on a FileWriter derived from f.
1024     * @param f the File to write to
1025     * @param useKeyIDs a boolean, true to print key IDs not mnemonics
1026     * @throws IOException if there is a problem writing the file.
1027     */
1028    public void write_rt0(File f, boolean useKeyIDs) throws IOException {
1029        write_rt0(new FileWriter(f), useKeyIDs);
1030    }
1031
1032    /**
1033     * Equivalent to write_rt0(w, false);
1034     * @param w a Writer to print on
1035     */
1036    public void write_rt0(Writer w) { write_rt0(w, false); }
1037
1038    /**
1039     * Equivalent to write_rt0(f, false);
1040     * @param f the File to write to
1041     * @throws IOException if there is a problem writing the file.
1042     */
1043    public void write_rt0(File f) throws IOException {
1044        write_rt0(new FileWriter(f), false);
1045    }
1046
1047    /**
1048     * Get to the SHA1 hash of the key.  Used by Roles and Identities to get a
1049     * key ID.
1050     * @param k the PublicKey to get the ID from.
1051     * @return a String with the key identifier
1052     */
1053    static String extractKeyID(PublicKey k) {
1054        SubjectPublicKeyInfo ki = extractSubjectPublicKeyInfo(k);
1055        SubjectKeyIdentifier id = 
1056            SubjectKeyIdentifier.createSHA1KeyIdentifier(ki);
1057
1058        // Now format it into a string for keeps
1059        Formatter fmt = new Formatter(new StringWriter());
1060        for (byte b: id.getKeyIdentifier())
1061            fmt.format("%02x", b);
1062        return fmt.out().toString();
1063    }
1064
1065    /**
1066     * Extratct the SubjectPublicKeyInfo.  Useful for some other encryptions,
1067     * notably Certificate.make_cert().
1068     * @param k the PublicKey to get the ID from.
1069     * @return a String with the key identifier
1070     */
1071    static SubjectPublicKeyInfo extractSubjectPublicKeyInfo(
1072            PublicKey k) {
1073        ASN1Sequence seq = null;
1074        try {
1075            seq = (ASN1Sequence) new ASN1InputStream(
1076                    k.getEncoded()).readObject();
1077        }
1078        catch (IOException ie) {
1079            // Badly formatted key??
1080            return null;
1081        }
1082        return new SubjectPublicKeyInfo(seq);
1083    }
1084
1085
1086}
Note: See TracBrowser for help on using the repository browser.