| var inherits = require('inherits'); |
| var Buffer = require('buffer').Buffer; |
| |
| var asn1 = require('../../asn1'); |
| var base = asn1.base; |
| |
| // Import DER constants |
| var der = asn1.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) { |
| base.Node.call(this, 'der', parent); |
| } |
| inherits(DERNode, base.Node); |
| |
| DERNode.prototype._encodeComposite = function encodeComposite(tag, |
| primitive, |
| cls, |
| content) { |
| var encodedTag = encodeTag(tag, primitive, cls, this.reporter); |
| |
| // Short form |
| if (content.length < 0x80) { |
| var header = new Buffer(2); |
| header[0] = encodedTag; |
| header[1] = content.length; |
| return this._createEncoderBuffer([ header, content ]); |
| } |
| |
| // Long form |
| // Count octets required to store length |
| var lenOctets = 1; |
| for (var i = content.length; i >= 0x100; i >>= 8) |
| lenOctets++; |
| |
| var header = new Buffer(1 + 1 + lenOctets); |
| header[0] = encodedTag; |
| header[1] = 0x80 | lenOctets; |
| |
| for (var 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') { |
| var buf = new Buffer(str.length * 2); |
| for (var 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 (var i = 0; i < id.length; i++) |
| id[i] |= 0; |
| } else if (Array.isArray(id)) { |
| id = id.slice(); |
| for (var 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 |
| var size = 0; |
| for (var i = 0; i < id.length; i++) { |
| var ident = id[i]; |
| for (size++; ident >= 0x80; ident >>= 7) |
| size++; |
| } |
| |
| var objid = new Buffer(size); |
| var offset = objid.length - 1; |
| for (var i = id.length - 1; i >= 0; i--) { |
| var 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) { |
| var str; |
| var date = new Date(time); |
| |
| if (tag === 'gentime') { |
| str = [ |
| two(date.getFullYear()), |
| 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.getFullYear() % 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)) { |
| var numArray = num.toArray(); |
| if (!num.sign && numArray[0] & 0x80) { |
| numArray.unshift(0); |
| } |
| num = new Buffer(numArray); |
| } |
| |
| if (Buffer.isBuffer(num)) { |
| var size = num.length; |
| if (num.length === 0) |
| size++; |
| |
| var out = new Buffer(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]); |
| |
| var size = 1; |
| for (var i = num; i >= 0x100; i >>= 8) |
| size++; |
| |
| var out = new Array(size); |
| for (var i = out.length - 1; i >= 0; i--) { |
| out[i] = num & 0xff; |
| num >>= 8; |
| } |
| if(out[0] & 0x80) { |
| out.unshift(0); |
| } |
| |
| return this._createEncoderBuffer(new Buffer(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) { |
| var state = this._baseState; |
| var i; |
| if (state['default'] === null) |
| return false; |
| |
| var 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) { |
| var 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; |
| } |