source: java/net/deterlab/abac/Context.java @ d69593c

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

Compatibility additions.

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