source: java/net/deterlab/abac/Context.java @ 1a80501

abac0-leakabac0-meicompt_changesgec13mei-idmei-rt0-nmei_rt0mei_rt2mei_rt2_fix_1meiyap-rt1meiyap1rt2tvf-new-xml
Last change on this file since 1a80501 was 1a80501, checked in by Ted Faber <faber@…>, 14 years ago

Fix query and concise credential and identity loading.

  • Property mode set to 100644
File size: 17.3 KB
Line 
1package net.deterlab.abac;
2
3import org.apache.commons.collections15.*;
4import org.apache.commons.collections15.functors.*;
5
6import edu.uci.ics.jung.graph.*;
7import edu.uci.ics.jung.graph.event.*;
8import edu.uci.ics.jung.graph.util.*;
9
10import java.awt.geom.Point2D;
11import java.io.*;
12import java.util.*;
13import java.util.zip.*;
14import java.security.*;
15import java.security.cert.*;
16
17import org.bouncycastle.asn1.*;
18import org.bouncycastle.asn1.x509.*;
19import org.bouncycastle.x509.*;
20import org.bouncycastle.x509.util.*;
21import org.bouncycastle.openssl.*;
22
23/**
24 * Represents a global graph of credentials in the form of principals and
25 * attributes.
26 */
27public class Context {
28    static final int ABAC_CERT_SUCCESS = 0;
29    static final int ABAC_CERT_INVALID = -1;
30    static final int ABAC_CERT_BAD_SIG = -2;
31    static final int ABAC_CERT_MISSING_ISSUER = -3;
32
33    protected Graph<Role,Credential> g;
34    protected Set<Credential> derived_edges;
35    protected Query pq;
36    protected boolean dirty;
37    protected Set<Identity> identities;
38
39    protected Map<String, String> nicknames;
40    protected Map<String, String> keys;
41
42    public class QueryResult {
43        protected Collection<Credential> creds;
44        protected boolean success;
45
46        public QueryResult(Collection<Credential> c, boolean s) {
47            creds = c;
48            success = s;
49        }
50
51        public QueryResult() { 
52            creds = new TreeSet<Credential>();
53            success = false;
54        }
55
56        public Collection<Credential> getCredentials() { return creds; }
57        public boolean getSuccess() { return success; }
58    }
59
60
61    public Context() {
62        /* create the graph */
63        g = Graphs.<Role,Credential>synchronizedDirectedGraph(
64                new DirectedSparseGraph<Role,Credential>());
65        derived_edges = new HashSet<Credential>();
66        pq = new Query(g);
67        dirty = false;
68        identities = new TreeSet<Identity>();
69        nicknames = new TreeMap<String, String>();
70        keys = new TreeMap<String, String>();
71    }
72
73    public Context(Context c) {
74        this();
75        for (Identity i: c.identities) 
76            loadIDChunk(i);
77        for (Credential cr: c.credentials()) 
78            loadAttributeChunk(cr);
79        derive_implied_edges();
80    }
81
82    public int loadIDFile(String fn) { return loadIDChunk(new File(fn)); }
83    public int loadIDFile(File fn) { return loadIDChunk(fn); }
84    public int loadIDChunk(Object c) {
85        try {
86            if (c instanceof Identity)
87                addIdentity((Identity) c);
88            else if (c instanceof String) 
89                addIdentity(new Identity((String) c));
90            else if (c instanceof File) 
91                addIdentity(new Identity((File) c));
92            else if (c instanceof X509Certificate) 
93                addIdentity(new Identity((X509Certificate) c));
94            else 
95                return ABAC_CERT_INVALID;
96        }
97        catch (SignatureException sig) {
98            return ABAC_CERT_BAD_SIG;
99        }
100        catch (Exception e) {
101            return ABAC_CERT_INVALID;
102        }
103        return ABAC_CERT_SUCCESS;
104    }
105
106    public int loadAttributeFile(String fn) { 
107        return loadAttributeChunk(new File(fn));
108    }
109
110    public int loadAttributeFile(File fn) { return loadAttributeChunk(fn); }
111
112    public int loadAttributeChunk(Object c) {
113        try {
114            if (c instanceof Credential)
115                add_credential((Credential) c);
116            else if (c instanceof String) 
117                add_credential(new Credential((String) c, identities));
118            else if (c instanceof File) 
119                add_credential(new Credential((File) c, identities));
120            else if ( c instanceof X509V2AttributeCertificate) 
121                add_credential(new Credential((X509V2AttributeCertificate)c,
122                            identities));
123            else 
124                return ABAC_CERT_INVALID;
125        }
126        catch (SignatureException sig) {
127            return ABAC_CERT_BAD_SIG;
128        }
129        catch (Exception e) {
130            return ABAC_CERT_INVALID;
131        }
132        return ABAC_CERT_SUCCESS;
133    }
134
135    public QueryResult query(String role, String principal) {
136        derive_implied_edges();
137
138        Query q = new Query(g);
139        Graph<Role, Credential> rg = q.run(role, principal);
140
141        return new QueryResult(rg.getEdges(), q.successful());
142    }
143
144    /**
145     * Returns a collection of the credentials in the graph.
146     */
147    public Collection<Credential> credentials() {
148        Collection<Credential> creds = new HashSet<Credential>();
149
150        // only return creds with a cert: all others are derived edges
151        for (Credential cred : g.getEdges())
152            if (cred.cert() != null)
153                creds.add(cred);
154
155        return creds;
156    }
157
158    public boolean knowsIdentity(Identity i) { return identities.contains(i); }
159    public boolean knowsKeyID(String k) {
160        boolean known = false;
161        for (Identity i: identities)
162            if (k.equals(i.getKeyID())) return true;
163        return false;
164    }
165
166
167    /**
168     * Add a credential to the graph.
169     */
170    public void add_credential(Credential cred) {
171        Role tail = cred.tail();
172        Role head = cred.head();
173
174        /* explicitly add the vertices, otherwise get a null pointer exception */
175        if ( !g.containsVertex(head)) 
176            g.addVertex(head);
177        if ( !g.containsVertex(tail)) 
178            g.addVertex(tail);
179
180        if (!g.containsEdge(cred))
181            g.addEdge(cred, tail, head);
182
183        // add the prereqs of an intersection to the graph
184        if (tail.is_intersection())
185            for (Role prereq : tail.prereqs())
186                g.addVertex(prereq);
187
188        dirty = true;
189    }
190
191    /**
192     * Remove a credential from the graph.
193     */
194    public void remove_credential(Credential cred) {
195        if (g.containsEdge(cred))
196            g.removeEdge(cred);
197        dirty = true;
198    }
199
200    /**
201     * Add a role w/o an edge
202     */
203    public void add_vertex(Role v) {
204        if (!g.containsVertex(v)) {
205            g.addVertex(v);
206            dirty = true;
207        }
208    }
209
210    public void remove_vertex(Role v) {
211        if (g.containsVertex(v)) {
212            g.removeVertex(v);
213            dirty = true;
214        }
215    }
216    public Collection<Role> roles() {
217        return g.getVertices();
218    }
219
220    /**
221     * Derive the implied edges in the graph, according to RT0 derivation rules.
222     * They are added to this graph. See "Distributed Credential Chain Discovery
223     * in Trust Management" by Ninghui Li et al. for details. Note that a
224     * derived linking edge can imply a new intersection edge and vice versa.
225     * Therefore we iteratively derive edges, giving up when an iteration
226     * produces 0 new edges.
227     */
228    protected synchronized void derive_implied_edges() {
229        // nothing to do on a clean graph
230        if (!dirty)
231            return;
232
233        clear_old_edges();
234
235        // iteratively derive links. continue as long as new links are added
236        while (derive_links_iter() > 0)
237            ;
238        dirty = false;
239    }
240
241    /**
242     * Single iteration of deriving implied edges. Returns the number of new
243     * links added.
244     */
245    protected int derive_links_iter() {
246        int count = 0;
247
248        /* for every node in the graph.. */
249        for (Role vertex : g.getVertices()) {
250            if (vertex.is_intersection()) {
251                // for each prereq edge:
252                //     find set of principals that have the prereq
253                // find the intersection of all sets (i.e., principals that satisfy all prereqs)
254                // for each principal in intersection:
255                //     add derived edge
256
257                Set<Role> principals = null;
258
259                for (Role prereq : vertex.prereqs()) {
260                    Set<Role> cur_principals = pq.find_principals(prereq);
261
262                    if (principals == null)
263                        principals = cur_principals;
264                    else
265                        // no, they couldn't just call it "intersection"
266                        principals.retainAll(cur_principals);
267
268                    if (principals.size() == 0)
269                        break;
270                }
271
272                // add em
273                for (Role principal : principals)
274                    if (add_derived_edge(vertex, principal))
275                        ++count;
276            }
277
278            else if (vertex.is_linking()) {
279                // make the rest of the code a bit clearer
280                Role A_r1_r2 = vertex;
281
282                Role A_r1 = new Role(A_r1_r2.A_r1());
283                String r2 = A_r1_r2.r2();
284
285                /* locate the node A.r1 */
286                if (!g.containsVertex(A_r1)) continue; /* boring: nothing of the form A.r1 */
287
288                /* for each B that satisfies A_r1 */
289                for (Role principal : pq.find_principals(A_r1)) {
290                    Role B_r2 = new Role(principal + "." + r2);
291                    if (!g.containsVertex(B_r2)) continue;
292
293                    if (add_derived_edge(A_r1_r2, B_r2))
294                        ++count;
295                }
296            }
297        }
298
299        return count;
300    }
301
302    /**
303     * Add a derived edge in the graph. Returns true only if the edge does not
304     * exist.
305     */
306    protected boolean add_derived_edge(Role head, Role tail) {
307        // edge exists: return false
308        if (g.findEdge(tail, head) != null)
309            return false;
310
311        // add the new edge
312        Credential derived_edge = new Credential(head, tail);
313        derived_edges.add(derived_edge);
314        g.addEdge(derived_edge, tail, head);
315
316        return true;
317    }
318
319    /**
320     * Clear the derived edges that currently exist in the graph. This is done
321     * before the edges are rederived. The derived edges in filtered graphs are
322     * also cleared.
323     */
324    protected void clear_old_edges() { 
325        for (Credential i: derived_edges) 
326            g.removeEdge(i);
327        derived_edges = new HashSet<Credential>();
328    }
329    /**
330     * Put the Identity into the set of ids used to validate certificates.
331     * Also put the keyID and name into the translation mappings used by Roles
332     * to pretty print.  In the role mapping, if multiple ids use the same
333     * common name they are disambiguated.  Only one entry for keyid is
334     * allowed.
335     */
336    protected void addIdentity(Identity id) { 
337        identities.add(id);
338        if (id.getName() != null && id.getKeyID() != null) {
339            if ( !keys.containsKey(id.getKeyID()) ) {
340                String name = id.getName();
341                int n= 1;
342
343                while (nicknames.containsKey(name)) {
344                    name = id.getName() + n++;
345                }
346                nicknames.put(name, id.getKeyID());
347                keys.put(id.getKeyID(), name);
348            }
349        }
350    }
351    /**
352     * Translate either keys to nicknames or vice versa.  Break the string into
353     * space separated tokens and then each of them into period separated
354     * strings.  If any of the smallest strings is in the map, replace it with
355     * the value.
356     */
357    protected String replace(String is, Map<String, String> m) {
358        String rv = "";
359        for (String tok: is.split(" ")) {
360            String term = "";
361            for (String s: tok.split("\\.")) {
362                String next = m.containsKey(s) ? m.get(s) : s;
363
364                if (term.isEmpty()) term = next;
365                else term += "." + next;
366            }
367            if (rv.isEmpty()) rv = term;
368            else rv += " " + term;
369        }
370        return rv;
371    }
372
373    public String expandKeyID(String s) { return replace(s, nicknames); }
374    public String expandNickname(String s) { return replace(s, keys); }
375
376    /**
377     * Import a zip file.  First import all the identities
378     * (pem), then the credentials (der) into the credential graph then any
379     * alias files into the two maps.  If keys is not null, any key pairs in
380     * PEM files are put in there.  If errors is not null, errors reading files
381     * are added indexed by filename.
382     */
383    public void readZipFile(File zf, Collection<KeyPair> keys, 
384            Map<String, Exception> errors) throws IOException {
385        Vector<ZipEntry> derEntries = new Vector<ZipEntry>();
386        Map<String, Identity> ids = new TreeMap<String, Identity>();
387        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
388
389        ZipFile z = new ZipFile(zf);
390
391        for (Enumeration<? extends ZipEntry> ze = z.entries(); 
392                ze.hasMoreElements();) {
393            ZipEntry  f = ze.nextElement();
394            try {
395                PEMReader r = new PEMReader(
396                        new InputStreamReader(z.getInputStream(f)));
397                Object o = readPEM(r);
398
399                if ( o != null ) {
400                    if (o instanceof Identity) {
401                        Identity i = (Identity) o;
402                        String kid = i.getKeyID();
403
404                        if (kps.containsKey(kid) ) {
405                            i.setKeyPair(kps.get(kid));
406                            kps.remove(kid);
407                        }
408                        else if (i.getKeyPair() == null ) 
409                            ids.put(i.getKeyID(), i);
410
411                        loadIDChunk(i);
412                    }
413                    else if (o instanceof KeyPair ) {
414                        KeyPair kp = (KeyPair) o;
415                        String kid = extractKeyID(kp.getPublic());
416
417                        if (ids.containsKey(kid)) {
418                            Identity i = ids.get(kid);
419
420                            i.setKeyPair(kp);
421                            ids.remove(kid);
422                        }
423                        else {
424                            kps.put(kid, kp);
425                        }
426                    }
427                }
428                else {
429                    // Not a PEM file
430                    derEntries.add(f);
431                    continue;
432                }
433            }
434            catch (Exception e ) {
435                if (errors != null ) errors.put(f.getName(), e);
436            }
437        }
438
439        for ( ZipEntry f : derEntries ) {
440            try {
441                add_credential(new Credential(z.getInputStream(f), identities));
442            }
443            catch (Exception e ) {
444                if (errors != null ) errors.put(f.getName(), e);
445            }
446        }
447    }
448
449    public void readZipFile(File d) 
450            throws IOException {
451        readZipFile(d, null, null);
452    }
453    public void readZipFile(File d, 
454            Map<String, Exception> errors) throws IOException {
455        readZipFile(d, null, errors);
456    }
457    public void readZipFile(File d, 
458            Collection<KeyPair> keys) throws IOException {
459        readZipFile(d, keys, null);
460    }
461
462    protected Object readPEM(PEMReader r) throws IOException {
463        Identity i = null;
464        KeyPair keys = null;
465        Object o = null;
466
467        while ( (o = r.readObject()) != null ) {
468            if (o instanceof X509Certificate) {
469                if ( i == null ) {
470                    try {
471                        i = new Identity((X509Certificate)o);
472                    }
473                    catch (Exception e) {
474                        // Translate Idenitiy exceptions to IOException
475                        throw new IOException(e);
476                    }
477                    if (keys != null ) {
478                        i.setKeyPair(keys);
479                        keys = null;
480                    }
481                }
482                else throw new IOException("Two certificates");
483            }
484            else if (o instanceof KeyPair ) {
485                if ( i != null ) i.setKeyPair((KeyPair) o);
486                else keys = (KeyPair) o;
487            }
488            else {
489                throw new IOException("Unexpected PEM object: " + 
490                        o.getClass().getName());
491            }
492        }
493
494        if ( i != null ) return i;
495        else if ( keys != null) return keys;
496        else return null;
497    }
498
499    /**
500     * Import a directory full of files.  First import all the identities
501     * (pem), then the credentials (der) into the credential graph then any
502     * alias files into the two maps.  If keys is not null, any key pairs in
503     * PEM files are put in there.  If errors is not null, errors reading files
504     * are added indexed by filename.
505     */
506    public void readDirectory(File d, Collection<KeyPair> keys, 
507            Map<String, Exception> errors) {
508        Vector<File> derFiles = new Vector<File>();
509        Collection<File> files = new Vector<File>();
510        Map<String, Identity> ids = new TreeMap<String, Identity>();
511        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
512
513        if (d.isDirectory() ) 
514            for (File f : d.listFiles()) 
515                files.add(f);
516        else files.add(d);
517
518        for (File f: files ) {
519            try {
520                PEMReader r = new PEMReader(new FileReader(f));
521                Object o = readPEM(r);
522
523                if ( o != null ) {
524                    if (o instanceof Identity) {
525                        Identity i = (Identity) o;
526                        String kid = i.getKeyID();
527
528                        if (kps.containsKey(kid) ) {
529                            i.setKeyPair(kps.get(kid));
530                            kps.remove(kid);
531                        }
532                        else if (i.getKeyPair() == null ) 
533                            ids.put(i.getKeyID(), i);
534
535                        loadIDChunk(i);
536                    }
537                    else if (o instanceof KeyPair ) {
538                        KeyPair kp = (KeyPair) o;
539                        String kid = extractKeyID(kp.getPublic());
540
541                        if (ids.containsKey(kid)) {
542                            Identity i = ids.get(kid);
543
544                            i.setKeyPair(kp);
545                            ids.remove(kid);
546                        }
547                        else {
548                            kps.put(kid, kp);
549                        }
550                    }
551                }
552                else {
553                    // Not a PEM file
554                    derFiles.add(f);
555                    continue;
556                }
557            }
558            catch (Exception e ) {
559                if (errors != null ) errors.put(f.getName(), e);
560            }
561        }
562
563        for ( File f : derFiles ) {
564            try {
565                add_credential(new Credential(f, identities));
566            }
567            catch (Exception e ) {
568                if (errors != null ) errors.put(f.getName(), e);
569            }
570        }
571    }
572
573    public void readDirectory(File d) {
574        readDirectory(d, null, null);
575    }
576    public void readDirectory(File d, Map<String, Exception> errors) {
577        readDirectory(d, null, errors);
578    }
579    public void readDirectory(File d, Collection<KeyPair> keys) {
580        readDirectory(d, keys, null);
581    }
582
583    public void writeZipFile(File f, boolean allIDs, boolean withPrivateKeys) 
584            throws IOException {
585        ZipOutputStream z = new ZipOutputStream(new FileOutputStream(f));
586        Set<Identity> ids = allIDs ?  identities : new TreeSet<Identity>();
587
588        int n = 0;
589        for (Credential c: credentials()) {
590            z.putNextEntry(new ZipEntry("attr" + n++  + ".der"));
591            c.write(z);
592            z.closeEntry();
593            if ( c.getID() != null && !allIDs) ids.add(c.getID());
594        }
595        for (Identity i: ids) {
596            z.putNextEntry(new ZipEntry(i.getName() + ".pem"));
597            i.write(z);
598            if (withPrivateKeys)
599                i.writePrivateKey(z);
600            z.closeEntry();
601        }
602        z.close();
603    }
604
605    /**
606     * Get to the SHA1 hash of the key.
607     */
608    public static String extractKeyID(PublicKey k) {
609        SubjectPublicKeyInfo ki = extractSubjectPublicKeyInfo(k);
610        SubjectKeyIdentifier id = 
611            SubjectKeyIdentifier.createSHA1KeyIdentifier(ki);
612
613        // Now format it into a string for keeps
614        Formatter fmt = new Formatter(new StringWriter());
615        for (byte b: id.getKeyIdentifier())
616            fmt.format("%02x", b);
617        return fmt.out().toString();
618    }
619
620    /**
621     * Extratct the SubjectPublicKeyInfo.  Useful for some other encryptions,
622     * notably Certificate.make_cert().
623     */
624    public static SubjectPublicKeyInfo extractSubjectPublicKeyInfo(
625            PublicKey k) {
626        ASN1Sequence seq = null;
627        try {
628            seq = (ASN1Sequence) new ASN1InputStream(
629                    k.getEncoded()).readObject();
630        }
631        catch (IOException ie) {
632            // Badly formatted key??
633            return null;
634        }
635        return new SubjectPublicKeyInfo(seq);
636    }
637
638
639}
Note: See TracBrowser for help on using the repository browser.