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

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

Support simple rt0 format

  • Property mode set to 100644
File size: 30.0 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 Identity 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            if (c instanceof Credential)
232                add_credential((Credential) c);
233            else if (c instanceof String) 
234                add_credential(new Credential((String) c, m_identities));
235            else if (c instanceof File) 
236                add_credential(new Credential((File) c, m_identities));
237            else if ( c instanceof X509V2AttributeCertificate) 
238                add_credential(new Credential((X509V2AttributeCertificate)c,
239                            m_identities));
240            else 
241                return ABAC_CERT_INVALID;
242        }
243        catch (InvalidKeyException sig) {
244            return ABAC_CERT_MISSING_ISSUER ;
245        }
246        catch (SignatureException sig) {
247            return ABAC_CERT_BAD_SIG;
248        }
249        catch (Exception e) {
250            return ABAC_CERT_INVALID;
251        }
252        return ABAC_CERT_SUCCESS;
253    }
254
255    /**
256     * Determine if prinicpal possesses role in the current context.  If so,
257     * return a proof of that, otherwise return a partial proof of it.
258     * @param role a String encoding the role to check for.
259     * @param principal a String with the principal ID in it.
260     * @return a Context.QueryResult containing the result.
261     */
262    public QueryResult query(String role, String principal) {
263        derive_implied_edges();
264
265        Query q = new Query(g);
266        Graph<Role, Credential> rg = q.run(role, principal);
267
268        return new QueryResult(rg.getEdges(), q.successful());
269    }
270
271    /**
272     * Return a collection of the credentials in the graph.s
273     * @return a collection of the credentials in the graph.
274     */
275    public Collection<Credential> credentials() {
276        Collection<Credential> creds = new HashSet<Credential>();
277
278        // only non-derived edges
279        for (Credential cred : g.getEdges())
280            if (!derived_edges.contains(cred))
281                creds.add(cred);
282
283        return creds;
284    }
285
286    /**
287     * Return all the Identities known in this context.  A jabac extension.
288     * @return all the Identities known in this context.
289     */
290    public Collection<Identity> identities() {
291        return m_identities;
292    }
293
294    /**
295     * Returns true if the given Identity is known in this Context.  A jabac
296     * extension.
297     * @param i the Identity to look for
298     * @return a boolean, true if the Identity is known.
299     */
300    public boolean knowsIdentity(Identity i) { return m_identities.contains(i);}
301    /**
302     * Returns true if an Identity with the given string representation is
303     * known in this Context.  A jabac extension.
304     * @param k the string representing the Identity to look for
305     * @return a boolean, true if the Identity is known.
306     */
307    public boolean knowsKeyID(String k) {
308        boolean known = false;
309        for (Identity i: m_identities)
310            if (k.equals(i.getKeyID())) return true;
311        return false;
312    }
313
314
315    /**
316     * Add a credential to the graph.
317     * @param cred the Credential to add
318     */
319    protected void add_credential(Credential cred) {
320        Role tail = cred.tail();
321        Role head = cred.head();
322
323        // explicitly add the vertices, to avoid a null pointer exception
324        if ( !g.containsVertex(head)) 
325            g.addVertex(head);
326        if ( !g.containsVertex(tail)) 
327            g.addVertex(tail);
328
329        if (!g.containsEdge(cred))
330            g.addEdge(cred, tail, head);
331
332        // add the prereqs of an intersection to the graph
333        if (tail.is_intersection())
334            for (Role prereq : tail.prereqs())
335                g.addVertex(prereq);
336
337        dirty = true;
338    }
339
340    /**
341     * Remove a credential from the graph.
342     * @param cred the Credential to remove
343     */
344    protected void remove_credential(Credential cred) {
345        if (g.containsEdge(cred))
346            g.removeEdge(cred);
347        dirty = true;
348    }
349
350    /**
351     * Add a role w/o an edge
352     * @param v the Role to add
353     */
354    protected void add_vertex(Role v) {
355        if (!g.containsVertex(v)) {
356            g.addVertex(v);
357            dirty = true;
358        }
359    }
360
361    /**
362     * Remove a role and connected edges.
363     * @param v the Role to remove
364     */
365    protected void remove_vertex(Role v) {
366        if (g.containsVertex(v)) {
367            g.removeVertex(v);
368            dirty = true;
369        }
370    }
371
372    /**
373     * Derive the implied edges in the graph, according to RT0 derivation rules.
374     * They are added to this graph. See "Distributed Credential Chain Discovery
375     * in Trust Management" by Ninghui Li et al. for details. Note that a
376     * derived linking edge can imply a new intersection edge and vice versa.
377     * Therefore we iteratively derive edges, giving up when an iteration
378     * produces 0 new edges.
379     */
380    protected synchronized void derive_implied_edges() {
381        // nothing to do on a clean graph
382        if (!dirty)
383            return;
384
385        clear_old_edges();
386
387        // iteratively derive links. continue as long as new links are added
388        while (derive_links_iter() > 0)
389            ;
390        dirty = false;
391    }
392
393    /**
394     * Single iteration of deriving implied edges. Returns the number of new
395     * links added.
396     * @return the number of new links added.
397     */
398    protected int derive_links_iter() {
399        int count = 0;
400
401        /* for every node in the graph.. */
402        for (Role vertex : g.getVertices()) {
403            if (vertex.is_intersection()) {
404                // for each prereq edge:
405                //     find set of principals that have the prereq
406                // find the intersection of all sets (i.e., principals
407                //     that satisfy all prereqs)
408                // for each principal in intersection:
409                //     add derived edge
410
411                Set<Role> principals = null;
412
413                for (Role prereq : vertex.prereqs()) {
414                    Set<Role> cur_principals = pq.find_principals(prereq);
415
416                    if (principals == null)
417                        principals = cur_principals;
418                    else
419                        // no, they couldn't just call it "intersection"
420                        principals.retainAll(cur_principals);
421
422                    if (principals.size() == 0)
423                        break;
424                }
425
426                // add em
427                for (Role principal : principals)
428                    if (add_derived_edge(vertex, principal))
429                        ++count;
430            }
431
432            else if (vertex.is_linking()) {
433                // make the rest of the code a bit clearer
434                Role A_r1_r2 = vertex;
435
436                Role A_r1 = new Role(A_r1_r2.A_r1());
437                String r2 = A_r1_r2.r2();
438
439                /* locate the node A.r1 */
440                if (!g.containsVertex(A_r1)) continue; 
441
442                /* for each B that satisfies A_r1 */
443                for (Role principal : pq.find_principals(A_r1)) {
444                    Role B_r2 = new Role(principal + "." + r2);
445                    if (!g.containsVertex(B_r2)) continue;
446
447                    if (add_derived_edge(A_r1_r2, B_r2))
448                        ++count;
449                }
450            }
451        }
452
453        return count;
454    }
455
456    /**
457     * Add a derived edge in the graph. Returns true only if the edge does not
458     * exist.
459     * @return a boolean, true if an edge has been added
460     */
461    protected boolean add_derived_edge(Role head, Role tail) {
462        // edge exists: return false
463        if (g.findEdge(tail, head) != null)
464            return false;
465
466        // add the new edge
467        Credential derived_edge = new Credential(head, tail);
468        derived_edges.add(derived_edge);
469        g.addEdge(derived_edge, tail, head);
470
471        return true;
472    }
473
474    /**
475     * Clear the derived edges that currently exist in the graph. This is done
476     * before the edges are rederived. The derived edges in filtered graphs are
477     * also cleared.
478     */
479    protected void clear_old_edges() { 
480        for (Credential i: derived_edges) 
481            g.removeEdge(i);
482        derived_edges = new HashSet<Credential>();
483    }
484    /**
485     * Put the Identity into the set of ids used to validate certificates.
486     * Also put the keyID and name into the translation mappings used by Roles
487     * to pretty print.  In the role mapping, if multiple ids use the same
488     * common name they are disambiguated.  Only one entry for keyid is
489     * allowed.
490     * @param id the Identity to add
491     */
492    protected void addIdentity(Identity id) { 
493        m_identities.add(id);
494        if (id.getName() != null && id.getKeyID() != null) {
495            if ( !keys.containsKey(id.getKeyID()) ) {
496                String name = id.getName();
497                int n= 1;
498
499                while (nicknames.containsKey(name)) {
500                    name = id.getName() + n++;
501                }
502                nicknames.put(name, id.getKeyID());
503                keys.put(id.getKeyID(), name);
504            }
505        }
506    }
507    /**
508     * Translate either keys to nicknames or vice versa.  Break the string into
509     * space separated tokens and then each of them into period separated
510     * strings.  If any of the smallest strings is in the map, replace it with
511     * the value.
512     * @param is the string to manipulate
513     * @param m the Map containing translations
514     * @return the string after modification
515     */
516    protected String replace(String is, Map<String, String> m) {
517        String rv = "";
518        for (String tok: is.split(" ")) {
519            String term = "";
520            for (String s: tok.split("\\.")) {
521                String next = m.containsKey(s) ? m.get(s) : s;
522
523                if (term.isEmpty()) term = next;
524                else term += "." + next;
525            }
526            if (rv.isEmpty()) rv = term;
527            else rv += " " + term;
528        }
529        return rv;
530    }
531
532    /**
533     * Expand menmonic names in a Role string, e.g. the CN of the issuer
534     * certificate, into the full key ID.  Used internally by Roles to provide
535     * transparent use of mnemonics
536     * @param s the string to expand
537     * @return the String after expansion.
538     */
539    String expandKeyID(String s) { return replace(s, nicknames); }
540    /**
541     * Convert key IDs to  menmonic names in a Role string.  The inverse of
542     * expandKeyID.
543     * @param s the string to expand
544     * @return the String after expansion.
545     */
546    String expandNickname(String s) { return replace(s, keys); }
547
548    /**
549     * Import a zip file.  First import all the identities
550     * (pem), then the credentials (der) into the credential graph then any
551     * alias files into the two maps.  If keys is not null, any key pairs in
552     * PEM files are put in there.  If errors is not null, errors reading files
553     * are added indexed by filename.  This is a jabac extension.
554     * @param zf the File to read
555     * @param keys a Collection into which to insert unmatched keys
556     * @param errors a Map from entry name to generated exception
557     * @throws IOException if the file is unreadable.  Per entry exceptions are
558     *                     returned in the errors parameter.
559     */
560    public void load_zip_file(File zf, Collection<KeyPair> keys, 
561            Map<String, Exception> errors) throws IOException {
562        Vector<ZipEntry> derEntries = new Vector<ZipEntry>();
563        Map<String, Identity> ids = new TreeMap<String, Identity>();
564        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
565
566        ZipFile z = new ZipFile(zf);
567
568        for (Enumeration<? extends ZipEntry> ze = z.entries(); 
569                ze.hasMoreElements();) {
570            ZipEntry  f = ze.nextElement();
571            try {
572                PEMReader r = new PEMReader(
573                        new InputStreamReader(z.getInputStream(f)));
574                Object o = readPEM(r);
575
576                if ( o != null ) {
577                    if (o instanceof Identity) {
578                        Identity i = (Identity) o;
579                        String kid = i.getKeyID();
580
581                        if (kps.containsKey(kid) ) {
582                            i.setKeyPair(kps.get(kid));
583                            kps.remove(kid);
584                        }
585                        else if (i.getKeyPair() == null ) 
586                            ids.put(i.getKeyID(), i);
587
588                        load_id_chunk(i);
589                    }
590                    else if (o instanceof KeyPair ) {
591                        KeyPair kp = (KeyPair) o;
592                        String kid = extractKeyID(kp.getPublic());
593
594                        if (ids.containsKey(kid)) {
595                            Identity i = ids.get(kid);
596
597                            i.setKeyPair(kp);
598                            ids.remove(kid);
599                        }
600                        else {
601                            kps.put(kid, kp);
602                        }
603                    }
604                }
605                else {
606                    // Not a PEM file
607                    derEntries.add(f);
608                    continue;
609                }
610            }
611            catch (Exception e ) {
612                if (errors != null ) errors.put(f.getName(), e);
613            }
614        }
615
616        for ( ZipEntry f : derEntries ) {
617            try {
618                add_credential(new Credential(z.getInputStream(f),
619                            m_identities));
620            }
621            catch (Exception e ) {
622                if (errors != null ) errors.put(f.getName(), e);
623            }
624        }
625    }
626    /**
627     * Equivalent to load_zip_file(d, null, null).
628     * @param d the File to read
629     * @throws IOException if the file is unreadable. To see per-entry
630     *                      exceptions use a signature with the errors parameter
631     */
632    public void load_zip_file(File d) 
633            throws IOException {
634        load_zip_file(d, null, null);
635    }
636    /**
637     * Equivalent to load_zip_file(d, null, errors).
638     * @param d the File to read
639     * @param errors a Map from entry name to generated exception
640     * @throws IOException if the file is unreadable.  Per entry exceptions are
641     *                     returned in the errors parameter.
642     */
643    public void load_zip_file(File d, 
644            Map<String, Exception> errors) throws IOException {
645        load_zip_file(d, null, errors);
646    }
647    /**
648     * Equivalent to load_zip_file(d, keys, null).
649     * @param d the File to read
650     * @param keys a Collection into which to insert unmatched keys
651     * @throws IOException if the file is unreadable. To see per-entry
652     *                      exceptions use a signature with the errors parameter
653     */
654    public void load_zip_file(File d, 
655            Collection<KeyPair> keys) throws IOException {
656        load_zip_file(d, keys, null);
657    }
658
659    /**
660     * Read a PEM file that contains an X509 Certificate, a key pair, or both.
661     * If a cert is present it is converted into an Identity.  A key pair is
662     * returned as a java.security.KeyPair and both are returned as an Identity
663     * with an associated key pair.
664     * @param r a PEMReader from which to read
665     * @return an object encoding the contents (as above)
666     * @throws IOException for an unreadable or badly formated input
667     */
668    protected Object readPEM(PEMReader r) throws IOException {
669        Identity i = null;
670        KeyPair keys = null;
671        Object o = null;
672
673        while ( (o = r.readObject()) != null ) {
674            if (o instanceof X509Certificate) {
675                if ( i == null ) {
676                    try {
677                        i = new Identity((X509Certificate)o);
678                    }
679                    catch (Exception e) {
680                        // Translate Idenitiy exceptions to IOException
681                        throw new IOException(e);
682                    }
683                    if (keys != null ) {
684                        i.setKeyPair(keys);
685                        keys = null;
686                    }
687                }
688                else throw new IOException("Two certificates");
689            }
690            else if (o instanceof KeyPair ) {
691                if ( i != null ) i.setKeyPair((KeyPair) o);
692                else keys = (KeyPair) o;
693            }
694            else {
695                throw new IOException("Unexpected PEM object: " + 
696                        o.getClass().getName());
697            }
698        }
699
700        if ( i != null ) return i;
701        else if ( keys != null) return keys;
702        else return null;
703    }
704
705    /**
706     * Import a directory full of files.  First import all the identities
707     * (pem), then the credentials (der) into the credential graph then any
708     * alias files into the two maps.  If keys is not null, any key pairs in
709     * PEM files are put in there.  If errors is not null, errors reading files
710     * are added indexed by filename.  This behaves slightly differently from
711     * the load_directory description in the general libabac documentation.
712     * @param d the File to read.  If it is a directory its contents are read
713     * @param keys a Collection into which to insert unmatched keys
714     * @param errors a Map from entry name to generated exception
715     * @throws IOException if the file is unreadable.  Per file exceptions are
716     *                     returned in the errors parameter.
717     */
718    public void load_directory(File d, Collection<KeyPair> keys, 
719            Map<String, Exception> errors) {
720        Vector<File> derFiles = new Vector<File>();
721        Collection<File> files = new Vector<File>();
722        Map<String, Identity> ids = new TreeMap<String, Identity>();
723        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
724
725        if (d.isDirectory() ) 
726            for (File f : d.listFiles()) 
727                files.add(f);
728        else files.add(d);
729
730        for (File f: files ) {
731            try {
732                PEMReader r = new PEMReader(new FileReader(f));
733                Object o = readPEM(r);
734
735                if ( o != null ) {
736                    if (o instanceof Identity) {
737                        Identity i = (Identity) o;
738                        String kid = i.getKeyID();
739
740                        if (kps.containsKey(kid) ) {
741                            i.setKeyPair(kps.get(kid));
742                            kps.remove(kid);
743                        }
744                        else if (i.getKeyPair() == null ) 
745                            ids.put(i.getKeyID(), i);
746
747                        load_id_chunk(i);
748                    }
749                    else if (o instanceof KeyPair ) {
750                        KeyPair kp = (KeyPair) o;
751                        String kid = extractKeyID(kp.getPublic());
752
753                        if (ids.containsKey(kid)) {
754                            Identity i = ids.get(kid);
755
756                            i.setKeyPair(kp);
757                            ids.remove(kid);
758                        }
759                        else {
760                            kps.put(kid, kp);
761                        }
762                    }
763                }
764                else {
765                    // Not a PEM file
766                    derFiles.add(f);
767                    continue;
768                }
769            }
770            catch (Exception e ) {
771                if (errors != null ) errors.put(f.getName(), e);
772            }
773        }
774
775        for ( File f : derFiles ) {
776            try {
777                add_credential(new Credential(f, m_identities));
778            }
779            catch (Exception e ) {
780                if (errors != null ) errors.put(f.getName(), e);
781            }
782        }
783    }
784    /**
785     * Equivalent to load_directory(d, null, null).
786     * @param d the File to read.  If it is a directory its contents are read
787     * @throws IOException if the file is unreadable.  To see per-file
788     *                     exceptions use a signature with the errors parameter.
789     */
790    public void load_directory(File d) {
791        load_directory(d, null, null);
792    }
793    /**
794     * Equivalent to load_directory(d, null, null).
795     * @param d the File to read.  If it is a directory its contents are read
796     * @param errors a Map from entry name to generated exception
797     * @throws IOException if the file is unreadable.  Per file exceptions are
798     *                     returned in the errors parameter.
799     */
800    public void load_directory(File d, Map<String, Exception> errors) {
801        load_directory(d, null, errors);
802    }
803    /**
804     * Equivalent to load_directory(d, null, null).
805     * @param d the File to read.  If it is a directory its contents are read
806     * @param keys a Collection into which to insert unmatched keys
807     * @throws IOException if the file is unreadable.  To see per-file
808     *                     exceptions use a signature with the errors parameter.
809     */
810    public void load_directory(File d, Collection<KeyPair> keys) {
811        load_directory(d, keys, null);
812    }
813
814    /**
815     * Load from a simple rt0 text format.  A jabac extension.  The format is
816     * <br/>
817     * # comments<br/>
818     * role &lt;- role<br/>
819     * <br/>
820     *
821     * Spaces are not significant around the arrow and the tail can be as long
822     * as needed.
823     * @param f the File to load
824     * @param errors a Map of lines to errors
825     * @throws IOException if there is an error getting the file open
826     */
827    public void load_rt0_file(File f, Map<String, Exception> errors) 
828            throws IOException {
829        Pattern comment = Pattern.compile("(^\\s*#|^\\s*$)");
830        Pattern rule = Pattern.compile("([\\w\\.]+)\\s*<-+\\s*([\\w\\.\\&]+)");
831        LineNumberReader r = new LineNumberReader(new FileReader(f));
832        String line = null;
833
834        while ((line = r.readLine()) != null) {
835            Matcher cm = comment.matcher(line);
836            Matcher rm = rule.matcher(line);
837
838            if (cm.find()) continue;
839            if (rm.find()) 
840                add_credential(new Credential(new Role(rm.group(1)), 
841                            new Role(rm.group(2))));
842            else if (errors != null) 
843                errors.put("Line " + r.getLineNumber(), 
844                        new RuntimeException("Unexpected format"));
845        }
846    }
847
848    /**
849     * Equivalent to load_rt0_file(d, null).
850     * @param f the File to load
851     * @throws IOException if there is an error getting the file open
852     */
853    public void load_rt0_file(File f) throws IOException {
854        load_rt0_file(f, null);
855    }
856       
857
858    /**
859     * Write the certificates that make up the context as a zip file, with an
860     * entry for each credential or identity.  The files are all zipped in a
861     * directory derived from the filename.
862     * @param f the File to write
863     * @param allIDs a boolean, if true write certificates for all Identities,
864     * whether used in signing a credential or not.
865     * @param withPrivateKeys a boolean, if true write the Identities as PEM
866     * file containing both the certificate and the private keys.
867     * @throws IOException if there is a problem writing the file.
868     */
869    public void write_zip_file(File f, boolean allIDs, boolean withPrivateKeys) 
870            throws IOException {
871        ZipOutputStream z = new ZipOutputStream(new FileOutputStream(f));
872        Set<Identity> ids = allIDs ?  m_identities : new TreeSet<Identity>();
873        String baseDir = f.getName();
874        int idx = baseDir.indexOf('.');
875
876
877        if (idx != -1) 
878            baseDir = baseDir.substring(0, idx);
879
880        int n = 0;
881        for (Credential c: credentials()) {
882            z.putNextEntry(new ZipEntry(baseDir + File.separator + 
883                        "attr" + n++  + ".der"));
884            c.write(z);
885            z.closeEntry();
886            if ( c.issuer() != null && !allIDs) ids.add(c.issuer());
887        }
888        for (Identity i: ids) {
889            z.putNextEntry(new ZipEntry(baseDir + File.separator + 
890                        i.getName() + ".pem"));
891            i.write(z);
892            if (withPrivateKeys)
893                i.writePrivateKey(z);
894            z.closeEntry();
895        }
896        z.close();
897    }
898
899    /**
900     * Write to a simple rt0 text format.  A jabac extension.
901     * The format is
902     * <br/>
903     * role &lt;- role<br/>
904     * <br/>
905     *
906     * @param w a Writer to print on
907     * @param useKeyIDs a boolean, true to print key IDs not mnemonics
908     */
909    public void write_rt0(Writer w, boolean useKeyIDs) {
910        PrintWriter pw = w instanceof PrintWriter ? 
911            (PrintWriter) w : new PrintWriter(w);
912
913        for (Credential c: credentials()) 
914            pw.println(useKeyIDs ? c.toString() : c.simpleString(this));
915        pw.flush();
916    }
917
918    /**
919     * Call write_rt0 on a FileWriter derived from f.
920     * @param f the File to write to
921     * @param useKeyIDs a boolean, true to print key IDs not mnemonics
922     * @throws IOException if there is a problem writing the file.
923     */
924    public void write_rt0(File f, boolean useKeyIDs) throws IOException {
925        write_rt0(new FileWriter(f), useKeyIDs);
926    }
927
928    /**
929     * Equivalent to write_rt0(w, false);
930     * @param w a Writer to print on
931     */
932    public void write_rt0(Writer w) { write_rt0(w, false); }
933
934    /**
935     * Equivalent to write_rt0(f, false);
936     * @param f the File to write to
937     * @throws IOException if there is a problem writing the file.
938     */
939    public void write_rt0(File f) throws IOException {
940        write_rt0(new FileWriter(f), false);
941    }
942
943    /**
944     * Get to the SHA1 hash of the key.  Used by Roles and Identities to get a
945     * key ID.
946     * @param k the PublicKey to get the ID from.
947     * @return a String with the key identifier
948     */
949    static String extractKeyID(PublicKey k) {
950        SubjectPublicKeyInfo ki = extractSubjectPublicKeyInfo(k);
951        SubjectKeyIdentifier id = 
952            SubjectKeyIdentifier.createSHA1KeyIdentifier(ki);
953
954        // Now format it into a string for keeps
955        Formatter fmt = new Formatter(new StringWriter());
956        for (byte b: id.getKeyIdentifier())
957            fmt.format("%02x", b);
958        return fmt.out().toString();
959    }
960
961    /**
962     * Extratct the SubjectPublicKeyInfo.  Useful for some other encryptions,
963     * notably Certificate.make_cert().
964     * @param k the PublicKey to get the ID from.
965     * @return a String with the key identifier
966     */
967    static SubjectPublicKeyInfo extractSubjectPublicKeyInfo(
968            PublicKey k) {
969        ASN1Sequence seq = null;
970        try {
971            seq = (ASN1Sequence) new ASN1InputStream(
972                    k.getEncoded()).readObject();
973        }
974        catch (IOException ie) {
975            // Badly formatted key??
976            return null;
977        }
978        return new SubjectPublicKeyInfo(seq);
979    }
980
981
982}
Note: See TracBrowser for help on using the repository browser.