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

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

Stop throwing this as a RunTimeException?

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