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.
		
		
		
		
		
			
		
			
				
					336 lines
				
				7.9 KiB
			
		
		
			
		
	
	
					336 lines
				
				7.9 KiB
			| 
											3 years ago
										 | 'use strict'; | ||
|  | 
 | ||
|  | const inherits = require('inherits'); | ||
|  | 
 | ||
|  | const bignum = require('bn.js'); | ||
|  | const DecoderBuffer = require('../base/buffer').DecoderBuffer; | ||
|  | const Node = require('../base/node'); | ||
|  | 
 | ||
|  | // Import DER constants
 | ||
|  | const der = require('../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 (!DecoderBuffer.isDecoderBuffer(data)) { | ||
|  |     data = new DecoderBuffer(data, options); | ||
|  |   } | ||
|  | 
 | ||
|  |   return this.tree._decode(data, options); | ||
|  | }; | ||
|  | 
 | ||
|  | // Tree methods
 | ||
|  | 
 | ||
|  | function DERNode(parent) { | ||
|  |   Node.call(this, 'der', parent); | ||
|  | } | ||
|  | inherits(DERNode, Node); | ||
|  | 
 | ||
|  | DERNode.prototype._peekTag = function peekTag(buffer, tag, any) { | ||
|  |   if (buffer.isEmpty()) | ||
|  |     return false; | ||
|  | 
 | ||
|  |   const state = buffer.save(); | ||
|  |   const 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) { | ||
|  |   const decodedTag = derDecodeTag(buffer, | ||
|  |     'Failed to decode tag of "' + tag + '"'); | ||
|  |   if (buffer.isError(decodedTag)) | ||
|  |     return decodedTag; | ||
|  | 
 | ||
|  |   let 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
 | ||
|  |   const state = buffer.save(); | ||
|  |   const 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) { | ||
|  |   for (;;) { | ||
|  |     const tag = derDecodeTag(buffer, fail); | ||
|  |     if (buffer.isError(tag)) | ||
|  |       return tag; | ||
|  |     const len = derDecodeLen(buffer, tag.primitive, fail); | ||
|  |     if (buffer.isError(len)) | ||
|  |       return len; | ||
|  | 
 | ||
|  |     let 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) { | ||
|  |   const result = []; | ||
|  |   while (!buffer.isEmpty()) { | ||
|  |     const possibleEnd = this._peekTag(buffer, 'end'); | ||
|  |     if (buffer.isError(possibleEnd)) | ||
|  |       return possibleEnd; | ||
|  | 
 | ||
|  |     const 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') { | ||
|  |     const unused = buffer.readUInt8(); | ||
|  |     if (buffer.isError(unused)) | ||
|  |       return unused; | ||
|  |     return { unused: unused, data: buffer.raw() }; | ||
|  |   } else if (tag === 'bmpstr') { | ||
|  |     const raw = buffer.raw(); | ||
|  |     if (raw.length % 2 === 1) | ||
|  |       return buffer.error('Decoding of string type: bmpstr length mismatch'); | ||
|  | 
 | ||
|  |     let str = ''; | ||
|  |     for (let i = 0; i < raw.length / 2; i++) { | ||
|  |       str += String.fromCharCode(raw.readUInt16BE(i * 2)); | ||
|  |     } | ||
|  |     return str; | ||
|  |   } else if (tag === 'numstr') { | ||
|  |     const 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') { | ||
|  |     const 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) { | ||
|  |   let result; | ||
|  |   const identifiers = []; | ||
|  |   let ident = 0; | ||
|  |   let subident = 0; | ||
|  |   while (!buffer.isEmpty()) { | ||
|  |     subident = buffer.readUInt8(); | ||
|  |     ident <<= 7; | ||
|  |     ident |= subident & 0x7f; | ||
|  |     if ((subident & 0x80) === 0) { | ||
|  |       identifiers.push(ident); | ||
|  |       ident = 0; | ||
|  |     } | ||
|  |   } | ||
|  |   if (subident & 0x80) | ||
|  |     identifiers.push(ident); | ||
|  | 
 | ||
|  |   const first = (identifiers[0] / 40) | 0; | ||
|  |   const second = identifiers[0] % 40; | ||
|  | 
 | ||
|  |   if (relative) | ||
|  |     result = identifiers; | ||
|  |   else | ||
|  |     result = [first, second].concat(identifiers.slice(1)); | ||
|  | 
 | ||
|  |   if (values) { | ||
|  |     let 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) { | ||
|  |   const str = buffer.raw().toString(); | ||
|  | 
 | ||
|  |   let year; | ||
|  |   let mon; | ||
|  |   let day; | ||
|  |   let hour; | ||
|  |   let min; | ||
|  |   let sec; | ||
|  |   if (tag === '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; | ||
|  |   } else if (tag === '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; | ||
|  |   } 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() { | ||
|  |   return null; | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._decodeBool = function decodeBool(buffer) { | ||
|  |   const 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)
 | ||
|  |   const raw = buffer.raw(); | ||
|  |   let 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) { | ||
|  |   let tag = buf.readUInt8(fail); | ||
|  |   if (buf.isError(tag)) | ||
|  |     return tag; | ||
|  | 
 | ||
|  |   const cls = der.tagClass[tag >> 6]; | ||
|  |   const primitive = (tag & 0x20) === 0; | ||
|  | 
 | ||
|  |   // Multi-octet tag - load
 | ||
|  |   if ((tag & 0x1f) === 0x1f) { | ||
|  |     let 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; | ||
|  |   } | ||
|  |   const tagStr = der.tag[tag]; | ||
|  | 
 | ||
|  |   return { | ||
|  |     cls: cls, | ||
|  |     primitive: primitive, | ||
|  |     tag: tag, | ||
|  |     tagStr: tagStr | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function derDecodeLen(buf, primitive, fail) { | ||
|  |   let 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
 | ||
|  |   const num = len & 0x7f; | ||
|  |   if (num > 4) | ||
|  |     return buf.error('length octect is too long'); | ||
|  | 
 | ||
|  |   len = 0; | ||
|  |   for (let i = 0; i < num; i++) { | ||
|  |     len <<= 8; | ||
|  |     const j = buf.readUInt8(fail); | ||
|  |     if (buf.isError(j)) | ||
|  |       return j; | ||
|  |     len |= j; | ||
|  |   } | ||
|  | 
 | ||
|  |   return len; | ||
|  | } |