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