Initial commit
This commit is contained in:
+296
@@ -0,0 +1,296 @@
|
||||
/* global BigInt */
|
||||
const { inherits } = require('util')
|
||||
|
||||
const { DecoderBuffer } = require('../base/buffer')
|
||||
const Node = require('../base/node')
|
||||
|
||||
// Import DER constants
|
||||
const der = require('../constants/der')
|
||||
|
||||
function DERDecoder (entity) {
|
||||
this.enc = 'der'
|
||||
this.name = entity.name
|
||||
this.entity = entity
|
||||
|
||||
// Construct base tree
|
||||
this.tree = new DERNode()
|
||||
this.tree._init(entity.body)
|
||||
}
|
||||
|
||||
DERDecoder.prototype.decode = function decode (data, options) {
|
||||
if (!DecoderBuffer.isDecoderBuffer(data)) {
|
||||
data = new DecoderBuffer(data, options)
|
||||
}
|
||||
|
||||
return this.tree._decode(data, options)
|
||||
}
|
||||
|
||||
// Tree methods
|
||||
|
||||
function DERNode (parent) {
|
||||
Node.call(this, 'der', parent)
|
||||
}
|
||||
inherits(DERNode, Node)
|
||||
|
||||
DERNode.prototype._peekTag = function peekTag (buffer, tag, any) {
|
||||
if (buffer.isEmpty()) { return false }
|
||||
|
||||
const state = buffer.save()
|
||||
const decodedTag = derDecodeTag(buffer, `Failed to peek tag: "${tag}"`)
|
||||
if (buffer.isError(decodedTag)) { return decodedTag }
|
||||
|
||||
buffer.restore(state)
|
||||
|
||||
return decodedTag.tag === tag || decodedTag.tagStr === tag || (decodedTag.tagStr + 'of') === tag || any
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeTag = function decodeTag (buffer, tag, any) {
|
||||
const decodedTag = derDecodeTag(buffer,
|
||||
`Failed to decode tag of "${tag}"`)
|
||||
if (buffer.isError(decodedTag)) { return decodedTag }
|
||||
|
||||
let len = derDecodeLen(buffer,
|
||||
decodedTag.primitive,
|
||||
`Failed to get length of "${tag}"`)
|
||||
|
||||
// Failure
|
||||
if (buffer.isError(len)) { return len }
|
||||
|
||||
if (!any &&
|
||||
decodedTag.tag !== tag &&
|
||||
decodedTag.tagStr !== tag &&
|
||||
decodedTag.tagStr + 'of' !== tag) {
|
||||
return buffer.error(`Failed to match tag: "${tag}"`)
|
||||
}
|
||||
|
||||
if (decodedTag.primitive || len !== null) { return buffer.skip(len, `Failed to match body of: "${tag}"`) }
|
||||
|
||||
// Indefinite length... find END tag
|
||||
const state = buffer.save()
|
||||
const res = this._skipUntilEnd(
|
||||
buffer,
|
||||
`Failed to skip indefinite length body: "${this.tag}"`)
|
||||
if (buffer.isError(res)) { return res }
|
||||
|
||||
len = buffer.offset - state.offset
|
||||
buffer.restore(state)
|
||||
return buffer.skip(len, `Failed to match body of: "${tag}"`)
|
||||
}
|
||||
|
||||
DERNode.prototype._skipUntilEnd = function skipUntilEnd (buffer, fail) {
|
||||
for (;;) {
|
||||
const tag = derDecodeTag(buffer, fail)
|
||||
if (buffer.isError(tag)) { return tag }
|
||||
const len = derDecodeLen(buffer, tag.primitive, fail)
|
||||
if (buffer.isError(len)) { return len }
|
||||
|
||||
let res
|
||||
if (tag.primitive || len !== null) { res = buffer.skip(len) } else { res = this._skipUntilEnd(buffer, fail) }
|
||||
|
||||
// Failure
|
||||
if (buffer.isError(res)) { return res }
|
||||
|
||||
if (tag.tagStr === 'end') { break }
|
||||
}
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeList = function decodeList (buffer, tag, decoder,
|
||||
options) {
|
||||
const result = []
|
||||
while (!buffer.isEmpty()) {
|
||||
const possibleEnd = this._peekTag(buffer, 'end')
|
||||
if (buffer.isError(possibleEnd)) { return possibleEnd }
|
||||
|
||||
const res = decoder.decode(buffer, 'der', options)
|
||||
if (buffer.isError(res) && possibleEnd) { break }
|
||||
result.push(res)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeStr = function decodeStr (buffer, tag) {
|
||||
if (tag === 'bitstr') {
|
||||
const unused = buffer.readUInt8()
|
||||
if (buffer.isError(unused)) { return unused }
|
||||
return { unused: unused, data: buffer.raw() }
|
||||
} else if (tag === 'bmpstr') {
|
||||
const raw = buffer.raw()
|
||||
if (raw.length % 2 === 1) { return buffer.error('Decoding of string type: bmpstr length mismatch') }
|
||||
|
||||
let str = ''
|
||||
for (let i = 0; i < raw.length / 2; i++) {
|
||||
str += String.fromCharCode(raw.readUInt16BE(i * 2))
|
||||
}
|
||||
return str
|
||||
} else if (tag === 'numstr') {
|
||||
const numstr = buffer.raw().toString('ascii')
|
||||
if (!this._isNumstr(numstr)) {
|
||||
return buffer.error('Decoding of string type: numstr unsupported characters')
|
||||
}
|
||||
return numstr
|
||||
} else if (tag === 'octstr') {
|
||||
return buffer.raw()
|
||||
} else if (tag === 'objDesc') {
|
||||
return buffer.raw()
|
||||
} else if (tag === 'printstr') {
|
||||
const printstr = buffer.raw().toString('ascii')
|
||||
if (!this._isPrintstr(printstr)) {
|
||||
return buffer.error('Decoding of string type: printstr unsupported characters')
|
||||
}
|
||||
return printstr
|
||||
} else if (/str$/.test(tag)) {
|
||||
return buffer.raw().toString()
|
||||
} else {
|
||||
return buffer.error(`Decoding of string type: ${tag} unsupported`)
|
||||
}
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeObjid = function decodeObjid (buffer, values, relative) {
|
||||
let result
|
||||
const identifiers = []
|
||||
let ident = 0
|
||||
let subident = 0
|
||||
while (!buffer.isEmpty()) {
|
||||
subident = buffer.readUInt8()
|
||||
ident <<= 7
|
||||
ident |= subident & 0x7f
|
||||
if ((subident & 0x80) === 0) {
|
||||
identifiers.push(ident)
|
||||
ident = 0
|
||||
}
|
||||
}
|
||||
if (subident & 0x80) { identifiers.push(ident) }
|
||||
|
||||
const first = (identifiers[0] / 40) | 0
|
||||
const second = identifiers[0] % 40
|
||||
|
||||
if (relative) { result = identifiers } else { result = [first, second].concat(identifiers.slice(1)) }
|
||||
|
||||
if (values) {
|
||||
let tmp = values[result.join(' ')]
|
||||
if (tmp === undefined) { tmp = values[result.join('.')] }
|
||||
if (tmp !== undefined) { result = tmp }
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeTime = function decodeTime (buffer, tag) {
|
||||
const str = buffer.raw().toString()
|
||||
|
||||
let year
|
||||
let mon
|
||||
let day
|
||||
let hour
|
||||
let min
|
||||
let sec
|
||||
if (tag === 'gentime') {
|
||||
year = str.slice(0, 4) | 0
|
||||
mon = str.slice(4, 6) | 0
|
||||
day = str.slice(6, 8) | 0
|
||||
hour = str.slice(8, 10) | 0
|
||||
min = str.slice(10, 12) | 0
|
||||
sec = str.slice(12, 14) | 0
|
||||
} else if (tag === 'utctime') {
|
||||
year = str.slice(0, 2) | 0
|
||||
mon = str.slice(2, 4) | 0
|
||||
day = str.slice(4, 6) | 0
|
||||
hour = str.slice(6, 8) | 0
|
||||
min = str.slice(8, 10) | 0
|
||||
sec = str.slice(10, 12) | 0
|
||||
if (year < 70) { year = 2000 + year } else { year = 1900 + year }
|
||||
} else {
|
||||
return buffer.error(`Decoding ${tag} time is not supported yet`)
|
||||
}
|
||||
|
||||
return Date.UTC(year, mon - 1, day, hour, min, sec, 0)
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeNull = function decodeNull () {
|
||||
return null
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeBool = function decodeBool (buffer) {
|
||||
const res = buffer.readUInt8()
|
||||
if (buffer.isError(res)) { return res } else { return res !== 0 }
|
||||
}
|
||||
|
||||
DERNode.prototype._decodeInt = function decodeInt (buffer, values) {
|
||||
// Bigint, return as it is (assume big endian)
|
||||
const raw = buffer.raw()
|
||||
let res = BigInt(`0x${raw.toString('hex')}`)
|
||||
|
||||
if (values) {
|
||||
res = values[res.toString(10)] || res
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
DERNode.prototype._use = function use (entity, obj) {
|
||||
if (typeof entity === 'function') { entity = entity(obj) }
|
||||
return entity._getDecoder('der').tree
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
|
||||
function derDecodeTag (buf, fail) {
|
||||
let tag = buf.readUInt8(fail)
|
||||
if (buf.isError(tag)) { return tag }
|
||||
|
||||
const cls = der.tagClass[tag >> 6]
|
||||
const primitive = (tag & 0x20) === 0
|
||||
|
||||
// Multi-octet tag - load
|
||||
if ((tag & 0x1f) === 0x1f) {
|
||||
let oct = tag
|
||||
tag = 0
|
||||
while ((oct & 0x80) === 0x80) {
|
||||
oct = buf.readUInt8(fail)
|
||||
if (buf.isError(oct)) { return oct }
|
||||
|
||||
tag <<= 7
|
||||
tag |= oct & 0x7f
|
||||
}
|
||||
} else {
|
||||
tag &= 0x1f
|
||||
}
|
||||
const tagStr = der.tag[tag]
|
||||
|
||||
return {
|
||||
cls: cls,
|
||||
primitive: primitive,
|
||||
tag: tag,
|
||||
tagStr: tagStr
|
||||
}
|
||||
}
|
||||
|
||||
function derDecodeLen (buf, primitive, fail) {
|
||||
let len = buf.readUInt8(fail)
|
||||
if (buf.isError(len)) { return len }
|
||||
|
||||
// Indefinite form
|
||||
if (!primitive && len === 0x80) { return null }
|
||||
|
||||
// Definite form
|
||||
if ((len & 0x80) === 0) {
|
||||
// Short form
|
||||
return len
|
||||
}
|
||||
|
||||
// Long form
|
||||
const num = len & 0x7f
|
||||
if (num > 4) { return buf.error('length octect is too long') }
|
||||
|
||||
len = 0
|
||||
for (let i = 0; i < num; i++) {
|
||||
len <<= 8
|
||||
const j = buf.readUInt8(fail)
|
||||
if (buf.isError(j)) { return j }
|
||||
len |= j
|
||||
}
|
||||
|
||||
return len
|
||||
}
|
||||
|
||||
module.exports = DERDecoder
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
der: require('./der'),
|
||||
pem: require('./pem')
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
const { inherits } = require('util')
|
||||
|
||||
const DERDecoder = require('./der')
|
||||
|
||||
function PEMDecoder (entity) {
|
||||
DERDecoder.call(this, entity)
|
||||
this.enc = 'pem'
|
||||
}
|
||||
inherits(PEMDecoder, DERDecoder)
|
||||
|
||||
PEMDecoder.prototype.decode = function decode (data, options) {
|
||||
const lines = data.toString().split(/[\r\n]+/g)
|
||||
|
||||
const label = options.label.toUpperCase()
|
||||
|
||||
const re = /^-----(BEGIN|END) ([^-]+)-----$/
|
||||
let start = -1
|
||||
let end = -1
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const match = lines[i].match(re)
|
||||
if (match === null) { continue }
|
||||
|
||||
if (match[2] !== label) { continue }
|
||||
|
||||
if (start === -1) {
|
||||
if (match[1] !== 'BEGIN') { break }
|
||||
start = i
|
||||
} else {
|
||||
if (match[1] !== 'END') { break }
|
||||
end = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (start === -1 || end === -1) { throw new Error(`PEM section not found for: ${label}`) }
|
||||
|
||||
const base64 = lines.slice(start + 1, end).join('')
|
||||
// Remove excessive symbols
|
||||
base64.replace(/[^a-z0-9+/=]+/gi, '')
|
||||
|
||||
const input = Buffer.from(base64, 'base64')
|
||||
return DERDecoder.prototype.decode.call(this, input, options)
|
||||
}
|
||||
|
||||
module.exports = PEMDecoder
|
||||
Reference in New Issue
Block a user