UNPKG

@indrajala/asn1der

Version:

parse ASN.1 DER structures

247 lines (180 loc) 7.2 kB
import { bitsAreSet, getStandAloneBitsValue } from "./bitwise"; import { Asn1Node } from "./model/Asn1Node"; import { Asn1Construction } from "./model/enums"; import { IAsn1Identifier } from "./model/interfaces"; const parseIdentifer = (raw: Buffer): { identifier: IAsn1Identifier, lengthValueRemainder: Buffer } => { const b0 = raw.readUInt8(0); //console.log(`b0: ${b0.toString()}d 0x${b0.toString(16)} ${b0.toString(2).padStart(8, '0')}b`); const klass = getStandAloneBitsValue(b0, [7,8]); const construction = getStandAloneBitsValue(b0, [6]); const initialOctetTagNumber = getStandAloneBitsValue(b0, [1,2,3,4,5]); let remainder = raw.subarray(1); const isLongFormTag = initialOctetTagNumber == 31; // long-form tag // let longFormTagNumber = 0; if (isLongFormTag) { let tagByteCount = 1; while (bitsAreSet(remainder.readUInt8(tagByteCount - 1), [8])) { tagByteCount = tagByteCount + 1; if (tagByteCount > remainder.length) { throw 'badly encoded long-form tag, infinite subsequent tag bytes'; } } //console.log(`tag byte count: ${tagByteCount}`); const tagBytes = remainder.subarray(0, tagByteCount); //console.log(`tagBytes.length: ${tagBytes.length}`); remainder = remainder.subarray(tagByteCount); // TODO common with parseOID ? const sevenBitStrings = [...tagBytes] .map(it => it.toString(2)) .map(it => it.padStart(8, '0')) .map(it => it.substring(1)); //console.log(`sevenBitStrings: ${sevenBitStrings}`); const joined = sevenBitStrings.join(''); //console.log(`joined: ${joined}`); const padded = joined.padStart(joined.length + ( 8 - (joined.length % 8)), '0'); //console.log(`padded: ${padded}`); longFormTagNumber = parseInt(padded, 2); //console.log(`long-form tag number: ${longFormTagNumber}`); } const tagNumber = (isLongFormTag) ? longFormTagNumber : initialOctetTagNumber; //console.log(`final tag number: ${tagNumber} => ${tagDescription}`); return { identifier:{ raw: Buffer.from([b0]), class: klass, construction, tagNumber, }, lengthValueRemainder: remainder }; }; const parseLength = ( raw: Buffer ): { length: number, contentRemainder: Buffer } => { const firstLengthByte = raw.readUInt8(0); let remainder = raw.subarray(1); const isLongFormLength = bitsAreSet(firstLengthByte, [8]); //console.log(isLongFormLength ? 'long form length' : 'short form length'); let longFormLength = 0; if (isLongFormLength) { const numberOfSubsequentLengthBytes = getStandAloneBitsValue(firstLengthByte, [1,2,3,4,5,6,7]); //console.log(`content length is coded on ${numberOfSubsequentLengthBytes} subsequent bytes`); const lengthBytes = remainder.subarray(0, numberOfSubsequentLengthBytes); remainder = remainder.subarray(numberOfSubsequentLengthBytes); longFormLength = parseInt(lengthBytes.toString('hex'), 16); //console.log(`long form length: ${longFormLength}`); } const length = (isLongFormLength) ? longFormLength : firstLengthByte; //console.log(`length ${length}`); return { length, contentRemainder: remainder }; }; export const pemToDER = (pem: string): Buffer => { const toRemove = [ '-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----', '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----', '-----BEGIN CERTIFICATE REQUEST-----', '-----END CERTIFICATE REQUEST-----', '\n', ' ' ]; const b64 = toRemove .reduce((ac, token) => ac.replace(new RegExp(token, 'g'), ''), pem); return Buffer.from(b64, 'base64'); }; export const parseOID = (b: Buffer): string => { const firstByte = b.readInt8(0); const firstNodeValue = Math.floor(firstByte / 40); const secondNodeValue = firstByte - (firstNodeValue * 40); const nodes = [firstNodeValue, secondNodeValue]; let remainder = b.subarray(1); while (remainder.length > 0) { const isLongForm = bitsAreSet(remainder.readUInt8(0), [8]); if (!isLongForm) { nodes.push(remainder.readUInt8(0)); remainder = remainder.subarray(1); } else { const head = []; while (bitsAreSet(remainder.readUInt8(0), [8])) { head.push(remainder.readUInt8(0)); remainder = remainder.subarray(1); } head.push(remainder.readUInt8(0)); remainder = remainder.subarray(1); const sevenBitStrings = [...head] .map(it => it.toString(2)) .map(it => it.padStart(8, '0')) .map(it => it.substring(1)); //console.log(`sevenBitStrings: ${sevenBitStrings}`); const joined = sevenBitStrings.join(''); //console.log(`joined: ${joined}`); const padded = joined.padStart(joined.length + ( 8 - (joined.length % 8)), '0'); //console.log(`padded: ${padded}`); const longFormOID = parseInt(padded, 2); nodes.push(longFormOID); } } return nodes.join('.'); }; export const parseDER = ( der: Buffer ): Array<Asn1Node> => { // console.log(der.toString('hex').substring(0, 20), "..."); const { identifier, lengthValueRemainder } = parseIdentifer(der); // console.log(`${' '.repeat(indent)} ${identifier.classDescription // } ${identifier.constructionDescription // } #${identifier.tagNumber // } (${identifier.tagDescription})`); // const tagDescription = asn1TagDescription.has(identifier.tagNumber) // ? asn1TagDescription.get(identifier.tagNumber) // : null; // const label = (tagDescription != null) // ? tagDescription // : `${identifier.tagNumber}`; // parse length const { length, contentRemainder } = parseLength(lengthValueRemainder); const content = contentRemainder.subarray(0, length); const residualRemainder = contentRemainder.subarray(length); // console.log(`${' '.repeat(indent)}[${label}] ${content.toString('hex').substring(0, 60)}`); // console.log(`content: ${content.toString('hex')}`); const children = (identifier.construction == Asn1Construction.Primitive) ? [] : (content.length > 0) ? parseDER(content) : []; // if ( // (identifier.construction == Asn1Construction.Constructed) // && // (content.length == 0) // ) { // console.log(`constructed element with zero length content:`); // console.log(identifier); // } const peers = (residualRemainder.length > 0) ? parseDER(residualRemainder) : []; const self = new Asn1Node( der, identifier, length, content, children, ); return [ self, ...peers ]; };