4 * A representation for a X509 Certificate, with
\r
5 * methods to parse, verify and sign it.
\r
6 * Copyright (c) 2007 Henri Torgemane
\r
8 * See LICENSE.txt for full license information.
\r
10 package com.hurlant.crypto.cert {
\r
11 import com.hurlant.crypto.hash.IHash;
\r
12 import com.hurlant.crypto.hash.MD2;
\r
13 import com.hurlant.crypto.hash.MD5;
\r
14 import com.hurlant.crypto.hash.SHA1;
\r
15 import com.hurlant.crypto.rsa.RSAKey;
\r
16 import com.hurlant.util.ArrayUtil;
\r
17 import com.hurlant.util.Base64;
\r
18 import com.hurlant.util.der.ByteString;
\r
19 import com.hurlant.util.der.DER;
\r
20 import com.hurlant.util.der.OID;
\r
21 import com.hurlant.util.der.ObjectIdentifier;
\r
22 import com.hurlant.util.der.PEM;
\r
23 import com.hurlant.util.der.PrintableString;
\r
24 import com.hurlant.util.der.Sequence;
\r
25 import com.hurlant.util.der.Type;
\r
27 import flash.utils.ByteArray;
\r
29 public class X509Certificate {
\r
30 private var _loaded:Boolean;
\r
31 private var _param:*;
\r
32 private var _obj:Object;
\r
33 public function X509Certificate(p:*) {
\r
36 // lazy initialization, to avoid unnecessary parsing of every builtin CA at start-up.
\r
38 private function load():void {
\r
39 if (_loaded) return;
\r
43 b = PEM.readCertIntoArray(p as String);
\r
44 } else if (p is ByteArray) {
\r
48 _obj = DER.parse(b, Type.TLS_CERT);
\r
51 throw new Error("Invalid x509 Certificate parameter: "+p);
\r
54 public function isSigned(store:X509CertificateCollection, CAs:X509CertificateCollection, time:Date=null):Boolean {
\r
56 // check timestamps first. cheapest.
\r
60 var notBefore:Date = getNotBefore();
\r
61 var notAfter:Date = getNotAfter();
\r
62 if (time.getTime()<notBefore.getTime()) return false; // cert isn't born yet.
\r
63 if (time.getTime()>notAfter.getTime()) return false; // cert died of old age.
\r
65 var subject:String = getIssuerPrincipal();
\r
66 // try from CA first, since they're treated better.
\r
67 var parent:X509Certificate = CAs.getCertificate(subject);
\r
68 var parentIsAuthoritative:Boolean = false;
\r
69 if (parent == null) {
\r
70 parent = store.getCertificate(subject);
\r
71 if (parent == null) {
\r
72 return false; // issuer not found
\r
75 parentIsAuthoritative = true;
\r
77 if (parent == this) { // pathological case. avoid infinite loop
\r
78 return false; // isSigned() returns false if we're self-signed.
\r
80 if (!(parentIsAuthoritative&&parent.isSelfSigned(time)) &&
\r
81 !parent.isSigned(store, CAs, time)) {
\r
84 var key:RSAKey = parent.getPublicKey();
\r
85 return verifyCertificate(key);
\r
87 public function isSelfSigned(time:Date):Boolean {
\r
90 var key:RSAKey = getPublicKey();
\r
91 return verifyCertificate(key);
\r
93 private function verifyCertificate(key:RSAKey):Boolean {
\r
94 var algo:String = getAlgorithmIdentifier();
\r
98 case OID.SHA1_WITH_RSA_ENCRYPTION:
\r
100 oid = OID.SHA1_ALGORITHM;
\r
102 case OID.MD2_WITH_RSA_ENCRYPTION:
\r
104 oid = OID.MD2_ALGORITHM;
\r
106 case OID.MD5_WITH_RSA_ENCRYPTION:
\r
108 oid = OID.MD5_ALGORITHM;
\r
113 var data:ByteArray = _obj.signedCertificate_bin;
\r
114 var buf:ByteArray = new ByteArray;
\r
115 key.verify(_obj.encrypted, buf, _obj.encrypted.length);
\r
117 data = hash.hash(data);
\r
118 var obj:Object = DER.parse(buf, Type.RSA_SIGNATURE);
\r
119 if (obj.algorithm.algorithmId.toString() != oid) {
\r
120 return false; // wrong algorithm
\r
122 if (!ArrayUtil.equals(obj.hash, data)) {
\r
123 return false; // hashes don't match
\r
129 * This isn't used anywhere so far.
\r
130 * It would become useful if we started to offer facilities
\r
131 * to generate and sign X509 certificates.
\r
138 private function signCertificate(key:RSAKey, algo:String):ByteArray {
\r
142 case OID.SHA1_WITH_RSA_ENCRYPTION:
\r
144 oid = OID.SHA1_ALGORITHM;
\r
146 case OID.MD2_WITH_RSA_ENCRYPTION:
\r
148 oid = OID.MD2_ALGORITHM;
\r
150 case OID.MD5_WITH_RSA_ENCRYPTION:
\r
152 oid = OID.MD5_ALGORITHM;
\r
157 var data:ByteArray = _obj.signedCertificate_bin;
\r
158 data = hash.hash(data);
\r
159 var seq1:Sequence = new Sequence;
\r
160 seq1[0] = new Sequence;
\r
161 seq1[0][0] = new ObjectIdentifier(0,0, oid);
\r
163 seq1[1] = new ByteString;
\r
164 seq1[1].writeBytes(data);
\r
165 data = seq1.toDER();
\r
166 var buf:ByteArray = new ByteArray;
\r
167 key.sign(data, buf, data.length);
\r
171 public function getPublicKey():RSAKey {
\r
173 var pk:ByteArray = _obj.signedCertificate.subjectPublicKeyInfo.subjectPublicKey as ByteArray;
\r
175 var rsaKey:Object = DER.parse(pk, [{name:"N"},{name:"E"}]);
\r
176 return new RSAKey(rsaKey.N, rsaKey.E.valueOf());
\r
180 * Returns a subject principal, as an opaque base64 string.
\r
181 * This is only used as a hash key for known certificates.
\r
183 * Note that this assumes X509 DER-encoded certificates are uniquely encoded,
\r
184 * as we look for exact matches between Issuer and Subject fields.
\r
187 public function getSubjectPrincipal():String {
\r
189 return Base64.encodeByteArray(_obj.signedCertificate.subject_bin);
\r
192 * Returns an issuer principal, as an opaque base64 string.
\r
193 * This is only used to quickly find matching parent certificates.
\r
195 * Note that this assumes X509 DER-encoded certificates are uniquely encoded,
\r
196 * as we look for exact matches between Issuer and Subject fields.
\r
199 public function getIssuerPrincipal():String {
\r
201 return Base64.encodeByteArray(_obj.signedCertificate.issuer_bin);
\r
203 public function getAlgorithmIdentifier():String {
\r
204 return _obj.algorithmIdentifier.algorithmId.toString();
\r
206 public function getNotBefore():Date {
\r
207 return _obj.signedCertificate.validity.notBefore.date;
\r
209 public function getNotAfter():Date {
\r
210 return _obj.signedCertificate.validity.notAfter.date;
\r
213 public function getCommonName():String {
\r
214 var subject:Sequence = _obj.signedCertificate.subject;
\r
215 return (subject.findAttributeValue(OID.COMMON_NAME) as PrintableString).getString();
\r