source: java/net/deterlab/abac/Context.java @ 79f516b

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

Properly read nicknames from creds

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