source: java/net/deterlab/abac/Identity.java @ 1d5066d

Last change on this file since 1d5066d was 461edba, checked in by Ted Faber <faber@…>, 10 years ago

Clean up javadoc for v8.

  • Property mode set to 100644
File size: 15.1 KB
Line 
1package net.deterlab.abac;
2
3import java.io.*;
4
5import java.util.*;
6import java.security.*;
7import java.security.cert.*;
8import javax.security.auth.x500.*;
9
10import java.math.BigInteger;
11
12import org.bouncycastle.asn1.*;
13import org.bouncycastle.asn1.util.*;
14import org.bouncycastle.asn1.x509.*;
15import org.bouncycastle.x509.*;
16import org.bouncycastle.openssl.*;
17
18
19/**
20 * An ABAC identity.  An X509 Certificate-encoded public key.  The key
21 * identifier is used as the name of the Identity.  This whole class is
22 * something of a jabac extension.
23 * @author <a href="http://abac.deterlab.net">ISI ABAC team</a>
24 * @version 1.5
25 */
26public class Identity implements Comparable {
27    /** Default validity period (in seconds) */
28    static public long defaultValidity = 3600L * 24L * 365L;
29    /** The underlying X509 certificate. */
30    protected X509Certificate cert;
31    /** The public key id used as this principal's name */
32    protected String keyid;
33    /** The common name in the certificate, used as a mnemonic */
34    protected String cn;
35    /** The keypair, if any, used to sign for this Identity */
36    protected KeyPair kp;
37    /** The expiration for this Identity */
38    protected Date expiration;
39
40    /** Make sure BouncyCastle is loaded */
41    static { Context.loadBouncyCastle(); } 
42
43    /**
44     * Initialize from PEM cert in a reader.  Use a PEMReader to get
45     * the certificate, and call init(cert) on it.
46     * @param r a Reader containing the certificate
47     * @throws CertInvalidException if the stream is unparsable
48     * @throws MissingIssuerException if none of the Identities can validate the
49     *                              certificate
50     * @throws BadSignatureException if the signature check fails
51     * @throws ABACException if an uncategorized error occurs
52     */
53    protected void init(Reader r) throws ABACException {
54        PEMReader pr = new PEMReader(r);
55        Object c = null;
56
57        try {
58            while ( ( c= pr.readObject()) != null ){
59
60                if (c instanceof X509Certificate) {
61                    if ( cn == null ) 
62                        init((X509Certificate)c);
63                    else
64                        throw new CertInvalidException("Two certs in one");
65                }
66                else if (c instanceof KeyPair) setKeyPair((KeyPair)c);
67                else 
68                    throw new CertInvalidException(
69                            "Not an identity certificate");
70            }
71        }
72        catch (IOException e) {
73            throw new CertInvalidException(e.getMessage(), e);
74        }
75        // If there's nothing for the PEM reader to parse, the cert is invalid.
76        if (cn == null) 
77            throw new CertInvalidException("Not an identity certificate");
78    }
79
80    /**
81     * Initialize internals from cert.  Confirm it is self signed,  and then
82     * the keyid and common name.  There's some work to get this stuff, but
83     * it's all an incantation of getting the right classes to get the right
84     * data.  Looks more complex than it is.
85     * @param c an X509Certificate to init from
86     * @throws CertInvalidException if the stream is unparsable
87     * @throws MissingIssuerException if none of the Identities can validate the
88     *                              certificate
89     * @throws BadSignatureException if the signature check fails
90     * @throws ABACException if an uncategorized error occurs
91     */
92    protected void init(X509Certificate c) throws ABACException {
93        cert = (X509Certificate) c;
94        try {
95            cert.verify(cert.getPublicKey());
96        }
97        catch (SignatureException e) {
98            // XXX: the cert is not signed by the key we provided.  Right now
99            // we check each cert as if it were self-signed. Other signing
100            // strategies are allowed here by default.  We expect outside
101            // sources to validate ID certs if they expect different chains of
102        }
103        catch (CertificateException e) {
104            throw new CertInvalidException(e.getMessage(), e);
105        }
106        catch (InvalidKeyException e) {
107            // XXX: the cert is not signed by the key we provided.  Right now
108            // we check each cert as if it were self-signed. Other signing
109            // strategies are allowed here by default.  We expect outside
110            // sources to validate ID certs if they expect different chains of
111            // trust.
112        }
113        catch (GeneralSecurityException e) {
114            throw new ABACException(e.getMessage(), e);
115        }
116
117        // So far so good.  Check the validity (dates)
118        try {
119            cert.checkValidity();
120        }
121        catch (CertificateException e) {
122            // Validity exceprions are derived from CertificateExceptions
123            throw new CertInvalidException(e.getMessage(), e);
124        }
125
126        // Cert is valid, fill in the CN and keyid
127        keyid = Context.extractKeyID(cert.getPublicKey());
128        cn = cert.getSubjectDN().getName();
129        expiration = cert.getNotAfter();
130        /// XXX: better parse
131        if (cn.startsWith("CN=")) cn = cn.substring(3);
132    }
133
134    /**
135     * Construct from a string, used as a CN.  Keys are generated.  If signer
136     * and signingKey are given, sign the certificate with them.  If neither is
137     * given, self sign it.  If one is given and not the other, throw an
138     * ABACException.
139     * @param cn a String containing the menomnic name
140     * @param validity a long containing the validity period (in seconds)
141     * @param signer an X509Certificate that is signing the Identity
142     * @param signingKey the key with which to sign
143     * @throws CertInvalidException if the stream is unparsable
144     * @throws MissingIssuerException if none of the Identities can validate the
145     *                              certificate
146     * @throws BadSignatureException if the signature check fails
147     * @throws ABACException if an uncategorized error occurs
148     */
149    public Identity(String cn, long validity, X509Certificate signer, 
150            PrivateKey signingKey) 
151            throws ABACException {
152
153        if ( (signer != null && signingKey == null) || 
154                (signer == null && signingKey != null) )
155            throw new ABACException("Both signer and signingKey must be "+ 
156                    "given or neither");
157
158        X509V1CertificateGenerator gen = new X509V1CertificateGenerator();
159        try {
160            kp = KeyPairGenerator.getInstance("RSA").genKeyPair();
161        }
162        catch (NoSuchAlgorithmException e) {
163            throw new ABACException(e.getMessage(), e);
164        }
165        X509Certificate a = null;
166        X500Principal sp = (signer != null ) ?
167            signer.getSubjectX500Principal() : new X500Principal("CN=" + cn);
168        PrivateKey sk = (signingKey != null ) ? signingKey : kp.getPrivate();
169
170        gen.setIssuerDN(sp);
171        gen.setSubjectDN(new X500Principal("CN=" + cn));
172        gen.setNotAfter(new Date(System.currentTimeMillis() +
173                1000L * validity));
174        gen.setNotBefore(new Date(System.currentTimeMillis()));
175        gen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
176        gen.setPublicKey(kp.getPublic());
177        gen.setSignatureAlgorithm("SHA256WithRSAEncryption");
178        try {
179            a = (X509Certificate) gen.generate(sk, "BC");
180        }
181        catch (CertificateEncodingException e) {
182            throw new CertInvalidException(e.getMessage(), e);
183        }
184        catch (GeneralSecurityException e) {
185            throw new ABACException(e.getMessage(), e);
186        }
187
188        init(a);
189    }
190    /**
191     * Construct from a string, used as a CN.  Keys are generated.
192     * @param cn a String containing the menomnic name
193     * @param validity a long containing the validity period (in seconds)
194     * @throws CertInvalidException if the stream is unparsable
195     * @throws MissingIssuerException if none of the Identities can validate the
196     *                              certificate
197     * @throws BadSignatureException if the signature check fails
198     * @throws ABACException if an uncategorized error occurs
199     */
200    public Identity(String cn, long validity) throws ABACException {
201        this(cn, validity, null, null);
202    }
203
204    /**
205     * Construct from a string, used as a CN.  Keys are generated.
206     * @param cn a String containing the menomnic name
207     * @throws CertInvalidException if the stream is unparsable
208     * @throws MissingIssuerException if none of the Identities can validate the
209     *                              certificate
210     * @throws BadSignatureException if the signature check fails
211     * @throws ABACException if an uncategorized error occurs
212     */
213    public Identity(String cn) throws ABACException { 
214        this(cn, defaultValidity);
215    }
216
217    /**
218     * Construct from a file containing a self-signed PEM certificate.
219     * @param file the File to read
220     * @throws CertInvalidException if the stream is unparsable
221     * @throws MissingIssuerException if none of the Identities can validate the
222     *                              certificate
223     * @throws BadSignatureException if the signature check fails
224     * @throws ABACException if an uncategorized error occurs
225     * @throws FileNotFoundException if the file is invalid
226     */
227    public Identity(File file) throws ABACException, FileNotFoundException { 
228        kp = null;
229        init(new FileReader(file));
230    }
231
232    /**
233     * Construct from a reader containing a self-signed PEM certificate.
234     * @param r the Reader containing the certificate
235     * @throws CertInvalidException if the stream is unparsable
236     * @throws MissingIssuerException if none of the Identities can validate the
237     *                              certificate
238     * @throws BadSignatureException if the signature check fails
239     * @throws ABACException if an uncategorized error occurs
240     */
241    public Identity(Reader r) throws ABACException {
242        kp = null;
243        init(r);
244    }
245
246    /**
247     * Construct from an InputStream containing a self-signed PEM certificate.
248     * @param s the InputStream containing the certificate
249     * @throws CertInvalidException if the stream is unparsable
250     * @throws MissingIssuerException if none of the Identities can validate the
251     *                              certificate
252     * @throws BadSignatureException if the signature check fails
253     * @throws ABACException if an uncategorized error occurs
254     */
255    public Identity(InputStream s) throws ABACException {
256        kp = null;
257        init(new InputStreamReader(s));
258    }
259
260    /**
261     * Construct from an X509Certificate
262     * @param cert an X509Certificate to init from
263     * @throws CertInvalidException if the stream is unparsable
264     * @throws MissingIssuerException if none of the Identities can validate the
265     *                              certificate
266     * @throws BadSignatureException if the signature check fails
267     * @throws ABACException if an uncategorized error occurs
268     */
269    public Identity(X509Certificate cert) throws ABACException {
270        kp = null;
271        init(cert);
272    }
273
274    /**
275     * Write the PEM key to the given writer.
276     * @param w the Writer
277     * @return true if the Identity had a keypair and wrote the key
278     * @throws IOException if writing fails
279     */
280    public boolean writePrivateKey(Writer w) throws IOException {
281        if (kp != null ) {
282            PEMWriter pw = new PEMWriter(w);
283
284            pw.writeObject(kp.getPrivate());
285            pw.flush();
286            return true;
287        }
288        else return false;
289    }
290
291    /**
292     * Write the PEM key to a file with the given name.
293     * @param fn a file name to write to
294     * @return true if the Identity had a keypair and wrote the key
295     * @throws IOException if writing fails (including bad file name)
296     */
297    public boolean writePrivateKey(String fn) throws IOException {
298        return writePrivateKey(new FileWriter(fn));
299    }
300
301    /**
302     * Write the PEM key to the given file.
303     * @param fn a String with the output file name
304     * @return true if the Identity had a keypair and wrote the key
305     * @throws IOException if writing fails (including bad file name)
306     */
307    public boolean writePrivateKey(File fn) throws IOException {
308        return writePrivateKey(new FileWriter(fn));
309    }
310
311    /**
312     * Write the PEM key to the given OutputStream.
313     * @param s an OutputStream to write on
314     * @return true if the Identity had a keypair and wrote the key
315     * @throws IOException if writing fails (including bad file name)
316     */
317    public boolean writePrivateKey(OutputStream s) throws IOException {
318        return writePrivateKey(new OutputStreamWriter(s));
319    }
320
321
322    /**
323     * Write the PEM cert to the given writer.
324     * @param w a Writer to write on
325     * @throws IOException if writing fails
326     */
327    public void write(Writer w) throws IOException {
328        PEMWriter pw = new PEMWriter(w);
329
330        pw.writeObject(cert);
331        pw.flush();
332    }
333
334    /**
335     * Write the PEM cert to a file with the given name.
336     * @param fn a file name to write to
337     * @throws IOException if writing fails (including bad file name)
338     */
339    public void write(String fn) throws IOException {
340        write(new FileWriter(fn));
341    }
342
343    /**
344     * Write the PEM cert to the given file.
345     * @param fn a String with the output file name
346     * @throws IOException if writing fails
347     */
348    public void write(File fn) throws IOException {
349        write(new FileWriter(fn));
350    }
351
352    /**
353     * Write the PEM cert to the given OutputStream.
354     * @param s an OutputStream to write on
355     * @throws IOException if writing fails
356     */
357    public void write(OutputStream s) throws IOException {
358        write(new OutputStreamWriter(s));
359    }
360
361
362    /**
363     * Return the Identity's KeyID
364     * @return the Identity's KeyID
365     */
366    public String getKeyID() { return keyid; }
367    /**
368     * Return the Identity's mnemonic name
369     * @return the Identity's mnemonic name
370     */
371    public String getName() { return cn; }
372    /**
373     * Return the Identity's X509 Certificate
374     * @return the Identity's X509 Certificate
375     */
376    public X509Certificate getCertificate() { return cert; }
377
378    /**
379     * Return the expiration time of the Identity
380     * @return a Date the expiration time of the Identity
381     */
382    public Date getExpiration(){ return expiration; }
383
384    /**
385     * Return a simple string rep of the Identity.
386     * @return a simple string rep of the Identity.
387     */
388    public String toString() { 
389        String s = keyid + " (" + cn ;
390
391        if (getKeyPair() != null ) s += " [keyed]";
392        s += ")";
393        return s;
394    }
395    /**
396     * Associate a keypair with this Identity.  If the ID has a certificate,
397     * make sure that the keypair matches it.  If not throw an
398     * IllegalArgumentException.
399     * @param k the KeyPair to connect
400     * @throws IllegalArgumentException if the keypair does not
401     *                              match the pubkey in the X509 certificate
402     */
403    public void setKeyPair(KeyPair k) {
404        if (keyid != null) {
405            String kid = Context.extractKeyID(k.getPublic());
406
407            if ( kid != null && kid.equals(keyid)) kp = k;
408            else 
409                throw new IllegalArgumentException(
410                        "Keypair does not match certificate");
411        }
412        else kp = k;
413    }
414
415    /**
416     * Return the keypair associated with this Identity (if any)
417     * @return the keypair associated with this Identity (if any)
418     */
419    public KeyPair getKeyPair() { return kp; }
420
421    /**
422     * Return true if the two identites refer to teh same key.  Two Identities
423     * are equal if their key ID's match.
424     * @return true if the two key ID's are equal.
425     */
426    public boolean equals(Object o) { 
427        if ( o == null ) return false;
428        else if ( ! (o instanceof Identity) ) return false;
429        else return getKeyID().equals(((Identity)o).getKeyID());
430    }
431
432    /**
433     * Return a hash code for the Identity - the hash of its KeyID()
434     * @return an int, the hashCode
435     */
436    public int hashCode() {
437        if (keyid == null) return super.hashCode();
438        return keyid.hashCode();
439    }
440
441
442    /**
443     * Order 2 identities for sorting.  They are ordered by their key ID's.
444     * @param o an Object to compare
445     * @return -1 if this Identity is before, 0 if they are the same, and 1
446     *              if this Identity is after the given object.
447     */
448    public int compareTo(Object o) { 
449        if ( ! (o instanceof Identity) ) return 1;
450        else return getKeyID().compareTo(((Identity)o).getKeyID());
451    }
452
453};
Note: See TracBrowser for help on using the repository browser.