/**
* X509Certificate
*
* A representation for a X509 Certificate, with
* methods to parse, verify and sign it.
* Copyright (c) 2007 Henri Torgemane
*
* See LICENSE.txt for full license information.
*/
package com.hurlant.crypto.cert {
import com.hurlant.crypto.hash.IHash;
import com.hurlant.crypto.hash.MD2;
import com.hurlant.crypto.hash.MD5;
import com.hurlant.crypto.hash.SHA1;
import com.hurlant.crypto.rsa.RSAKey;
import com.hurlant.util.ArrayUtil;
import com.hurlant.util.Base64;
import com.hurlant.util.der.ByteString;
import com.hurlant.util.der.DER;
import com.hurlant.util.der.OID;
import com.hurlant.util.der.ObjectIdentifier;
import com.hurlant.util.der.PEM;
import com.hurlant.util.der.PrintableString;
import com.hurlant.util.der.Sequence;
import com.hurlant.util.der.Type;
import flash.utils.ByteArray;
public class X509Certificate {
private var _loaded:Boolean;
private var _param:*;
private var _obj:Object;
public function X509Certificate(p:*) {
_loaded = false;
_param = p;
// lazy initialization, to avoid unnecessary parsing of every builtin CA at start-up.
}
private function load():void {
if (_loaded) return;
var p:* = _param;
var b:ByteArray;
if (p is String) {
b = PEM.readCertIntoArray(p as String);
} else if (p is ByteArray) {
b = p;
}
if (b!=null) {
_obj = DER.parse(b, Type.TLS_CERT);
_loaded = true;
} else {
throw new Error("Invalid x509 Certificate parameter: "+p);
}
}
public function isSigned(store:X509CertificateCollection, CAs:X509CertificateCollection, time:Date=null):Boolean {
load();
// check timestamps first. cheapest.
if (time==null) {
time = new Date;
}
var notBefore:Date = getNotBefore();
var notAfter:Date = getNotAfter();
if (time.getTime()<notBefore.getTime()) return false; // cert isn't born yet.
if (time.getTime()>notAfter.getTime()) return false; // cert died of old age.
// check signature.
var subject:String = getIssuerPrincipal();
// try from CA first, since they're treated better.
var parent:X509Certificate = CAs.getCertificate(subject);
var parentIsAuthoritative:Boolean = false;
if (parent == null) {
parent = store.getCertificate(subject);
if (parent == null) {
return false; // issuer not found
}
} else {
parentIsAuthoritative = true;
}
if (parent == this) { // pathological case. avoid infinite loop
return false; // isSigned() returns false if we're self-signed.
}
if (!(parentIsAuthoritative&&parent.isSelfSigned(time)) &&
!parent.isSigned(store, CAs, time)) {
return false;
}
var key:RSAKey = parent.getPublicKey();
return verifyCertificate(key);
}
public function isSelfSigned(time:Date):Boolean {
load();
var key:RSAKey = getPublicKey();
return verifyCertificate(key);
}
private function verifyCertificate(key:RSAKey):Boolean {
var algo:String = getAlgorithmIdentifier();
var hash:IHash;
var oid:String;
switch (algo) {
case OID.SHA1_WITH_RSA_ENCRYPTION:
hash = new SHA1;
oid = OID.SHA1_ALGORITHM;
break;
case OID.MD2_WITH_RSA_ENCRYPTION:
hash = new MD2;
oid = OID.MD2_ALGORITHM;
break;
case OID.MD5_WITH_RSA_ENCRYPTION:
hash = new MD5;
oid = OID.MD5_ALGORITHM;
break;
default:
return false;
}
var data:ByteArray = _obj.signedCertificate_bin;
var buf:ByteArray = new ByteArray;
key.verify(_obj.encrypted, buf, _obj.encrypted.length);
buf.position=0;
data = hash.hash(data);
var obj:Object = DER.parse(buf, Type.RSA_SIGNATURE);
if (obj.algorithm.algorithmId.toString() != oid) {
return false; // wrong algorithm
}
if (!ArrayUtil.equals(obj.hash, data)) {
return false; // hashes don't match
}
return true;
}
/**
* This isn't used anywhere so far.
* It would become useful if we started to offer facilities
* to generate and sign X509 certificates.
*
* @param key
* @param algo
* @return
*
*/
private function signCertificate(key:RSAKey, algo:String):ByteArray {
var hash:IHash;
var oid:String;
switch (algo) {
case OID.SHA1_WITH_RSA_ENCRYPTION:
hash = new SHA1;
oid = OID.SHA1_ALGORITHM;
break;
case OID.MD2_WITH_RSA_ENCRYPTION:
hash = new MD2;
oid = OID.MD2_ALGORITHM;
break;
case OID.MD5_WITH_RSA_ENCRYPTION:
hash = new MD5;
oid = OID.MD5_ALGORITHM;
break;
default:
return null
}
var data:ByteArray = _obj.signedCertificate_bin;
data = hash.hash(data);
var seq1:Sequence = new Sequence;
seq1[0] = new Sequence;
seq1[0][0] = new ObjectIdentifier(0,0, oid);
seq1[0][1] = null;
seq1[1] = new ByteString;
seq1[1].writeBytes(data);
data = seq1.toDER();
var buf:ByteArray = new ByteArray;
key.sign(data, buf, data.length);
return buf;
}
public function getPublicKey():RSAKey {
load();
var pk:ByteArray = _obj.signedCertificate.subjectPublicKeyInfo.subjectPublicKey as ByteArray;
pk.position = 0;
var rsaKey:Object = DER.parse(pk, [{name:"N"},{name:"E"}]);
return new RSAKey(rsaKey.N, rsaKey.E.valueOf());
}
/**
* Returns a subject principal, as an opaque base64 string.
* This is only used as a hash key for known certificates.
*
* Note that this assumes X509 DER-encoded certificates are uniquely encoded,
* as we look for exact matches between Issuer and Subject fields.
*
*/
public function getSubjectPrincipal():String {
load();
return Base64.encodeByteArray(_obj.signedCertificate.subject_bin);
}
/**
* Returns an issuer principal, as an opaque base64 string.
* This is only used to quickly find matching parent certificates.
*
* Note that this assumes X509 DER-encoded certificates are uniquely encoded,
* as we look for exact matches between Issuer and Subject fields.
*
*/
public function getIssuerPrincipal():String {
load();
return Base64.encodeByteArray(_obj.signedCertificate.issuer_bin);
}
public function getAlgorithmIdentifier():String {
return _obj.algorithmIdentifier.algorithmId.toString();
}
public function getNotBefore():Date {
return _obj.signedCertificate.validity.notBefore.date;
}
public function getNotAfter():Date {
return _obj.signedCertificate.validity.notAfter.date;
}
public function getCommonName():String {
var subject:Sequence = _obj.signedCertificate.subject;
return (subject.findAttributeValue(OID.COMMON_NAME) as PrintableString).getString();
}
}
}
|