Skip to content

Commit

Permalink
refactor: introduce various registries to prepare for extension modules
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Dec 3, 2019
1 parent d5af0c3 commit a9f1d4b
Show file tree
Hide file tree
Showing 33 changed files with 367 additions and 416 deletions.
23 changes: 0 additions & 23 deletions lib/help/consts.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const { name: secp256k1 } = require('../jwk/key/secp256k1_crv')

module.exports.KEYOBJECT = Symbol('KEYOBJECT')
module.exports.PRIVATE_MEMBERS = Symbol('PRIVATE_MEMBERS')
module.exports.PUBLIC_MEMBERS = Symbol('PUBLIC_MEMBERS')
Expand All @@ -18,24 +16,3 @@ const USES = new Set(Object.keys(USES_MAPPING))
module.exports.USES_MAPPING = USES_MAPPING
module.exports.OPS = OPS
module.exports.USES = USES

module.exports.OKP_CURVES = new Set(['Ed25519', 'Ed448', 'X25519', 'X448'])
module.exports.EC_CURVES = new Set(['P-256', secp256k1, 'P-384', 'P-521'])
module.exports.ECDH_ALGS = ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']

module.exports.KEYLENGTHS = {
'A128CBC-HS256': 256,
'A192CBC-HS384': 384,
'A256CBC-HS512': 512,
A128GCM: 128,
A192GCM: 192,
A256GCM: 256
}

if ('electron' in process.versions) {
module.exports.OKP_CURVES.delete('Ed448')
module.exports.OKP_CURVES.delete('X25519')
module.exports.OKP_CURVES.delete('X448')
module.exports.EC_CURVES.delete(secp256k1)
module.exports.ECDH_ALGS.splice(1, module.exports.ECDH_ALGS.length - 1)
}
14 changes: 2 additions & 12 deletions lib/help/generate_iv.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
const { randomBytes } = require('crypto')

const IVLENGTHS = {
'A128CBC-HS256': 128 / 8,
A128GCM: 96 / 8,
A128GCMKW: 96 / 8,
'A192CBC-HS384': 128 / 8,
A192GCM: 96 / 8,
A192GCMKW: 96 / 8,
'A256CBC-HS512': 128 / 8,
A256GCM: 96 / 8,
A256GCMKW: 96 / 8
}
const { IVLENGTHS } = require('../registry')

module.exports = alg => randomBytes(IVLENGTHS[alg])
module.exports = alg => randomBytes(IVLENGTHS.get(alg) / 8)
2 changes: 1 addition & 1 deletion lib/help/key_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { createPublicKey } = require('./key_object')
const base64url = require('./base64url')
const asn1 = require('./asn1')
const computePrimes = require('./rsa_primes')
const { OKP_CURVES, EC_CURVES } = require('./consts')
const { OKP_CURVES, EC_CURVES } = require('../registry')

const BN = asn1.bignum
const oidHexToCurve = new Map([
Expand Down
12 changes: 8 additions & 4 deletions lib/jwa/aes_cbc_hmac_sha2.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { strict: assert } = require('assert')
const { createCipheriv, createDecipheriv } = require('crypto')
const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')

const uint64be = require('../help/uint64be')
const timingSafeEqual = require('../help/timing_safe_equal')
Expand Down Expand Up @@ -58,14 +58,18 @@ const decrypt = (size, sign, { [KEYOBJECT]: keyObject }, ciphertext, { iv, tag =
return cleartext
}

module.exports = (JWA) => {
module.exports = (JWA, JWK) => {
['A128CBC-HS256', 'A192CBC-HS384', 'A256CBC-HS512'].forEach((jwaAlg) => {
const size = parseInt(jwaAlg.substr(1, 3), 10)

assert(!JWA.encrypt.has(jwaAlg), `encrypt alg ${jwaAlg} already registered`)
assert(!JWA.decrypt.has(jwaAlg), `decrypt alg ${jwaAlg} already registered`)

JWA.encrypt.set(jwaAlg, encrypt.bind(undefined, size, JWA.sign.get(`HS${size * 2}`)))
JWA.decrypt.set(jwaAlg, decrypt.bind(undefined, size, JWA.sign.get(`HS${size * 2}`)))
const sign = JWA.sign.get(`HS${size * 2}`)
if (getCiphers().includes(`aes-${size}-cbc`)) {
JWA.encrypt.set(jwaAlg, encrypt.bind(undefined, size, sign))
JWA.decrypt.set(jwaAlg, decrypt.bind(undefined, size, sign))
JWK.oct.encrypt[jwaAlg] = JWK.oct.decrypt[jwaAlg] = key => (key.use === 'enc' || key.use === undefined) && key.length / 2 === size
}
})
}
11 changes: 7 additions & 4 deletions lib/jwa/aes_gcm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { strict: assert } = require('assert')
const { createCipheriv, createDecipheriv } = require('crypto')
const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')

const { KEYOBJECT } = require('../help/consts')
const { JWEInvalid, JWEDecryptionFailed } = require('../errors')
Expand Down Expand Up @@ -44,14 +44,17 @@ const decrypt = (size, { [KEYOBJECT]: keyObject }, ciphertext, { iv, tag = Buffe
}
}

module.exports = (JWA) => {
module.exports = (JWA, JWK) => {
['A128GCM', 'A192GCM', 'A256GCM'].forEach((jwaAlg) => {
const size = parseInt(jwaAlg.substr(1, 3), 10)

assert(!JWA.encrypt.has(jwaAlg), `encrypt alg ${jwaAlg} already registered`)
assert(!JWA.decrypt.has(jwaAlg), `decrypt alg ${jwaAlg} already registered`)

JWA.encrypt.set(jwaAlg, encrypt.bind(undefined, size))
JWA.decrypt.set(jwaAlg, decrypt.bind(undefined, size))
if (getCiphers().includes(`aes-${size}-gcm`)) {
JWA.encrypt.set(jwaAlg, encrypt.bind(undefined, size))
JWA.decrypt.set(jwaAlg, decrypt.bind(undefined, size))
JWK.oct.encrypt[jwaAlg] = JWK.oct.decrypt[jwaAlg] = key => (key.use === 'enc' || key.use === undefined) && key.length === size
}
})
}
24 changes: 14 additions & 10 deletions lib/jwa/aes_gcm_kw.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ const { strict: assert } = require('assert')
const generateIV = require('../help/generate_iv')
const base64url = require('../help/base64url')

module.exports = (JWA) => {
module.exports = (JWA, JWK) => {
['A128GCMKW', 'A192GCMKW', 'A256GCMKW'].forEach((jwaAlg) => {
assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)

const encAlg = jwaAlg.substr(0, 7)
const size = parseInt(jwaAlg.substr(1, 3), 10)
const encrypt = JWA.encrypt.get(encAlg)
const decrypt = JWA.decrypt.get(encAlg)

JWA.keyManagementEncrypt.set(jwaAlg, (key, payload) => {
const iv = generateIV(jwaAlg)
const { ciphertext, tag } = encrypt(key, payload, { iv })
return {
wrapped: ciphertext,
header: { tag: base64url.encodeBuffer(tag), iv: base64url.encodeBuffer(iv) }
}
})
JWA.keyManagementDecrypt.set(jwaAlg, decrypt)
if (encrypt && decrypt) {
JWA.keyManagementEncrypt.set(jwaAlg, (key, payload) => {
const iv = generateIV(jwaAlg)
const { ciphertext, tag } = encrypt(key, payload, { iv })
return {
wrapped: ciphertext,
header: { tag: base64url.encodeBuffer(tag), iv: base64url.encodeBuffer(iv) }
}
})
JWA.keyManagementDecrypt.set(jwaAlg, decrypt)
JWK.oct.wrapKey[jwaAlg] = JWK.oct.unwrapKey[jwaAlg] = key => (key.use === 'enc' || key.use === undefined) && key.length === size
}
})
}
11 changes: 7 additions & 4 deletions lib/jwa/aes_kw.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { strict: assert } = require('assert')
const { createCipheriv, createDecipheriv } = require('crypto')
const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')

const uint64be = require('../help/uint64be')
const timingSafeEqual = require('../help/timing_safe_equal')
Expand Down Expand Up @@ -88,14 +88,17 @@ const unwrapKey = (size, { [KEYOBJECT]: keyObject }, payload) => {
return Buffer.concat(R)
}

module.exports = (JWA) => {
module.exports = (JWA, JWK) => {
['A128KW', 'A192KW', 'A256KW'].forEach((jwaAlg) => {
const size = parseInt(jwaAlg.substr(1, 3), 10)

assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)

JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, size))
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, size))
if (getCiphers().includes(`aes${size}`)) {
JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, size))
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, size))
JWK.oct.wrapKey[jwaAlg] = JWK.oct.unwrapKey[jwaAlg] = key => (key.use === 'enc' || key.use === undefined) && key.length === size
}
})
}
20 changes: 10 additions & 10 deletions lib/jwa/ecdh/dir.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
const { strict: assert } = require('assert')

const { KEYLENGTHS } = require('../../help/consts')
const { KEYLENGTHS } = require('../../registry')
const { generateSync } = require('../../jwk/generate')
const { name: secp256k1 } = require('../../jwk/key/secp256k1_crv')

const derive = require('./derive')

const wrapKey = (key, payload, { enc }) => {
const epk = generateSync(key.kty, key.crv)

const derivedKey = derive(enc, KEYLENGTHS[enc], epk, key)
const derivedKey = derive(enc, KEYLENGTHS.get(enc), epk, key)

return {
wrapped: derivedKey,
Expand All @@ -17,15 +18,14 @@ const wrapKey = (key, payload, { enc }) => {
}

const unwrapKey = (key, payload, { apu, apv, epk, enc }) => {
return derive(enc, KEYLENGTHS[enc], key, epk, { apu, apv })
return derive(enc, KEYLENGTHS.get(enc), key, epk, { apu, apv })
}

const ALG = 'ECDH-ES'
module.exports = (JWA, JWK) => {
assert(!JWA.keyManagementEncrypt.has('ECDH-ES'), 'keyManagementEncrypt alg ECDH-ES already registered')
assert(!JWA.keyManagementDecrypt.has('ECDH-ES'), 'keyManagementDecrypt alg ECDH-ES already registered')

module.exports = (JWA) => {
assert(!JWA.keyManagementEncrypt.has(ALG), `keyManagementEncrypt alg ${ALG} already registered`)
assert(!JWA.keyManagementDecrypt.has(ALG), `keyManagementDecrypt alg ${ALG} already registered`)

JWA.keyManagementEncrypt.set(ALG, wrapKey)
JWA.keyManagementDecrypt.set(ALG, unwrapKey)
JWA.keyManagementEncrypt.set('ECDH-ES', wrapKey)
JWA.keyManagementDecrypt.set('ECDH-ES', unwrapKey)
JWK.EC.deriveKey['ECDH-ES'] = key => (key.use === 'enc' || key.use === undefined) && key.crv !== secp256k1
}
10 changes: 7 additions & 3 deletions lib/jwa/ecdh/kw.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { strict: assert } = require('assert')

const { KEYOBJECT } = require('../../help/consts')
const { generateSync } = require('../../jwk/generate')
const { name: secp256k1 } = require('../../jwk/key/secp256k1_crv')

const derive = require('./derive')

Expand All @@ -22,7 +23,7 @@ const unwrapKey = (unwrap, derive, key, payload, { apu, apv, epk }) => {
return unwrap({ [KEYOBJECT]: derivedKey }, payload)
}

module.exports = (JWA) => {
module.exports = (JWA, JWK) => {
['ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'].forEach((jwaAlg) => {
assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)
Expand All @@ -32,7 +33,10 @@ module.exports = (JWA) => {
const kwUnwrap = JWA.keyManagementDecrypt.get(kw)
const keylen = parseInt(jwaAlg.substr(9, 3), 10)

JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, kwWrap, derive.bind(undefined, jwaAlg, keylen)))
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, kwUnwrap, derive.bind(undefined, jwaAlg, keylen)))
if (kwWrap && kwUnwrap) {
JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, kwWrap, derive.bind(undefined, jwaAlg, keylen)))
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, kwUnwrap, derive.bind(undefined, jwaAlg, keylen)))
JWK.EC.deriveKey[jwaAlg] = key => (key.use === 'enc' || key.use === undefined) && key.crv !== secp256k1
}
})
}
40 changes: 37 additions & 3 deletions lib/jwa/ecdsa.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const { strict: assert } = require('assert')
const { sign: signOneShot, verify: verifyOneShot, createSign, createVerify } = require('crypto')
const { sign: signOneShot, verify: verifyOneShot, createSign, createVerify, getCurves } = require('crypto')

const { derToJose, joseToDer } = require('../help/ecdsa_signatures')
const { KEYOBJECT } = require('../help/consts')
const resolveNodeAlg = require('../help/node_alg')
const { asInput } = require('../help/key_object')
const { dsaEncodingSupported } = require('../help/runtime_support')
const { name: secp256k1 } = require('../jwk/key/secp256k1_crv')

let sign, verify

Expand Down Expand Up @@ -49,14 +50,47 @@ if (dsaEncodingSupported) { // >= 13.2.0
}
}

module.exports = (JWA) => {
['ES256', 'ES384', 'ES512', 'ES256K'].forEach((jwaAlg) => {
const crvToAlg = (crv) => {
switch (crv) {
case 'P-256':
return 'ES256'
case secp256k1:
return 'ES256K'
case 'P-384':
return 'ES384'
case 'P-521':
return 'ES512'
}
}

module.exports = (JWA, JWK) => {
const algs = []

if (getCurves().includes('prime256v1')) {
algs.push('ES256')
}

if (getCurves().includes('secp256k1')) {
algs.push('ES256K')
}

if (getCurves().includes('secp384r1')) {
algs.push('ES384')
}

if (getCurves().includes('secp521r1')) {
algs.push('ES512')
}

algs.forEach((jwaAlg) => {
const nodeAlg = resolveNodeAlg(jwaAlg)

assert(!JWA.sign.has(jwaAlg), `sign alg ${jwaAlg} already registered`)
assert(!JWA.verify.has(jwaAlg), `verify alg ${jwaAlg} already registered`)

JWA.sign.set(jwaAlg, sign.bind(undefined, jwaAlg, nodeAlg))
JWA.verify.set(jwaAlg, verify.bind(undefined, jwaAlg, nodeAlg))
JWK.EC.sign[jwaAlg] = key => key.private && JWK.EC.verify[jwaAlg](key)
JWK.EC.verify[jwaAlg] = key => (key.use === 'sig' || key.use === undefined) && crvToAlg(key.crv) === jwaAlg
})
}
17 changes: 10 additions & 7 deletions lib/jwa/eddsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { strict: assert } = require('assert')
const { sign: signOneShot, verify: verifyOneShot } = require('crypto')

const { KEYOBJECT } = require('../help/consts')
const { edDSASupported } = require('../help/runtime_support')

const sign = ({ [KEYOBJECT]: keyObject }, payload) => {
return signOneShot(undefined, payload, keyObject)
Expand All @@ -11,12 +12,14 @@ const verify = ({ [KEYOBJECT]: keyObject }, payload, signature) => {
return verifyOneShot(undefined, payload, keyObject, signature)
}

const ALG = 'EdDSA'
module.exports = (JWA, JWK) => {
assert(!JWA.sign.has('EdDSA'), 'sign alg EdDSA already registered')
assert(!JWA.verify.has('EdDSA'), 'verify alg EdDSA already registered')

module.exports = (JWA) => {
assert(!JWA.sign.has(ALG), `sign alg ${ALG} already registered`)
assert(!JWA.verify.has(ALG), `verify alg ${ALG} already registered`)

JWA.sign.set(ALG, sign)
JWA.verify.set(ALG, verify)
if (edDSASupported) {
JWA.sign.set('EdDSA', sign)
JWA.verify.set('EdDSA', verify)
JWK.OKP.sign.EdDSA = key => key.private && JWK.OKP.verify.EdDSA(key)
JWK.OKP.verify.EdDSA = key => (key.use === 'sig' || key.use === undefined) && key.keyObject.asymmetricKeyType.startsWith('ed')
}
}
3 changes: 2 additions & 1 deletion lib/jwa/hmac.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const verify = (jwaAlg, hmacAlg, { [KEYOBJECT]: keyObject }, payload, signature)
return timingSafeEqual(actual, expected)
}

module.exports = (JWA) => {
module.exports = (JWA, JWK) => {
['HS256', 'HS384', 'HS512'].forEach((jwaAlg) => {
const hmacAlg = resolveNodeAlg(jwaAlg)

Expand All @@ -30,5 +30,6 @@ module.exports = (JWA) => {

JWA.sign.set(jwaAlg, sign.bind(undefined, jwaAlg, hmacAlg))
JWA.verify.set(jwaAlg, verify.bind(undefined, jwaAlg, hmacAlg))
JWK.oct.sign[jwaAlg] = JWK.oct.verify[jwaAlg] = key => key.use === 'sig' || key.use === undefined
})
}
Loading

0 comments on commit a9f1d4b

Please sign in to comment.