source: java/net/deterlab/abac/Identity.java @ b7e77df

abac0-leakabac0-meitvf-new-xml
Last change on this file since b7e77df was 4bd50f4, checked in by Ted Faber <faber@…>, 11 years ago

Deal with non-self-signed certificates in java (and test)

  • Property mode set to 100644
File size: 13.2 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.4
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            throw new BadSignatureException(e.getMessage(), e);
99        }
100        catch (CertificateException e) {
101            throw new CertInvalidException(e.getMessage(), e);
102        }
103        catch (InvalidKeyException e) {
104            // XXX: the cert is not signed by the key we provided.  Right now
105            // we check each cert as if it were self-signed. Other signing
106            // strategies are allowed here by default.  We expect outside
107            // sources to validate ID certs if they expect different chains of
108            // trust.
109        }
110        catch (GeneralSecurityException e) {
111            throw new ABACException(e.getMessage(), e);
112        }
113        // Cert is valid, fill in the CN and keyid
114        keyid = Context.extractKeyID(cert.getPublicKey());
115        cn = cert.getSubjectDN().getName();
116        expiration = cert.getNotAfter();
117        /// XXX: better parse
118        if (cn.startsWith("CN=")) cn = cn.substring(3);
119    }
120
121    /**
122     * Construct from a string, used as a CN.  Keys are generated.
123     * @param cn a String containing the menomnic name
124     * @param validity a long containing the validity period (in seconds)
125     * @throws CertInvalidException if the stream is unparsable
126     * @throws MissingIssuerException if none of the Identities can validate the
127     *                              certificate
128     * @throws BadSignatureException if the signature check fails
129     * @throws ABACException if an uncategorized error occurs
130     */
131    public Identity(String cn, long validity) throws ABACException {
132        X509V1CertificateGenerator gen = new X509V1CertificateGenerator();
133        try {
134            kp = KeyPairGenerator.getInstance("RSA").genKeyPair();
135        }
136        catch (NoSuchAlgorithmException e) {
137            throw new ABACException(e.getMessage(), e);
138        }
139        X509Certificate a = null;
140
141        gen.setIssuerDN(new X500Principal("CN=" + cn));
142        gen.setSubjectDN(new X500Principal("CN=" + cn));
143        gen.setNotAfter(new Date(System.currentTimeMillis() +
144                1000L * validity));
145        gen.setNotBefore(new Date(System.currentTimeMillis()));
146        gen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
147        gen.setPublicKey(kp.getPublic());
148        gen.setSignatureAlgorithm("SHA256WithRSAEncryption");
149        try {
150            a = (X509Certificate) gen.generate(kp.getPrivate(), "BC");
151        }
152        catch (CertificateEncodingException e) {
153            throw new CertInvalidException(e.getMessage(), e);
154        }
155        catch (GeneralSecurityException e) {
156            throw new ABACException(e.getMessage(), e);
157        }
158
159        init(a);
160    }
161
162    /**
163     * Construct from a string, used as a CN.  Keys are generated.
164     * @param cn a String containing the menomnic name
165     * @throws CertInvalidException if the stream is unparsable
166     * @throws MissingIssuerException if none of the Identities can validate the
167     *                              certificate
168     * @throws BadSignatureException if the signature check fails
169     * @throws ABACException if an uncategorized error occurs
170     */
171    public Identity(String cn) throws ABACException { 
172        this(cn, defaultValidity);
173    }
174
175    /**
176     * Construct from a file containing a self-signed PEM certificate.
177     * @param file the File to read
178     * @throws CertInvalidException if the stream is unparsable
179     * @throws MissingIssuerException if none of the Identities can validate the
180     *                              certificate
181     * @throws BadSignatureException if the signature check fails
182     * @throws ABACException if an uncategorized error occurs
183     * @throws FileNotFoundException if the file is invalid
184     */
185    public Identity(File file) throws ABACException, FileNotFoundException { 
186        kp = null;
187        init(new FileReader(file));
188    }
189
190    /**
191     * Construct from a reader containing a self-signed PEM certificate.
192     * @param r the Reader containing the certificate
193     * @throws CertInvalidException if the stream is unparsable
194     * @throws MissingIssuerException if none of the Identities can validate the
195     *                              certificate
196     * @throws BadSignatureException if the signature check fails
197     * @throws ABACException if an uncategorized error occurs
198     */
199    public Identity(Reader r) throws ABACException {
200        kp = null;
201        init(r);
202    }
203
204    /**
205     * Construct from an InputStream containing a self-signed PEM certificate.
206     * @param s the InputStream containing the certificate
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(InputStream s) throws ABACException {
214        kp = null;
215        init(new InputStreamReader(s));
216    }
217
218    /**
219     * Construct from an X509Certificate
220     * @param cert an X509Certificate to init from
221     * @throws CertInvalidException if the stream is unparsable
222     * @throws MissingIssuerException if none of the Identities can validate the
223     *                              certificate
224     * @throws BadSignatureException if the signature check fails
225     * @throws ABACException if an uncategorized error occurs
226     */
227    Identity(X509Certificate cert) throws ABACException {
228        kp = null;
229        init(cert);
230    }
231
232    /**
233     * Write the PEM key to the given writer.
234     * @param w the Writer
235     * @return true if the Identity had a keypair and wrote the key
236     * @throws IOException if writing fails
237     */
238    public boolean writePrivateKey(Writer w) throws IOException {
239        if (kp != null ) {
240            PEMWriter pw = new PEMWriter(w);
241
242            pw.writeObject(kp.getPrivate());
243            pw.flush();
244            return true;
245        }
246        else return false;
247    }
248
249    /**
250     * Write the PEM key to a file with the given name.
251     */
252    public boolean writePrivateKey(String fn) 
253            throws IOException, FileNotFoundException {
254        return writePrivateKey(new FileWriter(fn));
255    }
256
257    /**
258     * Write the PEM key to the given file.
259     * @param fn a String with the output file name
260     * @return true if the Identity had a keypair and wrote the key
261     * @throws IOException if writing fails
262     */
263    public boolean writePrivateKey(File fn) 
264            throws IOException, FileNotFoundException {
265        return writePrivateKey(new FileWriter(fn));
266    }
267
268    /**
269     * Write the PEM key to the given OutputStream.
270     * @param s an OutputStream to write on
271     * @return true if the Identity had a keypair and wrote the key
272     * @throws IOException if writing fails
273     */
274    public boolean writePrivateKey(OutputStream s) 
275            throws IOException, FileNotFoundException {
276        return writePrivateKey(new OutputStreamWriter(s));
277    }
278
279
280    /**
281     * Write the PEM cert to the given writer.
282     * @param w a Writer to write on
283     * @throws IOException if writing fails
284     */
285    public void write(Writer w) throws IOException {
286        PEMWriter pw = new PEMWriter(w);
287
288        pw.writeObject(cert);
289        pw.flush();
290    }
291
292    /**
293     * Write the PEM cert to a file with the given name.
294     */
295    public void write(String fn) throws IOException, FileNotFoundException {
296        write(new FileWriter(fn));
297    }
298
299    /**
300     * Write the PEM cert to the given file.
301     * @param fn a String with the output file name
302     * @throws IOException if writing fails
303     */
304    public void write(File fn) throws IOException, FileNotFoundException {
305        write(new FileWriter(fn));
306    }
307
308    /**
309     * Write the PEM cert to the given OutputStream.
310     * @param s an OutputStream to write on
311     * @throws IOException if writing fails
312     */
313    public void write(OutputStream s) 
314        throws IOException, FileNotFoundException {
315        write(new OutputStreamWriter(s));
316    }
317
318
319    /**
320     * Return the Identity's KeyID
321     * @return the Identity's KeyID
322     */
323    public String getKeyID() { return keyid; }
324    /**
325     * Return the Identity's mnemonic name
326     * @return the Identity's mnemonic name
327     */
328    public String getName() { return cn; }
329    /**
330     * Return the Identity's X509 Certificate
331     * @return the Identity's X509 Certificate
332     */
333    public X509Certificate getCertificate() { return cert; }
334
335    /**
336     * Return the expiration time of the Identity
337     * @return a Date the expiration time of the Identity
338     */
339    public Date getExpiration(){ return expiration; }
340
341    /**
342     * Return a simple string rep of the Identity.
343     * @return a simple string rep of the Identity.
344     */
345    public String toString() { 
346        String s = keyid + " (" + cn ;
347
348        if (getKeyPair() != null ) s += " [keyed]";
349        s += ")";
350        return s;
351    }
352    /**
353     * Associate a keypair with this Identity.  If the ID has a certificate,
354     * make sure that the keypair matches it.  If not throw an
355     * IllegalArgumentException.
356     * @param k the KeyPair to connect
357     * @throws IllegalArgumentException if the keypair does not
358     *                              match the pubkey in the X509 certificate
359     */
360    public void setKeyPair(KeyPair k) {
361        if (keyid != null) {
362            String kid = Context.extractKeyID(k.getPublic());
363
364            if ( kid != null && kid.equals(keyid)) kp = k;
365            else 
366                throw new IllegalArgumentException(
367                        "Keypair does not match certificate");
368        }
369        else kp = k;
370    }
371
372    /**
373     * Return the keypair associated with this Identity (if any)
374     * @return the keypair associated with this Identity (if any)
375     */
376    public KeyPair getKeyPair() { return kp; }
377
378    /**
379     * Return true if the two identites refer to teh same key.  Two Identities
380     * are equal if their key ID's match.
381     * @return true if the two key ID's are equal.
382     */
383    public boolean equals(Object o) { 
384        if ( o == null ) return false;
385        else if ( ! (o instanceof Identity) ) return false;
386        else return getKeyID().equals(((Identity)o).getKeyID());
387    }
388
389    /**
390     * Return a hash code for the Identity - the hash of its KeyID()
391     * @return an int, the hashCode
392     */
393    public int hashCode() {
394        if (keyid == null) return super.hashCode();
395        return keyid.hashCode();
396    }
397
398
399    /**
400     * Order 2 identities for sorting.  They are ordered by their key ID's.
401     * @param o an Object to compare
402     * @return -1 if this Identity is before, 0 if they are the same, and 1
403     *              if this Identity is after the given object.
404     */
405    public int compareTo(Object o) { 
406        if ( ! (o instanceof Identity) ) return 1;
407        else return getKeyID().compareTo(((Identity)o).getKeyID());
408    }
409
410};
Note: See TracBrowser for help on using the repository browser.