2024-01-16 21:26:16 +08:00
|
|
|
'use strict';
|
2023-12-18 13:12:25 +08:00
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
const inherits = require('inherits');
|
|
|
|
|
|
|
|
const bignum = require('bn.js');
|
|
|
|
const DecoderBuffer = require('../base/buffer').DecoderBuffer;
|
|
|
|
const Node = require('../base/node');
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
// Import DER constants
|
2024-01-16 21:26:16 +08:00
|
|
|
const der = require('../constants/der');
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
function DERDecoder(entity) {
|
|
|
|
this.enc = 'der';
|
|
|
|
this.name = entity.name;
|
|
|
|
this.entity = entity;
|
|
|
|
|
|
|
|
// Construct base tree
|
|
|
|
this.tree = new DERNode();
|
|
|
|
this.tree._init(entity.body);
|
2024-01-16 21:26:16 +08:00
|
|
|
}
|
2023-12-18 13:12:25 +08:00
|
|
|
module.exports = DERDecoder;
|
|
|
|
|
|
|
|
DERDecoder.prototype.decode = function decode(data, options) {
|
2024-01-16 21:26:16 +08:00
|
|
|
if (!DecoderBuffer.isDecoderBuffer(data)) {
|
|
|
|
data = new DecoderBuffer(data, options);
|
|
|
|
}
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
return this.tree._decode(data, options);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Tree methods
|
|
|
|
|
|
|
|
function DERNode(parent) {
|
2024-01-16 21:26:16 +08:00
|
|
|
Node.call(this, 'der', parent);
|
2023-12-18 13:12:25 +08:00
|
|
|
}
|
2024-01-16 21:26:16 +08:00
|
|
|
inherits(DERNode, Node);
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
DERNode.prototype._peekTag = function peekTag(buffer, tag, any) {
|
|
|
|
if (buffer.isEmpty())
|
|
|
|
return false;
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
const state = buffer.save();
|
|
|
|
const decodedTag = derDecodeTag(buffer, 'Failed to peek tag: "' + tag + '"');
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(decodedTag))
|
|
|
|
return decodedTag;
|
|
|
|
|
|
|
|
buffer.restore(state);
|
|
|
|
|
|
|
|
return decodedTag.tag === tag || decodedTag.tagStr === tag ||
|
|
|
|
(decodedTag.tagStr + 'of') === tag || any;
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._decodeTag = function decodeTag(buffer, tag, any) {
|
2024-01-16 21:26:16 +08:00
|
|
|
const decodedTag = derDecodeTag(buffer,
|
|
|
|
'Failed to decode tag of "' + tag + '"');
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(decodedTag))
|
|
|
|
return decodedTag;
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
let len = derDecodeLen(buffer,
|
|
|
|
decodedTag.primitive,
|
|
|
|
'Failed to get length of "' + tag + '"');
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
// Failure
|
|
|
|
if (buffer.isError(len))
|
|
|
|
return len;
|
|
|
|
|
|
|
|
if (!any &&
|
|
|
|
decodedTag.tag !== tag &&
|
|
|
|
decodedTag.tagStr !== tag &&
|
|
|
|
decodedTag.tagStr + 'of' !== tag) {
|
|
|
|
return buffer.error('Failed to match tag: "' + tag + '"');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (decodedTag.primitive || len !== null)
|
|
|
|
return buffer.skip(len, 'Failed to match body of: "' + tag + '"');
|
|
|
|
|
|
|
|
// Indefinite length... find END tag
|
2024-01-16 21:26:16 +08:00
|
|
|
const state = buffer.save();
|
|
|
|
const res = this._skipUntilEnd(
|
|
|
|
buffer,
|
|
|
|
'Failed to skip indefinite length body: "' + this.tag + '"');
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(res))
|
|
|
|
return res;
|
|
|
|
|
|
|
|
len = buffer.offset - state.offset;
|
|
|
|
buffer.restore(state);
|
|
|
|
return buffer.skip(len, 'Failed to match body of: "' + tag + '"');
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer, fail) {
|
2024-01-16 21:26:16 +08:00
|
|
|
for (;;) {
|
|
|
|
const tag = derDecodeTag(buffer, fail);
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(tag))
|
|
|
|
return tag;
|
2024-01-16 21:26:16 +08:00
|
|
|
const len = derDecodeLen(buffer, tag.primitive, fail);
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(len))
|
|
|
|
return len;
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
let res;
|
2023-12-18 13:12:25 +08:00
|
|
|
if (tag.primitive || len !== null)
|
2024-01-16 21:26:16 +08:00
|
|
|
res = buffer.skip(len);
|
2023-12-18 13:12:25 +08:00
|
|
|
else
|
|
|
|
res = this._skipUntilEnd(buffer, fail);
|
|
|
|
|
|
|
|
// Failure
|
|
|
|
if (buffer.isError(res))
|
|
|
|
return res;
|
|
|
|
|
|
|
|
if (tag.tagStr === 'end')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._decodeList = function decodeList(buffer, tag, decoder,
|
2024-01-16 21:26:16 +08:00
|
|
|
options) {
|
|
|
|
const result = [];
|
2023-12-18 13:12:25 +08:00
|
|
|
while (!buffer.isEmpty()) {
|
2024-01-16 21:26:16 +08:00
|
|
|
const possibleEnd = this._peekTag(buffer, 'end');
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(possibleEnd))
|
|
|
|
return possibleEnd;
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
const res = decoder.decode(buffer, 'der', options);
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(res) && possibleEnd)
|
|
|
|
break;
|
|
|
|
result.push(res);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._decodeStr = function decodeStr(buffer, tag) {
|
|
|
|
if (tag === 'bitstr') {
|
2024-01-16 21:26:16 +08:00
|
|
|
const unused = buffer.readUInt8();
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(unused))
|
|
|
|
return unused;
|
|
|
|
return { unused: unused, data: buffer.raw() };
|
|
|
|
} else if (tag === 'bmpstr') {
|
2024-01-16 21:26:16 +08:00
|
|
|
const raw = buffer.raw();
|
2023-12-18 13:12:25 +08:00
|
|
|
if (raw.length % 2 === 1)
|
|
|
|
return buffer.error('Decoding of string type: bmpstr length mismatch');
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
let str = '';
|
|
|
|
for (let i = 0; i < raw.length / 2; i++) {
|
2023-12-18 13:12:25 +08:00
|
|
|
str += String.fromCharCode(raw.readUInt16BE(i * 2));
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
} else if (tag === 'numstr') {
|
2024-01-16 21:26:16 +08:00
|
|
|
const numstr = buffer.raw().toString('ascii');
|
2023-12-18 13:12:25 +08:00
|
|
|
if (!this._isNumstr(numstr)) {
|
|
|
|
return buffer.error('Decoding of string type: ' +
|
|
|
|
'numstr unsupported characters');
|
|
|
|
}
|
|
|
|
return numstr;
|
|
|
|
} else if (tag === 'octstr') {
|
|
|
|
return buffer.raw();
|
|
|
|
} else if (tag === 'objDesc') {
|
|
|
|
return buffer.raw();
|
|
|
|
} else if (tag === 'printstr') {
|
2024-01-16 21:26:16 +08:00
|
|
|
const printstr = buffer.raw().toString('ascii');
|
2023-12-18 13:12:25 +08:00
|
|
|
if (!this._isPrintstr(printstr)) {
|
|
|
|
return buffer.error('Decoding of string type: ' +
|
|
|
|
'printstr unsupported characters');
|
|
|
|
}
|
|
|
|
return printstr;
|
|
|
|
} else if (/str$/.test(tag)) {
|
|
|
|
return buffer.raw().toString();
|
|
|
|
} else {
|
|
|
|
return buffer.error('Decoding of string type: ' + tag + ' unsupported');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._decodeObjid = function decodeObjid(buffer, values, relative) {
|
2024-01-16 21:26:16 +08:00
|
|
|
let result;
|
|
|
|
const identifiers = [];
|
|
|
|
let ident = 0;
|
|
|
|
let subident = 0;
|
2023-12-18 13:12:25 +08:00
|
|
|
while (!buffer.isEmpty()) {
|
2024-01-16 21:26:16 +08:00
|
|
|
subident = buffer.readUInt8();
|
2023-12-18 13:12:25 +08:00
|
|
|
ident <<= 7;
|
|
|
|
ident |= subident & 0x7f;
|
|
|
|
if ((subident & 0x80) === 0) {
|
|
|
|
identifiers.push(ident);
|
|
|
|
ident = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (subident & 0x80)
|
|
|
|
identifiers.push(ident);
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
const first = (identifiers[0] / 40) | 0;
|
|
|
|
const second = identifiers[0] % 40;
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
if (relative)
|
|
|
|
result = identifiers;
|
|
|
|
else
|
|
|
|
result = [first, second].concat(identifiers.slice(1));
|
|
|
|
|
|
|
|
if (values) {
|
2024-01-16 21:26:16 +08:00
|
|
|
let tmp = values[result.join(' ')];
|
2023-12-18 13:12:25 +08:00
|
|
|
if (tmp === undefined)
|
|
|
|
tmp = values[result.join('.')];
|
|
|
|
if (tmp !== undefined)
|
|
|
|
result = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._decodeTime = function decodeTime(buffer, tag) {
|
2024-01-16 21:26:16 +08:00
|
|
|
const str = buffer.raw().toString();
|
|
|
|
|
|
|
|
let year;
|
|
|
|
let mon;
|
|
|
|
let day;
|
|
|
|
let hour;
|
|
|
|
let min;
|
|
|
|
let sec;
|
2023-12-18 13:12:25 +08:00
|
|
|
if (tag === 'gentime') {
|
2024-01-16 21:26:16 +08:00
|
|
|
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;
|
2023-12-18 13:12:25 +08:00
|
|
|
} else if (tag === 'utctime') {
|
2024-01-16 21:26:16 +08:00
|
|
|
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;
|
2023-12-18 13:12:25 +08:00
|
|
|
if (year < 70)
|
|
|
|
year = 2000 + year;
|
|
|
|
else
|
|
|
|
year = 1900 + year;
|
|
|
|
} else {
|
|
|
|
return buffer.error('Decoding ' + tag + ' time is not supported yet');
|
|
|
|
}
|
|
|
|
|
|
|
|
return Date.UTC(year, mon - 1, day, hour, min, sec, 0);
|
|
|
|
};
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
DERNode.prototype._decodeNull = function decodeNull() {
|
2023-12-18 13:12:25 +08:00
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._decodeBool = function decodeBool(buffer) {
|
2024-01-16 21:26:16 +08:00
|
|
|
const res = buffer.readUInt8();
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buffer.isError(res))
|
|
|
|
return res;
|
|
|
|
else
|
|
|
|
return res !== 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._decodeInt = function decodeInt(buffer, values) {
|
|
|
|
// Bigint, return as it is (assume big endian)
|
2024-01-16 21:26:16 +08:00
|
|
|
const raw = buffer.raw();
|
|
|
|
let res = new bignum(raw);
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
if (values)
|
|
|
|
res = values[res.toString(10)] || res;
|
|
|
|
|
|
|
|
return res;
|
|
|
|
};
|
|
|
|
|
|
|
|
DERNode.prototype._use = function use(entity, obj) {
|
|
|
|
if (typeof entity === 'function')
|
|
|
|
entity = entity(obj);
|
|
|
|
return entity._getDecoder('der').tree;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Utility methods
|
|
|
|
|
|
|
|
function derDecodeTag(buf, fail) {
|
2024-01-16 21:26:16 +08:00
|
|
|
let tag = buf.readUInt8(fail);
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buf.isError(tag))
|
|
|
|
return tag;
|
|
|
|
|
2024-01-16 21:26:16 +08:00
|
|
|
const cls = der.tagClass[tag >> 6];
|
|
|
|
const primitive = (tag & 0x20) === 0;
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
// Multi-octet tag - load
|
|
|
|
if ((tag & 0x1f) === 0x1f) {
|
2024-01-16 21:26:16 +08:00
|
|
|
let oct = tag;
|
2023-12-18 13:12:25 +08:00
|
|
|
tag = 0;
|
|
|
|
while ((oct & 0x80) === 0x80) {
|
|
|
|
oct = buf.readUInt8(fail);
|
|
|
|
if (buf.isError(oct))
|
|
|
|
return oct;
|
|
|
|
|
|
|
|
tag <<= 7;
|
|
|
|
tag |= oct & 0x7f;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tag &= 0x1f;
|
|
|
|
}
|
2024-01-16 21:26:16 +08:00
|
|
|
const tagStr = der.tag[tag];
|
2023-12-18 13:12:25 +08:00
|
|
|
|
|
|
|
return {
|
|
|
|
cls: cls,
|
|
|
|
primitive: primitive,
|
|
|
|
tag: tag,
|
|
|
|
tagStr: tagStr
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function derDecodeLen(buf, primitive, fail) {
|
2024-01-16 21:26:16 +08:00
|
|
|
let len = buf.readUInt8(fail);
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buf.isError(len))
|
|
|
|
return len;
|
|
|
|
|
|
|
|
// Indefinite form
|
|
|
|
if (!primitive && len === 0x80)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
// Definite form
|
|
|
|
if ((len & 0x80) === 0) {
|
|
|
|
// Short form
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Long form
|
2024-01-16 21:26:16 +08:00
|
|
|
const num = len & 0x7f;
|
2023-12-18 13:12:25 +08:00
|
|
|
if (num > 4)
|
|
|
|
return buf.error('length octect is too long');
|
|
|
|
|
|
|
|
len = 0;
|
2024-01-16 21:26:16 +08:00
|
|
|
for (let i = 0; i < num; i++) {
|
2023-12-18 13:12:25 +08:00
|
|
|
len <<= 8;
|
2024-01-16 21:26:16 +08:00
|
|
|
const j = buf.readUInt8(fail);
|
2023-12-18 13:12:25 +08:00
|
|
|
if (buf.isError(j))
|
|
|
|
return j;
|
|
|
|
len |= j;
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|