270 lines
7.5 KiB
JavaScript
270 lines
7.5 KiB
JavaScript
|
/* global BigInt */
|
||
|
const { inherits } = require('util')
|
||
|
|
||
|
const Node = require('../base/node')
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
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 (!Object.prototype.hasOwnProperty.call(values, 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('')
|
||
|
}
|
||
|
|
||
|
function bnToBuf (bn) {
|
||
|
var hex = BigInt(bn).toString(16)
|
||
|
if (hex.length % 2) { hex = '0' + hex }
|
||
|
|
||
|
var len = hex.length / 2
|
||
|
var u8 = new Uint8Array(len)
|
||
|
|
||
|
var i = 0
|
||
|
var j = 0
|
||
|
while (i < len) {
|
||
|
u8[i] = parseInt(hex.slice(j, j + 2), 16)
|
||
|
i += 1
|
||
|
j += 2
|
||
|
}
|
||
|
|
||
|
return u8
|
||
|
}
|
||
|
|
||
|
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 (!Object.prototype.hasOwnProperty.call(values, num)) {
|
||
|
return this.reporter.error(`Values map doesn't contain: ${JSON.stringify(num)}`)
|
||
|
}
|
||
|
num = values[num]
|
||
|
}
|
||
|
|
||
|
if (typeof num === 'bigint') {
|
||
|
const numArray = [...bnToBuf(num)]
|
||
|
if (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 (Object.prototype.hasOwnProperty.call(der.tagByName, 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
|
||
|
}
|
||
|
|
||
|
module.exports = DEREncoder
|