initial import
[org.ibex.crypto.git] / src / org / ibex / crypto / X509.java
1 /*
2  * com.brian_web.x509.* - By Brian Alliet
3  * Copyright (C) 2004 Brian Alliet
4  * 
5  * Based on Bouncy Castle by The Legion Of The Bouncy Castle
6  * Copyright (c) 2000 The Legion Of The Bouncy Castle 
7  * (http://www.bouncycastle.org)
8  * 
9  * Permission is hereby granted, free of charge, to any person obtaining a 
10  * copy of this software and associated documentation files (the "Software"),
11  * to deal in the Software without restriction, including without limitation 
12  * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
13  * and/or sell copies of the Software, and to permit persons to whom the 
14  * Software is furnished to do so, subject to the following conditions:
15  * 
16  * The above copyright notice and this permission notice shall be included in
17  * all copies or substantial portions of the Software.
18  * 
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
25  * DEALINGS IN THE SOFTWARE.
26  */
27
28 package com.brian_web.x509;
29
30 import com.brian_web.der.*;
31 import com.brian_web.crypto.*;
32
33 import java.io.*;
34 import java.util.*;
35
36 public class X509 {
37     public static class Certificate {
38         public static final String RSA_ENCRYPTION           = "1.2.840.113549.1.1.1";
39         public static final String MD2_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.2";
40         public static final String MD4_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.3";
41         public static final String MD5_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.4";
42         public static final String SHA1_WITH_RSA_ENCRYPTION = "1.2.840.113549.1.1.5";
43     
44         public static final String BASIC_CONSTRAINTS = "2.5.29.19";
45     
46         private final byte[] certBytes;
47         private final byte[] tbsCertBytes;
48     
49         public final Number version;    
50         public final Number serialNo;
51         public final X509Name issuer;
52         public final Date startDate;
53         public final Date endDate;
54         public final X509Name subject;
55     
56         public final AlgorithmIdentifier publicKeyAlgorithm;
57         public final DERBitString publicKey;
58     
59         public final Object issuerUniqueID;
60         public final Object subjectUniqueID;
61     
62         public final Vector extensions;
63     
64         public final DERBitString signature;
65         public final AlgorithmIdentifier signatureAlgorithm;
66     
67         public final BC basicContraints;
68
69     
70         public Certificate(InputStream is) throws IOException {
71             int i;
72             RecordingInputStream certIS = new RecordingInputStream(is);
73             DERInputStream certSequence = new DERInputStream(certIS).getSequenceStream();
74             RecordingInputStream tbsCertIS = new RecordingInputStream(certSequence);
75         
76             try {
77                 Vector tbsSequence = (Vector) new DERInputStream(tbsCertIS).readObject();
78                 tbsCertBytes = tbsCertIS.getBytes();
79                 signatureAlgorithm = new AlgorithmIdentifier(certSequence.readObject());
80                 signature = (DERBitString) certSequence.readObject();
81             
82                 i=0;
83                 if(tbsSequence.elementAt(i) instanceof DERTaggedObject)
84                     version = (Number)((DERTaggedObject)tbsSequence.elementAt(i++)).object;
85                 else
86                     version = new Integer(0);
87             
88                 serialNo = (Number) tbsSequence.elementAt(i++);
89                 AlgorithmIdentifier signatureAlgorithm2 = new AlgorithmIdentifier(tbsSequence.elementAt(i++));
90                 if(!signatureAlgorithm2.equals(signatureAlgorithm))
91                     throw new DERException("AlgoritmIdentifier mismatch " + signatureAlgorithm + " vs " + signatureAlgorithm2);
92                 issuer = new X509Name(tbsSequence.elementAt(i++));
93             
94                 Vector validity = (Vector) tbsSequence.elementAt(i++);
95                 startDate = (Date) validity.elementAt(0);
96                 endDate = (Date) validity.elementAt(1);
97             
98                 subject = new X509Name(tbsSequence.elementAt(i++));
99             
100                 Vector publicKeyInfo = (Vector) tbsSequence.elementAt(i++);
101                 publicKeyAlgorithm = new AlgorithmIdentifier(publicKeyInfo.elementAt(0));
102                 publicKey = (DERBitString) publicKeyInfo.elementAt(1);
103           
104                 Object issuerUniqueID_=null,subjectUniqueID_=null;
105                 Vector extensions_=null;
106                 for(;i < tbsSequence.size();i++) {
107                     DERTaggedObject to = (DERTaggedObject) tbsSequence.elementAt(i);
108                     switch(to.tag) {
109                         case 1: issuerUniqueID_ = to.object; break;
110                         case 2: subjectUniqueID_ = to.object; break;
111                         case 3: extensions_ = (Vector) to.object; break;
112                     }
113                 }
114                 issuerUniqueID = issuerUniqueID_;
115                 subjectUniqueID = subjectUniqueID_;
116                 extensions = extensions_;
117             
118                 BC bc = null;
119             
120                 if(extensions != null) {
121                     for(Enumeration e = extensions.elements(); e.hasMoreElements(); ) {
122                         Vector extension = (Vector) e.nextElement();
123                         String oid = (String) extension.elementAt(0);
124                         byte[] data = (byte[]) extension.elementAt(extension.size()-1);
125                         if(oid.equals(BASIC_CONSTRAINTS))
126                             bc = new BC(new DERInputStream(new ByteArrayInputStream(data)).readObject());
127                     }
128                 }
129                 basicContraints = bc;
130             } catch(RuntimeException e) {
131                 e.printStackTrace();
132                 throw new DERException("Invalid x509 Certificate");
133             }
134             certBytes = certIS.getBytes();
135         }
136     
137     
138         public String getSubjectField(String fieldID) { return subject.get(fieldID); }
139         public String getCN() { return getSubjectField(X509Name.CN); }
140     
141         public boolean isValid() {
142             Date now = new Date();
143             return !now.after(endDate) && !now.before(startDate);
144         }
145     
146         public RSAPublicKey getRSAPublicKey() throws DERException {
147             if(!RSA_ENCRYPTION.equals(publicKeyAlgorithm.id)) throw new DERException("This isn't an RSA public key");
148             try {
149                 return new RSAPublicKey(new DERInputStream(new ByteArrayInputStream(publicKey.data)).readObject());
150             } catch(IOException e) {
151                 throw new DERException(e.getMessage());
152             } catch(RuntimeException e) {
153                 throw new DERException("Invalid RSA Public Key " + e.getMessage());
154             }
155         }
156     
157         public boolean isSignedBy(Certificate signer) throws DERException {
158             return isSignedWith(signer.getRSAPublicKey());
159         }
160         public boolean isSignedWith(RSAPublicKey rsapk) throws DERException {
161             try {
162                 Digest digest;
163                 if(signatureAlgorithm.id.equals(MD5_WITH_RSA_ENCRYPTION)) digest = new MD5();
164                 else if(signatureAlgorithm.id.equals(SHA1_WITH_RSA_ENCRYPTION)) digest = new SHA1();
165                 else if(signatureAlgorithm.id.equals(MD2_WITH_RSA_ENCRYPTION)) digest = new MD2();
166                 else throw new DERException("Unknown signing algorithm: " + signatureAlgorithm.id);
167                         
168                 PKCS1 pkcs1 = new PKCS1(new RSA(rsapk.modulus,rsapk.exponent,true));
169                 byte[] d = pkcs1.decode(signature.data);
170             
171                 Vector v = (Vector) new DERInputStream(new ByteArrayInputStream(d)).readObject();
172                 byte[] signedDigest = (byte[]) v.elementAt(1);
173                             
174                 if(signedDigest.length != digest.getDigestSize()) return false;
175             
176                 digest.update(tbsCertBytes,0,tbsCertBytes.length);
177                 byte[] ourDigest = new byte[digest.getDigestSize()];
178                 digest.doFinal(ourDigest,0);
179             
180                 for(int i=0;i<digest.getDigestSize();i++) if(ourDigest[i] != signedDigest[i]) return false;
181                 return true;
182             }
183             catch(RuntimeException e) { e.printStackTrace(); return false; }
184             catch(PKCS1.Exn e) { e.printStackTrace(); return false; }
185             catch(IOException e) { e.printStackTrace(); return false; }
186         }
187     
188         public byte[] getMD5Fingerprint() { return getFingerprint(new MD5()); }
189         public byte[] getSHA1Fingerprint() { return getFingerprint(new SHA1()); }
190         public byte[] getFingerprint(Digest h) {
191             h.update(certBytes,0,certBytes.length);
192             byte[] digest = new byte[h.getDigestSize()];
193             h.doFinal(digest,0);
194             return digest;
195         }
196     
197         private class RecordingInputStream extends FilterInputStream {
198             public ByteArrayOutputStream baos = new ByteArrayOutputStream();
199             private boolean on = true;
200             public void on() { on = true; }
201             public void off() { on = false; }
202             public RecordingInputStream(InputStream is) { super(is); }
203             public int read() throws IOException {
204                 int n = super.read();
205                 if(n != -1 && on) baos.write(n);
206                 return n;
207             }
208             public int read(byte[] buf, int off, int len) throws IOException {
209                 int n = super.read(buf,off,len);
210                 if(n != -1 && on) baos.write(buf,off,n);
211                 return n;
212             }
213             public byte[] getBytes() { return baos.toByteArray(); }
214         }
215     
216         public static class BC {
217             public final boolean isCA;
218             public final Number pathLenConstraint;
219             BC(Object o) {
220                 Vector seq = (Vector) o;
221                 isCA = seq.size() > 0 ? ((Boolean) seq.elementAt(0)).booleanValue() : false;
222                 pathLenConstraint = seq.size() > 1 ? (Number) seq.elementAt(1) : null;
223             }
224         }
225     
226         public static class AlgorithmIdentifier {
227             public final String id;
228             public final Object parameters;
229         
230             AlgorithmIdentifier(Object o) {
231                 Vector seq = (Vector) o;
232                 id = (String) seq.elementAt(0);
233                 parameters = seq.elementAt(1);
234             }
235             public boolean equals(Object o_) {
236                 if(o_ == this) return true;
237                 if(!(o_ instanceof AlgorithmIdentifier)) return false;
238                 AlgorithmIdentifier o = (AlgorithmIdentifier) o_;
239                 return o.id.equals(id) && o.parameters.equals(parameters);
240             }
241             public int hashCode() { return id.hashCode() ^ parameters.hashCode(); }
242         }
243     
244         /*public static void main(String[] args) throws Exception {
245           Certificate cert = new Certificate(new FileInputStream(args[0]));
246           System.err.println("CN: " + cert.getCN());
247           System.err.println("Subject: " + cert.subject);
248           System.err.println("Issuer: " + cert.issuer);
249           System.err.println("Start Date: " + cert.startDate);
250           System.err.println("End Date: " + cert.endDate);
251           System.err.println("SHA1 Fingerprint: " + prettyBytes(cert.getSHA1Fingerprint()));
252           RSAPublicKey key = cert.getRSAPublicKey();
253           System.err.println("Modulus: " + prettyBytes(key.modulus.toByteArray()));
254           System.err.println("Exponent: " + key.exponent);
255           System.err.println("Signature: " + prettyBytes(cert.signature.data));
256           }
257     
258           public static String prettyBytes(byte[] fp) {
259           StringBuffer sb = new StringBuffer(fp.length*3);
260           for(int i=0;i<fp.length;i++) {
261           if(i>0) sb.append(":");
262           sb.append("0123456789abcdef".charAt((fp[i] & 0xf0) >>> 4));
263           sb.append("0123456789abcdef".charAt((fp[i] & 0x0f) >>> 0));
264           }
265           return sb.toString();
266           }*/
267     }
268
269     public static class Name {
270         // Some common OIDs
271         public static final String C = "2.5.4.6";
272         public static final String O = "2.5.4.10";
273         public static final String T = "2.5.4.12";
274         public static final String SN = "2.5.4.5";
275         public static final String L = "2.5.4.7";
276         public static final String ST = "2.5.4.8";
277         public static final String OU = "2.5.4.11";
278         public static final String CN = "2.5.4.3";
279         public static final String E = "1.2.840.113549.1.9.1";
280     
281         private final Vector keys = new Vector();
282         private final Vector values = new Vector();
283     
284         public Name(Object seq_) throws DERException {
285             try {
286                 Vector seq = (Vector) seq_;
287                 for(Enumeration e = seq.elements();e.hasMoreElements();) {
288                     Vector component = (Vector) ((Vector)e.nextElement()).elementAt(0);
289                     keys.add(component.elementAt(0));
290                     values.add(component.elementAt(1));
291                 }
292             } catch(RuntimeException e) {
293                 e.printStackTrace();
294                 throw new DERException("Invalid Name " + e.toString());
295             }
296         }
297     
298         public boolean equals(Object o_) {
299             if(o_ instanceof String) return toString().equals(o_);
300             if(!(o_ instanceof Name)) return false;
301             Name o = (Name) o_;
302             if(keys.size() != o.keys.size()) return false;
303             int size = keys.size();
304             for(int i=0;i<size;i++) {
305                 String oid = (String) keys.elementAt(i);
306                 String oid2 = (String) o.keys.elementAt(i);
307                 if(!oid.equals(oid2)) return false;
308             
309                 String val1 = (String) values.elementAt(i);
310                 String val2 = (String) o.values.elementAt(i);
311                 if(val1.equals(val2)) continue;
312             
313                 val1 = val1.trim().toLowerCase();
314                 val2 = val2.trim().toLowerCase();
315                 if(val1.equals(val2)) continue;
316             
317                 val1 = removeExtraSpaces(val1);
318                 val2 = removeExtraSpaces(val2);
319                 if(val1.equals(val2)) continue;
320             
321                 return false;
322             }
323             return true;
324         }
325     
326         public int hashCode() { return keys.hashCode() ^ values.hashCode(); }
327     
328         public String get(String fieldID) {
329             int i = keys.indexOf(fieldID);
330             return i == -1 ? null : (String)values.elementAt(i);
331         }
332     
333         public String[] getOIDs() {
334             String[] ret = new String[keys.size()];
335             keys.copyInto(ret);
336             return ret;
337         }
338     
339         public String[] getValues() {
340             String[] ret = new String[values.size()];
341             values.copyInto(ret);
342             return ret;
343         }
344     
345         private static String removeExtraSpaces(String s) {
346             if(s.indexOf(' ') == -1) return s;
347             StringBuffer sb = new StringBuffer(s.length());
348             int l = s.length();
349             boolean inWhitespace = false;
350             for(int i=0;i<l;i++) {
351                 if(s.charAt(i) == ' ') {
352                     if(inWhitespace) continue;
353                     inWhitespace = true;
354                 } else if(inWhitespace) {
355                     inWhitespace = false;
356                 }
357                 sb.append(s.charAt(i));
358             }
359             return sb.toString();
360         }
361     
362         private final static Hashtable oidMap = new Hashtable();
363         static {
364             oidMap.put(Name.C,"C");
365             oidMap.put(Name.O,"O");
366             oidMap.put(Name.T,"T");
367             oidMap.put(Name.SN,"SN");
368             oidMap.put(Name.L,"L");
369             oidMap.put(Name.ST,"ST");
370             oidMap.put(Name.OU,"OU");
371             oidMap.put(Name.CN,"CN");
372             oidMap.put(Name.E,"E");
373         }
374     
375         public String toString() {
376             StringBuffer sb = new StringBuffer();
377             int size = keys.size();
378             for(int i=0;i<size;i++) {
379                 if(sb.length() > 0) sb.append(",");
380                 String fieldID = (String) keys.elementAt(i);
381                 String fieldName = (String) oidMap.get(fieldID);
382                 sb.append(fieldName != null ? fieldName : fieldID).append("=").append(values.elementAt(i));
383             }
384             return sb.toString();
385         }
386     }
387 }