source: java/net/deterlab/abac/Context.java @ 4fd9436

abac0-leakabac0-meitvf-new-xml
Last change on this file since 4fd9436 was 4fd9436, checked in by Ted Faber <faber@…>, 11 years ago

SPlit out the keyid <-> mnemonic mapping

  • Property mode set to 100644
File size: 33.7 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.4
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.4
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        dirty = true;
366    }
367
368    /**
369     * Remove a credential from the graph.
370     * @param cred the Credential to remove
371     */
372    protected void remove_credential(Credential cred) {
373        if (g.containsEdge(cred))
374            g.removeEdge(cred);
375        dirty = true;
376    }
377
378    /**
379     * Add a role w/o an edge
380     * @param v the Role to add
381     */
382    protected void add_vertex(Role v) {
383        if (!g.containsVertex(v)) {
384            g.addVertex(v);
385            dirty = true;
386        }
387    }
388
389    /**
390     * Remove a role and connected edges.
391     * @param v the Role to remove
392     */
393    protected void remove_vertex(Role v) {
394        if (g.containsVertex(v)) {
395            g.removeVertex(v);
396            dirty = true;
397        }
398    }
399
400    /**
401     * Derive the implied edges in the graph, according to RT0 derivation rules.
402     * They are added to this graph. See "Distributed Credential Chain Discovery
403     * in Trust Management" by Ninghui Li et al. for details. Note that a
404     * derived linking edge can imply a new intersection edge and vice versa.
405     * Therefore we iteratively derive edges, giving up when an iteration
406     * produces 0 new edges.
407     */
408    protected synchronized void derive_implied_edges() {
409        // nothing to do on a clean graph
410        if (!dirty)
411            return;
412
413        clear_old_edges();
414
415        // iteratively derive links. continue as long as new links are added
416        while (derive_links_iter() > 0)
417            ;
418        dirty = false;
419    }
420
421    /**
422     * Single iteration of deriving implied edges. Returns the number of new
423     * links added.
424     * @return the number of new links added.
425     */
426    protected int derive_links_iter() {
427        int count = 0;
428
429        /* for every node in the graph.. */
430        for (Role vertex : g.getVertices()) {
431            if (vertex.is_intersection()) {
432                // for each prereq edge:
433                //     find set of principals that have the prereq
434                // find the intersection of all sets (i.e., principals
435                //     that satisfy all prereqs)
436                // for each principal in intersection:
437                //     add derived edge
438
439                Set<Role> principals = null;
440                try {
441                    for (Role prereq : vertex.prereqs()) {
442                        Set<Role> cur_principals = pq.find_principals(prereq);
443
444                        if (principals == null)
445                            principals = cur_principals;
446                        else
447                            // no, they couldn't just call it "intersection"
448                            principals.retainAll(cur_principals);
449
450                        if (principals.size() == 0)
451                            break;
452                    }
453                }
454                catch (ABACException ignored) { }
455
456                // add em
457                for (Role principal : principals)
458                    if (add_derived_edge(vertex, principal))
459                        ++count;
460            }
461
462            else if (vertex.is_linking()) {
463                // make the rest of the code a bit clearer
464                Role A_r1_r2 = vertex;
465
466                Role A_r1 = new Role(A_r1_r2.A_r1());
467                String r2 = A_r1_r2.r2();
468
469                /* locate the node A.r1 */
470                if (!g.containsVertex(A_r1)) continue; 
471
472                /* for each B that satisfies A_r1 */
473                for (Role principal : pq.find_principals(A_r1)) {
474                    Role B_r2 = new Role(principal + "." + r2);
475                    if (!g.containsVertex(B_r2)) continue;
476
477                    if (add_derived_edge(A_r1_r2, B_r2))
478                        ++count;
479                }
480            }
481        }
482
483        return count;
484    }
485
486    /**
487     * Add a derived edge in the graph. Returns true only if the edge does not
488     * exist.
489     * @return a boolean, true if an edge has been added
490     */
491    protected boolean add_derived_edge(Role head, Role tail) {
492        // edge exists: return false
493        if (g.findEdge(tail, head) != null)
494            return false;
495
496        // add the new edge
497        Credential derived_edge = new InternalCredential(head, tail);
498        derived_edges.add(derived_edge);
499        g.addEdge(derived_edge, tail, head);
500
501        return true;
502    }
503
504    /**
505     * Clear the derived edges that currently exist in the graph. This is done
506     * before the edges are rederived. The derived edges in filtered graphs are
507     * also cleared.
508     */
509    protected void clear_old_edges() { 
510        for (Credential i: derived_edges) 
511            g.removeEdge(i);
512        derived_edges = new HashSet<Credential>();
513    }
514
515    /**
516     * Put the Identity into the set of ids used to validate certificates.
517     * Also put the keyID and name into the translation mappings used by Roles
518     * to pretty print.  In the role mapping, if multiple ids use the same
519     * common name they are disambiguated.  Only one entry for keyid is
520     * allowed.
521     * @param id the Identity to add
522     */
523    protected void addIdentity(Identity id) { 
524        if (m_identities.contains(id)) 
525            return;
526        m_identities.add(id);
527        if (id.getName() != null && id.getKeyID() != null) 
528            keyMap.addNickname(id.getKeyID(), id.getName());
529    }
530
531    /**
532     * Expand menmonic names in a Role string, e.g. the CN of the issuer
533     * certificate, into the full key ID.  Used internally by Roles to provide
534     * transparent use of mnemonics
535     * @param s the string to expand
536     * @return the String after expansion.
537     */
538    String expandKeyID(String s) { return keyMap.expandKeyID(s); }
539
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 keyMap.expandNickname(s); }
547
548    /**
549     * Read the current ZipEntry's bytes from z.  Tedious because there's no
550     * way to reliably tell how big the entry is, so we have to rely on a
551     * simple expanding array read of the bytes.
552     */
553    protected byte[] readCurrentZipEntry(ZipInputStream z) throws IOException {
554        final int bsize = 4096;
555        byte[] buf = new byte[bsize];
556        byte[] rv = new byte[0];
557        int r = 0;
558
559        // z.read returns -1 at the end of entry
560        while ((r = z.read(buf, 0, bsize)) != -1 ) {
561            byte[] b = new byte[rv.length + r];
562
563            System.arraycopy(rv, 0, b, 0, rv.length);
564            System.arraycopy(buf, 0, b, rv.length, r);
565            rv = b;
566        }
567        return rv;
568    }
569
570    /**
571     * Import a zip file.  First import all the identities
572     * (pem), then the credentials (der) into the credential graph then any
573     * alias files into the two maps.  If keys is not null, any key pairs in
574     * PEM files are put in there.  If errors is not null, errors reading files
575     * are added indexed by filename.  This is a jabac extension.
576     * @param s the InputStream to read
577     * @param keys a Collection into which to insert unmatched keys
578     * @param errors a Map from entry name to generated exception
579     * @throws IOException if the file is unreadable.  Per entry exceptions are
580     *                     returned in the errors parameter.
581     */
582    public void load_zip(InputStream s, Collection<KeyPair> keys, 
583            Map<String, Exception> errors) throws IOException {
584        Map<String, byte[]> derEntries = new HashMap<String, byte[]>();
585        Map<String, Identity> ids = new TreeMap<String, Identity>();
586        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
587        int entries = 0;
588
589        ZipInputStream z = new ZipInputStream(s);
590
591        for (ZipEntry ze = z.getNextEntry(); ze != null; ze = z.getNextEntry()){
592            try {
593                entries++;
594                byte[] buf = readCurrentZipEntry(z);
595                PEMReader r = new PEMReader(
596                        new InputStreamReader(new ByteArrayInputStream(buf)));
597                Object o = readPEM(r);
598
599                if ( o != null ) {
600                    if (o instanceof Identity) {
601                        Identity i = (Identity) o;
602                        String kid = i.getKeyID();
603
604                        if (kps.containsKey(kid) ) {
605                            i.setKeyPair(kps.get(kid));
606                            kps.remove(kid);
607                        }
608                        else if (i.getKeyPair() == null ) 
609                            ids.put(i.getKeyID(), i);
610
611                        load_id_chunk(i);
612                    }
613                    else if (o instanceof KeyPair ) {
614                        KeyPair kp = (KeyPair) o;
615                        String kid = extractKeyID(kp.getPublic());
616
617                        if (ids.containsKey(kid)) {
618                            Identity i = ids.get(kid);
619
620                            i.setKeyPair(kp);
621                            ids.remove(kid);
622                        }
623                        else {
624                            kps.put(kid, kp);
625                        }
626                    }
627                }
628                else {
629                    // Not a PEM file
630                    derEntries.put(ze.getName(),buf);
631                    continue;
632                }
633            }
634            catch (Exception e ) {
635                if (errors != null ) errors.put(ze.getName(), e);
636            }
637        }
638
639        for ( String k: derEntries.keySet() ) {
640            try {
641                Credential[] creds = credentialFactory.parseCredential(
642                            new ByteArrayInputStream(derEntries.get(k)),
643                            m_identities);
644                for (Credential c: creds) 
645                    add_credential(c);
646            }
647            catch (Exception e ) {
648                if (errors != null ) errors.put(k, e);
649            }
650        }
651
652        if (entries == 0) 
653            throw new IOException("Not a ZIP file (or empty ZIP file)");
654    }
655    /**
656     * Equivalent to load_zip(s, null, null).
657     * @param s the InputStream to read
658     * @throws IOException if the file is unreadable. To see per-entry
659     *                      exceptions use a signature with the errors parameter
660     */
661    public void load_zip(InputStream s) 
662            throws IOException {
663        load_zip(s, null, null);
664    }
665    /**
666     * Equivalent to load_zip(s, null, errors).
667     * @param s the InputStream to read
668     * @param errors a Map from entry name to generated exception
669     * @throws IOException if the file is unreadable.  Per entry exceptions are
670     *                     returned in the errors parameter.
671     */
672    public void load_zip(InputStream s, 
673            Map<String, Exception> errors) throws IOException {
674        load_zip(s, null, errors);
675    }
676    /**
677     * Equivalent to load_zip(s, keys, null).
678     * @param s the InputStream to read
679     * @param keys a Collection into which to insert unmatched keys
680     * @throws IOException if the file is unreadable. To see per-entry
681     *                      exceptions use a signature with the errors parameter
682     */
683    public void load_zip(InputStream s, 
684            Collection<KeyPair> keys) throws IOException {
685        load_zip(s, keys, null);
686    }
687
688    /**
689     * Loads a zip file.  Equivalent to
690     * load_zip(new FileInputStream(zf), keys, errors).
691     * @param zf the File to read
692     * @param keys a Collection into which to insert unmatched keys
693     * @param errors a Map from entry name to generated exception
694     * @throws IOException if the file is unreadable.  Per entry exceptions are
695     *                     returned in the errors parameter.
696     */
697    public void load_zip(File zf, Collection<KeyPair> keys, 
698            Map<String, Exception> errors) throws IOException {
699        load_zip(new FileInputStream(zf), keys, errors);
700    }
701    /**
702     * Equivalent to load_zip(d, null, null).
703     * @param d the File to read
704     * @throws IOException if the file is unreadable. To see per-entry
705     *                      exceptions use a signature with the errors parameter
706     */
707    public void load_zip(File d) 
708            throws IOException {
709        load_zip(d, null, null);
710    }
711    /**
712     * Equivalent to load_zip(d, null, errors).
713     * @param d the File to read
714     * @param errors a Map from entry name to generated exception
715     * @throws IOException if the file is unreadable.  Per entry exceptions are
716     *                     returned in the errors parameter.
717     */
718    public void load_zip(File d, 
719            Map<String, Exception> errors) throws IOException {
720        load_zip(d, null, errors);
721    }
722    /**
723     * Equivalent to load_zip(d, keys, null).
724     * @param d the File to read
725     * @param keys a Collection into which to insert unmatched keys
726     * @throws IOException if the file is unreadable. To see per-entry
727     *                      exceptions use a signature with the errors parameter
728     */
729    public void load_zip(File d, 
730            Collection<KeyPair> keys) throws IOException {
731        load_zip(d, keys, null);
732    }
733
734    /**
735     * Read a PEM file that contains an X509 Certificate, a key pair, or both.
736     * If a cert is present it is converted into an Identity.  A key pair is
737     * returned as a java.security.KeyPair and both are returned as an Identity
738     * with an associated key pair.
739     * @param r a PEMReader from which to read
740     * @return an object encoding the contents (as above)
741     * @throws IOException for an unreadable or badly formated input
742     */
743    protected Object readPEM(PEMReader r) throws IOException {
744        Identity i = null;
745        KeyPair keys = null;
746        Object o = null;
747
748        while ( (o = r.readObject()) != null ) {
749            if (o instanceof X509Certificate) {
750                if ( i == null ) {
751                    try {
752                        i = new Identity((X509Certificate)o);
753                    }
754                    catch (Exception e) {
755                        // Translate Idenitiy exceptions to IOException
756                        throw new IOException(e);
757                    }
758                    if (keys != null ) {
759                        i.setKeyPair(keys);
760                        keys = null;
761                    }
762                }
763                else throw new IOException("Two certificates");
764            }
765            else if (o instanceof KeyPair ) {
766                if ( i != null ) i.setKeyPair((KeyPair) o);
767                else keys = (KeyPair) o;
768            }
769            else {
770                throw new IOException("Unexpected PEM object: " + 
771                        o.getClass().getName());
772            }
773        }
774
775        if ( i != null ) return i;
776        else if ( keys != null) return keys;
777        else return null;
778    }
779
780    /**
781     * Import a directory full of files.  First import all the identities
782     * (pem), then the credentials (der) into the credential graph then any
783     * alias files into the two maps.  If keys is not null, any key pairs in
784     * PEM files are put in there.  If errors is not null, errors reading files
785     * are added indexed by filename.  This behaves slightly differently from
786     * the load_directory description in the general libabac documentation.
787     * @param d the File to read.  If it is a directory its contents are read
788     * @param keys a Collection into which to insert unmatched keys
789     * @param errors a Map from entry name to generated exception
790     * @throws IOException if the file is unreadable.  Per file exceptions are
791     *                     returned in the errors parameter.
792     */
793    public void load_directory(File d, Collection<KeyPair> keys, 
794            Map<String, Exception> errors) {
795        Vector<File> derFiles = new Vector<File>();
796        Collection<File> files = new Vector<File>();
797        Map<String, Identity> ids = new TreeMap<String, Identity>();
798        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
799
800        if (d.isDirectory() ) 
801            for (File f : d.listFiles()) 
802                files.add(f);
803        else files.add(d);
804
805        for (File f: files ) {
806            try {
807                PEMReader r = new PEMReader(new FileReader(f));
808                Object o = readPEM(r);
809
810                if ( o != null ) {
811                    if (o instanceof Identity) {
812                        Identity i = (Identity) o;
813                        String kid = i.getKeyID();
814
815                        if (kps.containsKey(kid) ) {
816                            i.setKeyPair(kps.get(kid));
817                            kps.remove(kid);
818                        }
819                        else if (i.getKeyPair() == null ) 
820                            ids.put(i.getKeyID(), i);
821
822                        load_id_chunk(i);
823                    }
824                    else if (o instanceof KeyPair ) {
825                        KeyPair kp = (KeyPair) o;
826                        String kid = extractKeyID(kp.getPublic());
827
828                        if (ids.containsKey(kid)) {
829                            Identity i = ids.get(kid);
830
831                            i.setKeyPair(kp);
832                            ids.remove(kid);
833                        }
834                        else {
835                            kps.put(kid, kp);
836                        }
837                    }
838                }
839                else {
840                    // Not a PEM file
841                    derFiles.add(f);
842                    continue;
843                }
844            }
845            catch (Exception e ) {
846                if (errors != null ) errors.put(f.getName(), e);
847            }
848        }
849
850        for ( File f : derFiles ) {
851            try {
852                Credential[] creds = credentialFactory.parseCredential(f, 
853                        m_identities);
854                for (Credential c: creds) 
855                    add_credential(c);
856            }
857            catch (Exception e ) {
858                if (errors != null ) errors.put(f.getName(), e);
859            }
860        }
861    }
862    /**
863     * Equivalent to load_directory(d, null, null).
864     * @param d the File to read.  If it is a directory its contents are read
865     * @throws IOException if the file is unreadable.  To see per-file
866     *                     exceptions use a signature with the errors parameter.
867     */
868    public void load_directory(File d) {
869        load_directory(d, null, null);
870    }
871    /**
872     * Equivalent to load_directory(d, null, null).
873     * @param d the File to read.  If it is a directory its contents are read
874     * @param errors a Map from entry name to generated exception
875     * @throws IOException if the file is unreadable.  Per file exceptions are
876     *                     returned in the errors parameter.
877     */
878    public void load_directory(File d, Map<String, Exception> errors) {
879        load_directory(d, null, errors);
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 keys a Collection into which to insert unmatched keys
885     * @throws IOException if the file is unreadable.  To see per-file
886     *                     exceptions use a signature with the errors parameter.
887     */
888    public void load_directory(File d, Collection<KeyPair> keys) {
889        load_directory(d, keys, null);
890    }
891
892    /**
893     * Load from a simple rt0 text format.  A jabac extension.  The format is
894     * <br/>
895     * # comments<br/>
896     * role &lt;- role<br/>
897     * <br/>
898     *
899     * Spaces are not significant around the arrow and the tail can be as long
900     * as needed.
901     * @param s the InputStream to load
902     * @throws IOException if there is an error getting the file open or in
903     * format
904     */
905    public void load_rt0(InputStream s) 
906            throws IOException {
907        Pattern comment = Pattern.compile("(^\\s*#|^\\s*$)");
908        Pattern rule = Pattern.compile("([\\w\\.]+)\\s*<-+\\s*(.+)");
909        LineNumberReader r = new LineNumberReader(new InputStreamReader(s));
910        String line = null;
911
912        while ((line = r.readLine()) != null) {
913            Matcher cm = comment.matcher(line);
914            Matcher rm = rule.matcher(line);
915
916            if (cm.find()) continue;
917            if (rm.find()) 
918                add_credential(new InternalCredential(new Role(rm.group(1)), 
919                            new Role(rm.group(2))));
920            else 
921                throw new IOException("Unexpected format: line " + 
922                        r.getLineNumber());
923        }
924    }
925    /**
926     * Equivalent to load_rt0(new FileInputStream(f)
927     * @param f the File to load
928     * @throws IOException if there is an error getting the file open
929     */
930    public void load_rt0(File f) throws IOException {
931        load_rt0(new FileInputStream(f));
932    }
933       
934
935    /**
936     * Write the certificates that make up the context as a zip file, with an
937     * entry for each credential or identity.  The files are all zipped in a
938     * directory derived from the filename.
939     * @param s the OutputStream to write
940     * @param allIDs a boolean, if true write certificates for all Identities,
941     * whether used in signing a credential or not.
942     * @param withPrivateKeys a boolean, if true write the Identities as PEM
943     * file containing both the certificate and the private keys.
944     * @throws IOException if there is a problem writing the file.
945     */
946    public void write_zip(OutputStream s, boolean allIDs, 
947            boolean withPrivateKeys) throws IOException {
948        ZipOutputStream z = new ZipOutputStream(s);
949        Set<Identity> ids = allIDs ?  m_identities : new TreeSet<Identity>();
950        String baseDir = "creds";
951        int idx = baseDir.indexOf('.');
952
953
954        if (idx != -1) 
955            baseDir = baseDir.substring(0, idx);
956
957        int n = 0;
958        for (Credential c: credentials()) {
959            z.putNextEntry(new ZipEntry(baseDir + File.separator + 
960                        "attr" + n++  + c.getSuffix()));
961            c.write(z);
962            z.closeEntry();
963            if ( c.issuer() != null && !allIDs) ids.add(c.issuer());
964        }
965        for (Identity i: ids) {
966            z.putNextEntry(new ZipEntry(baseDir + File.separator + 
967                        i.getName() + ".pem"));
968            i.write(z);
969            if (withPrivateKeys)
970                i.writePrivateKey(z);
971            z.closeEntry();
972        }
973        z.close();
974    }
975    /**
976     * Equivalent to
977     * write_zip(new FileOutputStream(f), allIDs, withPrivateKeys).
978     * @param f the File to write
979     * @param allIDs a boolean, if true write certificates for all Identities,
980     * whether used in signing a credential or not.
981     * @param withPrivateKeys a boolean, if true write the Identities as PEM
982     * file containing both the certificate and the private keys.
983     * @throws IOException if there is a problem writing the file.
984     */
985    public void write_zip(File f, boolean allIDs, boolean withPrivateKeys) 
986            throws IOException {
987        write_zip(new FileOutputStream(f), allIDs, withPrivateKeys);
988    }
989
990    /**
991     * Write to a simple rt0 text format.  A jabac extension.
992     * The format is
993     * <br/>
994     * role &lt;- role<br/>
995     * <br/>
996     *
997     * @param w a Writer to print on
998     * @param useKeyIDs a boolean, true to print key IDs not mnemonics
999     */
1000    public void write_rt0(Writer w, boolean useKeyIDs) {
1001        PrintWriter pw = w instanceof PrintWriter ? 
1002            (PrintWriter) w : new PrintWriter(w);
1003
1004        for (Credential c: credentials()) 
1005            pw.println(useKeyIDs ? c.toString() : c.simpleString(this));
1006        pw.flush();
1007    }
1008
1009    /**
1010     * Call write_rt0 on a FileWriter derived from f.
1011     * @param f the File to write to
1012     * @param useKeyIDs a boolean, true to print key IDs not mnemonics
1013     * @throws IOException if there is a problem writing the file.
1014     */
1015    public void write_rt0(File f, boolean useKeyIDs) throws IOException {
1016        write_rt0(new FileWriter(f), useKeyIDs);
1017    }
1018
1019    /**
1020     * Equivalent to write_rt0(w, false);
1021     * @param w a Writer to print on
1022     */
1023    public void write_rt0(Writer w) { write_rt0(w, false); }
1024
1025    /**
1026     * Equivalent to write_rt0(f, false);
1027     * @param f the File to write to
1028     * @throws IOException if there is a problem writing the file.
1029     */
1030    public void write_rt0(File f) throws IOException {
1031        write_rt0(new FileWriter(f), false);
1032    }
1033
1034    /**
1035     * Return this Context's CredentialFactory.
1036     * @return this Context's CredentialFactory.
1037     */
1038    public CredentialFactory getCredentialFactory() {
1039        return credentialFactory;
1040    }
1041
1042    /**
1043     * Set this Context's CredentialFactory.
1044     * @param cf the new CredentialFactoty
1045     */
1046    public void setCredentialFactory(CredentialFactory cf) { 
1047        credentialFactory = cf;
1048    }
1049
1050    /**
1051     * Return a new credential supported by this Context.  It is not inserted
1052     * in the Context.
1053     * @param head a Role, the head of the encoded ABAC statement
1054     * @param tail a Role, the tail of the decoded ABAC statement
1055     * @return a Credential encoding that ABAC statement
1056     */
1057    public Credential newCredential(Role head, Role tail) {
1058        return credentialFactory.generateCredential(head, tail);
1059    }
1060
1061    /**
1062     * Get to the SHA1 hash of the key.  Used by Roles and Identities to get a
1063     * key ID.
1064     * @param k the PublicKey to get the ID from.
1065     * @return a String with the key identifier
1066     */
1067    static String extractKeyID(PublicKey k) {
1068        SubjectPublicKeyInfo ki = extractSubjectPublicKeyInfo(k);
1069        SubjectKeyIdentifier id = 
1070            SubjectKeyIdentifier.createSHA1KeyIdentifier(ki);
1071
1072        // Now format it into a string for keeps
1073        Formatter fmt = new Formatter(new StringWriter());
1074        for (byte b: id.getKeyIdentifier())
1075            fmt.format("%02x", b);
1076        return fmt.out().toString();
1077    }
1078
1079    /**
1080     * Extratct the SubjectPublicKeyInfo.  Useful for some other encryptions,
1081     * notably Certificate.make_cert().
1082     * @param k the PublicKey to get the ID from.
1083     * @return a String with the key identifier
1084     */
1085    static SubjectPublicKeyInfo extractSubjectPublicKeyInfo(
1086            PublicKey k) {
1087        ASN1Sequence seq = null;
1088        try {
1089            seq = (ASN1Sequence) new ASN1InputStream(
1090                    k.getEncoded()).readObject();
1091        }
1092        catch (IOException ie) {
1093            // Badly formatted key??
1094            return null;
1095        }
1096        return new SubjectPublicKeyInfo(seq);
1097    }
1098
1099}
Note: See TracBrowser for help on using the repository browser.