refactor: remove generic errors

This commit is contained in:
Filip Skokan 2019-01-20 19:37:27 +01:00
parent 6862c78bed
commit faa45bc03b
22 changed files with 493 additions and 165 deletions

View file

@ -1,4 +1,5 @@
const CODES = {
TODO: 'ERR_TODO',
JWSInvalidHeader: 'ERR_JWS_INVALID_HEADER',
JWSMissingAlg: 'ERR_JWS_ALG_MISSING',
JWSNoRecipients: 'ERR_JWS_NO_RECIPIENTS',
@ -30,3 +31,5 @@ module.exports.JWTIssuerMismatch = class JWTIssuerMismatch extends JoseError {}
module.exports.JWTNonceMismatch = class JWTNonceMismatch extends JoseError {}
module.exports.JWTSubjectMismatch = class JWTSubjectMismatch extends JoseError {}
module.exports.JWTTokenIdMismatch = class JWTTokenIdMismatch extends JoseError {}
module.exports.TODO = class TODO extends JoseError {}

View file

@ -126,14 +126,14 @@ module.exports.joseToDer = (signature, alg) => {
}
if (!paramBytesForAlg[alg]) {
throw new Error(`Unknown algorithm "${alg}"`)
throw new TypeError(`Unknown algorithm "${alg}"`)
}
const paramBytes = paramBytesForAlg[alg]
const signatureBytes = signature.length
if (signatureBytes !== paramBytes * 2) {
throw new TypeError(`"${alg}" signatures must be "${paramBytes * 2}" bytes, saw "${signatureBytes}"`)
throw new Error(`"${alg}" signatures must be "${paramBytes * 2}" bytes, saw "${signatureBytes}"`)
}
const rPadding = countPadding(signature, 0, paramBytes)

View file

@ -1,3 +1,5 @@
const { TODO } = require('../errors')
const JWA = {
sign: new Map(),
verify: new Map()
@ -11,20 +13,20 @@ require('./rsassa_pkcs1')(JWA)
module.exports = {
sign: (alg, key, payload) => {
if (!JWA.sign.has(alg)) {
throw new Error(`sign alg ${alg} not implemented`)
throw new TODO(`sign alg ${alg} not implemented`)
}
if (!key.algorithms('sign').has(alg)) {
throw new Error(`the key does not support ${alg} sign algorithm`)
throw new TODO(`the key does not support ${alg} sign algorithm`)
}
return JWA.sign.get(alg)(key, payload)
},
verify: (alg, key, payload, signature) => {
if (!JWA.verify.has(alg)) {
throw new Error(`verify alg ${alg} not implemented`)
throw new TODO(`verify alg ${alg} not implemented`)
}
if (!key.algorithms('verify').has(alg)) {
throw new Error(`the key does not support ${alg} verify algorithm`)
throw new TODO(`the key does not support ${alg} verify algorithm`)
}
return JWA.verify.get(alg)(key, payload, signature)

22
lib/jwe/generate_cek.js Normal file
View file

@ -0,0 +1,22 @@
const { randomBytes, createSecretKey } = require('crypto')
const OctKey = require('../jwk/key/oct')
const KEYLENGTHS = {
'A128CBC-HS256': 256 / 8,
'A192CBC-HS384': 384 / 8,
'A256CBC-HS512': 512 / 8,
'A128GCM': 128 / 8,
'A192GCM': 192 / 8,
'A256GCM': 256 / 8
}
module.exports = (alg) => {
const byteLength = KEYLENGTHS[alg]
if (byteLength === undefined) {
throw new TypeError('unsupported intended content encryption key alg')
}
return new OctKey(createSecretKey(randomBytes(byteLength)))
}

20
lib/jwk/generate.js Normal file
View file

@ -0,0 +1,20 @@
const RSAKey = require('./key/rsa')
const ECKey = require('./key/ec')
const OctKey = require('./key/oct')
function generate (kty, ...args) {
switch (kty) {
case 'rsa':
case 'RSA':
return RSAKey.generate(...args)
case 'ec':
case 'EC':
return ECKey.generate(...args)
case 'oct':
return OctKey.generate(...args)
default:
throw new TypeError('invalid key type')
}
}
module.exports = generate

View file

@ -5,6 +5,7 @@ const RSAKey = require('./key/rsa')
const ECKey = require('./key/ec')
const OctKey = require('./key/oct')
const base64url = require('../help/base64url')
const { TODO } = require('../errors')
const { PrivateKeyObject, PublicKeyObject, SecretKeyObject } = require('../help/key_objects')
const { jwkToPem } = require('../help/key_utils')
@ -51,7 +52,7 @@ function importKey (key, opts) {
}
if (!privateKey && !publicKey && !secret) {
throw new Error('import failed')
throw new TODO('import failed')
}
const keyObject = privateKey || publicKey || secret

View file

@ -1 +1,2 @@
module.exports.importKey = require('./import')
module.exports.generate = require('./generate')

View file

@ -1,5 +1,6 @@
const { pemToJwk } = require('../../help/key_utils')
const thumbprint = require('../thumbprint')
const { TODO } = require('../../errors')
const RSA_PUBLIC = ['e', 'n']
const RSA_PRIVATE = [...RSA_PUBLIC, 'd', 'p', 'q', 'dp', 'dq', 'qi']
@ -88,7 +89,7 @@ class Key {
}
thumbprintMaterial () {
throw new Error('not implemented')
throw new TODO('not implemented')
}
}

View file

@ -1,3 +1,6 @@
const { promisify } = require('util')
const generateKeyPair = promisify(require('crypto').generateKeyPair)
const Key = require('./base')
const SIG_ALGS = new Set([
@ -6,12 +9,12 @@ const SIG_ALGS = new Set([
'ES512'
])
// const WRAP_ALGS = new Set([
// 'ECDH-ES',
// 'ECDH-ES+A128KW',
// 'ECDH-ES+A192KW',
// 'ECDH-ES+A256KW'
// ])
const WRAP_ALGS = new Set([
'ECDH-ES',
'ECDH-ES+A128KW',
'ECDH-ES+A192KW',
'ECDH-ES+A256KW'
])
class ECKey extends Key {
constructor (...args) {
@ -36,53 +39,67 @@ class ECKey extends Key {
}
algorithms (operation) {
// TODO: operation empty, return all supported or throw
switch (operation) {
case 'encrypt':
case 'decrypt':
return new Set()
case 'sign':
if (this.public || this.use === 'enc') {
return new Set([])
return new Set()
}
if (this.alg) {
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set([`ES${this.length === 521 ? 512 : this.length}`])
case 'verify':
if (this.use === 'enc') {
return new Set([])
return new Set()
}
if (this.alg) {
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set([`ES${this.length === 521 ? 512 : this.length}`])
// case 'unwrapKey':
// if (this.public || this.use === 'sig') {
// return new Set([])
// }
//
// if (this.alg) {
// return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
// }
//
// return new Set(WRAP_ALGS)
// case 'wrapKey':
// if (this.use === 'sig') {
// return new Set([])
// }
//
// if (this.alg) {
// return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
// }
//
// return new Set(WRAP_ALGS)
case 'wrapKey':
if (this.use === 'sig') {
return new Set()
}
if (this.alg) {
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set(WRAP_ALGS)
case 'unwrapKey':
if (this.public || this.use === 'sig') {
return new Set()
}
if (this.alg) {
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set(WRAP_ALGS)
case undefined:
return new Set([
...this.algorithms('sign'),
...this.algorithms('verify'),
...this.algorithms('wrapKey'),
...this.algorithms('unwrapKey')
])
default:
return new Set([])
throw new TypeError('invalid key operation')
}
}
static async generate (crv = 'P-256', opts, privat = true) {
const { privateKey, publicKey } = await generateKeyPair('ec', { namedCurve: crv })
return new ECKey(privat ? privateKey : publicKey, opts)
}
}
module.exports = ECKey

View file

@ -1,3 +1,5 @@
const { randomBytes, createSecretKey } = require('crypto')
const Key = require('./base')
const base64url = require('../../help/base64url')
@ -7,6 +9,42 @@ const SIG_ALGS = new Set([
'HS512'
])
const ENC_LEN = new Set([
128,
192,
256,
384,
512
])
const ENC_ALGS = new Set([
'A128GCM',
'A192GCM',
'A256GCM',
'A128CBC-HS256',
'A192CBC-HS384',
'A256CBC-HS512'
])
const WRAP_LEN = new Set([
128,
192,
256
])
const WRAP_ALGS = new Set([
'A128KW',
'A192KW',
'A256KW',
'A128GCMKW',
'A192GCMKW',
'A256GCMKW',
'PBES2-HS256+A128KW',
'PBES2-HS384+A192KW',
'PBES2-HS512+A256KW',
'dir'
])
class OctKey extends Key {
constructor (...args) {
super(...args)
@ -15,8 +53,16 @@ class OctKey extends Key {
value: this.keyObject.symmetricKeySize * 8
},
k: {
value: base64url.encode(this.keyObject.export()),
enumerable: true
enumerable: true,
get () {
Object.defineProperty(this, 'k', {
value: base64url.encode(this.keyObject.export()),
configurable: false
})
return this.k
},
configurable: true
}
})
}
@ -26,22 +72,22 @@ class OctKey extends Key {
}
algorithms (operation) {
// TODO: operation empty, return all supported or throw
switch (operation) {
case 'sign':
if (this.use === 'enc') {
return new Set([])
case 'encrypt':
case 'decrypt':
if (this.use === 'sig' || !ENC_LEN.has(this.length)) {
return new Set()
}
if (this.alg) {
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
return new Set(ENC_ALGS.has(this.alg) ? [this.alg] : [])
}
return new Set(SIG_ALGS)
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 (this.use === 'enc') {
return new Set([])
return new Set()
}
if (this.alg) {
@ -49,10 +95,41 @@ class OctKey extends Key {
}
return new Set(SIG_ALGS)
case 'wrapKey':
case 'unwrapKey':
if (this.use === 'sig') {
return new Set()
}
if (this.alg) {
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
}
const algs = new Set(['dir', 'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW'])
if (WRAP_LEN.has(this.length)) {
algs.add(`A${this.length}KW`)
algs.add(`A${this.length}GCMKW`)
}
return algs
case undefined:
return new Set([
...this.algorithms('encrypt'),
...this.algorithms('sign'),
...this.algorithms('wrapKey')
])
default:
return new Set([])
throw new TypeError('invalid key operation')
}
}
static async generate (len = 256, opts) {
if (!Number.isSafeInteger(len) || !len || len % 8 !== 0) {
throw new TypeError('invalid bit length')
}
return new OctKey(createSecretKey(randomBytes(len / 8)), opts)
}
}
module.exports = OctKey

View file

@ -1,3 +1,6 @@
const { promisify } = require('util')
const generateKeyPair = promisify(require('crypto').generateKeyPair)
const Key = require('./base')
const SIG_ALGS = new Set([
@ -9,10 +12,10 @@ const SIG_ALGS = new Set([
'PS512'
])
// const WRAP_ALGS = new Set([
// 'RSA-OAEP',
// 'RSA1_5'
// ])
const WRAP_ALGS = new Set([
'RSA-OAEP',
'RSA1_5'
])
class RSAKey extends Key {
constructor (...args) {
@ -37,53 +40,71 @@ class RSAKey extends Key {
}
algorithms (operation) {
// TODO: operation empty, return all supported or throw
switch (operation) {
case 'encrypt':
case 'decrypt':
return new Set()
case 'sign':
if (this.public || this.use === 'enc') {
return new Set([])
return new Set()
}
if (this.alg) {
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set(SIG_ALGS)
case 'verify':
if (this.use === 'enc') {
return new Set([])
return new Set()
}
if (this.alg) {
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set(SIG_ALGS)
// case 'unwrapKey':
// if (this.public || this.use === 'sig') {
// return new Set([])
// }
//
// if (this.alg) {
// return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
// }
//
// return new Set(WRAP_ALGS)
// case 'wrapKey':
// if (this.use === 'sig') {
// return new Set([])
// }
//
// if (this.alg) {
// return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
// }
//
// return new Set(WRAP_ALGS)
case 'wrapKey':
if (this.use === 'sig') {
return new Set()
}
if (this.alg) {
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set(WRAP_ALGS)
case 'unwrapKey':
if (this.public || this.use === 'sig') {
return new Set()
}
if (this.alg) {
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : undefined)
}
return new Set(WRAP_ALGS)
case undefined:
return new Set([
...this.algorithms('sign'),
...this.algorithms('verify'),
...this.algorithms('wrapKey'),
...this.algorithms('unwrapKey')
])
default:
return new Set([])
throw new TypeError('invalid key operation')
}
}
static async generate (len = 2048, opts, privat = true) {
if (!Number.isSafeInteger(len) || len < 512 || len % 8 !== 0) {
throw new TypeError('invalid bit length')
}
const { privateKey, publicKey } = await generateKeyPair('rsa', { modulusLength: len })
return new RSAKey(privat ? privateKey : publicKey, opts)
}
}
module.exports = RSAKey

View file

@ -1,3 +1,5 @@
const { TODO } = require('../errors')
function compactSerializer (payload, [{ signature, protected: prot }]) {
return `${prot}.${payload}.${signature}`
}
@ -50,7 +52,7 @@ function detect (input) {
}
}
throw new Error('invalid serialization')
throw new TODO('invalid serialization')
}
module.exports = {

View file

@ -2,7 +2,7 @@ const base64url = require('../help/base64url')
const serializers = require('./serializers')
const Key = require('../jwk/key/base')
const { JWSInvalidHeader, JWSMissingAlg } = require('../errors')
const { JWSInvalidHeader, JWSMissingAlg, TODO } = require('../errors')
const { sign } = require('../jwa')
function process ({ payload, sharedProtectedHeader, sharedUnprotectedHeader }, recipient) {
@ -64,12 +64,12 @@ class Sign {
*/
sign (serialization = 'compact') {
if (!this.recipients.length) {
throw new Error('missing recipients')
throw new TODO('missing recipients')
}
const serializer = serializers[serialization]
if (!serializer) {
throw new TypeError('invalid serialization')
throw new TODO('invalid serialization')
}
serializer.validate(this, this.recipients)

View file

@ -1,18 +1,19 @@
const base64url = require('../help/base64url')
const { TODO } = require('../errors')
module.exports = (jwt, { complete = false } = {}) => {
if (typeof jwt !== 'string') {
throw TypeError('jwt must be a string')
throw new TypeError('jwt must be a string')
}
const parts = jwt.split('.')
if (parts.length === 5) {
throw new Error('jwt appears to be encrypted')
throw new TODO('jwt appears to be encrypted')
}
if (parts.length !== 3) {
throw new Error('jwt malformed')
throw new TODO('jwt malformed')
}
let [header, payload] = parts
@ -21,7 +22,7 @@ module.exports = (jwt, { complete = false } = {}) => {
header = base64url.JSON.decode(header)
payload = base64url.JSON.decode(payload)
} catch (err) {
throw new Error('jwt malformed')
throw new TODO('jwt malformed')
}
return complete ? { header, payload, signature: parts[2] } : payload

View file

@ -3,10 +3,6 @@ const ms = require('ms')
const Key = require('../jwk/key/base')
const { sign } = require('../jws')
const epoch = require('../help/epoch')
const importKey = require('../jwk/import')
let lastInput
let lastKey
module.exports = (
payload = {},
@ -29,13 +25,7 @@ module.exports = (
} = {}
) => {
if (!(key instanceof Key)) {
if (key === lastInput) {
key = lastKey
} else {
const imported = importKey(key)
lastInput = key
key = lastKey = imported
}
throw new TypeError('key must be an instance of a key instantiated by JWK.importKey')
}
// TODO: full options assert

View file

@ -3,14 +3,10 @@ const ms = require('ms')
const Key = require('../jwk/key/base')
const { verify } = require('../jws')
const epoch = require('../help/epoch')
const importKey = require('../jwk/import')
const errors = require('../errors')
const decode = require('./decode')
let lastInput
let lastKey
module.exports = (
jwt,
key,
@ -31,13 +27,7 @@ module.exports = (
} = {}
) => {
if (!(key instanceof Key)) {
if (key === lastInput) {
key = lastKey
} else {
const imported = importKey(key)
lastInput = key
key = lastKey = imported
}
throw new TypeError('key must be an instance of a key instantiated by JWK.importKey')
}
const { header, payload, signature } = decode(jwt, { complete: true })

View file

@ -15,7 +15,7 @@ test('.derToJose no signature', t => {
return derToJose()
}
t.throws(fn, TypeError)
t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' })
})
test('.derToJose non buffer or base64 signature', t => {
@ -23,7 +23,7 @@ test('.derToJose non buffer or base64 signature', t => {
return derToJose(123)
}
t.throws(fn, TypeError)
t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' })
})
test('.derToJose unknown algorithm', t => {
@ -31,7 +31,7 @@ test('.derToJose unknown algorithm', t => {
return derToJose(decodeToBuffer('Zm9vLmJhci5iYXo'), 'foobar')
}
t.throws(fn, /"foobar"/)
t.throws(fn, { instanceOf: Error, message: 'Unknown algorithm "foobar"' })
})
Object.entries({
@ -47,7 +47,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /expected "seq"/)
t.throws(fn, { instanceOf: Error, message: /expected "seq"/ })
})
test(`.derToJose seq length exceeding input (${alg})`, t => {
@ -59,7 +59,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /length/)
t.throws(fn, { instanceOf: Error, message: /length/ })
})
test(`.derToJose r is not marked as int (${alg})`, t => {
@ -72,7 +72,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /expected "int".+"r"/)
t.throws(fn, { instanceOf: Error, message: /expected "int".+"r"/ })
})
test(`.derToJose r length exceeds available input (${alg})`, t => {
@ -86,7 +86,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /"r".+length/)
t.throws(fn, { instanceOf: Error, message: /"r".+length/ })
})
test(`.derToJose r length exceeds sensical param length (${alg})`, t => {
@ -100,7 +100,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /"r".+length.+acceptable/)
t.throws(fn, { instanceOf: Error, message: /"r".+length.+acceptable/ })
})
test(`.derToJose s is not marked as int (${alg})`, t => {
@ -117,7 +117,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /expected "int".+"s"/)
t.throws(fn, { instanceOf: Error, message: /expected "int".+"s"/ })
})
test(`.derToJose s length exceeds available input (${alg})`, t => {
@ -135,7 +135,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /"s".+length/)
t.throws(fn, { instanceOf: Error, message: /"s".+length/ })
})
test(`.derToJose s length does not consume available input (${alg})`, t => {
@ -153,7 +153,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /"s".+length/)
t.throws(fn, { instanceOf: Error, message: /"s".+length/ })
})
test(`.derToJose s length exceeds sensical param length (${alg})`, t => {
@ -171,7 +171,7 @@ Object.entries({
derToJose(input, alg)
}
t.throws(fn, Error, /"s".+length.+acceptable/)
t.throws(fn, { instanceOf: Error, message: /"s".+length.+acceptable/ })
})
})
@ -180,7 +180,7 @@ test('.joseToDer no signature', t => {
return joseToDer()
}
t.throws(fn, TypeError)
t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' })
})
test('.joseToDer non buffer or base64 signature', t => {
@ -188,7 +188,7 @@ test('.joseToDer non buffer or base64 signature', t => {
return joseToDer(123)
}
t.throws(fn, TypeError)
t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' })
})
test('.joseToDer unknown algorithm', t => {
@ -196,7 +196,7 @@ test('.joseToDer unknown algorithm', t => {
return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo='), 'foobar')
}
t.throws(fn, /"foobar"/)
t.throws(fn, { instanceOf: Error, message: /"foobar"/ })
})
test('.joseToDer incorrect signature length (ES256)', t => {
@ -204,7 +204,7 @@ test('.joseToDer incorrect signature length (ES256)', t => {
return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES256')
}
t.throws(fn, /"64"/)
t.throws(fn, { instanceOf: Error, message: /"64"/ })
})
test('.joseToDer incorrect signature length (ES384)', t => {
@ -212,7 +212,7 @@ test('.joseToDer incorrect signature length (ES384)', t => {
return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES384')
}
t.throws(fn, /"96"/)
t.throws(fn, { instanceOf: Error, message: /"96"/ })
})
test('.joseToDer incorrect signature length (ES512)', t => {
@ -220,7 +220,7 @@ test('.joseToDer incorrect signature length (ES512)', t => {
return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES512')
}
t.throws(fn, /"132"/)
t.throws(fn, { instanceOf: Error, message: '"ES512" signatures must be "132" bytes, saw "11"' })
})
test('ES256 should jose -> der -> jose', t => {

View file

@ -5,6 +5,11 @@ const fixtures = require('../fixtures')
const ECKey = require('../../lib/jwk/key/ec')
test(`EC key .algorithms invalid operation`, t => {
const key = new ECKey(createPrivateKey(fixtures.PEM['P-256'].private))
t.throws(() => key.algorithms('foo'), { instanceOf: TypeError, message: 'invalid key operation' })
})
Object.entries({
'P-256': [256, 'rDd6H6t9-nJUoz72nTpz8tInvypVWhE2iQoPznj8ZY8'],
'P-384': [384, '5gebayAhpztJCs4Pxo-z1hhsN0upoyG2NAoKpiiH2b0'],
@ -32,6 +37,19 @@ Object.entries({
test(`${crv} EC Private key`, hasProperty, key, 'public', false)
test(`${crv} EC Private key`, hasProperty, key, 'use', undefined)
test(`${crv} EC Private key algorithms (no operation)`, t => {
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], [alg, 'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'])
})
test(`${crv} EC Private key algorithms (no operation, w/ alg)`, t => {
const key = new ECKey(keyObject, { alg })
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], [alg])
})
test(`${crv} EC Private key supports sign alg (no use)`, t => {
const result = key.algorithms('sign')
t.is(result.constructor, Set)
@ -86,11 +104,43 @@ Object.entries({
t.deepEqual([...result], [])
})
test.todo(`${crv} EC Private key .algorithms() no arg`)
test.todo(`${crv} EC Private key .algorithms("encrypt")`)
test.todo(`${crv} EC Private key .algorithms("decrypt")`)
test.todo(`${crv} EC Private key .algorithms("wrapKey")`)
test.todo(`${crv} EC Private key .algorithms("unwrapKey")`)
test(`${crv} EC Private key .algorithms("encrypt")`, t => {
const result = key.algorithms('encrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test(`${crv} EC Private key .algorithms("decrypt")`, t => {
const result = key.algorithms('decrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test(`${crv} EC Private key .algorithms("wrapKey")`, t => {
const result = key.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'])
})
test(`${crv} EC Private key .algorithms("wrapKey") when use is sig`, t => {
const sigKey = new ECKey(keyObject, { use: 'sig' })
const result = sigKey.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test(`${crv} EC Private key .algorithms("unwrapKey")`, t => {
const result = key.algorithms('unwrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'])
})
test(`${crv} EC Private key .algorithms("unwrapKey") when use is sig`, t => {
const sigKey = new ECKey(keyObject, { use: 'sig' })
const result = sigKey.algorithms('unwrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
})()
// public
@ -113,6 +163,19 @@ Object.entries({
test(`${crv} EC Public key`, hasProperty, key, 'public', true)
test(`${crv} EC Public key`, hasProperty, key, 'use', undefined)
test(`${crv} EC Public key algorithms (no operation)`, t => {
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], [alg, 'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'])
})
test(`${crv} EC Public key algorithms (no operation, w/ alg)`, t => {
const key = new ECKey(keyObject, { alg })
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], [alg])
})
test(`${crv} EC Public key cannot sign`, t => {
const result = key.algorithms('sign')
t.is(result.constructor, Set)
@ -167,10 +230,35 @@ Object.entries({
t.deepEqual([...result], [])
})
test.todo(`${crv} EC Public key .algorithms() no arg`)
test.todo(`${crv} EC Public key .algorithms("encrypt")`)
test.todo(`${crv} EC Public key .algorithms("decrypt")`)
test.todo(`${crv} EC Public key .algorithms("wrapKey")`)
test.todo(`${crv} EC Public key .algorithms("unwrapKey")`)
test(`${crv} EC Public key .algorithms("encrypt")`, t => {
const result = key.algorithms('encrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test(`${crv} EC Public key .algorithms("decrypt")`, t => {
const result = key.algorithms('decrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test(`${crv} EC Public key .algorithms("wrapKey")`, t => {
const result = key.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'])
})
test(`${crv} EC Public key .algorithms("wrapKey") when use is sig`, t => {
const sigKey = new ECKey(keyObject, { use: 'sig' })
const result = sigKey.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test(`${crv} EC Public key .algorithms("unwrapKey")`, t => {
const result = key.algorithms('unwrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
})()
})

View file

@ -7,6 +7,10 @@ const OctKey = require('../../lib/jwk/key/oct')
const keyObject = createSecretKey(Buffer.from('secret'))
const key = new OctKey(keyObject)
test(`RSA key .algorithms invalid operation`, t => {
t.throws(() => key.algorithms('foo'), { instanceOf: TypeError, message: 'invalid key operation' })
})
test('oct key .thumbprintMaterial()', hasProperties, key.thumbprintMaterial(), 'k', 'kty')
test('oct key (with alg)', hasProperty, new OctKey(keyObject, { alg: 'HS256' }), 'alg', 'HS256')
test('oct key (with kid)', hasProperty, new OctKey(keyObject, { kid: 'foobar' }), 'kid', 'foobar')

View file

@ -5,6 +5,11 @@ const fixtures = require('../fixtures')
const RSAKey = require('../../lib/jwk/key/rsa')
test(`RSA key .algorithms invalid operation`, t => {
const key = new RSAKey(createPrivateKey(fixtures.PEM.RSA.private))
t.throws(() => key.algorithms('foo'), { instanceOf: TypeError, message: 'invalid key operation' })
})
// private
;(() => {
const keyObject = createPrivateKey(fixtures.PEM.RSA.private)
@ -25,6 +30,19 @@ const RSAKey = require('../../lib/jwk/key/rsa')
test(`RSA Private key`, hasProperty, key, 'public', false)
test(`RSA Private key`, hasProperty, key, 'use', undefined)
test('RSA Private key algorithms (no operation)', t => {
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'RSA-OAEP', 'RSA1_5'])
})
test('RSA Private key algorithms (no operation, w/ alg)', t => {
const key = new RSAKey(keyObject, { alg: 'RS256' })
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], ['RS256'])
})
test(`RSA Private key supports sign alg (no use)`, t => {
const result = key.algorithms('sign')
t.is(result.constructor, Set)
@ -79,11 +97,43 @@ const RSAKey = require('../../lib/jwk/key/rsa')
t.deepEqual([...result], [])
})
test.todo(`RSA Private key .algorithms() no arg`)
test.todo(`RSA Private key .algorithms("encrypt")`)
test.todo(`RSA Private key .algorithms("decrypt")`)
test.todo(`RSA Private key .algorithms("wrapKey")`)
test.todo(`RSA Private key .algorithms("unwrapKey")`)
test('RSA Private key .algorithms("encrypt")', t => {
const result = key.algorithms('encrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test('RSA Private key .algorithms("decrypt")', t => {
const result = key.algorithms('decrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test('RSA Private key .algorithms("wrapKey")', t => {
const result = key.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
})
test('RSA Private key .algorithms("wrapKey") when use is sig', t => {
const sigKey = new RSAKey(keyObject, { use: 'sig' })
const result = sigKey.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test('RSA Private key .algorithms("unwrapKey")', t => {
const result = key.algorithms('unwrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
})
test('RSA Private key .algorithms("unwrapKey") when use is sig', t => {
const sigKey = new RSAKey(keyObject, { use: 'sig' })
const result = sigKey.algorithms('unwrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
})()
// public
@ -106,6 +156,19 @@ const RSAKey = require('../../lib/jwk/key/rsa')
test(`RSA Public key`, hasProperty, key, 'public', true)
test(`RSA Public key`, hasProperty, key, 'use', undefined)
test('RSA EC Public key algorithms (no operation)', t => {
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'RSA-OAEP', 'RSA1_5'])
})
test('RSA EC Public key algorithms (no operation, w/ alg)', t => {
const key = new RSAKey(keyObject, { alg: 'RS256' })
const result = key.algorithms()
t.is(result.constructor, Set)
t.deepEqual([...result], ['RS256'])
})
test(`RSA Public key cannot sign`, t => {
const result = key.algorithms('sign')
t.is(result.constructor, Set)
@ -160,9 +223,34 @@ const RSAKey = require('../../lib/jwk/key/rsa')
t.deepEqual([...result], [])
})
test.todo(`RSA Public key .algorithms() no arg`)
test.todo(`RSA Public key .algorithms("encrypt")`)
test.todo(`RSA Public key .algorithms("decrypt")`)
test.todo(`RSA Public key .algorithms("wrapKey")`)
test.todo(`RSA Public key .algorithms("unwrapKey")`)
test('RSA Public key .algorithms("encrypt")', t => {
const result = key.algorithms('encrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test('RSA Public key .algorithms("decrypt")', t => {
const result = key.algorithms('decrypt')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test('RSA Public key .algorithms("wrapKey")', t => {
const result = key.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
})
test('RSA Public key .algorithms("wrapKey") when use is sig', t => {
const sigKey = new RSAKey(keyObject, { use: 'sig' })
const result = sigKey.algorithms('wrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
test('RSA Public key .algorithms("unwrapKey")', t => {
const result = key.algorithms('unwrapKey')
t.is(result.constructor, Set)
t.deepEqual([...result], [])
})
})()

View file

@ -1,17 +1,17 @@
const test = require('ava')
const { sign } = require('../../lib/jwt')
const { JWT: { sign }, JWK: { importKey } } = require('../..')
const { compactJwt } = require('../macros')
const fixtures = require('../fixtures')
const oct = Buffer.from('foo')
const oct = importKey(Buffer.from('foo'))
test('uses HS256 for oct keys', compactJwt, () => sign({}, oct, { noTimestamp: true }), { alg: 'HS256' }, {})
test('uses RS256 for RSA keys', compactJwt, () => sign({}, fixtures.PEM.RSA.private, { noTimestamp: true }), { alg: 'RS256' }, {})
test('uses ES256 for P-256 keys', compactJwt, () => sign({}, fixtures.PEM['P-256'].private, { noTimestamp: true }), { alg: 'ES256' }, {})
test('uses ES384 for P-384 keys', compactJwt, () => sign({}, fixtures.PEM['P-384'].private, { noTimestamp: true }), { alg: 'ES384' }, {})
test('uses ES512 for P-521 keys', compactJwt, () => sign({}, fixtures.PEM['P-521'].private, { noTimestamp: true }), { alg: 'ES512' }, {})
test('uses RS256 for RSA keys', compactJwt, () => sign({}, importKey(fixtures.PEM.RSA.private), { noTimestamp: true }), { alg: 'RS256' }, {})
test('uses ES256 for P-256 keys', compactJwt, () => sign({}, importKey(fixtures.PEM['P-256'].private), { noTimestamp: true }), { alg: 'ES256' }, {})
test('uses ES384 for P-384 keys', compactJwt, () => sign({}, importKey(fixtures.PEM['P-384'].private), { noTimestamp: true }), { alg: 'ES384' }, {})
test('uses ES512 for P-521 keys', compactJwt, () => sign({}, importKey(fixtures.PEM['P-521'].private), { noTimestamp: true }), { alg: 'ES512' }, {})
;(() => {
// header alg

View file

@ -1,21 +1,21 @@
const test = require('ava')
const { verify } = require('../../lib/jwt')
const { JWT: { verify }, JWK: { importKey } } = require('../..')
const fixtures = require('../fixtures')
const oct = Buffer.from('foo')
const oct = importKey(Buffer.from('foo'))
const empties = {
HS256: [oct, 'eyJhbGciOiJIUzI1NiJ9.e30.wFzc1AaKc-h2sBmMo-q5Btx4pthqj4E1uI9iieszJB4'],
HS384: [oct, 'eyJhbGciOiJIUzM4NCJ9.e30.hoxjcQRPLn3wGXBykB2WQFbRiwSM6DQEQaRF1HK8IkBMagxlsXl9tig9jeRmxyr0'],
HS512: [oct, 'eyJhbGciOiJIUzUxMiJ9.e30.TbtVTdmZC0L9tIR2MrFUQVD1WEdTUIYZRHv6hV14WvbPB7xhxQq0Obf-XcUTlMLEadnc8dsbLkWO4oRAsn3Fgg'],
RS256: [fixtures.PEM.RSA.private, 'eyJhbGciOiJSUzI1NiJ9.e30.UJ3uM7z7mtwef9cru3MqO-G-8jLFLm4_vvW8kbPwIlLK4-J037vKE0oKHCu6kzFxlV8TwmYG8Otv4MtM3DlnbJED76E0OG0akkjXcAVQmjEVDq1maLePgZwCiwjhiAYOISXJCe__l0-NXkgHSma49uMqF2DDMK46XveOLWveKyj008SPCtGJNMal2mcCWceta1ay6IQ8_bsVnRCziWsN-2x4AM4BjF0hbE0Qv0regEPpPCVERKuFfQg_wT2CpJQFjf0qqgfYwZk-F-hE3AvBpkTnU-zd-5rxh_8ZuPpbj1xnRPVIni9FUkjkcDTtU1ZqY6d1T-WdfISGwRIx-mI9Rw'],
RS384: [fixtures.PEM.RSA.private, 'eyJhbGciOiJSUzM4NCJ9.e30.dJ78iwcXqKu5x4UqdJjt8Gk88e8XaAm1j4VurNRlWjR7pDQDtJv9a9yMgBpndc1LHgiDUDEPyDEf-PkKFL9LCmsc3QuJCJleJiLpwJAFiSLh4O9M0oCXWA-rNt3zuSAT7TZJOfOWcYoN8JrhJtUMeJbmssRa6q85Umt9-x3GPFq5B5N4N14_hXeoOyq-BfkW7hRfn3s4WaDS1wnRioU_i8FZDhgaYjuToD3qpbh1NODPu4eAGSBiGnFS2B7vL3igSx6o6tufXAMKe3uEeV6A_vLw8CS5PcRBI0SJb1ZoE6PpuG3a_QAhWOrwlEqI7kdrGsBI_6dn6fua37mYz5NDfA'],
RS512: [fixtures.PEM.RSA.private, 'eyJhbGciOiJSUzUxMiJ9.e30.TjpvzV_wg9tZcCTHoC0WfGocXlx7uHru1JDciJuCH9K2UxR-zKw4oYOeVWAWXfCjdksfq6dFHMcP1XOljjrwZndcd0DxngTePeI7KhNBUuoBRMW6UZgAVUxu6fGGKKIw2_iH-fhnYNcS2uAdJawmtSwrIOIYziSHHY4vPZR2cFQpZDRY_kLLzWVvjVRbYdQgGrAlpV9iwKvdfL24TRPNhDx4_PK8fdr92KFUv7RQEHB5B13n26jEM6hzAaGH9EBdZOj7qdQhDrheedytrkVrQrqsEZnpTY_NaGP9pWYJO0JOSWQFXGEuFwJbuD4pjYcyeyNNNO2LcaVBdlgETaQHmQ'],
ES256: [fixtures.PEM['P-256'].private, 'eyJhbGciOiJFUzI1NiJ9.e30.oFrJJCBiFG9VKFoB0SUWOE9nc4LP5ftKPupczd23stw8rAG-acek8mSvPtQwteq4C7Ztl4kAvedIwya0nyNdsQ'],
ES384: [fixtures.PEM['P-384'].private, 'eyJhbGciOiJFUzM4NCJ9.e30.Rl7rliQOo8L8kA83-lkDPiK6kwi_NPanU9tdpYB94Bja3W_fnqQbpkiKgjij9BB1Aaj5mlKcovSzRJFTKLaI0CUAsxETrg6FscHtKC2nog0bXHFRKBtuRjO7vFCg381S'],
ES512: [fixtures.PEM['P-521'].private, 'eyJhbGciOiJFUzUxMiJ9.e30.AcE4SOw8dm6gXAthcm1eY4m1cVZk844Xnt26X8bsahyc7xcb-zIJpwzqsKRX-H_rYaPM1RUkTgFJbLEnCuYvv8qSAV1mJUvjJqVlWfkQE2NByiLf1eFDR2YA8T5i9dAM6NLobl5B0koUd8m9YHj4tfgRWNdorQs3WZ7tkxfq-jXu1OeP']
RS256: [importKey(fixtures.PEM.RSA.private), 'eyJhbGciOiJSUzI1NiJ9.e30.UJ3uM7z7mtwef9cru3MqO-G-8jLFLm4_vvW8kbPwIlLK4-J037vKE0oKHCu6kzFxlV8TwmYG8Otv4MtM3DlnbJED76E0OG0akkjXcAVQmjEVDq1maLePgZwCiwjhiAYOISXJCe__l0-NXkgHSma49uMqF2DDMK46XveOLWveKyj008SPCtGJNMal2mcCWceta1ay6IQ8_bsVnRCziWsN-2x4AM4BjF0hbE0Qv0regEPpPCVERKuFfQg_wT2CpJQFjf0qqgfYwZk-F-hE3AvBpkTnU-zd-5rxh_8ZuPpbj1xnRPVIni9FUkjkcDTtU1ZqY6d1T-WdfISGwRIx-mI9Rw'],
RS384: [importKey(fixtures.PEM.RSA.private), 'eyJhbGciOiJSUzM4NCJ9.e30.dJ78iwcXqKu5x4UqdJjt8Gk88e8XaAm1j4VurNRlWjR7pDQDtJv9a9yMgBpndc1LHgiDUDEPyDEf-PkKFL9LCmsc3QuJCJleJiLpwJAFiSLh4O9M0oCXWA-rNt3zuSAT7TZJOfOWcYoN8JrhJtUMeJbmssRa6q85Umt9-x3GPFq5B5N4N14_hXeoOyq-BfkW7hRfn3s4WaDS1wnRioU_i8FZDhgaYjuToD3qpbh1NODPu4eAGSBiGnFS2B7vL3igSx6o6tufXAMKe3uEeV6A_vLw8CS5PcRBI0SJb1ZoE6PpuG3a_QAhWOrwlEqI7kdrGsBI_6dn6fua37mYz5NDfA'],
RS512: [importKey(fixtures.PEM.RSA.private), 'eyJhbGciOiJSUzUxMiJ9.e30.TjpvzV_wg9tZcCTHoC0WfGocXlx7uHru1JDciJuCH9K2UxR-zKw4oYOeVWAWXfCjdksfq6dFHMcP1XOljjrwZndcd0DxngTePeI7KhNBUuoBRMW6UZgAVUxu6fGGKKIw2_iH-fhnYNcS2uAdJawmtSwrIOIYziSHHY4vPZR2cFQpZDRY_kLLzWVvjVRbYdQgGrAlpV9iwKvdfL24TRPNhDx4_PK8fdr92KFUv7RQEHB5B13n26jEM6hzAaGH9EBdZOj7qdQhDrheedytrkVrQrqsEZnpTY_NaGP9pWYJO0JOSWQFXGEuFwJbuD4pjYcyeyNNNO2LcaVBdlgETaQHmQ'],
ES256: [importKey(fixtures.PEM['P-256'].private), 'eyJhbGciOiJFUzI1NiJ9.e30.oFrJJCBiFG9VKFoB0SUWOE9nc4LP5ftKPupczd23stw8rAG-acek8mSvPtQwteq4C7Ztl4kAvedIwya0nyNdsQ'],
ES384: [importKey(fixtures.PEM['P-384'].private), 'eyJhbGciOiJFUzM4NCJ9.e30.Rl7rliQOo8L8kA83-lkDPiK6kwi_NPanU9tdpYB94Bja3W_fnqQbpkiKgjij9BB1Aaj5mlKcovSzRJFTKLaI0CUAsxETrg6FscHtKC2nog0bXHFRKBtuRjO7vFCg381S'],
ES512: [importKey(fixtures.PEM['P-521'].private), 'eyJhbGciOiJFUzUxMiJ9.e30.AcE4SOw8dm6gXAthcm1eY4m1cVZk844Xnt26X8bsahyc7xcb-zIJpwzqsKRX-H_rYaPM1RUkTgFJbLEnCuYvv8qSAV1mJUvjJqVlWfkQE2NByiLf1eFDR2YA8T5i9dAM6NLobl5B0koUd8m9YHj4tfgRWNdorQs3WZ7tkxfq-jXu1OeP']
}
const jwtverify = (t, jwt, key, ePayload, options) => {