source: java/net/deterlab/abac/Context.java @ 56ec930

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

Remove roles. Scope graph stuff out of user hands.

  • Property mode set to 100644
File size: 17.2 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    protected void add_credential(Credential cred) {
171        Role tail = cred.tail();
172        Role head = cred.head();
173
174        // explicitly add the vertices, to avoid 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    protected 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    protected void add_vertex(Role v) {
204        if (!g.containsVertex(v)) {
205            g.addVertex(v);
206            dirty = true;
207        }
208    }
209
210    protected void remove_vertex(Role v) {
211        if (g.containsVertex(v)) {
212            g.removeVertex(v);
213            dirty = true;
214        }
215    }
216
217    /**
218     * Derive the implied edges in the graph, according to RT0 derivation rules.
219     * They are added to this graph. See "Distributed Credential Chain Discovery
220     * in Trust Management" by Ninghui Li et al. for details. Note that a
221     * derived linking edge can imply a new intersection edge and vice versa.
222     * Therefore we iteratively derive edges, giving up when an iteration
223     * produces 0 new edges.
224     */
225    protected synchronized void derive_implied_edges() {
226        // nothing to do on a clean graph
227        if (!dirty)
228            return;
229
230        clear_old_edges();
231
232        // iteratively derive links. continue as long as new links are added
233        while (derive_links_iter() > 0)
234            ;
235        dirty = false;
236    }
237
238    /**
239     * Single iteration of deriving implied edges. Returns the number of new
240     * links added.
241     */
242    protected int derive_links_iter() {
243        int count = 0;
244
245        /* for every node in the graph.. */
246        for (Role vertex : g.getVertices()) {
247            if (vertex.is_intersection()) {
248                // for each prereq edge:
249                //     find set of principals that have the prereq
250                // find the intersection of all sets (i.e., principals
251                //     that satisfy all prereqs)
252                // for each principal in intersection:
253                //     add derived edge
254
255                Set<Role> principals = null;
256
257                for (Role prereq : vertex.prereqs()) {
258                    Set<Role> cur_principals = pq.find_principals(prereq);
259
260                    if (principals == null)
261                        principals = cur_principals;
262                    else
263                        // no, they couldn't just call it "intersection"
264                        principals.retainAll(cur_principals);
265
266                    if (principals.size() == 0)
267                        break;
268                }
269
270                // add em
271                for (Role principal : principals)
272                    if (add_derived_edge(vertex, principal))
273                        ++count;
274            }
275
276            else if (vertex.is_linking()) {
277                // make the rest of the code a bit clearer
278                Role A_r1_r2 = vertex;
279
280                Role A_r1 = new Role(A_r1_r2.A_r1());
281                String r2 = A_r1_r2.r2();
282
283                /* locate the node A.r1 */
284                if (!g.containsVertex(A_r1)) continue; 
285
286                /* for each B that satisfies A_r1 */
287                for (Role principal : pq.find_principals(A_r1)) {
288                    Role B_r2 = new Role(principal + "." + r2);
289                    if (!g.containsVertex(B_r2)) continue;
290
291                    if (add_derived_edge(A_r1_r2, B_r2))
292                        ++count;
293                }
294            }
295        }
296
297        return count;
298    }
299
300    /**
301     * Add a derived edge in the graph. Returns true only if the edge does not
302     * exist.
303     */
304    protected boolean add_derived_edge(Role head, Role tail) {
305        // edge exists: return false
306        if (g.findEdge(tail, head) != null)
307            return false;
308
309        // add the new edge
310        Credential derived_edge = new Credential(head, tail);
311        derived_edges.add(derived_edge);
312        g.addEdge(derived_edge, tail, head);
313
314        return true;
315    }
316
317    /**
318     * Clear the derived edges that currently exist in the graph. This is done
319     * before the edges are rederived. The derived edges in filtered graphs are
320     * also cleared.
321     */
322    protected void clear_old_edges() { 
323        for (Credential i: derived_edges) 
324            g.removeEdge(i);
325        derived_edges = new HashSet<Credential>();
326    }
327    /**
328     * Put the Identity into the set of ids used to validate certificates.
329     * Also put the keyID and name into the translation mappings used by Roles
330     * to pretty print.  In the role mapping, if multiple ids use the same
331     * common name they are disambiguated.  Only one entry for keyid is
332     * allowed.
333     */
334    protected void addIdentity(Identity id) { 
335        identities.add(id);
336        if (id.getName() != null && id.getKeyID() != null) {
337            if ( !keys.containsKey(id.getKeyID()) ) {
338                String name = id.getName();
339                int n= 1;
340
341                while (nicknames.containsKey(name)) {
342                    name = id.getName() + n++;
343                }
344                nicknames.put(name, id.getKeyID());
345                keys.put(id.getKeyID(), name);
346            }
347        }
348    }
349    /**
350     * Translate either keys to nicknames or vice versa.  Break the string into
351     * space separated tokens and then each of them into period separated
352     * strings.  If any of the smallest strings is in the map, replace it with
353     * the value.
354     */
355    protected String replace(String is, Map<String, String> m) {
356        String rv = "";
357        for (String tok: is.split(" ")) {
358            String term = "";
359            for (String s: tok.split("\\.")) {
360                String next = m.containsKey(s) ? m.get(s) : s;
361
362                if (term.isEmpty()) term = next;
363                else term += "." + next;
364            }
365            if (rv.isEmpty()) rv = term;
366            else rv += " " + term;
367        }
368        return rv;
369    }
370
371    public String expandKeyID(String s) { return replace(s, nicknames); }
372    public String expandNickname(String s) { return replace(s, keys); }
373
374    /**
375     * Import a zip file.  First import all the identities
376     * (pem), then the credentials (der) into the credential graph then any
377     * alias files into the two maps.  If keys is not null, any key pairs in
378     * PEM files are put in there.  If errors is not null, errors reading files
379     * are added indexed by filename.
380     */
381    public void readZipFile(File zf, Collection<KeyPair> keys, 
382            Map<String, Exception> errors) throws IOException {
383        Vector<ZipEntry> derEntries = new Vector<ZipEntry>();
384        Map<String, Identity> ids = new TreeMap<String, Identity>();
385        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
386
387        ZipFile z = new ZipFile(zf);
388
389        for (Enumeration<? extends ZipEntry> ze = z.entries(); 
390                ze.hasMoreElements();) {
391            ZipEntry  f = ze.nextElement();
392            try {
393                PEMReader r = new PEMReader(
394                        new InputStreamReader(z.getInputStream(f)));
395                Object o = readPEM(r);
396
397                if ( o != null ) {
398                    if (o instanceof Identity) {
399                        Identity i = (Identity) o;
400                        String kid = i.getKeyID();
401
402                        if (kps.containsKey(kid) ) {
403                            i.setKeyPair(kps.get(kid));
404                            kps.remove(kid);
405                        }
406                        else if (i.getKeyPair() == null ) 
407                            ids.put(i.getKeyID(), i);
408
409                        loadIDChunk(i);
410                    }
411                    else if (o instanceof KeyPair ) {
412                        KeyPair kp = (KeyPair) o;
413                        String kid = extractKeyID(kp.getPublic());
414
415                        if (ids.containsKey(kid)) {
416                            Identity i = ids.get(kid);
417
418                            i.setKeyPair(kp);
419                            ids.remove(kid);
420                        }
421                        else {
422                            kps.put(kid, kp);
423                        }
424                    }
425                }
426                else {
427                    // Not a PEM file
428                    derEntries.add(f);
429                    continue;
430                }
431            }
432            catch (Exception e ) {
433                if (errors != null ) errors.put(f.getName(), e);
434            }
435        }
436
437        for ( ZipEntry f : derEntries ) {
438            try {
439                add_credential(new Credential(z.getInputStream(f), identities));
440            }
441            catch (Exception e ) {
442                if (errors != null ) errors.put(f.getName(), e);
443            }
444        }
445    }
446
447    public void readZipFile(File d) 
448            throws IOException {
449        readZipFile(d, null, null);
450    }
451    public void readZipFile(File d, 
452            Map<String, Exception> errors) throws IOException {
453        readZipFile(d, null, errors);
454    }
455    public void readZipFile(File d, 
456            Collection<KeyPair> keys) throws IOException {
457        readZipFile(d, keys, null);
458    }
459
460    protected Object readPEM(PEMReader r) throws IOException {
461        Identity i = null;
462        KeyPair keys = null;
463        Object o = null;
464
465        while ( (o = r.readObject()) != null ) {
466            if (o instanceof X509Certificate) {
467                if ( i == null ) {
468                    try {
469                        i = new Identity((X509Certificate)o);
470                    }
471                    catch (Exception e) {
472                        // Translate Idenitiy exceptions to IOException
473                        throw new IOException(e);
474                    }
475                    if (keys != null ) {
476                        i.setKeyPair(keys);
477                        keys = null;
478                    }
479                }
480                else throw new IOException("Two certificates");
481            }
482            else if (o instanceof KeyPair ) {
483                if ( i != null ) i.setKeyPair((KeyPair) o);
484                else keys = (KeyPair) o;
485            }
486            else {
487                throw new IOException("Unexpected PEM object: " + 
488                        o.getClass().getName());
489            }
490        }
491
492        if ( i != null ) return i;
493        else if ( keys != null) return keys;
494        else return null;
495    }
496
497    /**
498     * Import a directory full of files.  First import all the identities
499     * (pem), then the credentials (der) into the credential graph then any
500     * alias files into the two maps.  If keys is not null, any key pairs in
501     * PEM files are put in there.  If errors is not null, errors reading files
502     * are added indexed by filename.
503     */
504    public void readDirectory(File d, Collection<KeyPair> keys, 
505            Map<String, Exception> errors) {
506        Vector<File> derFiles = new Vector<File>();
507        Collection<File> files = new Vector<File>();
508        Map<String, Identity> ids = new TreeMap<String, Identity>();
509        Map<String, KeyPair> kps = new TreeMap<String, KeyPair>();
510
511        if (d.isDirectory() ) 
512            for (File f : d.listFiles()) 
513                files.add(f);
514        else files.add(d);
515
516        for (File f: files ) {
517            try {
518                PEMReader r = new PEMReader(new FileReader(f));
519                Object o = readPEM(r);
520
521                if ( o != null ) {
522                    if (o instanceof Identity) {
523                        Identity i = (Identity) o;
524                        String kid = i.getKeyID();
525
526                        if (kps.containsKey(kid) ) {
527                            i.setKeyPair(kps.get(kid));
528                            kps.remove(kid);
529                        }
530                        else if (i.getKeyPair() == null ) 
531                            ids.put(i.getKeyID(), i);
532
533                        loadIDChunk(i);
534                    }
535                    else if (o instanceof KeyPair ) {
536                        KeyPair kp = (KeyPair) o;
537                        String kid = extractKeyID(kp.getPublic());
538
539                        if (ids.containsKey(kid)) {
540                            Identity i = ids.get(kid);
541
542                            i.setKeyPair(kp);
543                            ids.remove(kid);
544                        }
545                        else {
546                            kps.put(kid, kp);
547                        }
548                    }
549                }
550                else {
551                    // Not a PEM file
552                    derFiles.add(f);
553                    continue;
554                }
555            }
556            catch (Exception e ) {
557                if (errors != null ) errors.put(f.getName(), e);
558            }
559        }
560
561        for ( File f : derFiles ) {
562            try {
563                add_credential(new Credential(f, identities));
564            }
565            catch (Exception e ) {
566                if (errors != null ) errors.put(f.getName(), e);
567            }
568        }
569    }
570
571    public void readDirectory(File d) {
572        readDirectory(d, null, null);
573    }
574    public void readDirectory(File d, Map<String, Exception> errors) {
575        readDirectory(d, null, errors);
576    }
577    public void readDirectory(File d, Collection<KeyPair> keys) {
578        readDirectory(d, keys, null);
579    }
580
581    public void writeZipFile(File f, boolean allIDs, boolean withPrivateKeys) 
582            throws IOException {
583        ZipOutputStream z = new ZipOutputStream(new FileOutputStream(f));
584        Set<Identity> ids = allIDs ?  identities : new TreeSet<Identity>();
585
586        int n = 0;
587        for (Credential c: credentials()) {
588            z.putNextEntry(new ZipEntry("attr" + n++  + ".der"));
589            c.write(z);
590            z.closeEntry();
591            if ( c.issuer() != null && !allIDs) ids.add(c.issuer());
592        }
593        for (Identity i: ids) {
594            z.putNextEntry(new ZipEntry(i.getName() + ".pem"));
595            i.write(z);
596            if (withPrivateKeys)
597                i.writePrivateKey(z);
598            z.closeEntry();
599        }
600        z.close();
601    }
602
603    /**
604     * Get to the SHA1 hash of the key.
605     */
606    public static String extractKeyID(PublicKey k) {
607        SubjectPublicKeyInfo ki = extractSubjectPublicKeyInfo(k);
608        SubjectKeyIdentifier id = 
609            SubjectKeyIdentifier.createSHA1KeyIdentifier(ki);
610
611        // Now format it into a string for keeps
612        Formatter fmt = new Formatter(new StringWriter());
613        for (byte b: id.getKeyIdentifier())
614            fmt.format("%02x", b);
615        return fmt.out().toString();
616    }
617
618    /**
619     * Extratct the SubjectPublicKeyInfo.  Useful for some other encryptions,
620     * notably Certificate.make_cert().
621     */
622    public static SubjectPublicKeyInfo extractSubjectPublicKeyInfo(
623            PublicKey k) {
624        ASN1Sequence seq = null;
625        try {
626            seq = (ASN1Sequence) new ASN1InputStream(
627                    k.getEncoded()).readObject();
628        }
629        catch (IOException ie) {
630            // Badly formatted key??
631            return null;
632        }
633        return new SubjectPublicKeyInfo(seq);
634    }
635
636
637}
Note: See TracBrowser for help on using the repository browser.