source: java/net/deterlab/abac/Context.java @ 6f988c6

abac0-leakabac0-meimei-idmei-rt0-nmei_rt0tvf-new-xml
Last change on this file since 6f988c6 was 7f16578, checked in by Ted Faber <faber@…>, 11 years ago

checkpoint

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