cleanup
[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 HOLDER.S 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 org.ibex.crypto;
29
30 import java.io.*;
31 import java.util.*;
32
33 public class X509 {
34     private X509() { }
35     
36     public static class Certificate {
37         public static final String RSA_ENCRYPTION           = "1.2.840.113549.1.1.1";
38         public static final String MD2_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.2";
39         public static final String MD4_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.3";
40         public static final String MD5_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.4";
41         public static final String SHA1_WITH_RSA_ENCRYPTION = "1.2.840.113549.1.1.5";
42     
43         public static final String BASIC_CONSTRAINTS = "2.5.29.19";
44     
45         private final byte[] certBytes;
46         private final byte[] tbsCertBytes;
47     
48         public final Number version;    
49         public final Number serialNo;
50         public final X509.Name issuer;
51         public final Date startDate;
52         public final Date endDate;
53         public final X509.Name subject;
54     
55         public final AlgorithmIdentifier publicKeyAlgorithm;
56         public final DER.BitString publicKey;
57     
58         public final Object issuerUniqueID;
59         public final Object subjectUniqueID;
60     
61         public final Vector extensions;
62     
63         public final DER.BitString signature;
64         public final AlgorithmIdentifier signatureAlgorithm;
65     
66         public final BC basicContraints;
67
68     
69         public Certificate(InputStream is) throws IOException {
70             int i;
71             RecordingInputStream certIS = new RecordingInputStream(is);
72             DER.InputStream certSequence = DER.InputStream.New(certIS).getSequenceStream();
73             RecordingInputStream tbsCertIS = new RecordingInputStream(certSequence);
74         
75             try {
76                 Vector tbsSequence = (Vector) DER.InputStream.New(tbsCertIS).readObject();
77                 tbsCertBytes = tbsCertIS.getBytes();
78                 signatureAlgorithm = new AlgorithmIdentifier(certSequence.readObject());
79                 signature = (DER.BitString) certSequence.readObject();
80             
81                 i=0;
82                 if(tbsSequence.elementAt(i) instanceof DER.TaggedObject)
83                     version = (Number)((DER.TaggedObject)tbsSequence.elementAt(i++)).object;
84                 else
85                     version = new Integer(0);
86             
87                 serialNo = (Number) tbsSequence.elementAt(i++);
88                 AlgorithmIdentifier signatureAlgorithm2 = new AlgorithmIdentifier(tbsSequence.elementAt(i++));
89                 if(!signatureAlgorithm2.equals(signatureAlgorithm))
90                     throw new DER.Exception("AlgoritmIdentifier mismatch " + signatureAlgorithm + " vs " + signatureAlgorithm2);
91                 issuer = new X509.Name(tbsSequence.elementAt(i++));
92             
93                 Vector validity = (Vector) tbsSequence.elementAt(i++);
94                 startDate = (Date) validity.elementAt(0);
95                 endDate = (Date) validity.elementAt(1);
96             
97                 subject = new X509.Name(tbsSequence.elementAt(i++));
98             
99                 Vector publicKeyInfo = (Vector) tbsSequence.elementAt(i++);
100                 publicKeyAlgorithm = new AlgorithmIdentifier(publicKeyInfo.elementAt(0));
101                 publicKey = (DER.BitString) publicKeyInfo.elementAt(1);
102           
103                 Object issuerUniqueID_=null,subjectUniqueID_=null;
104                 Vector extensions_=null;
105                 for(;i < tbsSequence.size();i++) {
106                     DER.TaggedObject to = (DER.TaggedObject) tbsSequence.elementAt(i);
107                     switch(to.tag) {
108                         case 1: issuerUniqueID_ = to.object; break;
109                         case 2: subjectUniqueID_ = to.object; break;
110                         case 3: extensions_ = (Vector) to.object; break;
111                     }
112                 }
113                 issuerUniqueID = issuerUniqueID_;
114                 subjectUniqueID = subjectUniqueID_;
115                 extensions = extensions_;
116             
117                 BC bc = null;
118             
119                 if(extensions != null) {
120                     for(Enumeration e = extensions.elements(); e.hasMoreElements(); ) {
121                         Vector extension = (Vector) e.nextElement();
122                         String oid = (String) extension.elementAt(0);
123                         byte[] data = (byte[]) extension.elementAt(extension.size()-1);
124                         if(oid.equals(BASIC_CONSTRAINTS))
125                             bc = new BC(DER.InputStream.New(new ByteArrayInputStream(data)).readObject());
126                     }
127                 }
128                 basicContraints = bc;
129             } catch(RuntimeException e) {
130                 e.printStackTrace();
131                 throw new DER.Exception("Invalid x509 Certificate");
132             }
133             certBytes = certIS.getBytes();
134         }
135     
136     
137         public String getSubjectField(String fieldID) { return subject.get(fieldID); }
138         public String getCN() { return getSubjectField(X509.Name.CN); }
139     
140         public boolean isValid() {
141             Date now = new Date();
142             return !now.after(endDate) && !now.before(startDate);
143         }
144     
145         public RSA.PublicKey getRSAPublicKey() throws DER.Exception {
146             if(!RSA_ENCRYPTION.equals(publicKeyAlgorithm.id)) throw new DER.Exception("This isn't an RSA public key");
147             try {
148                 return new RSA.PublicKey(DER.InputStream.New(new ByteArrayInputStream(publicKey.data)).readObject());
149             } catch(IOException e) {
150                 throw new DER.Exception(e.getMessage());
151             } catch(RuntimeException e) {
152                 throw new DER.Exception("Invalid RSA Public Key " + e.getMessage());
153             }
154         }
155     
156         public boolean isSignedBy(Certificate signer) throws DER.Exception {
157             return isSignedWith(signer.getRSAPublicKey());
158         }
159         public boolean isSignedWith(RSA.PublicKey rsapk) throws DER.Exception {
160             try {
161                 Digest digest;
162                 if(signatureAlgorithm.id.equals(MD5_WITH_RSA_ENCRYPTION)) digest = new MD5();
163                 else if(signatureAlgorithm.id.equals(SHA1_WITH_RSA_ENCRYPTION)) digest = new SHA1();
164                 else if(signatureAlgorithm.id.equals(MD2_WITH_RSA_ENCRYPTION)) digest = new MD2();
165                 else throw new DER.Exception("Unknown signing algorithm: " + signatureAlgorithm.id);
166                         
167                 PKCS1 pkcs1 = new PKCS1(new RSA(rsapk.modulus,rsapk.exponent,true));
168                 byte[] d = pkcs1.decode(signature.data);
169             
170                 Vector v = (Vector) DER.InputStream.New(new ByteArrayInputStream(d)).readObject();
171                 byte[] signedDigest = (byte[]) v.elementAt(1);
172                             
173                 if(signedDigest.length != digest.getDigestSize()) return false;
174             
175                 digest.update(tbsCertBytes,0,tbsCertBytes.length);
176                 byte[] ourDigest = new byte[digest.getDigestSize()];
177                 digest.doFinal(ourDigest,0);
178             
179                 for(int i=0;i<digest.getDigestSize();i++) if(ourDigest[i] != signedDigest[i]) return false;
180                 return true;
181             }
182             catch(RuntimeException e) { e.printStackTrace(); return false; }
183             catch(PKCS1.Exn e) { e.printStackTrace(); return false; }
184             catch(IOException e) { e.printStackTrace(); return false; }
185         }
186     
187         public byte[] getMD5Fingerprint() { return getFingerprint(new MD5()); }
188         public byte[] getSHA1Fingerprint() { return getFingerprint(new SHA1()); }
189         public byte[] getFingerprint(Digest h) {
190             h.update(certBytes,0,certBytes.length);
191             byte[] digest = new byte[h.getDigestSize()];
192             h.doFinal(digest,0);
193             return digest;
194         }
195     
196         private class RecordingInputStream extends FilterInputStream {
197             public ByteArrayOutputStream baos = new ByteArrayOutputStream();
198             private boolean on = true;
199             public void on() { on = true; }
200             public void off() { on = false; }
201             public RecordingInputStream(InputStream is) { super(is); }
202             public int read() throws IOException {
203                 int n = super.read();
204                 if(n != -1 && on) baos.write(n);
205                 return n;
206             }
207             public int read(byte[] buf, int off, int len) throws IOException {
208                 int n = super.read(buf,off,len);
209                 if(n != -1 && on) baos.write(buf,off,n);
210                 return n;
211             }
212             public byte[] getBytes() { return baos.toByteArray(); }
213         }
214     
215         public static class BC {
216             public final boolean isCA;
217             public final Number pathLenConstraint;
218             BC(Object o) {
219                 Vector seq = (Vector) o;
220                 isCA = seq.size() > 0 ? ((Boolean) seq.elementAt(0)).booleanValue() : false;
221                 pathLenConstraint = seq.size() > 1 ? (Number) seq.elementAt(1) : null;
222             }
223         }
224     
225         public static class AlgorithmIdentifier {
226             public final String id;
227             public final Object parameters;
228         
229             AlgorithmIdentifier(Object o) {
230                 Vector seq = (Vector) o;
231                 id = (String) seq.elementAt(0);
232                 parameters = seq.elementAt(1);
233             }
234             public boolean equals(Object o_) {
235                 if(o_ == this) return true;
236                 if(!(o_ instanceof AlgorithmIdentifier)) return false;
237                 AlgorithmIdentifier o = (AlgorithmIdentifier) o_;
238                 return o.id.equals(id) && o.parameters.equals(parameters);
239             }
240             public int hashCode() { return id.hashCode() ^ parameters.hashCode(); }
241         }
242     
243         /*public static void main(String[] args) throws Exception {
244           Certificate cert = new Certificate(new FileInputStream(args[0]));
245           System.err.println("CN: " + cert.getCN());
246           System.err.println("Subject: " + cert.subject);
247           System.err.println("Issuer: " + cert.issuer);
248           System.err.println("Start Date: " + cert.startDate);
249           System.err.println("End Date: " + cert.endDate);
250           System.err.println("SHA1 Fingerprint: " + prettyBytes(cert.getSHA1Fingerprint()));
251           RSA.PublicKey key = cert.getRSA.PublicKey();
252           System.err.println("Modulus: " + prettyBytes(key.modulus.toByteArray()));
253           System.err.println("Exponent: " + key.exponent);
254           System.err.println("Signature: " + prettyBytes(cert.signature.data));
255           }
256     
257           public static String prettyBytes(byte[] fp) {
258           StringBuffer sb = new StringBuffer(fp.length*3);
259           for(int i=0;i<fp.length;i++) {
260           if(i>0) sb.append(":");
261           sb.append("0123456789abcdef".charAt((fp[i] & 0xf0) >>> 4));
262           sb.append("0123456789abcdef".charAt((fp[i] & 0x0f) >>> 0));
263           }
264           return sb.toString();
265           }*/
266     }
267
268     public static class Name {
269         // Some common OIDs
270         public static final String C = "2.5.4.6";
271         public static final String O = "2.5.4.10";
272         public static final String T = "2.5.4.12";
273         public static final String SN = "2.5.4.5";
274         public static final String L = "2.5.4.7";
275         public static final String ST = "2.5.4.8";
276         public static final String OU = "2.5.4.11";
277         public static final String CN = "2.5.4.3";
278         public static final String E = "1.2.840.113549.1.9.1";
279     
280         private final Vector keys = new Vector();
281         private final Vector values = new Vector();
282     
283         public Name(Object seq_) throws DER.Exception {
284             try {
285                 Vector seq = (Vector) seq_;
286                 for(Enumeration e = seq.elements();e.hasMoreElements();) {
287                     Vector component = (Vector) ((Vector)e.nextElement()).elementAt(0);
288                     keys.addElement(component.elementAt(0));
289                     values.addElement(component.elementAt(1));
290                 }
291             } catch(RuntimeException e) {
292                 e.printStackTrace();
293                 throw new DER.Exception("Invalid Name " + e.toString());
294             }
295         }
296     
297         public boolean equals(Object o_) {
298             if(o_ instanceof String) return toString().equals(o_);
299             if(!(o_ instanceof Name)) return false;
300             Name o = (Name) o_;
301             if(keys.size() != o.keys.size()) return false;
302             int size = keys.size();
303             for(int i=0;i<size;i++) {
304                 String oid = (String) keys.elementAt(i);
305                 String oid2 = (String) o.keys.elementAt(i);
306                 if(!oid.equals(oid2)) return false;
307             
308                 String val1 = (String) values.elementAt(i);
309                 String val2 = (String) o.values.elementAt(i);
310                 if(val1.equals(val2)) continue;
311             
312                 val1 = val1.trim().toLowerCase();
313                 val2 = val2.trim().toLowerCase();
314                 if(val1.equals(val2)) continue;
315             
316                 val1 = removeExtraSpaces(val1);
317                 val2 = removeExtraSpaces(val2);
318                 if(val1.equals(val2)) continue;
319             
320                 return false;
321             }
322             return true;
323         }
324     
325         public int hashCode() { return keys.hashCode() ^ values.hashCode(); }
326     
327         public String get(String fieldID) {
328             int i = keys.indexOf(fieldID);
329             return i == -1 ? null : (String)values.elementAt(i);
330         }
331     
332         public String[] getOIDs() {
333             String[] ret = new String[keys.size()];
334             keys.copyInto(ret);
335             return ret;
336         }
337     
338         public String[] getValues() {
339             String[] ret = new String[values.size()];
340             values.copyInto(ret);
341             return ret;
342         }
343     
344         private static String removeExtraSpaces(String s) {
345             if(s.indexOf(' ') == -1) return s;
346             StringBuffer sb = new StringBuffer(s.length());
347             int l = s.length();
348             boolean inWhitespace = false;
349             for(int i=0;i<l;i++) {
350                 if(s.charAt(i) == ' ') {
351                     if(inWhitespace) continue;
352                     inWhitespace = true;
353                 } else if(inWhitespace) {
354                     inWhitespace = false;
355                 }
356                 sb.append(s.charAt(i));
357             }
358             return sb.toString();
359         }
360     
361         private final static Hashtable oidMap = new Hashtable();
362         static {
363             oidMap.put(Name.C,"C");
364             oidMap.put(Name.O,"O");
365             oidMap.put(Name.T,"T");
366             oidMap.put(Name.SN,"SN");
367             oidMap.put(Name.L,"L");
368             oidMap.put(Name.ST,"ST");
369             oidMap.put(Name.OU,"OU");
370             oidMap.put(Name.CN,"CN");
371             oidMap.put(Name.E,"E");
372         }
373     
374         public String toString() {
375             StringBuffer sb = new StringBuffer();
376             int size = keys.size();
377             for(int i=0;i<size;i++) {
378                 if(sb.length() > 0) sb.append(",");
379                 String fieldID = (String) keys.elementAt(i);
380                 String fieldName = (String) oidMap.get(fieldID);
381                 sb.append(fieldName != null ? fieldName : fieldID).append("=").append(values.elementAt(i));
382             }
383             return sb.toString();
384         }
385     }
386 }