/**
* DER
*
* A basic class to parse DER structures.
* It is very incomplete, but sufficient to extract whatever data we need so far.
* Copyright (c) 2007 Henri Torgemane
*
* See LICENSE.txt for full license information.
*/
package com.hurlant.util.der
{
import com.hurlant.math.BigInteger;
import flash.utils.ByteArray;
import com.hurlant.util.der.Sequence;
import com.hurlant.util.Hex;
// goal 1: to be able to parse an RSA Private Key PEM file.
// goal 2: to parse an X509v3 cert. kinda.
/**
* DER for dummies:
* http://luca.ntop.org/Teaching/Appunti/asn1.html
*
* This class does the bare minimum to get by. if that.
*/
public class DER
{
public static var indent:String = "";
public static function parse(der:ByteArray, structure:*=null):IAsn1Type {
/* if (der.position==0) {
trace("DER.parse: "+Hex.fromArray(der));
}
*/ // type
var type:int = der.readUnsignedByte();
var constructed:Boolean = (type&0x20)!=0;
type &=0x1F;
// length
var len:int = der.readUnsignedByte();
if (len>=0x80) {
// long form of length
var count:int = len & 0x7f;
len = 0;
while (count>0) {
len = (len<<8) | der.readUnsignedByte();
count--;
}
}
// data
var b:ByteArray
switch (type) {
case 0x00: // WHAT IS THIS THINGY? (seen as 0xa0)
// (note to self: read a spec someday.)
// for now, treat as a sequence.
case 0x10: // SEQUENCE/SEQUENCE OF. whatever
// treat as an array
var p:int = der.position;
var o:Sequence = new Sequence(type, len);
var arrayStruct:Array = structure as Array;
if (arrayStruct!=null) {
// copy the array, as we destroy it later.
arrayStruct = arrayStruct.concat();
}
while (der.position < p+len) {
var tmpStruct:Object = null
if (arrayStruct!=null) {
tmpStruct = arrayStruct.shift();
}
if (tmpStruct!=null) {
while (tmpStruct && tmpStruct.optional) {
// make sure we have something that looks reasonable. XXX I'm winging it here..
var wantConstructed:Boolean = (tmpStruct.value is Array);
var isConstructed:Boolean = isConstructedType(der);
if (wantConstructed!=isConstructed) {
// not found. put default stuff, or null
o.push(tmpStruct.defaultValue);
o[tmpStruct.name] = tmpStruct.defaultValue;
// try the next thing
tmpStruct = arrayStruct.shift();
} else {
break;
}
}
}
if (tmpStruct!=null) {
var name:String = tmpStruct.name;
var value:* = tmpStruct.value;
if (tmpStruct.extract) {
// we need to keep a binary copy of this element
var size:int = getLengthOfNextElement(der);
var ba:ByteArray = new ByteArray;
ba.writeBytes(der, der.position, size);
o[name+"_bin"] = ba;
}
var obj:IAsn1Type = DER.parse(der, value);
o.push(obj);
o[name] = obj;
} else {
o.push(DER.parse(der));
}
}
return o;
case 0x11: // SET/SET OF
p = der.position;
var s:Set = new Set(type, len);
while (der.position < p+len) {
s.push(DER.parse(der));
}
return s;
case 0x02: // INTEGER
// put in a BigInteger
b = new ByteArray;
der.readBytes(b,0,len);
b.position=0;
return new Integer(type, len, b);
case 0x06: // OBJECT IDENTIFIER:
b = new ByteArray;
der.readBytes(b,0,len);
b.position=0;
return new ObjectIdentifier(type, len, b);
default:
trace("I DONT KNOW HOW TO HANDLE DER stuff of TYPE "+type);
// fall through
case 0x03: // BIT STRING
if (der[der.position]==0) {
//trace("Horrible Bit String pre-padding removal hack."); // I wish I had the patience to find a spec for this.
der.position++;
len--;
}
case 0x04: // OCTET STRING
// stuff in a ByteArray for now.
var bs:ByteString = new ByteString(type, len);
der.readBytes(bs,0,len);
return bs;
case 0x05: // NULL
// if len!=0, something's horribly wrong.
// should I check?
return null;
case 0x13: // PrintableString
var ps:PrintableString = new PrintableString(type, len);
ps.setString(der.readMultiByte(len, "US-ASCII"));
return ps;
case 0x22: // XXX look up what this is. openssl uses this to store my email.
case 0x14: // T61String - an horrible format we don't even pretend to support correctly
ps = new PrintableString(type, len);
ps.setString(der.readMultiByte(len, "latin1"));
return ps;
case 0x17: // UTCTime
var ut:UTCTime = new UTCTime(type, len);
ut.setUTCTime(der.readMultiByte(len, "US-ASCII"));
return ut;
}
}
private static function getLengthOfNextElement(b:ByteArray):int {
var p:uint = b.position;
// length
b.position++;
var len:int = b.readUnsignedByte();
if (len>=0x80) {
// long form of length
var count:int = len & 0x7f;
len = 0;
while (count>0) {
len = (len<<8) | b.readUnsignedByte();
count--;
}
}
len += b.position-p; // length of length
b.position = p;
return len;
}
private static function isConstructedType(b:ByteArray):Boolean {
var type:int = b[b.position];
return (type&0x20)!=0;
}
public static function wrapDER(type:int, data:ByteArray):ByteArray {
var d:ByteArray = new ByteArray;
d.writeByte(type);
var len:int = data.length;
if (len<128) {
d.writeByte(len);
} else if (len<256) {
d.writeByte(1 | 0x80);
d.writeByte(len);
} else if (len<65536) {
d.writeByte(2 | 0x80);
d.writeByte(len>>8);
d.writeByte(len);
} else if (len<65536*256) {
d.writeByte(3 | 0x80);
d.writeByte(len>>16);
d.writeByte(len>>8);
d.writeByte(len);
} else {
d.writeByte(4 | 0x80);
d.writeByte(len>>24);
d.writeByte(len>>16);
d.writeByte(len>>8);
d.writeByte(len);
}
d.writeBytes(data);
d.position=0;
return d;
}
}
}