| 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; |
| } |