diff --git a/lib/errors.js b/lib/errors.js index 04e202cc..7f1958e1 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -1,37 +1,43 @@ const CODES = { TODO: 'ERR_TODO', + JOSEAlgNotSupported: 'ERR_JOSE_ALG_NOT_SUPPORTED', + JWEDecryptionFailed: 'ERR_JWE_DECRYPTION_FAILED', + JWEInvalid: 'ERR_JWE_INVALID', JWEInvalidHeader: 'ERR_JWE_INVALID_HEADER', + JWENoRecipients: 'ERR_JWE_NO_RECIPIENTS', + JWKImportFailed: 'ERR_JWK_IMPORT_FAILED', + JWKKeySupport: 'ERR_JWK_KEY_SUPPORT', JWSInvalidHeader: 'ERR_JWS_INVALID_HEADER', - JWSMissingAlg: 'ERR_JWS_ALG_MISSING', JWSNoRecipients: 'ERR_JWS_NO_RECIPIENTS', - JWSVerificationFailed: 'ERR_JWS_VERIFICATION_FAILED', - JWTAudienceMismatch: 'ERR_JWT_AUDIENCE_MISMATCH', - JWTInvalidAlgorithm: 'ERR_JWT_INVALID_ALGORITHM', - JWTIssuerMismatch: 'ERR_JWT_ISSUER_MISMATCH', - JWTNonceMismatch: 'ERR_JWT_NONCE_MISMATCH', - JWTSubjectMismatch: 'ERR_JWT_SUBJECT_MISMATCH', - JWTTokenIdMismatch: 'ERR_JWT_TOKEN_ID_MISMATCH' + JWSVerificationFailed: 'ERR_JWS_VERIFICATION_FAILED' +} + +const DEFAULT_MESSAGES = { + JWEDecryptionFailed: 'decryption operation failed', + JWSVerificationFailed: 'signature verification failed' } class JoseError extends Error { constructor (message) { super(message) + if (message === undefined) { + message = DEFAULT_MESSAGES[this.constructor.name] + } this.name = this.constructor.name this.code = CODES[this.constructor.name] Error.captureStackTrace(this, this.constructor) } } +module.exports.JOSEAlgNotSupported = class JOSEAlgNotSupported extends JoseError {} +module.exports.JWEDecryptionFailed = class JWEDecryptionFailed extends JoseError {} +module.exports.JWEInvalid = class JWEInvalid extends JoseError {} module.exports.JWEInvalidHeader = class JWEInvalidHeader extends JoseError {} +module.exports.JWENoRecipients = class JWENoRecipients extends JoseError {} +module.exports.JWKImportFailed = class JWKImportFailed extends JoseError {} +module.exports.JWKKeySupport = class JWKKeySupport extends JoseError {} module.exports.JWSInvalidHeader = class JWSInvalidHeader extends JoseError {} -module.exports.JWSMissingAlg = class JWSMissingAlg extends JoseError {} module.exports.JWSNoRecipients = class JWSNoRecipients extends JoseError {} module.exports.JWSVerificationFailed = class JWSVerificationFailed extends JoseError {} -module.exports.JWTAudienceMismatch = class JWTAudienceMismatch extends JoseError {} -module.exports.JWTInvalidAlgorithm = class JWTInvalidAlgorithm extends JoseError {} -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 {} diff --git a/lib/help/generate_iv.js b/lib/help/generate_iv.js index 7372eddb..01cda4a8 100644 --- a/lib/help/generate_iv.js +++ b/lib/help/generate_iv.js @@ -12,12 +12,4 @@ const IVLENGTHS = { 'A256GCMKW': 96 / 8 } -module.exports = (alg) => { - const byteLength = IVLENGTHS[alg] - - if (byteLength === undefined) { - throw new TypeError('unsupported intended content encryption key alg') - } - - return randomBytes(byteLength) -} +module.exports = alg => randomBytes(IVLENGTHS[alg]) diff --git a/lib/help/key_objects.js b/lib/help/key_objects.js index ff7b576e..d9fb9e27 100644 --- a/lib/help/key_objects.js +++ b/lib/help/key_objects.js @@ -1,8 +1,6 @@ const { generateKeyPairSync, createSecretKey } = require('crypto') -// TODO: reach out to @tniessen to request constructors being exposed for this exact purpose -// (verifying inputs are already KeyObjects) -// TODO: what's the least blocking way to get to the constructors +// TODO: keep an eye out for utils.isCryptoKeyObject() for verifying inputs are already KeyObjects const { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' }) const PrivateKeyObject = privateKey.constructor diff --git a/lib/jwa/aes_cbc_hmac_sha2.js b/lib/jwa/aes_cbc_hmac_sha2.js index fab97478..30f6c1fd 100644 --- a/lib/jwa/aes_cbc_hmac_sha2.js +++ b/lib/jwa/aes_cbc_hmac_sha2.js @@ -1,19 +1,22 @@ const { createCipheriv, createDecipheriv } = require('crypto') const { strict: assert } = require('assert') -const { TODO } = require('../errors') +const { JWEInvalid, JWEDecryptionFailed } = require('../errors') const uint64be = require('../help/uint64be') const timingSafeEqual = require('../help/timing_safe_equal') -const ivCheck = (iv) => { - if (!iv || iv.length !== 16) { - throw new TODO('invalid iv') +const checkInput = (iv) => { + if (!iv) { + throw new JWEInvalid('missing iv') + } + if (iv.length !== 16) { + throw new JWEInvalid('invalid iv') } } const encrypt = (size, sign, { keyObject }, cleartext, { iv, aad = Buffer.alloc(0) }) => { const key = keyObject.export() - ivCheck(iv) + checkInput(iv) const keySize = size / 8 const encKey = key.slice(keySize) @@ -28,24 +31,28 @@ const encrypt = (size, sign, { keyObject }, cleartext, { iv, aad = Buffer.alloc( } const decrypt = (size, sign, { keyObject }, ciphertext, { iv, tag = Buffer.alloc(0), aad = Buffer.alloc(0) }) => { - const key = keyObject.export() - ivCheck(iv) + checkInput(iv) const keySize = size / 8 + const key = keyObject.export() const encKey = key.slice(keySize) const macKey = key.slice(0, keySize) const macData = Buffer.concat([aad, iv, ciphertext, uint64be(aad.length * 8)]) - const expectedTag = sign({ keyObject: macKey }, macData, tag).slice(0, keySize) + const macCheckPassed = timingSafeEqual(tag, expectedTag) - if (!timingSafeEqual(tag, expectedTag)) { - throw new TODO('mac check failed') + let cleartext + try { + const cipher = createDecipheriv(`AES-${size}-CBC`, encKey, iv) + cleartext = Buffer.concat([cipher.update(ciphertext), cipher.final()]) + } catch (err) {} + + if (!cleartext || !macCheckPassed) { + throw new JWEDecryptionFailed() } - const cipher = createDecipheriv(`AES-${size}-CBC`, encKey, iv) - - return Buffer.concat([cipher.update(ciphertext), cipher.final()]) + return cleartext } module.exports = (JWA) => { diff --git a/lib/jwa/aes_gcm.js b/lib/jwa/aes_gcm.js index c171b4dd..a3edbc99 100644 --- a/lib/jwa/aes_gcm.js +++ b/lib/jwa/aes_gcm.js @@ -1,8 +1,24 @@ const { createCipheriv, createDecipheriv } = require('crypto') const { strict: assert } = require('assert') +const { JWEInvalid, JWEDecryptionFailed } = require('../errors') + +const checkInput = (size, keyLen, iv, tag) => { + if (keyLen * 8 !== size) { + throw new JWEInvalid('invalid key length') + } + if (!iv) { + throw new JWEInvalid('missing iv') + } + if (iv.length !== 12) { + throw new JWEInvalid('invalid iv') + } + if (tag !== undefined && tag.length !== 16) { + throw new JWEInvalid('invalid tag length') + } +} const encrypt = (size, { keyObject }, cleartext, { iv, aad = Buffer.alloc(0) }) => { - // TODO: commonCheck + checkInput(size, keyObject.symmetricKeySize, iv) const cipher = createCipheriv(`AES-${size}-GCM`, keyObject, iv) cipher.setAAD(aad) @@ -14,13 +30,17 @@ const encrypt = (size, { keyObject }, cleartext, { iv, aad = Buffer.alloc(0) }) } const decrypt = (size, { keyObject }, ciphertext, { iv, tag = Buffer.alloc(0), aad = Buffer.alloc(0) }) => { - // TODO: commonCheck + checkInput(size, keyObject.symmetricKeySize, iv, tag, aad) - const cipher = createDecipheriv(`AES-${size}-GCM`, keyObject, iv) - cipher.setAuthTag(tag) - cipher.setAAD(aad) + try { + const cipher = createDecipheriv(`AES-${size}-GCM`, keyObject, iv) + cipher.setAuthTag(tag) + cipher.setAAD(aad) - return Buffer.concat([cipher.update(ciphertext), cipher.final()]) + return Buffer.concat([cipher.update(ciphertext), cipher.final()]) + } catch (err) { + throw new JWEDecryptionFailed() + } } module.exports = (JWA) => { diff --git a/lib/jwa/aes_kw.js b/lib/jwa/aes_kw.js index 8c944095..af62ae61 100644 --- a/lib/jwa/aes_kw.js +++ b/lib/jwa/aes_kw.js @@ -1,13 +1,23 @@ +const { SecretKeyObject } = require('../help/key_objects') const { createCipheriv, createDecipheriv } = require('crypto') const { strict: assert } = require('assert') -const { TODO } = require('../errors') +const { JWEInvalid, JWEDecryptionFailed } = require('../errors') const uint64be = require('../help/uint64be') const timingSafeEqual = require('../help/timing_safe_equal') +const checkInput = (size, keyLen, data) => { + if (keyLen * 8 !== size) { + throw new JWEInvalid('invalid key length') + } + if (data !== undefined && data.length % 8 !== 0) { + throw new JWEInvalid('invalid data length') + } +} + const A0 = Buffer.alloc(8, 'a6', 'hex') -function xor (a, b) { +const xor = (a, b) => { const len = Math.max(a.length, b.length) const result = Buffer.alloc(len) for (let idx = 0; len > idx; idx++) { @@ -17,7 +27,7 @@ function xor (a, b) { return result } -function split (input, size) { +const split = (input, size) => { const output = [] for (let idx = 0; input.length > idx; idx += size) { output.push(input.slice(idx, idx + size)) @@ -25,8 +35,20 @@ function split (input, size) { return output } +const getKeyLen = (keyObject) => { + if (Buffer.isBuffer(keyObject)) { + return keyObject.length + } + + if (keyObject instanceof SecretKeyObject) { + return keyObject.symmetricKeySize + } + + throw new TypeError('invalid key object') +} + const wrapKey = (size, { keyObject }, payload) => { - // TODO: commonCheck + checkInput(size, getKeyLen(keyObject), payload) const iv = Buffer.alloc(16) let R = split(payload, 8) @@ -51,7 +73,7 @@ const wrapKey = (size, { keyObject }, payload) => { } const unwrapKey = (size, { keyObject }, payload) => { - // TODO: commonCheck + checkInput(size, getKeyLen(keyObject), payload) const iv = Buffer.alloc(16) @@ -75,7 +97,7 @@ const unwrapKey = (size, { keyObject }, payload) => { } if (!timingSafeEqual(A0, A)) { - throw new TODO('decryption failed') + throw new JWEDecryptionFailed() // TODO: different error } return Buffer.concat(R) diff --git a/lib/jwa/ecdh/kw.js b/lib/jwa/ecdh/kw.js index 8592e291..91a4a2f4 100644 --- a/lib/jwa/ecdh/kw.js +++ b/lib/jwa/ecdh/kw.js @@ -3,20 +3,20 @@ const { strict: assert } = require('assert') const ECKey = require('../../jwk/key/ec') const derive = require('./derive') -const wrapKey = (kw, derive, key, payload) => { +const wrapKey = (wrap, derive, key, payload) => { const epk = ECKey.generateSync(key.crv) const derivedKey = derive(epk, key, payload) - const result = kw({ keyObject: derivedKey }, payload) + const result = wrap({ keyObject: derivedKey }, payload) result.header = { epk: { kty: 'EC', crv: key.crv, x: epk.x, y: epk.y } } return result } -const unwrapKey = (kw, derive, key, payload, { apu, apv, epk }) => { +const unwrapKey = (unwrap, derive, key, payload, { apu, apv, epk }) => { const derivedKey = derive(key, epk, { apu, apv }) - return kw({ keyObject: derivedKey }, payload) + return unwrap({ keyObject: derivedKey }, payload) } module.exports = (JWA) => { diff --git a/lib/jwa/index.js b/lib/jwa/index.js index 66910cee..1c4ae83d 100644 --- a/lib/jwa/index.js +++ b/lib/jwa/index.js @@ -1,4 +1,4 @@ -const { TODO } = require('../errors') +const { JWKKeySupport, JOSEAlgNotSupported } = require('../errors') const JWA = { sign: new Map(), @@ -26,65 +26,40 @@ require('./pbes2')(JWA) require('./ecdh/kw')(JWA) require('./ecdh/dir')(JWA) -module.exports = { - sign: (alg, key, payload) => { - if (!JWA.sign.has(alg)) { - throw new TODO(`sign alg ${alg} not implemented`) - } - if (!key.algorithms('sign').has(alg)) { - throw new TODO(`the key does not support ${alg} sign algorithm`) +const check = (key, op, alg) => { + if (JWA[op].has(alg) || (alg === 'dir' && op === 'unwrapKey')) { + if (!key.algorithms(op).has(alg)) { + throw new JWKKeySupport(`the key does not support ${alg} ${op} algorithm`) } + } else { + throw new JOSEAlgNotSupported(`${op} alg ${alg} not implemented`) + } +} +module.exports = { + check, + sign: (alg, key, payload) => { + check(key, 'sign', alg) return JWA.sign.get(alg)(key, payload) }, verify: (alg, key, payload, signature) => { - if (!JWA.verify.has(alg)) { - throw new TODO(`verify alg ${alg} not implemented`) - } - if (!key.algorithms('verify').has(alg)) { - throw new TODO(`the key does not support ${alg} verify algorithm`) - } - + check(key, 'verify', alg) return JWA.verify.get(alg)(key, payload, signature) }, wrapKey: (alg, key, payload, opts) => { - if (!JWA.wrapKey.has(alg)) { - throw new TODO(`wrapKey alg ${alg} not implemented`) - } - if (!key.algorithms('wrapKey').has(alg)) { - throw new TODO(`the key does not support ${alg} wrapKey algorithm`) - } - + check(key, 'wrapKey', alg) return JWA.wrapKey.get(alg)(key, payload, opts) }, unwrapKey: (alg, key, payload, opts) => { - if (!JWA.unwrapKey.has(alg)) { - throw new TODO(`unwrapKey alg ${alg} not implemented`) - } - if (!key.algorithms('unwrapKey').has(alg)) { - throw new TODO(`the key does not support ${alg} unwrapKey algorithm`) - } - + check(key, 'unwrapKey', alg) return JWA.unwrapKey.get(alg)(key, payload, opts) }, encrypt: (alg, key, cleartext, opts) => { - if (!JWA.encrypt.has(alg)) { - throw new TODO(`encrypt alg ${alg} not implemented`) - } - if (!key.algorithms('encrypt').has(alg)) { - throw new TODO(`the key does not support ${alg} encrypt algorithm`) - } - + check(key, 'encrypt', alg) return JWA.encrypt.get(alg)(key, cleartext, opts) }, decrypt: (alg, key, ciphertext, opts) => { - if (!JWA.decrypt.has(alg)) { - throw new TODO(`decrypt alg ${alg} not implemented`) - } - if (!key.algorithms('decrypt').has(alg)) { - throw new TODO(`the key does not support ${alg} decrypt algorithm`) - } - + check(key, 'decrypt', alg) return JWA.decrypt.get(alg)(key, ciphertext, opts) } } diff --git a/lib/jwa/pbes2.js b/lib/jwa/pbes2.js index c8c9807b..d09ca597 100644 --- a/lib/jwa/pbes2.js +++ b/lib/jwa/pbes2.js @@ -4,40 +4,37 @@ const { pbkdf2Sync: pbkdf2, randomBytes } = require('crypto') const base64url = require('../help/base64url') const SALT_LENGTH = 16 -const ITERATIONS = 8192 const NULL_BUFFER = Buffer.alloc(1, 0) -const concatSalt = (alg, ps2) => { +const concatSalt = (alg, p2s) => { return Buffer.concat([ Buffer.from(alg, 'utf8'), NULL_BUFFER, - ps2 + p2s ]) } -// TODO: -// Note that if password-based encryption is used for multiple -// recipients, it is expected that each recipient use different values -// for the PBES2 parameters "p2s" and "p2c". - -const wrapKey = (keylen, sha, concat, kw, { keyObject }, payload) => { +const wrapKey = (keylen, sha, concat, wrap, { keyObject }, payload) => { + // Note that if password-based encryption is used for multiple + // recipients, it is expected that each recipient use different values + // for the PBES2 parameters "p2s" and "p2c". + // here we generate p2c between 2048 and 4096 and random p2s + const p2c = Math.floor((Math.random() * 2049) + 2048) const p2s = randomBytes(SALT_LENGTH) - const p2c = ITERATIONS const salt = concat(p2s) const derivedKey = pbkdf2(keyObject.export(), salt, p2c, keylen, sha) - const result = kw({ keyObject: derivedKey }, payload) + const result = wrap({ keyObject: derivedKey }, payload) result.header = { p2c, p2s: base64url.encode(p2s) } return result } -const unwrapKey = (keylen, sha, concat, kw, { keyObject }, payload, { p2c, p2s }) => { - // TODO: validate p2c, p2s +const unwrapKey = (keylen, sha, concat, unwrap, { keyObject }, payload, { p2c, p2s }) => { const salt = concat(p2s) const derivedKey = pbkdf2(keyObject.export(), salt, p2c, keylen, sha) - return kw({ keyObject: derivedKey }, payload) + return unwrap({ keyObject: derivedKey }, payload) } module.exports = (JWA) => { diff --git a/lib/jwe/decrypt.js b/lib/jwe/decrypt.js index d42ba5c5..3c58ff1d 100644 --- a/lib/jwe/decrypt.js +++ b/lib/jwe/decrypt.js @@ -1,9 +1,10 @@ const { createSecretKey } = require('crypto') +const generateCEK = require('./generate_cek') const base64url = require('../help/base64url') const validateHeaders = require('./validate_headers') const { detect: resolveSerialization } = require('./serializers') -const { TODO } = require('../errors') -const { decrypt, unwrapKey } = require('../jwa') +const { JWEDecryptionFailed } = require('../errors') +const { check, decrypt, unwrapKey } = require('../jwa') const JWK = require('../jwk') const SINGLE_RECIPIENT = new Set(['compact', 'flattened']) @@ -41,20 +42,13 @@ const headerParams = (prot = {}, unprotected = {}, header = {}) => { */ // TODO: option to return everything not just the payload // TODO: add kid magic -const jweDecrypt = (skipValidateHeaders, serialization, jwe, key) => { +const jweDecrypt = (skipValidateHeaders = false, serialization, jwe, key) => { if (!serialization) { serialization = resolveSerialization(jwe) } let alg, ciphertext, enc, encryptedKey, iv, opts, prot, tag, unprotected, cek, aad, header - // TODO: To mitigate the attacks described in RFC 3218 [RFC3218], the - // recipient MUST NOT distinguish between format, padding, and length - // errors of encrypted keys. It is strongly recommended, in the event - // of receiving an improperly formatted key, that the recipient - // substitute a randomly generated CEK and proceed to the next step, to - // mitigate timing attacks. - if (SINGLE_RECIPIENT.has(serialization)) { if (serialization === 'compact') { // compact serialization format ([prot, encryptedKey, iv, ciphertext, tag] = jwe.split('.')) @@ -72,14 +66,30 @@ const jweDecrypt = (skipValidateHeaders, serialization, jwe, key) => { ;({ alg, enc } = opts) if (alg === 'dir') { - // TODO: validate its a secret - cek = JWK.importKey(key, { alg: enc, use: 'enc' }) - } else if (alg === 'ECDH-ES') { - const unwrapped = unwrapKey(alg, key, undefined, opts) - cek = JWK.importKey(createSecretKey(unwrapped), { alg: enc, use: 'enc' }) + check(key, 'decrypt', enc) } else { - const unwrapped = unwrapKey(alg, key, base64url.decodeToBuffer(encryptedKey), opts) - cek = JWK.importKey(createSecretKey(unwrapped), { alg: enc, use: 'enc' }) + check(key, 'unwrapKey', alg) + } + + try { + if (alg === 'dir') { + // TODO: validate its a secret + cek = JWK.importKey(key, { alg: enc, use: 'enc' }) + } else if (alg === 'ECDH-ES') { + const unwrapped = unwrapKey(alg, key, undefined, opts) + cek = JWK.importKey(createSecretKey(unwrapped), { alg: enc, use: 'enc' }) + } else { + const unwrapped = unwrapKey(alg, key, base64url.decodeToBuffer(encryptedKey), opts) + cek = JWK.importKey(createSecretKey(unwrapped), { alg: enc, use: 'enc' }) + } + } catch (err) { + // To mitigate the attacks described in RFC 3218, the + // recipient MUST NOT distinguish between format, padding, and length + // errors of encrypted keys. It is strongly recommended, in the event + // of receiving an improperly formatted key, that the recipient + // substitute a randomly generated CEK and proceed to the next step, to + // mitigate timing attacks. + cek = generateCEK(enc) } if (aad) { @@ -104,9 +114,8 @@ const jweDecrypt = (skipValidateHeaders, serialization, jwe, key) => { validateHeaders(jwe.protected, jwe.unprotected, jwe.recipients.map(({ header }) => ({ header }))) - const { recipients, ...root } = jwe - // general serialization format + const { recipients, ...root } = jwe for (const recipient of recipients) { try { return jweDecrypt(true, 'flattened', { ...root, ...recipient }, key) @@ -115,7 +124,7 @@ const jweDecrypt = (skipValidateHeaders, serialization, jwe, key) => { } } - throw new TODO('decryption failed') + throw new JWEDecryptionFailed() } module.exports = jweDecrypt.bind(undefined, false, undefined) diff --git a/lib/jwe/encrypt.js b/lib/jwe/encrypt.js index 09ad33c5..3545a594 100644 --- a/lib/jwe/encrypt.js +++ b/lib/jwe/encrypt.js @@ -1,5 +1,5 @@ const serializers = require('./serializers') -const { TODO } = require('../errors') +const { JWENoRecipients } = require('../errors') const Key = require('../jwk/key/base') const { createSecretKey } = require('crypto') @@ -7,11 +7,11 @@ const generateCEK = require('./generate_cek') const generateIV = require('../help/generate_iv') const base64url = require('../help/base64url') -const { wrapKey, encrypt } = require('../jwa') +const { check, wrapKey, encrypt } = require('../jwa') const OctKey = require('../jwk/key/oct') const validateHeaders = require('./validate_headers') -function process (encryptObj, recipient) { +const encryptForRecipient = (encryptObj, recipient) => { const { protectedHeader, unprotectedHeader } = encryptObj const jweHeader = { @@ -23,6 +23,12 @@ function process (encryptObj, recipient) { const { alg, enc } = jweHeader + if (alg === 'dir') { + check(key, 'encrypt', enc) + } else { + check(key, 'wrapKey', alg) + } + let wrapped let generatedHeader let direct @@ -76,29 +82,23 @@ class Encrypt { * @public */ encrypt (serialization) { - if (typeof serialization !== 'string') { - throw new TypeError('TODO') - } if (!this.recipients.length) { - throw new TODO('missing recipients') + throw new JWENoRecipients('missing recipients') } const serializer = serializers[serialization] if (!serializer) { - throw new TODO('invalid serialization') + throw new TypeError('serialization must be one of "compact", "flattened", "general"') } serializer.validate(this, this.recipients) const enc = validateHeaders(this.protectedHeader, this.unprotectedHeader, this.recipients) - const final = {} - this.cek = generateCEK(enc) - this.recipients.forEach(process.bind(undefined, this)) + this.recipients.forEach(encryptForRecipient.bind(undefined, this)) const iv = generateIV(enc) - final.iv = base64url.encode(iv) if (this.recipients.length === 1 && this.recipients[0].generatedHeader) { @@ -120,7 +120,14 @@ class Encrypt { aad = Buffer.from(final.protected || '') } - const { ciphertext, tag } = encrypt(enc, this.cek, this.cleartext, { iv, aad }) + let cleartext + if (!Buffer.isBuffer(this.cleartext) && !(typeof this.cleartext === 'string')) { + cleartext = base64url.JSON.encode(this.cleartext) + } else { + cleartext = this.cleartext + } + + const { ciphertext, tag } = encrypt(enc, this.cek, cleartext, { iv, aad }) final.tag = base64url.encode(tag) final.ciphertext = base64url.encode(ciphertext) diff --git a/lib/jwe/serializers.js b/lib/jwe/serializers.js index 32dcfcc3..9de72db8 100644 --- a/lib/jwe/serializers.js +++ b/lib/jwe/serializers.js @@ -1,6 +1,6 @@ const { TODO } = require('../errors') -function compactSerializer (enc, [recipient]) { +const compactSerializer = (enc, [recipient]) => { return `${enc.protected}.${recipient.encrypted_key}.${enc.iv}.${enc.ciphertext}.${enc.tag}` } compactSerializer.validate = (jwe, recipients) => { @@ -9,7 +9,7 @@ compactSerializer.validate = (jwe, recipients) => { } } -function flattenedSerializer (enc, [recipient]) { +const flattenedSerializer = (enc, [recipient]) => { const { header, encrypted_key: encryptedKey } = recipient return { @@ -29,7 +29,7 @@ flattenedSerializer.validate = (jwe, { length }) => { } } -function generalSerializer (enc, recipients) { +const generalSerializer = (enc, recipients) => { const result = { ...(enc.protected ? { protected: enc.protected } : undefined), ...(enc.unprotected ? { unprotected: enc.unprotected } : undefined), @@ -57,7 +57,7 @@ function generalSerializer (enc, recipients) { } generalSerializer.validate = () => {} -function detect (input) { +const detect = (input) => { if (typeof input === 'string') { return 'compact' } diff --git a/lib/jwk/generate.js b/lib/jwk/generate.js index 6709113c..4a9d886c 100644 --- a/lib/jwk/generate.js +++ b/lib/jwk/generate.js @@ -2,7 +2,7 @@ const RSAKey = require('./key/rsa') const ECKey = require('./key/ec') const OctKey = require('./key/oct') -function generate (kty, ...args) { +const generate = (kty, ...args) => { switch (kty) { case 'rsa': case 'RSA': @@ -17,7 +17,7 @@ function generate (kty, ...args) { } } -function generateSync (kty, ...args) { +const generateSync = (kty, ...args) => { switch (kty) { case 'rsa': case 'RSA': diff --git a/lib/jwk/import.js b/lib/jwk/import.js index 96c01884..f1d00631 100644 --- a/lib/jwk/import.js +++ b/lib/jwk/import.js @@ -1,39 +1,45 @@ const { createPublicKey, createPrivateKey, createSecretKey } = require('crypto') -const Key = require('./key/base') 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 { JWKImportFailed } = require('../errors') const { PrivateKeyObject, PublicKeyObject, SecretKeyObject } = require('../help/key_objects') const { jwkToPem } = require('../help/key_utils') -function importKey (key, opts) { +const importable = new Set(['string', 'buffer', 'object']) +const parametersTypes = new Set(['object', 'undefined']) + +const mergedParameters = (target = {}, source = {}) => { + return Object.assign({}, { alg: source.alg, use: source.use }, target) +} + +const importKey = (key, parameters) => { let privateKey, publicKey, secret - if (key instanceof Key) { - // TODO: carry over opts from the Key instance - if (key.private) { - privateKey = key.keyObject - } else if (key.public) { - publicKey = key.keyObject - } else { // secret - secret = key.keyObject - } - } else if (key instanceof PrivateKeyObject) { + + if (!importable.has(typeof key)) { + throw new TypeError('key argument must be a string, buffer or an object') + } + + if (!parametersTypes.has(typeof parameters)) { + throw new TypeError('parameters argument must be a string, buffer or an object') + } + + if (key instanceof PrivateKeyObject) { privateKey = key } else if (key instanceof PublicKeyObject) { publicKey = key } else if (key instanceof SecretKeyObject) { - secret = createSecretKey(key.export()) - } else if (key && key.kty === 'oct') { // symmetric key - // TODO: carry over opts from the JWK + secret = key + } else if (key.kty === 'oct') { // symmetric key + // TODO: carry over parameters from the JWK secret = createSecretKey(base64url.decodeToBuffer(key.k)) - } else if (key && key.kty) { // assume JWK formatted asymmetric key + parameters = mergedParameters(parameters, key) + } else if (key.kty) { // assume JWK formatted asymmetric key let parsedJWK try { - // TODO: carry over opts from the JWK parsedJWK = jwkToPem(key) } catch (err) {} if (parsedJWK && key.d) { @@ -41,6 +47,7 @@ function importKey (key, opts) { } else if (parsedJWK) { publicKey = createPublicKey(parsedJWK) } + parameters = mergedParameters(parameters, key) } else { // | | passed to crypto.createPrivateKey or crypto.createPublicKey or passed to crypto.createSecretKey try { privateKey = createPrivateKey(key) @@ -54,18 +61,18 @@ function importKey (key, opts) { } if (!privateKey && !publicKey && !secret) { - throw new TODO('import failed') + throw new JWKImportFailed('import failed') } const keyObject = privateKey || publicKey || secret switch (keyObject.asymmetricKeyType) { case 'rsa': - return new RSAKey(keyObject, opts) + return new RSAKey(keyObject, parameters) case 'ec': - return new ECKey(keyObject, opts) + return new ECKey(keyObject, parameters) default: - return new OctKey(keyObject, opts) + return new OctKey(keyObject, parameters) } } diff --git a/lib/jwk/key/base.js b/lib/jwk/key/base.js index bfd461a3..7e8619e0 100644 --- a/lib/jwk/key/base.js +++ b/lib/jwk/key/base.js @@ -20,7 +20,7 @@ const props = { const USES = new Set(['sig', 'enc']) -function defineLazyComponents (obj) { +const defineLazyComponents = (obj) => { Object.defineProperties(obj, props[obj.keyObject.asymmetricKeyType.toUpperCase()][obj.keyObject.type].reduce((acc, component) => { acc[component] = { get () { diff --git a/lib/jwk/key/oct.js b/lib/jwk/key/oct.js index 79cdabbf..a7bfe5e9 100644 --- a/lib/jwk/key/oct.js +++ b/lib/jwk/key/oct.js @@ -18,12 +18,12 @@ const ENC_LEN = new Set([ ]) const ENC_ALGS = new Set([ - 'A128GCM', - 'A192GCM', - 'A256GCM', 'A128CBC-HS256', + 'A128GCM', 'A192CBC-HS384', - 'A256CBC-HS512' + 'A192GCM', + 'A256CBC-HS512', + 'A256GCM' ]) const WRAP_LEN = new Set([ @@ -84,13 +84,17 @@ class OctKey extends Key { return new Set() } - const algs = new Set(['dir', 'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW']) + const algs = new Set() if (WRAP_LEN.has(this.length)) { algs.add(`A${this.length}KW`) algs.add(`A${this.length}GCMKW`) } + ['PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW'].forEach(Set.prototype.add.bind(algs)) + + algs.add('dir') + return algs case undefined: return new Set([ diff --git a/lib/jwk/key/rsa.js b/lib/jwk/key/rsa.js index 8c6b4ad8..2d83327f 100644 --- a/lib/jwk/key/rsa.js +++ b/lib/jwk/key/rsa.js @@ -6,12 +6,12 @@ const generateKeyPair = promisify(async) const Key = require('./base') const SIG_ALGS = new Set([ - 'RS256', - 'RS384', - 'RS512', 'PS256', + 'RS256', 'PS384', - 'PS512' + 'RS384', + 'PS512', + 'RS512' ]) const WRAP_ALGS = new Set([ diff --git a/lib/jws/serializers.js b/lib/jws/serializers.js index ee69f058..217fd438 100644 --- a/lib/jws/serializers.js +++ b/lib/jws/serializers.js @@ -1,6 +1,6 @@ const { TODO } = require('../errors') -function compactSerializer (payload, [recipient]) { +const compactSerializer = (payload, [recipient]) => { return `${recipient.protected}.${payload}.${recipient.signature}` } compactSerializer.validate = (jws, recipients) => { @@ -9,7 +9,7 @@ compactSerializer.validate = (jws, recipients) => { } } -function flattenedSerializer (payload, [recipient]) { +const flattenedSerializer = (payload, [recipient]) => { const { header, signature, protected: prot } = recipient return { @@ -25,7 +25,7 @@ flattenedSerializer.validate = (jws, { length }) => { } } -function generalSerializer (payload, recipients) { +const generalSerializer = (payload, recipients) => { return { payload, signatures: recipients.map(({ header, signature, protected: prot }) => { @@ -39,7 +39,7 @@ function generalSerializer (payload, recipients) { } generalSerializer.validate = () => {} -function detect (input) { +const detect = (input) => { if (typeof input === 'string') { return 'compact' } diff --git a/lib/jws/sign.js b/lib/jws/sign.js index 728bed1c..f09a6db3 100644 --- a/lib/jws/sign.js +++ b/lib/jws/sign.js @@ -2,11 +2,11 @@ const base64url = require('../help/base64url') const serializers = require('./serializers') const Key = require('../jwk/key/base') -const { JWSInvalidHeader, JWSMissingAlg, TODO } = require('../errors') -const { sign } = require('../jwa') +const { JWSInvalidHeader, JWSNoRecipients } = require('../errors') +const { check, sign } = require('../jwa') const isDisjoint = require('../help/is_disjoint') -function process (payload, recipient) { +const signForRecipient = (payload, recipient) => { const { key, protectedHeader, unprotectedHeader } = recipient const joseHeader = { @@ -18,9 +18,12 @@ function process (payload, recipient) { throw new JWSInvalidHeader('JWS Protected and JWS Unprotected Header Parameter names must be disjoint') } - const { alg } = { ...joseHeader.protected, ...joseHeader.unprotected } + const alg = joseHeader.protected.alg || joseHeader.unprotected.alg + + check(key, 'verify', alg) + if (!alg) { - throw new JWSMissingAlg('every JOSE header must contain an "alg" parameter') + throw new JWSInvalidHeader('every JOSE header must contain an "alg" parameter') } if (Object.keys(joseHeader.unprotected).length) { @@ -52,19 +55,16 @@ class Sign { * @public */ sign (serialization) { - if (typeof serialization !== 'string') { - throw new TypeError('TODO') - } - if (!this.recipients.length) { - throw new TODO('missing recipients') - } - const serializer = serializers[serialization] if (!serializer) { - throw new TODO('invalid serialization') + throw new TypeError('serialization must be one of "compact", "flattened", "general"') } serializer.validate(this, this.recipients) + if (!this.recipients.length) { + throw new JWSNoRecipients('missing recipients') + } + let payload if (!Buffer.isBuffer(this.payload) && !(typeof this.payload === 'string')) { payload = base64url.JSON.encode(this.payload) @@ -72,7 +72,7 @@ class Sign { payload = base64url.encode(this.payload) } - this.recipients.forEach(process.bind(undefined, payload)) + this.recipients.forEach(signForRecipient.bind(undefined, payload)) return serializer(payload, this.recipients) } diff --git a/lib/jws/verify.js b/lib/jws/verify.js index 37e037e7..f61381d4 100644 --- a/lib/jws/verify.js +++ b/lib/jws/verify.js @@ -1,7 +1,8 @@ const base64url = require('../help/base64url') const { detect: resolveSerialization } = require('./serializers') -const { JWSVerificationFailed, JWSMissingAlg } = require('../errors') -const { verify } = require('../jwa') +const { JWSVerificationFailed, JWSInvalidHeader } = require('../errors') +const { check, verify } = require('../jwa') +const isDisjoint = require('../help/is_disjoint') const SINGLE_RECIPIENT = new Set(['compact', 'flattened']) @@ -29,22 +30,17 @@ const jwsVerify = (serialization, jws, key) => { ({ protected: prot, payload, signature, header } = jws) } - // TODO: alg may also be unprotected but must it must be provided disjoint - const { alg: protectedAlg } = prot ? base64url.JSON.decode(prot) : {} - const { alg: unprotectedAlg } = header || {} - - if (!protectedAlg ^ !unprotectedAlg) { - alg = unprotectedAlg || protectedAlg - } else { - throw new JWSMissingAlg('missing alg') + const parsedProt = prot ? base64url.JSON.decode(prot) : {} + if (!isDisjoint(parsedProt, header)) { + throw new JWSInvalidHeader('JWS Protected and JWS Unprotected Header Parameter names must be disjoint') } - try { - if (!verify(alg, key, [prot, payload].join('.'), base64url.decodeToBuffer(signature))) { - throw new JWSVerificationFailed('verification failed') - } - } catch (err) { - throw new JWSVerificationFailed('verification failed') + alg = parsedProt.alg || header.alg + + check(key, 'verify', alg) + + if (!verify(alg, key, [prot, payload].join('.'), base64url.decodeToBuffer(signature))) { + throw new JWSVerificationFailed() } return base64url.JSON.decode.try(payload) @@ -52,8 +48,6 @@ const jwsVerify = (serialization, jws, key) => { // general serialization format const { signatures, ...root } = jws - - // general serialization format for (const recipient of signatures) { try { return jwsVerify('flattened', { ...root, ...recipient }, key) @@ -62,7 +56,7 @@ const jwsVerify = (serialization, jws, key) => { } } - throw new JWSVerificationFailed('verification failed') + throw new JWSVerificationFailed() } module.exports = jwsVerify.bind(undefined, undefined) diff --git a/package.json b/package.json index 10b213a3..6bf39acd 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@commitlint/config-conventional": "^7.5.0", "ava": "^1.2.1", "husky": "^1.3.1", - "nyc": "^13.2.0", + "nyc": "^13.3.0", "standard": "^12.0.1" }, "engines": { diff --git a/test/cookbook/verifiers.js b/test/cookbook/verifiers.js index 30e7c9b9..1434e426 100644 --- a/test/cookbook/verifiers.js +++ b/test/cookbook/verifiers.js @@ -1,5 +1,5 @@ const base64url = require('../../lib/help/base64url') -const withoutRandom = ({ p2s, epk, iv, tag, ...rest } = {}) => rest +const withoutRandom = ({ p2s, p2c, epk, iv, tag, ...rest } = {}) => rest const decodeWithoutRandom = (input) => { return withoutRandom(base64url.JSON.decode(input)) } diff --git a/test/help/ecdsa_signatures.test.js b/test/help/ecdsa_signatures.test.js index a8168975..9b1bc91e 100644 --- a/test/help/ecdsa_signatures.test.js +++ b/test/help/ecdsa_signatures.test.js @@ -11,27 +11,15 @@ const TAG_SEQ = (0x10 | PRIMITIVE_BIT) | (CLASS_UNIVERSAL << 6) const TAG_INT = 0x02 | (CLASS_UNIVERSAL << 6) test('.derToJose no signature', t => { - function fn () { - return derToJose() - } - - t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) + t.throws(() => derToJose(), { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) }) test('.derToJose non buffer or base64 signature', t => { - function fn () { - return derToJose(123) - } - - t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) + t.throws(() => derToJose(123), { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) }) test('.derToJose unknown algorithm', t => { - function fn () { - return derToJose(decodeToBuffer('Zm9vLmJhci5iYXo'), 'foobar') - } - - t.throws(fn, { instanceOf: Error, message: 'Unknown algorithm "foobar"' }) + t.throws(() => derToJose(decodeToBuffer('Zm9vLmJhci5iYXo'), 'foobar'), { instanceOf: Error, message: 'Unknown algorithm "foobar"' }) }) Object.entries({ @@ -43,11 +31,9 @@ Object.entries({ const input = Buffer.alloc(10) input[0] = TAG_SEQ + 1 // not seq - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /expected "seq"/ }) + }, { instanceOf: Error, message: /expected "seq"/ }) }) test(`.derToJose seq length exceeding input (${alg})`, t => { @@ -55,11 +41,9 @@ Object.entries({ input[0] = TAG_SEQ input[1] = 10 - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /length/ }) + }, { instanceOf: Error, message: /length/ }) }) test(`.derToJose r is not marked as int (${alg})`, t => { @@ -68,11 +52,9 @@ Object.entries({ input[1] = 8 input[2] = TAG_INT + 1 // not int - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /expected "int".+"r"/ }) + }, { instanceOf: Error, message: /expected "int".+"r"/ }) }) test(`.derToJose r length exceeds available input (${alg})`, t => { @@ -82,11 +64,9 @@ Object.entries({ input[2] = TAG_INT input[3] = 5 - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /"r".+length/ }) + }, { instanceOf: Error, message: /"r".+length/ }) }) test(`.derToJose r length exceeds sensical param length (${alg})`, t => { @@ -96,11 +76,9 @@ Object.entries({ input[2] = TAG_INT input[3] = len + 2 - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /"r".+length.+acceptable/ }) + }, { instanceOf: Error, message: /"r".+length.+acceptable/ }) }) test(`.derToJose s is not marked as int (${alg})`, t => { @@ -113,11 +91,9 @@ Object.entries({ input[5] = 0 input[6] = TAG_INT + 1 // not int - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /expected "int".+"s"/ }) + }, { instanceOf: Error, message: /expected "int".+"s"/ }) }) test(`.derToJose s length exceeds available input (${alg})`, t => { @@ -131,11 +107,9 @@ Object.entries({ input[6] = TAG_INT input[7] = 3 - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /"s".+length/ }) + }, { instanceOf: Error, message: /"s".+length/ }) }) test(`.derToJose s length does not consume available input (${alg})`, t => { @@ -149,11 +123,9 @@ Object.entries({ input[6] = TAG_INT input[7] = 1 - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /"s".+length/ }) + }, { instanceOf: Error, message: /"s".+length/ }) }) test(`.derToJose s length exceeds sensical param length (${alg})`, t => { @@ -167,60 +139,34 @@ Object.entries({ input[6] = TAG_INT input[7] = len + 2 - function fn () { + t.throws(() => { derToJose(input, alg) - } - - t.throws(fn, { instanceOf: Error, message: /"s".+length.+acceptable/ }) + }, { instanceOf: Error, message: /"s".+length.+acceptable/ }) }) }) test('.joseToDer no signature', t => { - function fn () { - return joseToDer() - } - - t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) + t.throws(() => joseToDer(), { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) }) test('.joseToDer non buffer or base64 signature', t => { - function fn () { - return joseToDer(123) - } - - t.throws(fn, { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) + t.throws(() => joseToDer(123), { instanceOf: TypeError, message: 'ECDSA signature must be a Buffer' }) }) test('.joseToDer unknown algorithm', t => { - function fn () { - return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo='), 'foobar') - } - - t.throws(fn, { instanceOf: Error, message: /"foobar"/ }) + t.throws(() => joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo='), 'foobar'), { instanceOf: Error, message: /"foobar"/ }) }) test('.joseToDer incorrect signature length (ES256)', t => { - function fn () { - return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES256') - } - - t.throws(fn, { instanceOf: Error, message: /"64"/ }) + t.throws(() => joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES256'), { instanceOf: Error, message: /"64"/ }) }) test('.joseToDer incorrect signature length (ES384)', t => { - function fn () { - return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES384') - } - - t.throws(fn, { instanceOf: Error, message: /"96"/ }) + t.throws(() => joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES384'), { instanceOf: Error, message: /"96"/ }) }) test('.joseToDer incorrect signature length (ES512)', t => { - function fn () { - return joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES512') - } - - t.throws(fn, { instanceOf: Error, message: '"ES512" signatures must be "132" bytes, saw "11"' }) + t.throws(() => joseToDer(decodeToBuffer('Zm9vLmJhci5iYXo'), 'ES512'), { instanceOf: Error, message: '"ES512" signatures must be "132" bytes, saw "11"' }) }) test('ES256 should jose -> der -> jose', t => { diff --git a/test/jwk/oct.test.js b/test/jwk/oct.test.js index 52c7b3c5..86036324 100644 --- a/test/jwk/oct.test.js +++ b/test/jwk/oct.test.js @@ -79,9 +79,3 @@ test('no verify support when `use` is "enc"', t => { t.is(result.constructor, Set) t.deepEqual([...result], []) }) - -test.todo('algorithms() no arg') -test.todo('algorithms("encrypt")') -test.todo('algorithms("decrypt")') -test.todo('algorithms("wrapKey")') -test.todo('algorithms("unwrapKey")') diff --git a/test/jwk/rsa.test.js b/test/jwk/rsa.test.js index dca282ad..062c76c0 100644 --- a/test/jwk/rsa.test.js +++ b/test/jwk/rsa.test.js @@ -33,7 +33,7 @@ test(`RSA key .algorithms invalid operation`, t => { 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']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA1_5']) }) test('RSA Private key algorithms (no operation, w/ alg)', t => { @@ -46,27 +46,27 @@ test(`RSA key .algorithms invalid operation`, t => { test(`RSA Private key supports sign alg (no use)`, t => { const result = key.algorithms('sign') t.is(result.constructor, Set) - t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']) }) test(`RSA Private key supports verify alg (no use)`, t => { const result = key.algorithms('verify') t.is(result.constructor, Set) - t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']) }) test(`RSA Private key supports sign alg when \`use\` is "sig")`, t => { const sigKey = new RSAKey(keyObject, { use: 'sig' }) const result = sigKey.algorithms('sign') t.is(result.constructor, Set) - t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']) }) test(`RSA Private key supports verify alg when \`use\` is "sig")`, t => { const sigKey = new RSAKey(keyObject, { use: 'sig' }) const result = sigKey.algorithms('verify') t.is(result.constructor, Set) - t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']) }) test(`RSA Private key supports single sign alg when \`alg\` is set)`, t => { @@ -159,7 +159,7 @@ test(`RSA key .algorithms invalid operation`, t => { 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']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA1_5']) }) test('RSA EC Public key algorithms (no operation, w/ alg)', t => { @@ -178,7 +178,7 @@ test(`RSA key .algorithms invalid operation`, t => { test(`RSA Public key supports verify alg (no use)`, t => { const result = key.algorithms('verify') t.is(result.constructor, Set) - t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']) }) test(`RSA Public key cannot sign even when \`use\` is "sig")`, t => { @@ -192,7 +192,7 @@ test(`RSA key .algorithms invalid operation`, t => { const sigKey = new RSAKey(keyObject, { use: 'sig' }) const result = sigKey.algorithms('verify') t.is(result.constructor, Set) - t.deepEqual([...result], ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']) + t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']) }) test(`RSA Public key cannot sign even when \`alg\` is set)`, t => {