549 lines
11 KiB
JavaScript
549 lines
11 KiB
JavaScript
/*!
|
|
* 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';
|
|
|
|
const BufferReader = require('./reader');
|
|
|
|
/**
|
|
* @exports utils/asn1
|
|
*/
|
|
|
|
const ASN1 = exports;
|
|
|
|
/**
|
|
* Read next tag.
|
|
* @param {BufferReader} br
|
|
* @returns {Object}
|
|
*/
|
|
|
|
ASN1.readTag = function readTag(br) {
|
|
let type = br.readU8();
|
|
const primitive = (type & 0x20) === 0;
|
|
|
|
if ((type & 0x1f) === 0x1f) {
|
|
let 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) {
|
|
let size = br.readU8();
|
|
|
|
// Indefinite form
|
|
if (!primitive && size === 0x80)
|
|
throw new Error('Indefinite size.');
|
|
|
|
// Definite form
|
|
if ((size & 0x80) === 0) {
|
|
// Short form
|
|
return size;
|
|
}
|
|
|
|
// Long form
|
|
const bytes = size & 0x7f;
|
|
|
|
if (bytes > 3)
|
|
throw new Error('Length octet is too long.');
|
|
|
|
size = 0;
|
|
for (let i = 0; i < bytes; i++) {
|
|
size <<= 8;
|
|
size |= br.readU8();
|
|
}
|
|
|
|
return size;
|
|
};
|
|
|
|
/**
|
|
* Read implicit SEQ.
|
|
* @param {BufferReader} br
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
ASN1.readSeq = function readSeq(br) {
|
|
const 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) {
|
|
const 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) {
|
|
const offset = br.offset;
|
|
const 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?} cast
|
|
* @returns {Buffer|Number}
|
|
*/
|
|
|
|
ASN1.readInt = function readInt(br, cast) {
|
|
const tag = ASN1.implicit(br, 0x02);
|
|
const num = br.readBytes(tag.size);
|
|
|
|
if (cast)
|
|
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) {
|
|
const tag = ASN1.implicit(br, 0x03);
|
|
const 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) {
|
|
const tag = ASN1.readTag(br);
|
|
|
|
switch (tag.type) {
|
|
case 0x03: { // bitstr
|
|
const str = br.readBytes(tag.size);
|
|
return ASN1.alignBitstr(str).toString('utf8');
|
|
}
|
|
// 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 alignBitstr(data) {
|
|
const padding = data[0];
|
|
const bits = (data.length - 1) * 8 - padding;
|
|
const buf = data.slice(1);
|
|
const shift = 8 - (bits % 8);
|
|
|
|
if (shift === 8 || buf.length === 0)
|
|
return buf;
|
|
|
|
const out = Buffer.allocUnsafe(buf.length);
|
|
out[0] = buf[0] >>> shift;
|
|
|
|
for (let 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) {
|
|
const 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) {
|
|
const 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) {
|
|
const 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) {
|
|
const tag = ASN1.readTag(br);
|
|
const str = br.readString('ascii', tag.size);
|
|
let 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) {
|
|
const tag = ASN1.implicit(br, 0x06);
|
|
const 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) {
|
|
const br = new BufferReader(data);
|
|
const ids = [];
|
|
let ident = 0;
|
|
let subident = 0;
|
|
|
|
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);
|
|
|
|
const first = (ids[0] / 40) | 0;
|
|
const second = ids[0] % 40;
|
|
const result = [first, second].concat(ids.slice(1));
|
|
|
|
return result.join('.');
|
|
};
|
|
|
|
/**
|
|
* Read algorithm identifier.
|
|
* @param {BufferReader} br
|
|
* @returns {Object}
|
|
*/
|
|
|
|
ASN1.readAlgIdent = function readAlgIdent(br) {
|
|
let params = null;
|
|
|
|
br = ASN1.seq(br);
|
|
|
|
const alg = ASN1.readOID(br);
|
|
|
|
if (br.left() > 0) {
|
|
const 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));
|
|
};
|