2 * com.brian_web.x509.* - By Brian Alliet
3 * Copyright (C) 2004 Brian Alliet
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)
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:
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
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.
28 package com.brian_web.x509;
30 import com.brian_web.der.*;
31 import com.brian_web.crypto.*;
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";
44 public static final String BASIC_CONSTRAINTS = "2.5.29.19";
46 private final byte[] certBytes;
47 private final byte[] tbsCertBytes;
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;
56 public final AlgorithmIdentifier publicKeyAlgorithm;
57 public final DERBitString publicKey;
59 public final Object issuerUniqueID;
60 public final Object subjectUniqueID;
62 public final Vector extensions;
64 public final DERBitString signature;
65 public final AlgorithmIdentifier signatureAlgorithm;
67 public final BC basicContraints;
70 public Certificate(InputStream is) throws IOException {
72 RecordingInputStream certIS = new RecordingInputStream(is);
73 DERInputStream certSequence = new DERInputStream(certIS).getSequenceStream();
74 RecordingInputStream tbsCertIS = new RecordingInputStream(certSequence);
77 Vector tbsSequence = (Vector) new DERInputStream(tbsCertIS).readObject();
78 tbsCertBytes = tbsCertIS.getBytes();
79 signatureAlgorithm = new AlgorithmIdentifier(certSequence.readObject());
80 signature = (DERBitString) certSequence.readObject();
83 if(tbsSequence.elementAt(i) instanceof DERTaggedObject)
84 version = (Number)((DERTaggedObject)tbsSequence.elementAt(i++)).object;
86 version = new Integer(0);
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++));
94 Vector validity = (Vector) tbsSequence.elementAt(i++);
95 startDate = (Date) validity.elementAt(0);
96 endDate = (Date) validity.elementAt(1);
98 subject = new X509Name(tbsSequence.elementAt(i++));
100 Vector publicKeyInfo = (Vector) tbsSequence.elementAt(i++);
101 publicKeyAlgorithm = new AlgorithmIdentifier(publicKeyInfo.elementAt(0));
102 publicKey = (DERBitString) publicKeyInfo.elementAt(1);
104 Object issuerUniqueID_=null,subjectUniqueID_=null;
105 Vector extensions_=null;
106 for(;i < tbsSequence.size();i++) {
107 DERTaggedObject to = (DERTaggedObject) tbsSequence.elementAt(i);
109 case 1: issuerUniqueID_ = to.object; break;
110 case 2: subjectUniqueID_ = to.object; break;
111 case 3: extensions_ = (Vector) to.object; break;
114 issuerUniqueID = issuerUniqueID_;
115 subjectUniqueID = subjectUniqueID_;
116 extensions = extensions_;
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());
129 basicContraints = bc;
130 } catch(RuntimeException e) {
132 throw new DERException("Invalid x509 Certificate");
134 certBytes = certIS.getBytes();
138 public String getSubjectField(String fieldID) { return subject.get(fieldID); }
139 public String getCN() { return getSubjectField(X509Name.CN); }
141 public boolean isValid() {
142 Date now = new Date();
143 return !now.after(endDate) && !now.before(startDate);
146 public RSAPublicKey getRSAPublicKey() throws DERException {
147 if(!RSA_ENCRYPTION.equals(publicKeyAlgorithm.id)) throw new DERException("This isn't an RSA public key");
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());
157 public boolean isSignedBy(Certificate signer) throws DERException {
158 return isSignedWith(signer.getRSAPublicKey());
160 public boolean isSignedWith(RSAPublicKey rsapk) throws DERException {
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);
168 PKCS1 pkcs1 = new PKCS1(new RSA(rsapk.modulus,rsapk.exponent,true));
169 byte[] d = pkcs1.decode(signature.data);
171 Vector v = (Vector) new DERInputStream(new ByteArrayInputStream(d)).readObject();
172 byte[] signedDigest = (byte[]) v.elementAt(1);
174 if(signedDigest.length != digest.getDigestSize()) return false;
176 digest.update(tbsCertBytes,0,tbsCertBytes.length);
177 byte[] ourDigest = new byte[digest.getDigestSize()];
178 digest.doFinal(ourDigest,0);
180 for(int i=0;i<digest.getDigestSize();i++) if(ourDigest[i] != signedDigest[i]) return false;
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; }
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()];
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);
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);
213 public byte[] getBytes() { return baos.toByteArray(); }
216 public static class BC {
217 public final boolean isCA;
218 public final Number pathLenConstraint;
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;
226 public static class AlgorithmIdentifier {
227 public final String id;
228 public final Object parameters;
230 AlgorithmIdentifier(Object o) {
231 Vector seq = (Vector) o;
232 id = (String) seq.elementAt(0);
233 parameters = seq.elementAt(1);
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);
241 public int hashCode() { return id.hashCode() ^ parameters.hashCode(); }
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));
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));
265 return sb.toString();
269 public static class Name {
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";
281 private final Vector keys = new Vector();
282 private final Vector values = new Vector();
284 public Name(Object seq_) throws DERException {
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));
292 } catch(RuntimeException e) {
294 throw new DERException("Invalid Name " + e.toString());
298 public boolean equals(Object o_) {
299 if(o_ instanceof String) return toString().equals(o_);
300 if(!(o_ instanceof Name)) return false;
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;
309 String val1 = (String) values.elementAt(i);
310 String val2 = (String) o.values.elementAt(i);
311 if(val1.equals(val2)) continue;
313 val1 = val1.trim().toLowerCase();
314 val2 = val2.trim().toLowerCase();
315 if(val1.equals(val2)) continue;
317 val1 = removeExtraSpaces(val1);
318 val2 = removeExtraSpaces(val2);
319 if(val1.equals(val2)) continue;
326 public int hashCode() { return keys.hashCode() ^ values.hashCode(); }
328 public String get(String fieldID) {
329 int i = keys.indexOf(fieldID);
330 return i == -1 ? null : (String)values.elementAt(i);
333 public String[] getOIDs() {
334 String[] ret = new String[keys.size()];
339 public String[] getValues() {
340 String[] ret = new String[values.size()];
341 values.copyInto(ret);
345 private static String removeExtraSpaces(String s) {
346 if(s.indexOf(' ') == -1) return s;
347 StringBuffer sb = new StringBuffer(s.length());
349 boolean inWhitespace = false;
350 for(int i=0;i<l;i++) {
351 if(s.charAt(i) == ' ') {
352 if(inWhitespace) continue;
354 } else if(inWhitespace) {
355 inWhitespace = false;
357 sb.append(s.charAt(i));
359 return sb.toString();
362 private final static Hashtable oidMap = new Hashtable();
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");
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));
384 return sb.toString();