source: java/net/deterlab/abac/Context.java @ 8ee55e7

abac0-leakabac0-meimei-idmei-rt0-nmei_rt0tvf-new-xml
Last change on this file since 8ee55e7 was f84d71e, checked in by Ted Faber <faber@…>, 12 years ago

More credential parsing

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