UNPKG

@indrajala/asn1der

Version:

parse ASN.1 DER structures

168 lines 7.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseDER = exports.parseOID = exports.pemToDER = void 0; const bitwise_1 = require("./bitwise"); const Asn1Node_1 = require("./model/Asn1Node"); const enums_1 = require("./model/enums"); const parseIdentifer = (raw) => { const b0 = raw.readUInt8(0); //console.log(`b0: ${b0.toString()}d 0x${b0.toString(16)} ${b0.toString(2).padStart(8, '0')}b`); const klass = bitwise_1.getStandAloneBitsValue(b0, [7, 8]); const construction = bitwise_1.getStandAloneBitsValue(b0, [6]); const initialOctetTagNumber = bitwise_1.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 (bitwise_1.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) => { const firstLengthByte = raw.readUInt8(0); let remainder = raw.subarray(1); const isLongFormLength = bitwise_1.bitsAreSet(firstLengthByte, [8]); //console.log(isLongFormLength ? 'long form length' : 'short form length'); let longFormLength = 0; if (isLongFormLength) { const numberOfSubsequentLengthBytes = bitwise_1.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 }; }; exports.pemToDER = (pem) => { 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'); }; exports.parseOID = (b) => { 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 = bitwise_1.bitsAreSet(remainder.readUInt8(0), [8]); if (!isLongForm) { nodes.push(remainder.readUInt8(0)); remainder = remainder.subarray(1); } else { const head = []; while (bitwise_1.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('.'); }; exports.parseDER = (der) => { // 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 == enums_1.Asn1Construction.Primitive) ? [] : (content.length > 0) ? exports.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) ? exports.parseDER(residualRemainder) : []; const self = new Asn1Node_1.Asn1Node(der, identifier, length, content, children); return [ self, ...peers ]; }; //# sourceMappingURL=asn1der.js.map