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.
		
		
		
		
		
			
		
			
				
					296 lines
				
				7.7 KiB
			
		
		
			
		
	
	
					296 lines
				
				7.7 KiB
			| 
											3 years ago
										 | 'use strict'; | ||
|  | 
 | ||
|  | const inherits = require('inherits'); | ||
|  | const Buffer = require('safer-buffer').Buffer; | ||
|  | const Node = require('../base/node'); | ||
|  | 
 | ||
|  | // Import DER constants
 | ||
|  | const der = require('../constants/der'); | ||
|  | 
 | ||
|  | function DEREncoder(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 = DEREncoder; | ||
|  | 
 | ||
|  | DEREncoder.prototype.encode = function encode(data, reporter) { | ||
|  |   return this.tree._encode(data, reporter).join(); | ||
|  | }; | ||
|  | 
 | ||
|  | // Tree methods
 | ||
|  | 
 | ||
|  | function DERNode(parent) { | ||
|  |   Node.call(this, 'der', parent); | ||
|  | } | ||
|  | inherits(DERNode, Node); | ||
|  | 
 | ||
|  | DERNode.prototype._encodeComposite = function encodeComposite(tag, | ||
|  |   primitive, | ||
|  |   cls, | ||
|  |   content) { | ||
|  |   const encodedTag = encodeTag(tag, primitive, cls, this.reporter); | ||
|  | 
 | ||
|  |   // Short form
 | ||
|  |   if (content.length < 0x80) { | ||
|  |     const header = Buffer.alloc(2); | ||
|  |     header[0] = encodedTag; | ||
|  |     header[1] = content.length; | ||
|  |     return this._createEncoderBuffer([ header, content ]); | ||
|  |   } | ||
|  | 
 | ||
|  |   // Long form
 | ||
|  |   // Count octets required to store length
 | ||
|  |   let lenOctets = 1; | ||
|  |   for (let i = content.length; i >= 0x100; i >>= 8) | ||
|  |     lenOctets++; | ||
|  | 
 | ||
|  |   const header = Buffer.alloc(1 + 1 + lenOctets); | ||
|  |   header[0] = encodedTag; | ||
|  |   header[1] = 0x80 | lenOctets; | ||
|  | 
 | ||
|  |   for (let i = 1 + lenOctets, j = content.length; j > 0; i--, j >>= 8) | ||
|  |     header[i] = j & 0xff; | ||
|  | 
 | ||
|  |   return this._createEncoderBuffer([ header, content ]); | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._encodeStr = function encodeStr(str, tag) { | ||
|  |   if (tag === 'bitstr') { | ||
|  |     return this._createEncoderBuffer([ str.unused | 0, str.data ]); | ||
|  |   } else if (tag === 'bmpstr') { | ||
|  |     const buf = Buffer.alloc(str.length * 2); | ||
|  |     for (let i = 0; i < str.length; i++) { | ||
|  |       buf.writeUInt16BE(str.charCodeAt(i), i * 2); | ||
|  |     } | ||
|  |     return this._createEncoderBuffer(buf); | ||
|  |   } else if (tag === 'numstr') { | ||
|  |     if (!this._isNumstr(str)) { | ||
|  |       return this.reporter.error('Encoding of string type: numstr supports ' + | ||
|  |                                  'only digits and space'); | ||
|  |     } | ||
|  |     return this._createEncoderBuffer(str); | ||
|  |   } else if (tag === 'printstr') { | ||
|  |     if (!this._isPrintstr(str)) { | ||
|  |       return this.reporter.error('Encoding of string type: printstr supports ' + | ||
|  |                                  'only latin upper and lower case letters, ' + | ||
|  |                                  'digits, space, apostrophe, left and rigth ' + | ||
|  |                                  'parenthesis, plus sign, comma, hyphen, ' + | ||
|  |                                  'dot, slash, colon, equal sign, ' + | ||
|  |                                  'question mark'); | ||
|  |     } | ||
|  |     return this._createEncoderBuffer(str); | ||
|  |   } else if (/str$/.test(tag)) { | ||
|  |     return this._createEncoderBuffer(str); | ||
|  |   } else if (tag === 'objDesc') { | ||
|  |     return this._createEncoderBuffer(str); | ||
|  |   } else { | ||
|  |     return this.reporter.error('Encoding of string type: ' + tag + | ||
|  |                                ' unsupported'); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._encodeObjid = function encodeObjid(id, values, relative) { | ||
|  |   if (typeof id === 'string') { | ||
|  |     if (!values) | ||
|  |       return this.reporter.error('string objid given, but no values map found'); | ||
|  |     if (!values.hasOwnProperty(id)) | ||
|  |       return this.reporter.error('objid not found in values map'); | ||
|  |     id = values[id].split(/[\s.]+/g); | ||
|  |     for (let i = 0; i < id.length; i++) | ||
|  |       id[i] |= 0; | ||
|  |   } else if (Array.isArray(id)) { | ||
|  |     id = id.slice(); | ||
|  |     for (let i = 0; i < id.length; i++) | ||
|  |       id[i] |= 0; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!Array.isArray(id)) { | ||
|  |     return this.reporter.error('objid() should be either array or string, ' + | ||
|  |                                'got: ' + JSON.stringify(id)); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!relative) { | ||
|  |     if (id[1] >= 40) | ||
|  |       return this.reporter.error('Second objid identifier OOB'); | ||
|  |     id.splice(0, 2, id[0] * 40 + id[1]); | ||
|  |   } | ||
|  | 
 | ||
|  |   // Count number of octets
 | ||
|  |   let size = 0; | ||
|  |   for (let i = 0; i < id.length; i++) { | ||
|  |     let ident = id[i]; | ||
|  |     for (size++; ident >= 0x80; ident >>= 7) | ||
|  |       size++; | ||
|  |   } | ||
|  | 
 | ||
|  |   const objid = Buffer.alloc(size); | ||
|  |   let offset = objid.length - 1; | ||
|  |   for (let i = id.length - 1; i >= 0; i--) { | ||
|  |     let ident = id[i]; | ||
|  |     objid[offset--] = ident & 0x7f; | ||
|  |     while ((ident >>= 7) > 0) | ||
|  |       objid[offset--] = 0x80 | (ident & 0x7f); | ||
|  |   } | ||
|  | 
 | ||
|  |   return this._createEncoderBuffer(objid); | ||
|  | }; | ||
|  | 
 | ||
|  | function two(num) { | ||
|  |   if (num < 10) | ||
|  |     return '0' + num; | ||
|  |   else | ||
|  |     return num; | ||
|  | } | ||
|  | 
 | ||
|  | DERNode.prototype._encodeTime = function encodeTime(time, tag) { | ||
|  |   let str; | ||
|  |   const date = new Date(time); | ||
|  | 
 | ||
|  |   if (tag === 'gentime') { | ||
|  |     str = [ | ||
|  |       two(date.getUTCFullYear()), | ||
|  |       two(date.getUTCMonth() + 1), | ||
|  |       two(date.getUTCDate()), | ||
|  |       two(date.getUTCHours()), | ||
|  |       two(date.getUTCMinutes()), | ||
|  |       two(date.getUTCSeconds()), | ||
|  |       'Z' | ||
|  |     ].join(''); | ||
|  |   } else if (tag === 'utctime') { | ||
|  |     str = [ | ||
|  |       two(date.getUTCFullYear() % 100), | ||
|  |       two(date.getUTCMonth() + 1), | ||
|  |       two(date.getUTCDate()), | ||
|  |       two(date.getUTCHours()), | ||
|  |       two(date.getUTCMinutes()), | ||
|  |       two(date.getUTCSeconds()), | ||
|  |       'Z' | ||
|  |     ].join(''); | ||
|  |   } else { | ||
|  |     this.reporter.error('Encoding ' + tag + ' time is not supported yet'); | ||
|  |   } | ||
|  | 
 | ||
|  |   return this._encodeStr(str, 'octstr'); | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._encodeNull = function encodeNull() { | ||
|  |   return this._createEncoderBuffer(''); | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._encodeInt = function encodeInt(num, values) { | ||
|  |   if (typeof num === 'string') { | ||
|  |     if (!values) | ||
|  |       return this.reporter.error('String int or enum given, but no values map'); | ||
|  |     if (!values.hasOwnProperty(num)) { | ||
|  |       return this.reporter.error('Values map doesn\'t contain: ' + | ||
|  |                                  JSON.stringify(num)); | ||
|  |     } | ||
|  |     num = values[num]; | ||
|  |   } | ||
|  | 
 | ||
|  |   // Bignum, assume big endian
 | ||
|  |   if (typeof num !== 'number' && !Buffer.isBuffer(num)) { | ||
|  |     const numArray = num.toArray(); | ||
|  |     if (!num.sign && numArray[0] & 0x80) { | ||
|  |       numArray.unshift(0); | ||
|  |     } | ||
|  |     num = Buffer.from(numArray); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (Buffer.isBuffer(num)) { | ||
|  |     let size = num.length; | ||
|  |     if (num.length === 0) | ||
|  |       size++; | ||
|  | 
 | ||
|  |     const out = Buffer.alloc(size); | ||
|  |     num.copy(out); | ||
|  |     if (num.length === 0) | ||
|  |       out[0] = 0; | ||
|  |     return this._createEncoderBuffer(out); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (num < 0x80) | ||
|  |     return this._createEncoderBuffer(num); | ||
|  | 
 | ||
|  |   if (num < 0x100) | ||
|  |     return this._createEncoderBuffer([0, num]); | ||
|  | 
 | ||
|  |   let size = 1; | ||
|  |   for (let i = num; i >= 0x100; i >>= 8) | ||
|  |     size++; | ||
|  | 
 | ||
|  |   const out = new Array(size); | ||
|  |   for (let i = out.length - 1; i >= 0; i--) { | ||
|  |     out[i] = num & 0xff; | ||
|  |     num >>= 8; | ||
|  |   } | ||
|  |   if(out[0] & 0x80) { | ||
|  |     out.unshift(0); | ||
|  |   } | ||
|  | 
 | ||
|  |   return this._createEncoderBuffer(Buffer.from(out)); | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._encodeBool = function encodeBool(value) { | ||
|  |   return this._createEncoderBuffer(value ? 0xff : 0); | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._use = function use(entity, obj) { | ||
|  |   if (typeof entity === 'function') | ||
|  |     entity = entity(obj); | ||
|  |   return entity._getEncoder('der').tree; | ||
|  | }; | ||
|  | 
 | ||
|  | DERNode.prototype._skipDefault = function skipDefault(dataBuffer, reporter, parent) { | ||
|  |   const state = this._baseState; | ||
|  |   let i; | ||
|  |   if (state['default'] === null) | ||
|  |     return false; | ||
|  | 
 | ||
|  |   const data = dataBuffer.join(); | ||
|  |   if (state.defaultBuffer === undefined) | ||
|  |     state.defaultBuffer = this._encodeValue(state['default'], reporter, parent).join(); | ||
|  | 
 | ||
|  |   if (data.length !== state.defaultBuffer.length) | ||
|  |     return false; | ||
|  | 
 | ||
|  |   for (i=0; i < data.length; i++) | ||
|  |     if (data[i] !== state.defaultBuffer[i]) | ||
|  |       return false; | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | // Utility methods
 | ||
|  | 
 | ||
|  | function encodeTag(tag, primitive, cls, reporter) { | ||
|  |   let res; | ||
|  | 
 | ||
|  |   if (tag === 'seqof') | ||
|  |     tag = 'seq'; | ||
|  |   else if (tag === 'setof') | ||
|  |     tag = 'set'; | ||
|  | 
 | ||
|  |   if (der.tagByName.hasOwnProperty(tag)) | ||
|  |     res = der.tagByName[tag]; | ||
|  |   else if (typeof tag === 'number' && (tag | 0) === tag) | ||
|  |     res = tag; | ||
|  |   else | ||
|  |     return reporter.error('Unknown tag: ' + tag); | ||
|  | 
 | ||
|  |   if (res >= 0x1f) | ||
|  |     return reporter.error('Multi-octet tag encoding unsupported'); | ||
|  | 
 | ||
|  |   if (!primitive) | ||
|  |     res |= 0x20; | ||
|  | 
 | ||
|  |   res |= (der.tagClassByName[cls || 'universal'] << 6); | ||
|  | 
 | ||
|  |   return res; | ||
|  | } |