mirror of
https://github.com/danbulant/jose
synced 2026-05-23 06:18:58 +00:00
refactor: cleanup, TODO chores
This commit is contained in:
parent
775ea638b6
commit
cc89d4e02b
25 changed files with 296 additions and 318 deletions
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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 <Object>
|
||||
// TODO: carry over opts from the JWK
|
||||
secret = key
|
||||
} else if (key.kty === 'oct') { // symmetric key <Object>
|
||||
// TODO: carry over parameters from the JWK
|
||||
secret = createSecretKey(base64url.decodeToBuffer(key.k))
|
||||
} else if (key && key.kty) { // assume JWK formatted asymmetric key <Object>
|
||||
parameters = mergedParameters(parameters, key)
|
||||
} else if (key.kty) { // assume JWK formatted asymmetric key <Object>
|
||||
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 { // <Object> | <string> | <Buffer> passed to crypto.createPrivateKey or crypto.createPublicKey or <Buffer> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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")')
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue