/** * 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; } } }