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