source: java/net/deterlab/abac/Context.java @ 5a16edf

abac0-leakabac0-meitvf-new-xml
Last change on this file since 5a16edf was 736a573, checked in by Ted Faber <faber@…>, 12 years ago

Proofs should actually contain the internal creds (Crudge expects this)

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