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

Last change on this file was 461edba, checked in by Ted Faber <faber@…>, 9 years ago

Clean up javadoc for v8.

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