/*! * asn1.js - asn1 parsing for bcoin * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin * * Parts of this software are based on asn1.js. * https://github.com/indutny/asn1.js * * Copyright Fedor Indutny, 2013. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ 'use strict'; var BufferReader = require('./reader'); /** * @exports utils/asn1 */ var ASN1 = exports; /** * Read next tag. * @param {BufferReader} br * @returns {Object} */ ASN1.readTag = function readTag(br) { var type = br.readU8(); var primitive = (type & 0x20) === 0; var oct; if ((type & 0x1f) === 0x1f) { oct = type; type = 0; while ((oct & 0x80) === 0x80) { oct = br.readU8(); type <<= 7; type |= oct & 0x7f; } } else { type &= 0x1f; } return { type: type, primitive: primitive, size: ASN1.readSize(br, primitive) }; }; /** * Read tag size. * @param {BufferReader} br * @param {Boolean} primitive * @returns {Number} * @throws on indefinite size */ ASN1.readSize = function readSize(br, primitive) { var size = br.readU8(); var bytes, i, j; // Indefinite form if (!primitive && size === 0x80) throw new Error('Indefinite size.'); // Definite form if ((size & 0x80) === 0) { // Short form return size; } // Long form bytes = size & 0x7f; if (bytes > 3) throw new Error('Length octet is too long.'); size = 0; for (i = 0; i < bytes; i++) { size <<= 8; j = br.readU8(); size |= j; } return size; }; /** * Read implicit SEQ. * @param {BufferReader} br * @returns {Buffer} */ ASN1.readSeq = function readSeq(br) { var tag = ASN1.implicit(br, 0x10); return br.readBytes(tag.size); }; /** * Read next tag and assert implicit. * @param {BufferReader} br * @param {Number} type * @returns {Object} * @throws on unexpected tag */ ASN1.implicit = function implicit(br, type) { var tag = ASN1.readTag(br); if (tag.type !== type) throw new Error('Unexpected tag: ' + tag.type + '.'); return tag; }; /** * Read implicit tag. * @param {BufferReader} br * @param {Number} type * @returns {Boolean} */ ASN1.explicit = function explicit(br, type) { var offset = br.offset; var tag = ASN1.readTag(br); if (tag.type !== type) { br.offset = offset; return false; } return true; }; /** * Read next implicit SEQ and return a new reader. * @param {BufferReader} br * @returns {BufferReader} */ ASN1.seq = function seq(br) { return new BufferReader(ASN1.readSeq(br), true); }; /** * Read implicit int. * @param {BufferReader} br * @param {Boolean?} readNum * @returns {Buffer|Number} */ ASN1.readInt = function readInt(br, readNum) { var tag = ASN1.implicit(br, 0x02); var num = br.readBytes(tag.size); if (readNum) return num.readUIntBE(0, num.length); return num; }; /** * Read explicit int. * @param {BufferReader} br * @param {Number} type * @param {Boolean?} readNum * @returns {Buffer|Number} `-1` on not present. */ ASN1.readExplicitInt = function readExplicitInt(br, type, readNum) { if (!ASN1.explicit(br, type)) return -1; return ASN1.readInt(br, readNum); }; /** * Read and align an implicit bitstr. * @param {BufferReader} br * @returns {Buffer} */ ASN1.readBitstr = function readBitstr(br) { var tag = ASN1.implicit(br, 0x03); var str = br.readBytes(tag.size); return ASN1.alignBitstr(str); }; /** * Read an implicit string (any type). * @param {BufferReader} br * @returns {String} */ ASN1.readString = function readString(br) { var tag = ASN1.readTag(br); var str; switch (tag.type) { case 0x03: // bitstr str = br.readBytes(tag.size); return ASN1.alignBitstr(str); // Note: // Fuck all these. case 0x04: // octstr case 0x12: // numstr case 0x13: // prinstr case 0x14: // t61str case 0x15: // videostr case 0x16: // ia5str case 0x19: // graphstr case 0x0c: // utf8str case 0x1a: // iso646str case 0x1b: // genstr case 0x1c: // unistr case 0x1d: // charstr case 0x1e: // bmpstr return br.readString('utf8', tag.size); default: throw new Error('Unexpected tag: ' + tag.type + '.'); } }; /** * Align a bitstr. * @param {Buffer} data * @returns {Buffer} */ ASN1.alignBitstr = function(data) { var padding = data[0]; var bits = (data.length - 1) * 8 - padding; var buf = data.slice(1); var shift = 8 - (bits % 8); var i, out; if (shift === 8 || buf.length === 0) return buf; out = new Buffer(buf.length); out[0] = buf[0] >>> shift; for (i = 1; i < buf.length; i++) { out[i] = buf[i - 1] << (8 - shift); out[i] |= buf[i] >>> shift; } return out; }; /** * Read an entire certificate. * @param {BufferReader} br * @returns {Object} */ ASN1.readCert = function readCert(br) { var buf = br; buf.start(); br = ASN1.seq(buf); return { tbs: ASN1.readTBS(br), sigAlg: ASN1.readAlgIdent(br), sig: ASN1.readBitstr(br), raw: buf.endData(true) }; }; /** * Read only the TBS certificate. * @param {BufferReader} br * @returns {Object} */ ASN1.readTBS = function readTBS(br) { var buf = br; buf.start(); br = ASN1.seq(buf); return { version: ASN1.readExplicitInt(br, 0x00, true), serial: ASN1.readInt(br), sig: ASN1.readAlgIdent(br), issuer: ASN1.readName(br), validity: ASN1.readValidity(br), subject: ASN1.readName(br), pubkey: ASN1.readPubkey(br), raw: buf.endData(true) }; }; /** * Read an implicit pubkey. * @param {BufferReader} br * @returns {Object} */ ASN1.readPubkey = function readPubkey(br) { br = ASN1.seq(br); return { alg: ASN1.readAlgIdent(br), pubkey: ASN1.readBitstr(br) }; }; /** * Read implicit name. * @param {BufferReader} br * @returns {Object[]} */ ASN1.readName = function readName(br) { var values = []; br = ASN1.seq(br); while (br.left()) { ASN1.implicit(br, 0x11); // set ASN1.implicit(br, 0x10); // seq values.push({ type: ASN1.readOID(br), value: ASN1.readString(br) }); } return values; }; /** * Read implicit validity timerange. * @param {BufferReader} br * @returns {Object} */ ASN1.readValidity = function readValidity(br) { br = ASN1.seq(br); return { notBefore: ASN1.readTime(br), notAfter: ASN1.readTime(br) }; }; /** * Read implicit timestamp. * @param {BufferReader} br * @returns {Number} */ ASN1.readTime = function readTime(br) { var tag = ASN1.readTag(br); var str = br.readString('ascii', tag.size); var year, mon, day, hour, min, sec; switch (tag.type) { case 0x17: // utctime year = str.slice(0, 2) | 0; mon = str.slice(2, 4) | 0; day = str.slice(4, 6) | 0; hour = str.slice(6, 8) | 0; min = str.slice(8, 10) | 0; sec = str.slice(10, 12) | 0; if (year < 70) year = 2000 + year; else year = 1900 + year; break; case 0x18: // gentime year = str.slice(0, 4) | 0; mon = str.slice(4, 6) | 0; day = str.slice(6, 8) | 0; hour = str.slice(8, 10) | 0; min = str.slice(10, 12) | 0; sec = str.slice(12, 14) | 0; break; default: throw new Error('Unexpected tag: ' + tag.type + '.'); } return Date.UTC(year, mon - 1, day, hour, min, sec, 0) / 1000; }; /** * Read and format OID to string. * @param {BufferReader} br * @returns {String} */ ASN1.readOID = function readOID(br) { var tag = ASN1.implicit(br, 0x06); var data = br.readBytes(tag.size); return ASN1.formatOID(data); }; /** * Format an OID buffer to a string. * @param {Buffer} data * @returns {String} */ ASN1.formatOID = function formatOID(data) { var br = new BufferReader(data); var ids = []; var ident = 0; var subident = 0; var result, first, second; while (br.left()) { subident = br.readU8(); ident <<= 7; ident |= subident & 0x7f; if ((subident & 0x80) === 0) { ids.push(ident); ident = 0; } } if (subident & 0x80) ids.push(ident); first = (ids[0] / 40) | 0; second = ids[0] % 40; result = [first, second].concat(ids.slice(1)); return result.join('.'); }; /** * Read algorithm identifier. * @param {BufferReader} br * @returns {Object} */ ASN1.readAlgIdent = function readAlgIdent(br) { var params = null; var alg, tag; br = ASN1.seq(br); alg = ASN1.readOID(br); if (br.left() > 0) { tag = ASN1.readTag(br); params = br.readBytes(tag.size); if (params.length === 0) params = null; } return { alg: alg, params: params }; }; /** * Read RSA public key. * @param {BufferReader} br * @returns {Object} */ ASN1.readRSAPublic = function readRSAPublic(br) { br = ASN1.seq(br); return { modulus: ASN1.readInt(br), publicExponent: ASN1.readInt(br) }; }; /** * Read RSA private key. * @param {BufferReader} br * @returns {Object} */ ASN1.readRSAPrivate = function readRSAPrivate(br) { br = ASN1.seq(br); return { version: ASN1.readInt(br, true), modulus: ASN1.readInt(br), publicExponent: ASN1.readInt(br), privateExponent: ASN1.readInt(br), prime1: ASN1.readInt(br), prime2: ASN1.readInt(br), exponent1: ASN1.readInt(br), exponent2: ASN1.readInt(br), coefficient: ASN1.readInt(br) }; }; /** * Read RSA public key from buffer. * @param {Buffer} data * @returns {Object} */ ASN1.parseRSAPublic = function parseRSAPublic(data) { return ASN1.readRSAPublic(new BufferReader(data, true)); }; /** * Read RSA private key from buffer. * @param {Buffer} data * @returns {Object} */ ASN1.parseRSAPrivate = function parseRSAPrivate(data) { return ASN1.readRSAPrivate(new BufferReader(data, true)); }; /** * Read certificate from buffer. * @param {Buffer} data * @returns {Object} */ ASN1.parseCert = function parseCert(data) { return ASN1.readCert(new BufferReader(data, true)); }; /** * Read TBS certificate from buffer. * @param {Buffer} data * @returns {Object} */ ASN1.parseTBS = function parseTBS(data) { return ASN1.readTBS(new BufferReader(data, true)); };