You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
7.9 KiB
324 lines
7.9 KiB
var inherits = require('inherits'); |
|
|
|
var asn1 = require('../../asn1'); |
|
var base = asn1.base; |
|
var bignum = asn1.bignum; |
|
|
|
// Import DER constants |
|
var der = asn1.constants.der; |
|
|
|
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); |
|
}; |
|
module.exports = DERDecoder; |
|
|
|
DERDecoder.prototype.decode = function decode(data, options) { |
|
if (!(data instanceof base.DecoderBuffer)) |
|
data = new base.DecoderBuffer(data, options); |
|
|
|
return this.tree._decode(data, options); |
|
}; |
|
|
|
// Tree methods |
|
|
|
function DERNode(parent) { |
|
base.Node.call(this, 'der', parent); |
|
} |
|
inherits(DERNode, base.Node); |
|
|
|
DERNode.prototype._peekTag = function peekTag(buffer, tag, any) { |
|
if (buffer.isEmpty()) |
|
return false; |
|
|
|
var state = buffer.save(); |
|
var decodedTag = derDecodeTag(buffer, 'Failed to peek tag: "' + tag + '"'); |
|
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) { |
|
var decodedTag = derDecodeTag(buffer, |
|
'Failed to decode tag of "' + tag + '"'); |
|
if (buffer.isError(decodedTag)) |
|
return decodedTag; |
|
|
|
var len = derDecodeLen(buffer, |
|
decodedTag.primitive, |
|
'Failed to get length of "' + tag + '"'); |
|
|
|
// 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 |
|
var state = buffer.save(); |
|
var res = this._skipUntilEnd( |
|
buffer, |
|
'Failed to skip indefinite length body: "' + this.tag + '"'); |
|
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) { |
|
while (true) { |
|
var tag = derDecodeTag(buffer, fail); |
|
if (buffer.isError(tag)) |
|
return tag; |
|
var len = derDecodeLen(buffer, tag.primitive, fail); |
|
if (buffer.isError(len)) |
|
return len; |
|
|
|
var res; |
|
if (tag.primitive || len !== null) |
|
res = buffer.skip(len) |
|
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, |
|
options) { |
|
var result = []; |
|
while (!buffer.isEmpty()) { |
|
var possibleEnd = this._peekTag(buffer, 'end'); |
|
if (buffer.isError(possibleEnd)) |
|
return possibleEnd; |
|
|
|
var res = decoder.decode(buffer, 'der', options); |
|
if (buffer.isError(res) && possibleEnd) |
|
break; |
|
result.push(res); |
|
} |
|
return result; |
|
}; |
|
|
|
DERNode.prototype._decodeStr = function decodeStr(buffer, tag) { |
|
if (tag === 'bitstr') { |
|
var unused = buffer.readUInt8(); |
|
if (buffer.isError(unused)) |
|
return unused; |
|
return { unused: unused, data: buffer.raw() }; |
|
} else if (tag === 'bmpstr') { |
|
var raw = buffer.raw(); |
|
if (raw.length % 2 === 1) |
|
return buffer.error('Decoding of string type: bmpstr length mismatch'); |
|
|
|
var str = ''; |
|
for (var i = 0; i < raw.length / 2; i++) { |
|
str += String.fromCharCode(raw.readUInt16BE(i * 2)); |
|
} |
|
return str; |
|
} else if (tag === 'numstr') { |
|
var numstr = buffer.raw().toString('ascii'); |
|
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') { |
|
var printstr = buffer.raw().toString('ascii'); |
|
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) { |
|
var result; |
|
var identifiers = []; |
|
var ident = 0; |
|
while (!buffer.isEmpty()) { |
|
var subident = buffer.readUInt8(); |
|
ident <<= 7; |
|
ident |= subident & 0x7f; |
|
if ((subident & 0x80) === 0) { |
|
identifiers.push(ident); |
|
ident = 0; |
|
} |
|
} |
|
if (subident & 0x80) |
|
identifiers.push(ident); |
|
|
|
var first = (identifiers[0] / 40) | 0; |
|
var second = identifiers[0] % 40; |
|
|
|
if (relative) |
|
result = identifiers; |
|
else |
|
result = [first, second].concat(identifiers.slice(1)); |
|
|
|
if (values) { |
|
var tmp = values[result.join(' ')]; |
|
if (tmp === undefined) |
|
tmp = values[result.join('.')]; |
|
if (tmp !== undefined) |
|
result = tmp; |
|
} |
|
|
|
return result; |
|
}; |
|
|
|
DERNode.prototype._decodeTime = function decodeTime(buffer, tag) { |
|
var str = buffer.raw().toString(); |
|
if (tag === 'gentime') { |
|
var year = str.slice(0, 4) | 0; |
|
var mon = str.slice(4, 6) | 0; |
|
var day = str.slice(6, 8) | 0; |
|
var hour = str.slice(8, 10) | 0; |
|
var min = str.slice(10, 12) | 0; |
|
var sec = str.slice(12, 14) | 0; |
|
} else if (tag === 'utctime') { |
|
var year = str.slice(0, 2) | 0; |
|
var mon = str.slice(2, 4) | 0; |
|
var day = str.slice(4, 6) | 0; |
|
var hour = str.slice(6, 8) | 0; |
|
var min = str.slice(8, 10) | 0; |
|
var sec = str.slice(10, 12) | 0; |
|
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); |
|
}; |
|
|
|
DERNode.prototype._decodeNull = function decodeNull(buffer) { |
|
return null; |
|
}; |
|
|
|
DERNode.prototype._decodeBool = function decodeBool(buffer) { |
|
var res = buffer.readUInt8(); |
|
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) |
|
var raw = buffer.raw(); |
|
var res = new bignum(raw); |
|
|
|
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) { |
|
var tag = buf.readUInt8(fail); |
|
if (buf.isError(tag)) |
|
return tag; |
|
|
|
var cls = der.tagClass[tag >> 6]; |
|
var primitive = (tag & 0x20) === 0; |
|
|
|
// Multi-octet tag - load |
|
if ((tag & 0x1f) === 0x1f) { |
|
var oct = tag; |
|
tag = 0; |
|
while ((oct & 0x80) === 0x80) { |
|
oct = buf.readUInt8(fail); |
|
if (buf.isError(oct)) |
|
return oct; |
|
|
|
tag <<= 7; |
|
tag |= oct & 0x7f; |
|
} |
|
} else { |
|
tag &= 0x1f; |
|
} |
|
var tagStr = der.tag[tag]; |
|
|
|
return { |
|
cls: cls, |
|
primitive: primitive, |
|
tag: tag, |
|
tagStr: tagStr |
|
}; |
|
} |
|
|
|
function derDecodeLen(buf, primitive, fail) { |
|
var len = buf.readUInt8(fail); |
|
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 |
|
var num = len & 0x7f; |
|
if (num > 4) |
|
return buf.error('length octect is too long'); |
|
|
|
len = 0; |
|
for (var i = 0; i < num; i++) { |
|
len <<= 8; |
|
var j = buf.readUInt8(fail); |
|
if (buf.isError(j)) |
|
return j; |
|
len |= j; |
|
} |
|
|
|
return len; |
|
}
|
|
|