@jc-lab/jose
Version:
JSON Web Almost Everything - JWA, JWS, JWE, JWK, JWT, JWKS for Node.js with minimal dependencies
158 lines (134 loc) • 4.65 kB
JavaScript
const { randomBytes } = require('crypto')
const { createSecretKey } = require('../../help/key_object')
const base64url = require('../../help/base64url')
const { KEYOBJECT } = require('../../help/consts')
const {
THUMBPRINT_MATERIAL, PUBLIC_MEMBERS, PRIVATE_MEMBERS,
KEY_MANAGEMENT_DECRYPT, KEY_MANAGEMENT_ENCRYPT
} = require('../../help/consts')
const Key = require('./base')
const ENC_ALGS = new Set(['A128CBC-HS256', 'A128GCM', 'A192CBC-HS384', 'A192GCM', 'A256CBC-HS512', 'A256GCM'])
const ENC_LEN = new Set([128, 192, 256, 384, 512])
const WRAP_LEN = new Set([128, 192, 256])
const OCT_PUBLIC = new Set()
Object.freeze(OCT_PUBLIC)
const OCT_PRIVATE = new Set(['k'])
Object.freeze(OCT_PRIVATE)
// Octet sequence Key Type
class OctKey extends Key {
constructor (...args) {
super(...args)
Object.defineProperties(this, {
kty: {
value: 'oct',
enumerable: true
},
length: {
value: this[KEYOBJECT] ? this[KEYOBJECT].symmetricKeySize * 8 : undefined
},
k: {
enumerable: false,
get () {
if (this[KEYOBJECT]) {
Object.defineProperty(this, 'k', {
value: base64url.encodeBuffer(this[KEYOBJECT].export()),
configurable: false
})
} else {
Object.defineProperty(this, 'k', {
value: undefined,
configurable: false
})
}
return this.k
},
configurable: true
}
})
}
static get [PUBLIC_MEMBERS] () {
return OCT_PUBLIC
}
static get [PRIVATE_MEMBERS] () {
return OCT_PRIVATE
}
// https://tc39.github.io/ecma262/#sec-ordinaryownpropertykeys no need for any special
// JSON.stringify handling in V8
[THUMBPRINT_MATERIAL] () {
if (!this[KEYOBJECT]) {
throw new TypeError('reference "oct" keys without "k" cannot have their thumbprint calculated')
}
return { k: this.k, kty: 'oct' }
}
algorithms (operation, /* second argument is private API */ { use = this.use, alg = this.alg, key_ops: ops = this.key_ops } = {}) {
if (!this[KEYOBJECT]) {
return new Set()
}
if (operation === KEY_MANAGEMENT_ENCRYPT || operation === KEY_MANAGEMENT_DECRYPT) {
return new Set([
...this.algorithms('wrapKey'),
...this.algorithms('deriveKey')
])
}
if (operation && ops && !ops.includes(operation)) {
return new Set()
}
if (alg) {
return new Set(this.algorithms(operation, { alg: null }).has(alg) ? [alg] : undefined)
}
switch (operation) {
case 'deriveKey':
if (use === 'sig' || 'electron' in process.versions) {
return new Set()
}
return new Set(['PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW'])
case 'encrypt':
case 'decrypt':
if (this.use === 'sig' || !ENC_LEN.has(this.length)) {
return new Set()
}
return new Set([`A${this.length / 2}CBC-HS${this.length}`, `A${this.length}GCM`].filter(a => ENC_ALGS.has(a)))
case 'sign':
case 'verify':
if (use === 'enc') {
return new Set()
}
return new Set(['HS256', 'HS384', 'HS512'])
case 'wrapKey':
case 'unwrapKey':
if (use === 'sig' || !WRAP_LEN.has(this.length)) {
return new Set()
}
if ('electron' in process.versions) {
return new Set([`A${this.length}GCMKW`])
}
return new Set([`A${this.length}KW`, `A${this.length}GCMKW`])
case undefined:
return new Set([
// just the ops needed to return all algs regardless of its use - symmetric keys
...this.algorithms('encrypt'),
...this.algorithms('decrypt'),
...this.algorithms('sign'),
...this.algorithms('verify'),
...this.algorithms('wrapKey'),
...this.algorithms('unwrapKey'),
...this.algorithms('deriveKey')
])
default:
throw new TypeError('invalid key operation')
}
}
static async generate (...args) {
return this.generateSync(...args)
}
static generateSync (len = 256, privat = true) {
if (!privat) {
throw new TypeError('"oct" keys cannot be generated as public')
}
if (!Number.isSafeInteger(len) || !len || len % 8 !== 0) {
throw new TypeError('invalid bit length')
}
return createSecretKey(randomBytes(len / 8))
}
}
module.exports = OctKey