diff --git a/.codecov.yml b/.codecov.yml
index b288422a..e6779cb5 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -1,8 +1,8 @@
coverage:
status:
- project: no
- patch: yes
- changes: yes
+ project: off
+ patch: off
+ changes: off
comment:
layout: diff
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 346cb9fb..1c0da784 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -20,8 +20,11 @@ jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
+ fail-fast: false
matrix:
node-version:
+ - 10.13.0
+ - 10
- 12.0.0
- 12
os:
@@ -58,6 +61,7 @@ jobs:
test-electron:
runs-on: ${{ matrix.os }}
strategy:
+ fail-fast: false
matrix:
electron-version:
- 6.0.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11be591e..485720b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -155,7 +155,7 @@ its original on-by-default form - v1.1.0 and v1.2.0
wrapKey/unwrapKey/deriveKey returns
* keystore.all and keystore.get `operation` option was
removed, `key_ops: string[]` supersedes it
-* node.js minimal version is now v12.0.0 due to its
+* Node.js minimal version is now v12.0.0 due to its
added EdDSA support (crypto.sign, crypto.verify and eddsa key objects)
@@ -323,7 +323,7 @@ Initial release
| AES | ✓ | A128KW, A192KW, A256KW |
| AES GCM | ✓ | A128GCMKW, A192GCMKW, A256GCMKW |
| Direct Key Agreement | ✓ | dir |
-| RSAES OAEP | ✓* | RSA-OAEP (*RSA-OAEP-256 is not supported due to its lack of support in Node.JS) |
+| RSAES OAEP | ✓* | RSA-OAEP (*RSA-OAEP-256 is not supported due to its lack of support in Node.js) |
| RSAES-PKCS1-v1_5 | ✓ | RSA1_5 |
| PBES2 | ✓ | PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW |
| ECDH-ES | ✓ | ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW |
diff --git a/README.md b/README.md
index 5241cd8b..27615cad 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,9 @@ Legend:
| JARM - [JWT Secured Authorization Response Mode for OAuth 2.0][draft-jarm] | ◯ ||
Notes
-- RSA-OAEP-256 is only supported when Node.js >= 12.9.0 runtime is detected
+- RSA-OAEP-256 JWE algorithm is only supported when Node.js >= 12.9.0 runtime is detected
+- Importing X.509 certificates and handling `x5c` is only supported when Node.js >= 12.0.0 runtime is detected
+- OKP keys are only supported when Node.js >= 12.0.0 runtime is detected
- See [#electron-support](#electron-support) for electron exceptions
---
@@ -126,7 +128,8 @@ If you or your business use @panva/jose, please consider becoming a [sponsor][su
## Usage
-For its improvements in the crypto module ⚠️ the minimal Node.js version required is **v12.0.0** ⚠️
+For the best performance Node.js version **>=12.0.0** is recommended, but **^10.13.0** lts/dubnium
+is also supported.
Installing @panva/jose
@@ -318,7 +321,7 @@ Electron v6.x runtime is supported to the extent of the crypto engine BoringSSL
standard Node.js OpenSSL. The following is disabled in Electron runtime because of its lack of
[support](https://github.com/panva/jose/blob/master/test/electron/electron.test.js).
-- JWE `A128KW`, `A192KW` and `A256KW` algs are not available, this also means that other JWAs
+- JWE `A128KW`, `A192KW` and `A256KW` algorithms are not available, this also means that other JWAs
depending on those are not working, those are `ECDH-ES+A128KW`, `ECDH-ES+A192KW`,
`ECDH-ES+A256KW`, `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW`, `PBES2-HS512+A256KW`)
- OKP curves `Ed448`, `X25519` and `X448` are not supported
@@ -335,8 +338,9 @@ private API and is subject to change between any versions.
#### How do I use it outside of Node.js
-It is **only built for Node.js** environment - it builds on top of the `crypto` module and requires
-the KeyObject API that was added in Node.js v11.6.0 and one-shot sign/verify API added in v12.0.0
+It is **only built for ^10.13.0 || >=12.0.0 Node.js** environment - including @panva/jose in
+transpiled browser-environment targetted projects is not supported and may result in unexpected
+results.
#### How is it different from [`jws`](https://github.com/brianloveswords/node-jws), [`jwa`](https://github.com/brianloveswords/node-jwa) or [`jsonwebtoken`](https://github.com/auth0/node-jsonwebtoken)?
diff --git a/lib/errors.js b/lib/errors.js
index f99508ca..b867d7da 100644
--- a/lib/errors.js
+++ b/lib/errors.js
@@ -37,19 +37,17 @@ class JOSEError extends Error {
const isMulti = e => e instanceof JOSEMultiError
class JOSEMultiError extends JOSEError {
- #errors
-
constructor (errors) {
super()
let i
while ((i = errors.findIndex(isMulti)) && i !== -1) {
errors.splice(i, 1, ...errors[i])
}
- this.#errors = errors
+ Object.defineProperty(this, 'errors', { value: errors })
}
* [Symbol.iterator] () {
- for (const error of this.#errors) {
+ for (const error of this.errors) {
yield error
}
}
diff --git a/lib/help/asn1/index.js b/lib/help/asn1/index.js
index 1684c6bb..88722281 100644
--- a/lib/help/asn1/index.js
+++ b/lib/help/asn1/index.js
@@ -26,5 +26,10 @@ types.set('RSAPrivateKey', RSAPrivateKey)
const RSAPublicKey = asn1.define('RSAPublicKey', require('./rsa_public_key'))
types.set('RSAPublicKey', RSAPublicKey)
+const OID = asn1.define('OID', function () {
+ return this.objid()
+})
+types.set('OID', OID)
+
module.exports = types
module.exports.bignum = asn1.bignum
diff --git a/lib/help/key_object.js b/lib/help/key_object.js
new file mode 100644
index 00000000..45b1b0e8
--- /dev/null
+++ b/lib/help/key_object.js
@@ -0,0 +1,420 @@
+let { createPublicKey, createPrivateKey, createSecretKey, KeyObject } = require('crypto')
+
+if (!createPublicKey || !createPrivateKey || !createSecretKey || !KeyObject) {
+ const { EOL } = require('os')
+
+ const errors = require('../errors')
+ const isObject = require('./is_object')
+ const asn1 = require('./asn1')
+
+ const namedCurve = Symbol('namedCurve')
+ const map = new WeakMap()
+
+ const i = (ctx) => {
+ if (!map.has(ctx)) {
+ map.set(ctx, {})
+ }
+ return map.get(ctx)
+ }
+
+ const pemToDer = pem => Buffer.from(pem.replace(/(?:-----(?:BEGIN|END)(?: (?:RSA|EC))? (?:PRIVATE|PUBLIC) KEY-----|\s)/g, ''), 'base64')
+ const derToPem = (der, label) => `-----BEGIN ${label}-----${EOL}${der.toString('base64').match(/.{1,64}/g).join(EOL)}${EOL}-----END ${label}-----`
+
+ KeyObject = class KeyObject {
+ export ({ cipher, passphrase, type, format } = {}) {
+ if (i(this).type === 'secret') {
+ return Buffer.from(i(this).buffer)
+ }
+
+ if (i(this).type === 'public') {
+ if (format !== 'pem' && format !== 'der') {
+ throw new TypeError('format must be one of "pem" or "der"')
+ }
+
+ if (this.asymmetricKeyType === 'rsa') {
+ switch (type) {
+ case 'pkcs1':
+ if (format === 'pem') {
+ return i(this).pem
+ }
+
+ return pemToDer(i(this).pem)
+ case 'spki': {
+ const PublicKeyInfo = asn1.get('PublicKeyInfo')
+ const pem = PublicKeyInfo.encode({
+ algorithm: {
+ algorithm: '1.2.840.113549.1.1.1'.split('.'),
+ parameters: Buffer.from('BQA=', 'base64')
+ },
+ publicKey: {
+ unused: 0,
+ data: pemToDer(i(this).pem)
+ }
+ }, 'pem', { label: 'PUBLIC KEY' })
+
+ return format === 'pem' ? pem : pemToDer(pem)
+ }
+ default:
+ throw new TypeError('type must be one of "pkcs1" or "spki"')
+ }
+ }
+
+ if (this.asymmetricKeyType === 'ec') {
+ if (type !== 'spki') {
+ throw new TypeError('type must be "spki"')
+ }
+
+ if (format === 'pem') {
+ return i(this).pem
+ }
+
+ return pemToDer(i(this).pem)
+ }
+ }
+
+ if (i(this).type === 'private') {
+ if (format !== 'pem' && format !== 'der') {
+ throw new TypeError('format must be one of "pem" or "der"')
+ }
+
+ if (passphrase !== undefined || cipher !== undefined) {
+ throw new errors.JOSENotSupported('encrypted private keys are not supported in your Node.js runtime version')
+ }
+
+ if (type === 'pkcs8') {
+ if (i(this).pkcs8) {
+ if (format === 'der' && typeof i(this).pkcs8 === 'string') {
+ return pemToDer(i(this).pkcs8)
+ }
+
+ if (format === 'pem' && Buffer.isBuffer(i(this).pkcs8)) {
+ return derToPem(i(this).pkcs8, 'PRIVATE KEY')
+ }
+
+ return i(this).pkcs8
+ }
+
+ if (this.asymmetricKeyType === 'rsa') {
+ const parsed = i(this).asn1
+ const RSAPrivateKey = asn1.get('RSAPrivateKey')
+ const privateKey = RSAPrivateKey.encode(parsed)
+ const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
+ const pkcs8 = PrivateKeyInfo.encode({
+ version: 0,
+ privateKey,
+ algorithm: {
+ algorithm: '1.2.840.113549.1.1.1'.split('.'),
+ parameters: Buffer.from('BQA=', 'base64')
+ }
+ })
+
+ i(this).pkcs8 = pkcs8
+
+ return this.export({ type, format })
+ }
+
+ if (this.asymmetricKeyType === 'ec') {
+ const parsed = i(this).asn1
+ const ECPrivateKey = asn1.get('ECPrivateKey')
+ const privateKey = ECPrivateKey.encode({
+ version: parsed.version,
+ privateKey: parsed.privateKey,
+ publicKey: parsed.publicKey
+ })
+ const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
+ const OID = asn1.get('OID')
+ const pkcs8 = PrivateKeyInfo.encode({
+ version: 0,
+ privateKey,
+ algorithm: {
+ algorithm: '1.2.840.10045.2.1'.split('.'),
+ parameters: OID.encode(i(this).asn1.parameters.value)
+ }
+ })
+
+ i(this).pkcs8 = pkcs8
+
+ return this.export({ type, format })
+ }
+ }
+
+ if (this.asymmetricKeyType === 'rsa' && type === 'pkcs1') {
+ if (format === 'pem') {
+ return i(this).pem
+ }
+
+ return pemToDer(i(this).pem)
+ } else if (this.asymmetricKeyType === 'ec' && type === 'sec1') {
+ if (format === 'pem') {
+ return i(this).pem
+ }
+
+ return pemToDer(i(this).pem)
+ } else {
+ throw new TypeError(`type must be one of "spki" or "${this.asymmetricKeyType === 'rsa' ? 'pkcs1' : 'sec1'}"`)
+ }
+ }
+ }
+
+ get type () {
+ return i(this).type
+ }
+
+ get asymmetricKeyType () {
+ return i(this).asymmetricKeyType
+ }
+
+ get symmetricKeySize () {
+ return i(this).symmetricKeySize
+ }
+
+ asInput (needsPublic = false) {
+ switch (i(this).type) {
+ case 'secret':
+ return i(this).buffer
+ case 'public':
+ return i(this).pem
+ default:
+ if (needsPublic) {
+ if (!('pub' in i(this))) {
+ i(this).pub = createPublicKey(this)
+ }
+
+ return i(this).pub.asInput()
+ }
+
+ return i(this).pem
+ }
+ }
+ }
+
+ createSecretKey = (buffer) => {
+ if (!Buffer.isBuffer(buffer) || !buffer.length) {
+ throw new TypeError('input must be a non-empty Buffer instance')
+ }
+
+ const keyObject = new KeyObject()
+ i(keyObject).buffer = Buffer.from(buffer)
+ i(keyObject).symmetricKeySize = buffer.length
+ i(keyObject).type = 'secret'
+
+ return keyObject
+ }
+
+ createPublicKey = (input) => {
+ if (input instanceof KeyObject) {
+ if (input.type !== 'private') {
+ throw new TypeError('expected a private key')
+ }
+
+ switch (input.asymmetricKeyType) {
+ case 'ec': {
+ const PublicKeyInfo = asn1.get('PublicKeyInfo')
+ const OID = asn1.get('OID')
+ const key = PublicKeyInfo.encode({
+ algorithm: {
+ algorithm: '1.2.840.10045.2.1'.split('.'),
+ parameters: OID.encode(i(input).asn1.parameters.value)
+ },
+ publicKey: i(input).asn1.publicKey
+ })
+
+ return createPublicKey({ key, format: 'der', type: 'spki' })
+ }
+ case 'rsa': {
+ const RSAPublicKey = asn1.get('RSAPublicKey')
+ const key = RSAPublicKey.encode(i(input).asn1)
+ return createPublicKey({ key, format: 'der', type: 'pkcs1' })
+ }
+ }
+ }
+
+ if (typeof input === 'string' || Buffer.isBuffer(input)) {
+ input = { key: input, format: 'pem' }
+ }
+
+ if (!isObject(input)) {
+ throw new TypeError('input must be a string, Buffer or an object')
+ }
+
+ const { format } = input
+ let { key, type } = input
+
+ if (typeof key !== 'string' && !Buffer.isBuffer(key)) {
+ throw new TypeError('key must be a string or Buffer')
+ }
+
+ if (format !== 'pem' && format !== 'der') {
+ throw new TypeError('format must be one of "pem" or "der"')
+ }
+
+ let label
+ if (format === 'pem') {
+ key = key.toString()
+ switch (key.split(/\r?\n/g)[0].toString()) {
+ case '-----BEGIN PUBLIC KEY-----':
+ type = 'spki'
+ label = 'PUBLIC KEY'
+ break
+ case '-----BEGIN RSA PUBLIC KEY-----':
+ type = 'pkcs1'
+ label = 'RSA PUBLIC KEY'
+ break
+ case '-----BEGIN CERTIFICATE-----':
+ throw new errors.JOSENotSupported('X.509 certificates are supported in your Node.js runtime version')
+ default:
+ throw new TypeError('unknown/unsupported PEM type')
+ }
+ }
+
+ switch (type) {
+ case 'spki': {
+ const PublicKeyInfo = asn1.get('PublicKeyInfo')
+ const parsed = PublicKeyInfo.decode(key, format, { label })
+
+ let type, keyObject
+ switch (parsed.algorithm.algorithm.join('.')) {
+ case '1.2.840.10045.2.1': {
+ keyObject = new KeyObject()
+ i(keyObject).asn1 = parsed
+ i(keyObject).asymmetricKeyType = 'ec'
+ i(keyObject).type = 'public'
+ i(keyObject).pem = PublicKeyInfo.encode(parsed, 'pem', { label: 'PUBLIC KEY' })
+
+ break
+ }
+ case '1.2.840.113549.1.1.1': {
+ type = 'pkcs1'
+ keyObject = createPublicKey({ type, key: parsed.publicKey.data, format: 'der' })
+ break
+ }
+ default:
+ throw new errors.JOSENotSupported(`OID ${parsed.algorithm.algorithm.join('.')} is not supported in your Node.js runtime version`)
+ }
+
+ return keyObject
+ }
+ case 'pkcs1': {
+ const RSAPublicKey = asn1.get('RSAPublicKey')
+ const parsed = RSAPublicKey.decode(key, format, { label })
+
+ const keyObject = new KeyObject()
+ i(keyObject).asn1 = parsed
+ i(keyObject).asymmetricKeyType = 'rsa'
+ i(keyObject).type = 'public'
+ i(keyObject).pem = RSAPublicKey.encode(parsed, 'pem', { label: 'RSA PUBLIC KEY' })
+
+ return keyObject
+ }
+ default:
+ throw new TypeError('type must be one of "pkcs1" or "spki"')
+ }
+ }
+
+ createPrivateKey = (input, hints) => {
+ if (typeof input === 'string' || Buffer.isBuffer(input)) {
+ input = { key: input, format: 'pem' }
+ }
+
+ if (!isObject(input)) {
+ throw new TypeError('input must be a string, Buffer or an object')
+ }
+
+ const { format, passphrase } = input
+ let { key, type } = input
+
+ if (typeof key !== 'string' && !Buffer.isBuffer(key)) {
+ throw new TypeError('key must be a string or Buffer')
+ }
+
+ if (passphrase !== undefined) {
+ throw new errors.JOSENotSupported('encrypted private keys are not supported in your Node.js runtime version')
+ }
+
+ if (format !== 'pem' && format !== 'der') {
+ throw new TypeError('format must be one of "pem" or "der"')
+ }
+
+ let label
+ if (format === 'pem') {
+ key = key.toString()
+ switch (key.split(/\r?\n/g)[0].toString()) {
+ case '-----BEGIN PRIVATE KEY-----':
+ type = 'pkcs8'
+ label = 'PRIVATE KEY'
+ break
+ case '-----BEGIN EC PRIVATE KEY-----':
+ type = 'sec1'
+ label = 'EC PRIVATE KEY'
+ break
+ case '-----BEGIN RSA PRIVATE KEY-----':
+ type = 'pkcs1'
+ label = 'RSA PRIVATE KEY'
+ break
+ default:
+ throw new TypeError('unknown/unsupported PEM type')
+ }
+ }
+
+ switch (type) {
+ case 'pkcs8': {
+ const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
+ const parsed = PrivateKeyInfo.decode(key, format, { label })
+
+ let type, keyObject
+ switch (parsed.algorithm.algorithm.join('.')) {
+ case '1.2.840.10045.2.1': {
+ const OID = asn1.get('OID')
+ type = 'sec1'
+ keyObject = createPrivateKey({ type, key: parsed.privateKey, format: 'der' }, { [namedCurve]: OID.decode(parsed.algorithm.parameters) })
+ break
+ }
+ case '1.2.840.113549.1.1.1': {
+ type = 'pkcs1'
+ keyObject = createPrivateKey({ type, key: parsed.privateKey, format: 'der' })
+ break
+ }
+ default:
+ throw new errors.JOSENotSupported(`OID ${parsed.algorithm.algorithm.join('.')} is not supported in your Node.js runtime version`)
+ }
+
+ i(keyObject).pkcs8 = key
+ return keyObject
+ }
+ case 'pkcs1': {
+ const RSAPrivateKey = asn1.get('RSAPrivateKey')
+ const parsed = RSAPrivateKey.decode(key, format, { label })
+
+ const keyObject = new KeyObject()
+ i(keyObject).asn1 = parsed
+ i(keyObject).asymmetricKeyType = 'rsa'
+ i(keyObject).type = 'private'
+ i(keyObject).pem = RSAPrivateKey.encode(parsed, 'pem', { label: 'RSA PRIVATE KEY' })
+
+ return keyObject
+ }
+ case 'sec1': {
+ const ECPrivateKey = asn1.get('ECPrivateKey')
+ let parsed = ECPrivateKey.decode(key, format, { label })
+
+ if (!('parameters' in parsed) && !hints[namedCurve]) {
+ throw new Error('invalid sec1')
+ } else if (!('parameters' in parsed)) {
+ parsed = { ...parsed, parameters: { type: 'namedCurve', value: hints[namedCurve] } }
+ }
+
+ const keyObject = new KeyObject()
+ i(keyObject).asn1 = parsed
+ i(keyObject).asymmetricKeyType = 'ec'
+ i(keyObject).type = 'private'
+ i(keyObject).pem = ECPrivateKey.encode(parsed, 'pem', { label: 'EC PRIVATE KEY' })
+
+ return keyObject
+ }
+ default:
+ throw new TypeError('type must be one of "pkcs8", "pkcs1" or "sec1"')
+ }
+ }
+}
+
+module.exports = { createPublicKey, createPrivateKey, createSecretKey, KeyObject }
diff --git a/lib/help/key_utils.js b/lib/help/key_utils.js
index 371fc909..36b9578f 100644
--- a/lib/help/key_utils.js
+++ b/lib/help/key_utils.js
@@ -1,8 +1,9 @@
-const { createPublicKey } = require('crypto')
+const { EOL } = require('os')
const { name: secp256k1 } = require('../jwk/key/secp256k1_crv')
const errors = require('../errors')
+const { createPublicKey } = require('./key_object')
const base64url = require('./base64url')
const asn1 = require('./asn1')
const computePrimes = require('./rsa_primes')
@@ -29,7 +30,7 @@ const crvToOidBuf = new Map([
['P-521', Buffer.from('06052b81040023', 'hex')]
])
-const formatPem = (base64pem, descriptor) => `-----BEGIN ${descriptor} KEY-----\n${base64pem.match(/.{1,64}/g).join('\n')}\n-----END ${descriptor} KEY-----`
+const formatPem = (base64pem, descriptor) => `-----BEGIN ${descriptor} KEY-----${EOL}${base64pem.match(/.{1,64}/g).join(EOL)}${EOL}-----END ${descriptor} KEY-----`
const okpToJWK = {
private (crv, keyObject) {
@@ -192,6 +193,19 @@ const concatEcPublicKey = (x, y) => ({
])
})
+const okpCrvToOid = (crv) => {
+ switch (crv) {
+ case 'X25519':
+ return '1.3.101.110'.split('.')
+ case 'X448':
+ return '1.3.101.111'.split('.')
+ case 'Ed25519':
+ return '1.3.101.112'.split('.')
+ case 'Ed448':
+ return '1.3.101.113'.split('.')
+ }
+}
+
const jwkToPem = {
RSA: {
private (jwk, { calculateMissingRSAPrimes }) {
@@ -288,19 +302,6 @@ const jwkToPem = {
}
}
-const okpCrvToOid = (crv) => {
- switch (crv) {
- case 'X25519':
- return '1.3.101.110'.split('.')
- case 'X448':
- return '1.3.101.111'.split('.')
- case 'Ed25519':
- return '1.3.101.112'.split('.')
- case 'Ed448':
- return '1.3.101.113'.split('.')
- }
-}
-
module.exports.jwkToPem = (jwk, { calculateMissingRSAPrimes = false } = {}) => {
switch (jwk.kty) {
case 'EC':
diff --git a/lib/help/node_support.js b/lib/help/node_support.js
index 25a5a9b6..2a23491c 100644
--- a/lib/help/node_support.js
+++ b/lib/help/node_support.js
@@ -1,5 +1,9 @@
+const { KeyObject, sign, verify } = require('crypto')
+
const [major, minor] = process.version.substr(1).split('.').map(x => parseInt(x, 10))
module.exports = {
- oaepHash: major > 12 || (major === 12 && minor >= 9)
+ oaepHashSupported: major > 12 || (major === 12 && minor >= 9),
+ keyObjectSupported: !!KeyObject,
+ edDSASupported: !!sign && !!verify
}
diff --git a/lib/jwa/aes_cbc_hmac_sha2.js b/lib/jwa/aes_cbc_hmac_sha2.js
index 264c8b6f..11607a83 100644
--- a/lib/jwa/aes_cbc_hmac_sha2.js
+++ b/lib/jwa/aes_cbc_hmac_sha2.js
@@ -23,7 +23,7 @@ const encrypt = (size, sign, { [KEYOBJECT]: keyObject }, cleartext, { iv, aad =
const keySize = size / 8
const encKey = key.slice(keySize)
- const cipher = createCipheriv(`AES-${size}-CBC`, encKey, iv)
+ const cipher = createCipheriv(`aes-${size}-cbc`, encKey, iv)
const ciphertext = Buffer.concat([cipher.update(cleartext), cipher.final()])
const macData = Buffer.concat([aad, iv, ciphertext, uint64be(aad.length * 8)])
@@ -47,7 +47,7 @@ const decrypt = (size, sign, { [KEYOBJECT]: keyObject }, ciphertext, { iv, tag =
let cleartext
try {
- const cipher = createDecipheriv(`AES-${size}-CBC`, encKey, iv)
+ const cipher = createDecipheriv(`aes-${size}-cbc`, encKey, iv)
cleartext = Buffer.concat([cipher.update(ciphertext), cipher.final()])
} catch (err) {}
diff --git a/lib/jwa/aes_gcm.js b/lib/jwa/aes_gcm.js
index 221dcd6d..4a326996 100644
--- a/lib/jwa/aes_gcm.js
+++ b/lib/jwa/aes_gcm.js
@@ -16,9 +16,10 @@ const checkInput = function (size, iv, tag) {
}
const encrypt = (size, { [KEYOBJECT]: keyObject }, cleartext, { iv, aad = Buffer.alloc(0) }) => {
+ const key = keyObject.asInput ? keyObject.asInput() : keyObject
checkInput(size, iv)
- const cipher = createCipheriv(`AES-${size}-GCM`, keyObject, iv)
+ const cipher = createCipheriv(`aes-${size}-gcm`, key, iv)
cipher.setAAD(aad)
const ciphertext = Buffer.concat([cipher.update(cleartext), cipher.final()])
@@ -28,10 +29,11 @@ const encrypt = (size, { [KEYOBJECT]: keyObject }, cleartext, { iv, aad = Buffer
}
const decrypt = (size, { [KEYOBJECT]: keyObject }, ciphertext, { iv, tag = Buffer.alloc(0), aad = Buffer.alloc(0) }) => {
+ const key = keyObject.asInput ? keyObject.asInput() : keyObject
checkInput(size, iv, tag)
try {
- const cipher = createDecipheriv(`AES-${size}-GCM`, keyObject, iv)
+ const cipher = createDecipheriv(`aes-${size}-gcm`, key, iv)
cipher.setAuthTag(tag)
cipher.setAAD(aad)
diff --git a/lib/jwa/aes_kw.js b/lib/jwa/aes_kw.js
index 0483b0b0..0dd6e2e6 100644
--- a/lib/jwa/aes_kw.js
+++ b/lib/jwa/aes_kw.js
@@ -32,6 +32,7 @@ const split = (input, size) => {
}
const wrapKey = (size, { [KEYOBJECT]: keyObject }, payload) => {
+ const key = keyObject.asInput ? keyObject.asInput() : keyObject
const iv = Buffer.alloc(16)
let R = split(payload, 8)
let A
@@ -41,7 +42,7 @@ const wrapKey = (size, { [KEYOBJECT]: keyObject }, payload) => {
for (let jdx = 0; jdx < 6; jdx++) {
for (let idx = 0; R.length > idx; idx++) {
count = (R.length * jdx) + idx + 1
- const cipher = createCipheriv(`AES${size}`, keyObject, iv)
+ const cipher = createCipheriv(`aes${size}`, key, iv)
B = Buffer.concat([A, R[idx]])
B = cipher.update(B)
@@ -55,6 +56,7 @@ const wrapKey = (size, { [KEYOBJECT]: keyObject }, payload) => {
}
const unwrapKey = (size, { [KEYOBJECT]: keyObject }, payload) => {
+ const key = keyObject.asInput ? keyObject.asInput() : keyObject
checkInput(payload)
const iv = Buffer.alloc(16)
@@ -70,7 +72,7 @@ const unwrapKey = (size, { [KEYOBJECT]: keyObject }, payload) => {
count = (R.length * jdx) + idx + 1
B = xor(A, uint64be(count))
B = Buffer.concat([B, R[idx], iv])
- const cipher = createDecipheriv(`AES${size}`, keyObject, iv)
+ const cipher = createDecipheriv(`aes${size}`, key, iv)
B = cipher.update(B)
A = B.slice(0, 8)
diff --git a/lib/jwa/ecdsa.js b/lib/jwa/ecdsa.js
index b71ac41b..e75e9f3d 100644
--- a/lib/jwa/ecdsa.js
+++ b/lib/jwa/ecdsa.js
@@ -1,19 +1,37 @@
const { strict: assert } = require('assert')
-const { sign: signOneShot, verify: verifyOneShot } = require('crypto')
+const { sign: signOneShot, verify: verifyOneShot, createSign, createVerify } = require('crypto')
const { derToJose, joseToDer } = require('../help/ecdsa_signatures')
const { KEYOBJECT } = require('../help/consts')
const resolveNodeAlg = require('../help/node_alg')
-const sign = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- return derToJose(signOneShot(nodeAlg, payload, keyObject), jwaAlg)
+let sign, verify
+
+if (signOneShot) {
+ sign = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ return derToJose(signOneShot(nodeAlg, payload, keyObject), jwaAlg)
+ }
+} else {
+ sign = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ return derToJose(createSign(nodeAlg).update(payload).sign(keyObject.asInput()), jwaAlg)
+ }
}
-const verify = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- try {
- return verifyOneShot(nodeAlg, payload, keyObject, joseToDer(signature, jwaAlg))
- } catch (err) {
- return false
+if (verifyOneShot) {
+ verify = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ try {
+ return verifyOneShot(nodeAlg, payload, keyObject, joseToDer(signature, jwaAlg))
+ } catch (err) {
+ return false
+ }
+ }
+} else {
+ verify = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ try {
+ return createVerify(nodeAlg).update(payload).verify(keyObject.asInput(true), joseToDer(signature, jwaAlg))
+ } catch (err) {
+ return false
+ }
}
}
diff --git a/lib/jwa/hmac.js b/lib/jwa/hmac.js
index 0b090147..766b0b1d 100644
--- a/lib/jwa/hmac.js
+++ b/lib/jwa/hmac.js
@@ -6,13 +6,13 @@ const timingSafeEqual = require('../help/timing_safe_equal')
const resolveNodeAlg = require('../help/node_alg')
const sign = (jwaAlg, hmacAlg, { [KEYOBJECT]: keyObject }, payload) => {
- const hmac = createHmac(hmacAlg, keyObject)
+ const hmac = createHmac(hmacAlg, keyObject.asInput ? keyObject.asInput() : keyObject)
hmac.update(payload)
return hmac.digest()
}
const verify = (jwaAlg, hmacAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- const hmac = createHmac(hmacAlg, keyObject)
+ const hmac = createHmac(hmacAlg, keyObject.asInput ? keyObject.asInput() : keyObject)
hmac.update(payload)
const expected = hmac.digest()
const actual = signature
diff --git a/lib/jwa/rsaes.js b/lib/jwa/rsaes.js
index bc236acc..4cba7e2f 100644
--- a/lib/jwa/rsaes.js
+++ b/lib/jwa/rsaes.js
@@ -25,11 +25,13 @@ const resolveOaepHash = (alg) => {
}
const wrapKey = (padding, oaepHash, { [KEYOBJECT]: keyObject }, payload) => {
- return { wrapped: publicEncrypt({ key: keyObject, oaepHash, padding }, payload) }
+ const key = keyObject.asInput ? keyObject.asInput(true) : keyObject
+ return { wrapped: publicEncrypt({ key, oaepHash, padding }, payload) }
}
const unwrapKey = (padding, oaepHash, { [KEYOBJECT]: keyObject }, payload) => {
- return privateDecrypt({ key: keyObject, oaepHash, padding }, payload)
+ const key = keyObject.asInput ? keyObject.asInput(false) : keyObject
+ return privateDecrypt({ key, oaepHash, padding }, payload)
}
module.exports = (JWA) => {
diff --git a/lib/jwa/rsassa.js b/lib/jwa/rsassa.js
index 9f15ce67..a64d9060 100644
--- a/lib/jwa/rsassa.js
+++ b/lib/jwa/rsassa.js
@@ -1,15 +1,33 @@
const { strict: assert } = require('assert')
-const { sign: signOneShot, verify: verifyOneShot } = require('crypto')
+const { sign: signOneShot, verify: verifyOneShot, createSign, createVerify } = require('crypto')
const { KEYOBJECT } = require('../help/consts')
const resolveNodeAlg = require('../help/node_alg')
-const sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- return signOneShot(nodeAlg, payload, keyObject)
+let sign, verify
+
+if (signOneShot) {
+ sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ return signOneShot(nodeAlg, payload, keyObject)
+ }
+} else {
+ sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ return createSign(nodeAlg).update(payload).sign(keyObject.asInput())
+ }
}
-const verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- return verifyOneShot(nodeAlg, payload, keyObject, signature)
+if (verifyOneShot) {
+ verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ return verifyOneShot(nodeAlg, payload, keyObject, signature)
+ }
+} else {
+ verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ try {
+ return createVerify(nodeAlg).update(payload).verify(keyObject.asInput(true), signature)
+ } catch (err) {
+ return false
+ }
+ }
}
module.exports = (JWA) => {
diff --git a/lib/jwa/rsassa_pss.js b/lib/jwa/rsassa_pss.js
index c62d6bd4..b96ce345 100644
--- a/lib/jwa/rsassa_pss.js
+++ b/lib/jwa/rsassa_pss.js
@@ -1,23 +1,53 @@
const { strict: assert } = require('assert')
-const { sign: signOneShot, verify: verifyOneShot, constants } = require('crypto')
+const {
+ sign: signOneShot,
+ verify: verifyOneShot,
+ createSign,
+ createVerify,
+ constants
+} = require('crypto')
const { KEYOBJECT } = require('../help/consts')
const resolveNodeAlg = require('../help/node_alg')
-const sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- return signOneShot(nodeAlg, payload, {
- key: keyObject,
- padding: constants.RSA_PKCS1_PSS_PADDING,
- saltLength: constants.RSA_PSS_SALTLEN_DIGEST
- })
+let sign, verify
+
+if (signOneShot) {
+ sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ return signOneShot(nodeAlg, payload, {
+ key: keyObject,
+ padding: constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: constants.RSA_PSS_SALTLEN_DIGEST
+ })
+ }
+} else {
+ sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ const key = keyObject.asInput()
+ return createSign(nodeAlg).update(payload).sign({
+ key,
+ padding: constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: constants.RSA_PSS_SALTLEN_DIGEST
+ })
+ }
}
-const verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- return verifyOneShot(nodeAlg, payload, {
- key: keyObject,
- padding: constants.RSA_PKCS1_PSS_PADDING,
- saltLength: constants.RSA_PSS_SALTLEN_DIGEST
- }, signature)
+if (verifyOneShot) {
+ verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ return verifyOneShot(nodeAlg, payload, {
+ key: keyObject,
+ padding: constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: constants.RSA_PSS_SALTLEN_DIGEST
+ }, signature)
+ }
+} else {
+ verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ const key = keyObject.asInput(true)
+ return createVerify(nodeAlg).update(payload).verify({
+ key,
+ padding: constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: constants.RSA_PSS_SALTLEN_DIGEST
+ }, signature)
+ }
}
module.exports = (JWA) => {
diff --git a/lib/jwe/decrypt.js b/lib/jwe/decrypt.js
index 8490d323..214a00f7 100644
--- a/lib/jwe/decrypt.js
+++ b/lib/jwe/decrypt.js
@@ -1,4 +1,3 @@
-const { createSecretKey } = require('crypto')
const { inflateRawSync } = require('zlib')
const base64url = require('../help/base64url')
@@ -8,6 +7,7 @@ const errors = require('../errors')
const { check, decrypt, keyManagementDecrypt } = require('../jwa')
const JWK = require('../jwk')
+const { createSecretKey } = require('../help/key_object')
const generateCEK = require('./generate_cek')
const validateHeaders = require('./validate_headers')
const { detect: resolveSerialization } = require('./serializers')
diff --git a/lib/jwe/encrypt.js b/lib/jwe/encrypt.js
index 017ea7e9..cdc6adae 100644
--- a/lib/jwe/encrypt.js
+++ b/lib/jwe/encrypt.js
@@ -1,10 +1,10 @@
-const { createSecretKey } = require('crypto')
const { deflateRawSync } = require('zlib')
const { KEYOBJECT } = require('../help/consts')
const generateIV = require('../help/generate_iv')
const base64url = require('../help/base64url')
const isObject = require('../help/is_object')
+const { createSecretKey } = require('../help/key_object')
const deepClone = require('../help/deep_clone')
const Key = require('../jwk/key/base')
const importKey = require('../jwk/import')
@@ -17,19 +17,16 @@ const validateHeaders = require('./validate_headers')
const PROCESS_RECIPIENT = Symbol('PROCESS_RECIPIENT')
+const map = new WeakMap()
+
+const i = (ctx) => {
+ if (!map.has(ctx)) {
+ map.set(ctx, {})
+ }
+ return map.get(ctx)
+}
+
class Encrypt {
- #aad
-
- #cek
-
- #unprotected
-
- #protected
-
- #cleartext
-
- #recipients
-
constructor (cleartext, protectedHeader, unprotectedHeader, aad) {
if (!Buffer.isBuffer(cleartext) && typeof cleartext !== 'string') {
throw new TypeError('cleartext argument must be a Buffer or a string')
@@ -49,11 +46,11 @@ class Encrypt {
throw new TypeError('unprotectedHeader argument must be a plain object when provided')
}
- this.#recipients = []
- this.#cleartext = cleartext
- this.#aad = aad
- this.#unprotected = unprotectedHeader ? deepClone(unprotectedHeader) : undefined
- this.#protected = protectedHeader ? deepClone(protectedHeader) : undefined
+ i(this).recipients = []
+ i(this).cleartext = cleartext
+ i(this).aad = aad
+ i(this).unprotected = unprotectedHeader ? deepClone(unprotectedHeader) : undefined
+ i(this).protected = protectedHeader ? deepClone(protectedHeader) : undefined
}
/*
@@ -68,7 +65,7 @@ class Encrypt {
throw new TypeError('header argument must be a plain object when provided')
}
- this.#recipients.push({
+ i(this).recipients.push({
key,
header: header ? deepClone(header) : undefined
})
@@ -80,9 +77,9 @@ class Encrypt {
* @private
*/
[PROCESS_RECIPIENT] (recipient) {
- const unprotectedHeader = this.#unprotected
- const protectedHeader = this.#protected
- const { length: recipientCount } = this.#recipients
+ const unprotectedHeader = i(this).unprotected
+ const protectedHeader = i(this).protected
+ const { length: recipientCount } = i(this).recipients
const jweHeader = {
...protectedHeader,
@@ -114,7 +111,7 @@ class Encrypt {
if (protectedHeader) {
protectedHeader.alg = alg
} else {
- this.#protected = { alg }
+ i(this).protected = { alg }
}
} else {
if (recipient.header) {
@@ -129,11 +126,11 @@ class Encrypt {
let generatedHeader
if (key.kty === 'oct' && alg === 'dir') {
- this.#cek = importKey(key[KEYOBJECT], { use: 'enc', alg: enc })
+ i(this).cek = importKey(key[KEYOBJECT], { use: 'enc', alg: enc })
} else {
- ({ wrapped, header: generatedHeader } = keyManagementEncrypt(alg, key, this.#cek[KEYOBJECT].export(), { enc, alg }))
+ ({ wrapped, header: generatedHeader } = keyManagementEncrypt(alg, key, i(this).cek[KEYOBJECT].export(), { enc, alg }))
if (alg === 'ECDH-ES') {
- this.#cek = importKey(createSecretKey(wrapped), { use: 'enc', alg: enc })
+ i(this).cek = importKey(createSecretKey(wrapped), { use: 'enc', alg: enc })
}
}
@@ -157,58 +154,58 @@ class Encrypt {
throw new TypeError('serialization must be one of "compact", "flattened", "general"')
}
- if (!this.#recipients.length) {
+ if (!i(this).recipients.length) {
throw new JWEInvalid('missing recipients')
}
- serializer.validate(this.#protected, this.#unprotected, this.#aad, this.#recipients)
+ serializer.validate(i(this).protected, i(this).unprotected, i(this).aad, i(this).recipients)
- let enc = validateHeaders(this.#protected, this.#unprotected, this.#recipients, false, this.#protected ? this.#protected.crit : undefined)
+ let enc = validateHeaders(i(this).protected, i(this).unprotected, i(this).recipients, false, i(this).protected ? i(this).protected.crit : undefined)
if (!enc) {
enc = 'A128CBC-HS256'
- if (this.#protected) {
- this.#protected.enc = enc
+ if (i(this).protected) {
+ i(this).protected.enc = enc
} else {
- this.#protected = { enc }
+ i(this).protected = { enc }
}
}
const final = {}
- this.#cek = generateCEK(enc)
+ i(this).cek = generateCEK(enc)
- this.#recipients.forEach(this[PROCESS_RECIPIENT].bind(this))
+ i(this).recipients.forEach(this[PROCESS_RECIPIENT].bind(this))
const iv = generateIV(enc)
final.iv = base64url.encodeBuffer(iv)
- if (this.#recipients.length === 1 && this.#recipients[0].generatedHeader) {
- const [{ generatedHeader }] = this.#recipients
- delete this.#recipients[0].generatedHeader
- this.#protected = Object.assign({}, this.#protected, generatedHeader)
+ if (i(this).recipients.length === 1 && i(this).recipients[0].generatedHeader) {
+ const [{ generatedHeader }] = i(this).recipients
+ delete i(this).recipients[0].generatedHeader
+ i(this).protected = Object.assign({}, i(this).protected, generatedHeader)
}
- if (this.#protected) {
- final.protected = base64url.JSON.encode(this.#protected)
+ if (i(this).protected) {
+ final.protected = base64url.JSON.encode(i(this).protected)
}
- final.unprotected = this.#unprotected
+ final.unprotected = i(this).unprotected
let aad
- if (this.#aad) {
- final.aad = base64url.encode(this.#aad)
+ if (i(this).aad) {
+ final.aad = base64url.encode(i(this).aad)
aad = Buffer.concat([Buffer.from(final.protected || ''), Buffer.from('.'), Buffer.from(final.aad)])
} else {
aad = Buffer.from(final.protected || '')
}
- let cleartext = this.#cleartext
- if (this.#protected && 'zip' in this.#protected) {
+ let cleartext = i(this).cleartext
+ if (i(this).protected && 'zip' in i(this).protected) {
cleartext = deflateRawSync(cleartext)
}
- const { ciphertext, tag } = encrypt(enc, this.#cek, cleartext, { iv, aad })
+ const { ciphertext, tag } = encrypt(enc, i(this).cek, cleartext, { iv, aad })
final.tag = base64url.encodeBuffer(tag)
final.ciphertext = base64url.encodeBuffer(ciphertext)
- return serializer(final, this.#recipients)
+ return serializer(final, i(this).recipients)
}
}
diff --git a/lib/jwe/generate_cek.js b/lib/jwe/generate_cek.js
index 49aa304b..d87d4efb 100644
--- a/lib/jwe/generate_cek.js
+++ b/lib/jwe/generate_cek.js
@@ -1,5 +1,6 @@
-const { randomBytes, createSecretKey } = require('crypto')
+const { randomBytes } = require('crypto')
+const { createSecretKey } = require('../help/key_object')
const { KEYLENGTHS } = require('../help/consts')
const importKey = require('../jwk/import')
diff --git a/lib/jwk/import.js b/lib/jwk/import.js
index 6bf31393..e78c1211 100644
--- a/lib/jwk/import.js
+++ b/lib/jwk/import.js
@@ -1,6 +1,6 @@
-const { createPublicKey, createPrivateKey, createSecretKey, KeyObject } = require('crypto')
const { deprecate } = require('util')
+const { createPublicKey, createPrivateKey, createSecretKey, KeyObject } = require('../help/key_object')
const base64url = require('../help/base64url')
const isObject = require('../help/is_object')
const { jwkToPem } = require('../help/key_utils')
diff --git a/lib/jwk/key/base.js b/lib/jwk/key/base.js
index ea4ea7c0..d1dbcb75 100644
--- a/lib/jwk/key/base.js
+++ b/lib/jwk/key/base.js
@@ -1,7 +1,9 @@
const { strict: assert } = require('assert')
-const { createPublicKey } = require('crypto')
const { inspect } = require('util')
+const { EOL } = require('os')
+const { keyObjectSupported } = require('../../help/node_support')
+const { createPublicKey } = require('../../help/key_object')
const { keyObjectToJWK } = require('../../help/key_utils')
const {
THUMBPRINT_MATERIAL, PUBLIC_MEMBERS, PRIVATE_MEMBERS, JWK_MEMBERS, KEYOBJECT,
@@ -47,7 +49,7 @@ class Key {
}
}
- if (x5c !== undefined) {
+ if (keyObjectSupported && x5c !== undefined) {
if (!Array.isArray(x5c) || !x5c.length || x5c.some(c => typeof c !== 'string')) {
throw new TypeError('`x5c` must be an array of one or more PKIX certificates when provided')
}
@@ -56,7 +58,7 @@ class Key {
let publicKey
try {
publicKey = createPublicKey({
- key: `-----BEGIN CERTIFICATE-----\n${cert.match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`, format: 'pem'
+ key: `-----BEGIN CERTIFICATE-----${EOL}${cert.match(/.{1,64}/g).join(EOL)}${EOL}-----END CERTIFICATE-----`, format: 'pem'
})
} catch (err) {
throw new errors.JWKInvalid(`\`x5c\` member at index ${i} is not a valid base64-encoded DER PKIX certificate`)
@@ -165,9 +167,16 @@ class Key {
throw new TypeError('public key cannot be exported as private')
}
- const result = Object.fromEntries(
- [...this.constructor[priv ? PRIVATE_MEMBERS : PUBLIC_MEMBERS]].map(k => [k, this[k]])
- )
+ const components = [...this.constructor[priv ? PRIVATE_MEMBERS : PUBLIC_MEMBERS]]
+ .map(k => [k, this[k]])
+
+ const result = {}
+
+ Object.keys(components).forEach((key) => {
+ const [k, v] = components[key]
+
+ result[k] = v
+ })
result.kty = this.kty
result.kid = this.kid
diff --git a/lib/jwk/key/ec.js b/lib/jwk/key/ec.js
index 65d15610..d23f9d59 100644
--- a/lib/jwk/key/ec.js
+++ b/lib/jwk/key/ec.js
@@ -5,6 +5,8 @@ const {
THUMBPRINT_MATERIAL, JWK_MEMBERS, PUBLIC_MEMBERS, EC_CURVES,
PRIVATE_MEMBERS, KEY_MANAGEMENT_DECRYPT, KEY_MANAGEMENT_ENCRYPT, ECDH_ALGS
} = require('../../help/consts')
+const { keyObjectSupported } = require('../../help/node_support')
+const { createPublicKey, createPrivateKey } = require('../../help/key_object')
const errors = require('../../errors')
const { name: secp256k1 } = require('./secp256k1_crv')
@@ -122,9 +124,24 @@ class ECKey extends Key {
crv = 'secp256k1'
}
- const { privateKey, publicKey } = await generateKeyPair('ec', { namedCurve: crv })
+ let privateKey, publicKey
- return privat ? privateKey : publicKey
+ if (keyObjectSupported) {
+ ({ privateKey, publicKey } = await generateKeyPair('ec', { namedCurve: crv }))
+ return privat ? privateKey : publicKey
+ }
+
+ ({ privateKey, publicKey } = await generateKeyPair('ec', {
+ namedCurve: crv,
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
+ }))
+
+ if (privat) {
+ return createPrivateKey(privateKey)
+ } else {
+ return createPublicKey(publicKey)
+ }
}
static generateSync (crv = 'P-256', privat = true) {
@@ -136,9 +153,24 @@ class ECKey extends Key {
crv = 'secp256k1'
}
- const { privateKey, publicKey } = generateKeyPairSync('ec', { namedCurve: crv })
+ let privateKey, publicKey
- return privat ? privateKey : publicKey
+ if (keyObjectSupported) {
+ ({ privateKey, publicKey } = generateKeyPairSync('ec', { namedCurve: crv }))
+ return privat ? privateKey : publicKey
+ }
+
+ ({ privateKey, publicKey } = generateKeyPairSync('ec', {
+ namedCurve: crv,
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
+ }))
+
+ if (privat) {
+ return createPrivateKey(privateKey)
+ } else {
+ return createPublicKey(publicKey)
+ }
}
}
diff --git a/lib/jwk/key/oct.js b/lib/jwk/key/oct.js
index 3eaa5476..b6340e2f 100644
--- a/lib/jwk/key/oct.js
+++ b/lib/jwk/key/oct.js
@@ -1,5 +1,6 @@
-const { randomBytes, createSecretKey } = require('crypto')
+const { randomBytes } = require('crypto')
+const { createSecretKey } = require('../../help/key_object')
const base64url = require('../../help/base64url')
const { KEYOBJECT } = require('../../help/consts')
const {
diff --git a/lib/jwk/key/rsa.js b/lib/jwk/key/rsa.js
index a5f7d432..cebab3e7 100644
--- a/lib/jwk/key/rsa.js
+++ b/lib/jwk/key/rsa.js
@@ -5,7 +5,8 @@ const {
THUMBPRINT_MATERIAL, JWK_MEMBERS, PUBLIC_MEMBERS,
PRIVATE_MEMBERS, KEY_MANAGEMENT_DECRYPT, KEY_MANAGEMENT_ENCRYPT
} = require('../../help/consts')
-const { oaepHash } = require('../../help/node_support')
+const { oaepHashSupported, keyObjectSupported } = require('../../help/node_support')
+const { createPublicKey, createPrivateKey } = require('../../help/key_object')
const Key = require('./base')
@@ -14,7 +15,7 @@ const generateKeyPair = promisify(async)
const SIG_ALGS = ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']
const WRAP_ALGS = ['RSA-OAEP', 'RSA1_5']
-if (oaepHash) {
+if (oaepHashSupported) {
WRAP_ALGS.splice(1, 0, 'RSA-OAEP-256')
}
@@ -151,9 +152,24 @@ class RSAKey extends Key {
throw new TypeError('invalid bit length')
}
- const { privateKey, publicKey } = await generateKeyPair('rsa', { modulusLength: len })
+ let privateKey, publicKey
- return privat ? privateKey : publicKey
+ if (keyObjectSupported) {
+ ({ privateKey, publicKey } = await generateKeyPair('rsa', { modulusLength: len }))
+ return privat ? privateKey : publicKey
+ }
+
+ ({ privateKey, publicKey } = await generateKeyPair('rsa', {
+ modulusLength: len,
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
+ }))
+
+ if (privat) {
+ return createPrivateKey(privateKey)
+ } else {
+ return createPublicKey(publicKey)
+ }
}
static generateSync (len = 2048, privat = true) {
@@ -161,9 +177,24 @@ class RSAKey extends Key {
throw new TypeError('invalid bit length')
}
- const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: len })
+ let privateKey, publicKey
- return privat ? privateKey : publicKey
+ if (keyObjectSupported) {
+ ({ privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: len }))
+ return privat ? privateKey : publicKey
+ }
+
+ ({ privateKey, publicKey } = generateKeyPairSync('rsa', {
+ modulusLength: len,
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
+ }))
+
+ if (privat) {
+ return createPrivateKey(privateKey)
+ } else {
+ return createPublicKey(publicKey)
+ }
}
}
diff --git a/lib/jwks/keystore.js b/lib/jwks/keystore.js
index 1eb425f5..7b03f16f 100644
--- a/lib/jwks/keystore.js
+++ b/lib/jwks/keystore.js
@@ -36,18 +36,32 @@ const keyscore = (key, { alg, kid, use, ops, x5t, x5t256 }) => {
return score
}
-class KeyStore {
- #keys
+const map = new WeakMap()
+const i = (ctx) => {
+ if (!map.has(ctx)) {
+ map.set(ctx, {})
+ }
+ return map.get(ctx)
+}
+
+class KeyStore {
constructor (...keys) {
while (keys.some(Array.isArray)) {
- keys = keys.flat()
+ keys = keys.flat ? keys.flat() : keys.reduce((acc, val) => {
+ if (Array.isArray(val)) {
+ return [...acc, ...val]
+ }
+
+ acc.push(val)
+ return acc
+ }, [])
}
if (keys.some(k => !(k instanceof Key))) {
throw new TypeError('all keys must be an instances of a key instantiated by JWK.asKey')
}
- this.#keys = new Set(keys)
+ i(this).keys = new Set(keys)
}
all ({ alg, kid, use, kty, key_ops: ops, x5t, 'x5t#S256': x5t256 } = {}) {
@@ -56,7 +70,7 @@ class KeyStore {
}
const search = { alg, kid, use, ops, x5t, x5t256 }
- return [...this.#keys]
+ return [...i(this).keys]
.filter((key) => {
let candidate = true
@@ -110,7 +124,7 @@ class KeyStore {
throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
}
- this.#keys.add(key)
+ i(this).keys.add(key)
}
remove (key) {
@@ -118,23 +132,23 @@ class KeyStore {
throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
}
- this.#keys.delete(key)
+ i(this).keys.delete(key)
}
toJWKS (priv = false) {
- return { keys: [...this.#keys.values()].map(key => key.toJWK(priv)) }
+ return { keys: [...i(this).keys.values()].map(key => key.toJWK(priv)) }
}
async generate (...args) {
- this.#keys.add(await generate(...args))
+ i(this).keys.add(await generate(...args))
}
generateSync (...args) {
- this.#keys.add(generateSync(...args))
+ i(this).keys.add(generateSync(...args))
}
get size () {
- return this.#keys.size
+ return i(this).keys.size
}
/* c8 ignore next 8 */
@@ -148,7 +162,7 @@ class KeyStore {
}
* [Symbol.iterator] () {
- for (const key of this.#keys) {
+ for (const key of i(this).keys) {
yield key
}
}
diff --git a/lib/jws/sign.js b/lib/jws/sign.js
index 110ba37c..d7c8adf8 100644
--- a/lib/jws/sign.js
+++ b/lib/jws/sign.js
@@ -10,13 +10,16 @@ const serializers = require('./serializers')
const PROCESS_RECIPIENT = Symbol('PROCESS_RECIPIENT')
+const map = new WeakMap()
+
+const i = (ctx) => {
+ if (!map.has(ctx)) {
+ map.set(ctx, {})
+ }
+ return map.get(ctx)
+}
+
class Sign {
- #b64
-
- #payload
-
- #recipients
-
constructor (payload) {
if (typeof payload === 'string') {
payload = base64url.encode(payload)
@@ -28,8 +31,8 @@ class Sign {
throw new TypeError('payload argument must be a Buffer, string or an object')
}
- this.#payload = payload
- this.#recipients = []
+ i(this).payload = payload
+ i(this).recipients = []
}
/*
@@ -52,7 +55,7 @@ class Sign {
throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint')
}
- this.#recipients.push({
+ i(this).recipients.push({
key,
protectedHeader: protectedHeader ? deepClone(protectedHeader) : undefined,
unprotectedHeader: unprotectedHeader ? deepClone(unprotectedHeader) : undefined
@@ -90,19 +93,19 @@ class Sign {
}
if (joseHeader.protected.crit && joseHeader.protected.crit.includes('b64')) {
- if (this.#b64 !== undefined && this.#b64 !== joseHeader.protected.b64) {
+ if (i(this).b64 !== undefined && i(this).b64 !== joseHeader.protected.b64) {
throw new JWSInvalid('the "b64" Header Parameter value MUST be the same for all recipients')
} else {
- this.#b64 = joseHeader.protected.b64
+ i(this).b64 = joseHeader.protected.b64
}
if (!joseHeader.protected.b64) {
- this.#payload = base64url.decode(this.#payload)
+ i(this).payload = base64url.decode(i(this).payload)
}
}
recipient.header = unprotectedHeader
recipient.protected = Object.keys(joseHeader.protected).length ? base64url.JSON.encode(joseHeader.protected) : ''
- recipient.signature = base64url.encodeBuffer(sign(alg, key, Buffer.from(`${recipient.protected}.${this.#payload}`)))
+ recipient.signature = base64url.encodeBuffer(sign(alg, key, Buffer.from(`${recipient.protected}.${i(this).payload}`)))
}
/*
@@ -114,15 +117,15 @@ class Sign {
throw new TypeError('serialization must be one of "compact", "flattened", "general"')
}
- if (!this.#recipients.length) {
+ if (!i(this).recipients.length) {
throw new JWSInvalid('missing recipients')
}
- serializer.validate(this, this.#recipients)
+ serializer.validate(this, i(this).recipients)
- this.#recipients.forEach(this[PROCESS_RECIPIENT].bind(this))
+ i(this).recipients.forEach(this[PROCESS_RECIPIENT].bind(this))
- return serializer(this.#payload, this.#recipients)
+ return serializer(i(this).payload, i(this).recipients)
}
}
diff --git a/package.json b/package.json
index 5818ede3..453c48e5 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
"standard": "^13.1.0"
},
"engines": {
- "node": ">=12.0.0"
+ "node": "^10.13.0 || >=12.0.0"
},
"ava": {
"babel": false,
diff --git a/test/cookbook/jwk.test.js b/test/cookbook/jwk.test.js
index 0f4000e3..ee0f6027 100644
--- a/test/cookbook/jwk.test.js
+++ b/test/cookbook/jwk.test.js
@@ -1,8 +1,11 @@
const test = require('ava')
+const { keyObjectSupported } = require('../../lib/help/node_support')
+
const recipes = require('./recipes')
const { JWK: { asKey }, JWKS: { KeyStore } } = require('../..')
+const errors = require('../../lib/errors')
test('public EC', t => {
const jwk = recipes.get('3.1')
@@ -27,9 +30,15 @@ test('public EC', t => {
test('private EC', t => {
const jwk = recipes.get('3.2')
const key = asKey(jwk)
- t.true(key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN ENCRYPTED PRIVATE KEY'))
t.true(key.toPEM(true, { type: 'sec1' }).includes('BEGIN EC PRIVATE KEY'))
- t.true(key.toPEM(true, { type: 'sec1', cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('ENCRYPTED'))
+ if (keyObjectSupported) {
+ t.true(key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN ENCRYPTED PRIVATE KEY'))
+ t.true(key.toPEM(true, { type: 'sec1', cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('ENCRYPTED'))
+ } else {
+ t.throws(() => {
+ key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN ENCRYPTED PRIVATE KEY')
+ }, { instanceOf: errors.JOSENotSupported, code: 'ERR_JOSE_NOT_SUPPORTED', message: 'encrypted private keys are not supported in your Node.js runtime version' })
+ }
t.true(key.toPEM(true).includes('BEGIN PRIVATE KEY'))
t.true(key.toPEM().includes('BEGIN PUBLIC KEY'))
t.deepEqual(key.toJWK(true), jwk)
@@ -62,9 +71,15 @@ test('private RSA', t => {
const jwk = recipes.get('3.4')
const key = asKey(jwk)
t.true(key.toPEM(true, { type: 'pkcs1' }).includes('BEGIN RSA PRIVATE KEY'))
- t.true(key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret', type: 'pkcs1' }).includes('ENCRYPTED'))
- t.true(key.toPEM(true, { type: 'pkcs1', cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN RSA PRIVATE KEY'))
- t.true(key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN ENCRYPTED PRIVATE KEY'))
+ if (keyObjectSupported) {
+ t.true(key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret', type: 'pkcs1' }).includes('ENCRYPTED'))
+ t.true(key.toPEM(true, { type: 'pkcs1', cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN RSA PRIVATE KEY'))
+ t.true(key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN ENCRYPTED PRIVATE KEY'))
+ } else {
+ t.throws(() => {
+ key.toPEM(true, { cipher: 'aes-256-cbc', passphrase: 'top secret' }).includes('BEGIN ENCRYPTED PRIVATE KEY')
+ }, { instanceOf: errors.JOSENotSupported, code: 'ERR_JOSE_NOT_SUPPORTED', message: 'encrypted private keys are not supported in your Node.js runtime version' })
+ }
t.true(key.toPEM(true).includes('BEGIN PRIVATE KEY'))
t.true(key.toPEM().includes('BEGIN PUBLIC KEY'))
t.deepEqual(key.toJWK(true), jwk)
diff --git a/test/cookbook/rfc8037.a4.ed25519.test.js b/test/cookbook/rfc8037.a4.ed25519.test.js
index 667d7eba..b82c7d82 100644
--- a/test/cookbook/rfc8037.a4.ed25519.test.js
+++ b/test/cookbook/rfc8037.a4.ed25519.test.js
@@ -1,5 +1,9 @@
const test = require('ava')
+const { edDSASupported } = require('../../lib/help/node_support')
+
+if (!edDSASupported) return
+
const recipe = require('./recipes').get('A.4 rfc8037')
const { JWS, JWK: { asKey, generateSync }, JWKS: { KeyStore }, errors } = require('../..')
diff --git a/test/help/P-256K.key_utils.test.js b/test/help/P-256K.key_utils.test.js
index f6e5a27f..b4db7984 100644
--- a/test/help/P-256K.key_utils.test.js
+++ b/test/help/P-256K.key_utils.test.js
@@ -5,7 +5,7 @@ const test = require('ava')
if ('electron' in process.versions) return
-const { createPublicKey, createPrivateKey } = require('crypto')
+const { createPublicKey, createPrivateKey } = require('../../lib/help/key_object')
const { keyObjectToJWK, jwkToPem } = require('../../lib/help/key_utils')
const { JWK: fixtures } = require('../fixtures')
diff --git a/test/help/key_utils.test.js b/test/help/key_utils.test.js
index f448b238..cc9a94ff 100644
--- a/test/help/key_utils.test.js
+++ b/test/help/key_utils.test.js
@@ -1,6 +1,7 @@
const test = require('ava')
-const { createPublicKey, createPrivateKey } = require('crypto')
+const { edDSASupported } = require('../../lib/help/node_support')
+const { createPublicKey, createPrivateKey } = require('../../lib/help/key_object')
const { errors } = require('../..')
const { keyObjectToJWK, jwkToPem } = require('../../lib/help/key_utils')
const { JWK: fixtures } = require('../fixtures')
@@ -40,24 +41,26 @@ test('RSA Private key', t => {
t.deepEqual(actual, expected)
})
-test('Ed25519 Public key', t => {
- const expected = clone(fixtures.Ed25519)
- delete expected.d
- const pem = createPublicKey(jwkToPem(expected))
- const actual = keyObjectToJWK(pem)
+if (edDSASupported) {
+ test('Ed25519 Public key', t => {
+ const expected = clone(fixtures.Ed25519)
+ delete expected.d
+ const pem = createPublicKey(jwkToPem(expected))
+ const actual = keyObjectToJWK(pem)
- t.deepEqual(actual, expected)
-})
+ t.deepEqual(actual, expected)
+ })
-test('Ed25519 Private key', t => {
- const expected = fixtures.Ed25519
- const pem = createPrivateKey(jwkToPem(expected))
- const actual = keyObjectToJWK(pem)
+ test('Ed25519 Private key', t => {
+ const expected = fixtures.Ed25519
+ const pem = createPrivateKey(jwkToPem(expected))
+ const actual = keyObjectToJWK(pem)
- t.deepEqual(actual, expected)
-})
+ t.deepEqual(actual, expected)
+ })
+}
-if (!('electron' in process.versions)) {
+if (!('electron' in process.versions) && edDSASupported) {
test('Ed448 Public key', t => {
const expected = clone(fixtures.Ed448)
delete expected.d
diff --git a/test/jwe/smoke.test.js b/test/jwe/smoke.test.js
index 10ac1332..90aef262 100644
--- a/test/jwe/smoke.test.js
+++ b/test/jwe/smoke.test.js
@@ -2,6 +2,7 @@ const test = require('ava')
const { randomBytes } = require('crypto')
+const { edDSASupported } = require('../../lib/help/node_support')
const { JWK: { asKey, generateSync } } = require('../..')
const ENCS = [
@@ -20,6 +21,7 @@ const { JWE: { success, failure } } = require('../macros')
Object.entries(fixtures.PEM).forEach(([type, { private: key, public: pub }]) => {
if (type === 'P-256K') return
if ('electron' in process.versions && (type.startsWith('X') || type === 'Ed448' || type === 'secp256k1')) return
+ if (!edDSASupported && (type.startsWith('Ed') || type.startsWith('X'))) return
const eKey = asKey(pub)
const dKey = asKey(key)
diff --git a/test/jwk/P-256K.import.test.js b/test/jwk/P-256K.import.test.js
index 0e85647d..f0e8a14b 100644
--- a/test/jwk/P-256K.import.test.js
+++ b/test/jwk/P-256K.import.test.js
@@ -5,7 +5,7 @@ const test = require('ava')
if ('electron' in process.versions) return
-const { createPrivateKey, createPublicKey } = require('crypto')
+const { createPrivateKey, createPublicKey } = require('../../lib/help/key_object')
const { hasProperty, hasNoProperties, hasProperties } = require('../macros')
const fixtures = require('../fixtures')
diff --git a/test/jwk/ec.test.js b/test/jwk/ec.test.js
index a0b92330..7499d008 100644
--- a/test/jwk/ec.test.js
+++ b/test/jwk/ec.test.js
@@ -1,6 +1,7 @@
const test = require('ava')
-const { createPrivateKey, createPublicKey, generateKeyPairSync } = require('crypto')
+const { createPrivateKey, createPublicKey } = require('../../lib/help/key_object')
const { hasProperty, hasNoProperties, hasProperties } = require('../macros')
+const { generateKeyPairSync } = require('../macros/generate')
const fixtures = require('../fixtures')
const errors = require('../../lib/errors')
diff --git a/test/jwk/generate.test.js b/test/jwk/generate.test.js
index 25768863..5c044f1f 100644
--- a/test/jwk/generate.test.js
+++ b/test/jwk/generate.test.js
@@ -1,5 +1,7 @@
const test = require('ava')
+const { edDSASupported } = require('../../lib/help/node_support')
+
const { JWK: { generate, generateSync }, errors } = require('../..')
;[
@@ -13,23 +15,23 @@ const { JWK: { generate, generateSync }, errors } = require('../..')
['RSA', 2048, { use: 'enc', alg: 'RSA-OAEP' }],
['RSA', 2048, { alg: 'PS256' }],
['RSA', 2048, { alg: 'RSA-OAEP' }],
- ['OKP'],
- ['OKP', undefined, undefined, true],
- ['OKP', undefined, undefined, false],
- ['OKP', 'Ed25519'],
- ['OKP', 'Ed25519', { use: 'sig' }],
+ edDSASupported ? ['OKP'] : undefined,
+ edDSASupported ? ['OKP', undefined, undefined, true] : undefined,
+ edDSASupported ? ['OKP', undefined, undefined, false] : undefined,
+ edDSASupported ? ['OKP', 'Ed25519'] : undefined,
+ edDSASupported ? ['OKP', 'Ed25519', { use: 'sig' }] : undefined,
// ['OKP', 'Ed25519', { use: 'sig', alg: 'EdDSA' }],
// ['OKP', 'Ed25519', { alg: 'EdDSA' }],
- ['OKP', 'Ed448'],
- ['OKP', 'Ed448', { use: 'sig' }],
+ edDSASupported ? ['OKP', 'Ed448'] : undefined,
+ edDSASupported ? ['OKP', 'Ed448', { use: 'sig' }] : undefined,
// ['OKP', 'Ed448', { use: 'sig', alg: 'EdDSA' }],
// ['OKP', 'Ed448', { alg: 'EdDSA' }],
- ['OKP', 'X25519'],
- ['OKP', 'X25519', { use: 'enc' }],
+ edDSASupported ? ['OKP', 'X25519'] : undefined,
+ edDSASupported ? ['OKP', 'X25519', { use: 'enc' }] : undefined,
// ['OKP', 'X25519', { use: 'enc', alg: 'ECDH-ES' }],
// ['OKP', 'X25519', { alg: 'ECDH-ES' }],
- ['OKP', 'X448'],
- ['OKP', 'X448', { use: 'enc' }],
+ edDSASupported ? ['OKP', 'X448'] : undefined,
+ edDSASupported ? ['OKP', 'X448', { use: 'enc' }] : undefined,
// ['OKP', 'X448', { use: 'enc', alg: 'ECDH-ES' }],
// ['OKP', 'X448', { alg: 'ECDH-ES' }],
['EC'],
@@ -71,7 +73,7 @@ const { JWK: { generate, generateSync }, errors } = require('../..')
['oct', 192, { use: 'enc', alg: 'A192GCM' }],
['oct', 192, { alg: 'HS256' }],
['oct', 192, { alg: 'A192GCM' }]
-].forEach((args) => {
+].filter(Boolean).forEach((args) => {
if ('electron' in process.versions) {
const [, crv] = args
if (crv === 'secp256k1' || String(crv).startsWith('X') || crv === 'Ed448') return
diff --git a/test/jwk/import.test.js b/test/jwk/import.test.js
index fe1d9cbf..df6bbb7d 100644
--- a/test/jwk/import.test.js
+++ b/test/jwk/import.test.js
@@ -1,22 +1,24 @@
const test = require('ava')
-const crypto = require('crypto')
const { JWS, JWE, JWK: { asKey, importKey, generate }, errors } = require('../..')
+const { edDSASupported, keyObjectSupported } = require('../../lib/help/node_support')
+const { createSecretKey } = require('../../lib/help/key_object')
+const { generateKeyPairSync } = require('../macros/generate')
const fixtures = require('../fixtures')
test('imports PrivateKeyObject and then its Key instance', t => {
- const k = asKey(crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }).privateKey)
+ const k = asKey(generateKeyPairSync('ec', { namedCurve: 'P-256' }).privateKey)
t.deepEqual(asKey(k).toJWK(), k.toJWK())
})
test('imports PublicKeyObject and then its Key instance', t => {
- const k = asKey(crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }).publicKey)
+ const k = asKey(generateKeyPairSync('ec', { namedCurve: 'P-256' }).publicKey)
t.deepEqual(asKey(k).toJWK(), k.toJWK())
})
test('imports SecretKeyObject and then its Key instance', t => {
- const k = asKey(crypto.createSecretKey(Buffer.from('foo')))
+ const k = asKey(createSecretKey(Buffer.from('foo')))
t.deepEqual(asKey(k).toJWK(), k.toJWK())
})
@@ -39,6 +41,7 @@ test('parameters must be a plain object', t => {
Object.entries(fixtures.PEM).forEach(([type, { private: priv, public: pub }]) => {
if (type === 'P-256K') return
if ('electron' in process.versions && (type.startsWith('X') || type === 'Ed448' || type === 'secp256k1')) return
+ if (!edDSASupported && (type.startsWith('Ed') || type.startsWith('X'))) return
test(`fails to import ${type} as invalid string`, t => {
t.throws(() => {
@@ -77,11 +80,11 @@ test('failed to import throws an error', t => {
}, { instanceOf: errors.JWKImportFailed, code: 'ERR_JWK_IMPORT_FAILED' })
})
-if (!('electron' in process.versions)) {
+if (!('electron' in process.versions) && keyObjectSupported) {
;[
`-----BEGIN PUBLIC KEY-----\nMIIBtjCCASsGByqGSM44BAEwggEeAoGBANuHjLdqQcKozzWf9fUfe/mw4i5NLT8k\nCIA75k+GNYNbBaGZ2lGNeKsrjHzM8w7mE5k6qx5hDB4n88qFoauqCsUZ4knbTybn\nYV08gfWS375l/EGSpt3c/1dezVZuT/FmEeXbMhOIDORf/9f/6PpEMFN3eghszLvN\ng+L/19HVpWAXAhUAnOFG9vvOiZIz/ZxdpR+EVv8o4T8CgYBDk/ChY3fo4DrxzLZT\n7AjsAiJOzO8QnsV07Gh8gSzUCBsb+Hb4GvMs2U6rB5mxOMib3S2HGbs791uBva2a\nA6pzNzRmgV/w6CyOcxhCkZdVL7MwO9y5iq6V65R4GgfkCrIAYi/BW6XdXOyw/7J0\nt/4wB0/wKtsXf541NLfmUprJ+QOBhAACgYBGbXflbrGGg02+w8Xo6RO+tHoekREZ\nlJA0KKBN4jT0S3/OsLQeHtO7k/gkdMMbXD1J1fae9tIxy1SwYVTR6csgydGuvuyG\nB4A/ZtXEb+dumCBbtw8dyred4Okhl44Fdrs79F1rjSWEcwKqJghxS+GsbA0vcTaf\nAHDL6OblN04uzg==\n-----END PUBLIC KEY-----`,
- crypto.generateKeyPairSync('dsa', { modulusLength: 1024 }).publicKey,
- crypto.generateKeyPairSync('dsa', { modulusLength: 1024 }).privateKey
+ generateKeyPairSync('dsa', { modulusLength: 1024 }).publicKey,
+ generateKeyPairSync('dsa', { modulusLength: 1024 }).privateKey
].forEach((unsupported, i) => {
test(`fails to import unsupported PEM ${i + 1}/4`, t => {
t.throws(() => {
diff --git a/test/jwk/key_ops.test.js b/test/jwk/key_ops.test.js
index 5c15935e..49279cbc 100644
--- a/test/jwk/key_ops.test.js
+++ b/test/jwk/key_ops.test.js
@@ -1,9 +1,11 @@
const test = require('ava')
-const crypto = require('crypto')
const errors = require('../../lib/errors')
const asKey = require('../../lib/jwk/import')
const { generateSync } = require('../../lib/jwk/generate')
+const { generateKeyPairSync } = require('../macros/generate')
+
+const { edDSASupported } = require('../../lib/help/node_support')
const jwk = asKey('foo').toJWK(true)
@@ -53,7 +55,7 @@ test('JWK asKey with invalid use / key_ops throws', t => {
})
test('keyObject asKey with invalid use / key_ops throws 1/2', t => {
- const { publicKey } = crypto.generateKeyPairSync('ed25519')
+ const { publicKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
t.throws(() => {
asKey(publicKey, { use: 'sig', key_ops: ['wrapKey'] })
@@ -61,7 +63,7 @@ test('keyObject asKey with invalid use / key_ops throws 1/2', t => {
})
test('keyObject asKey with invalid use / key_ops throws 2/2', t => {
- const { publicKey } = crypto.generateKeyPairSync('ed25519')
+ const { publicKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
t.throws(() => {
asKey(publicKey, { use: 'enc', key_ops: ['sign'] })
@@ -69,7 +71,7 @@ test('keyObject asKey with invalid use / key_ops throws 2/2', t => {
})
test('PEM asKey with invalid use / key_ops throws', t => {
- const { publicKey } = crypto.generateKeyPairSync('ed25519')
+ const { publicKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
t.throws(() => {
asKey(publicKey.export({ type: 'spki', format: 'pem' }), { use: 'sig', key_ops: ['wrapKey'] })
@@ -97,9 +99,11 @@ test('oct key key_ops', t => {
t.deepEqual([...k.algorithms('sign')], [])
})
-test('OKP key key_ops', t => {
- const k = generateSync('OKP', 'Ed25519', { key_ops: ['verify'] })
- t.deepEqual([...k.algorithms()], ['EdDSA'])
- t.deepEqual([...k.algorithms('verify')], ['EdDSA'])
- t.deepEqual([...k.algorithms('sign')], [])
-})
+if (edDSASupported) {
+ test('OKP key key_ops', t => {
+ const k = generateSync('OKP', 'Ed25519', { key_ops: ['verify'] })
+ t.deepEqual([...k.algorithms()], ['EdDSA'])
+ t.deepEqual([...k.algorithms('verify')], ['EdDSA'])
+ t.deepEqual([...k.algorithms('sign')], [])
+ })
+}
diff --git a/test/jwk/oct.test.js b/test/jwk/oct.test.js
index 6c105db0..bfc24094 100644
--- a/test/jwk/oct.test.js
+++ b/test/jwk/oct.test.js
@@ -1,5 +1,6 @@
const test = require('ava')
-const { createSecretKey } = require('crypto')
+
+const { createSecretKey } = require('../../lib/help/key_object')
const { hasProperty, hasNoProperties } = require('../macros')
const errors = require('../../lib/errors')
diff --git a/test/jwk/okp_enc.test.js b/test/jwk/okp_enc.test.js
index bac3ddfc..2fc29c7e 100644
--- a/test/jwk/okp_enc.test.js
+++ b/test/jwk/okp_enc.test.js
@@ -1,6 +1,8 @@
const test = require('ava')
-if ('electron' in process.versions) return
+const { keyObjectSupported } = require('../../lib/help/node_support')
+
+if ('electron' in process.versions || !keyObjectSupported) return
const { createPrivateKey, createPublicKey } = require('crypto')
const { hasProperty, hasNoProperties, hasProperties } = require('../macros')
diff --git a/test/jwk/okp_sig.test.js b/test/jwk/okp_sig.test.js
index 7133d441..e26ab5f5 100644
--- a/test/jwk/okp_sig.test.js
+++ b/test/jwk/okp_sig.test.js
@@ -1,4 +1,9 @@
const test = require('ava')
+
+const { keyObjectSupported } = require('../../lib/help/node_support')
+
+if (!keyObjectSupported) return
+
const { createPrivateKey, createPublicKey } = require('crypto')
const { hasProperty, hasNoProperties, hasProperties } = require('../macros')
const fixtures = require('../fixtures')
diff --git a/test/jwk/rsa.test.js b/test/jwk/rsa.test.js
index f87f96dd..8b1a934c 100644
--- a/test/jwk/rsa.test.js
+++ b/test/jwk/rsa.test.js
@@ -1,9 +1,9 @@
const test = require('ava')
-const { createPrivateKey, createPublicKey } = require('crypto')
+const { createPrivateKey, createPublicKey } = require('../../lib/help/key_object')
const { hasProperty, hasNoProperties, hasProperties } = require('../macros')
const fixtures = require('../fixtures')
-const { oaepHash } = require('../../lib/help/node_support')
+const { oaepHashSupported } = require('../../lib/help/node_support')
const { generateSync } = require('../../lib/jwk/generate')
const RSAKey = require('../../lib/jwk/key/rsa')
@@ -32,7 +32,7 @@ test(`RSA key .algorithms invalid operation`, t => {
test(`RSA Private key`, hasProperty, key, 'type', 'private')
test(`RSA Private key`, hasProperty, key, 'use', undefined)
- if (oaepHash) {
+ if (oaepHashSupported) {
test('RSA Private key algorithms (no operation)', t => {
const result = key.algorithms()
t.is(result.constructor, Set)
@@ -125,7 +125,7 @@ test(`RSA key .algorithms invalid operation`, t => {
t.deepEqual([...result], [])
})
- if (oaepHash) {
+ if (oaepHashSupported) {
test('RSA Private key .algorithms("wrapKey")', t => {
const result = key.algorithms('wrapKey')
t.is(result.constructor, Set)
@@ -146,7 +146,7 @@ test(`RSA key .algorithms invalid operation`, t => {
t.deepEqual([...result], [])
})
- if (oaepHash) {
+ if (oaepHashSupported) {
test('RSA Private key .algorithms("unwrapKey")', t => {
const result = key.algorithms('unwrapKey')
t.is(result.constructor, Set)
@@ -188,7 +188,7 @@ test(`RSA key .algorithms invalid operation`, t => {
test(`RSA Public key`, hasProperty, key, 'type', 'public')
test(`RSA Public key`, hasProperty, key, 'use', undefined)
- if (oaepHash) {
+ if (oaepHashSupported) {
test('RSA EC Public key algorithms (no operation)', t => {
const result = key.algorithms()
t.is(result.constructor, Set)
@@ -281,7 +281,7 @@ test(`RSA key .algorithms invalid operation`, t => {
t.deepEqual([...result], [])
})
- if (oaepHash) {
+ if (oaepHashSupported) {
test('RSA Public key .algorithms("wrapKey")', t => {
const result = key.algorithms('wrapKey')
t.is(result.constructor, Set)
@@ -336,7 +336,7 @@ test(`RSA key .algorithms invalid operation`, t => {
t.true(k.algorithms().has('RS512'))
})
- if (oaepHash) {
+ if (oaepHashSupported) {
test('RSA key >= 784 bits can do RSA-OAEP-256', t => {
const k = generateSync('RSA', 784)
t.true(k.algorithms().has('RSA-OAEP-256'))
@@ -370,7 +370,7 @@ test(`RSA key .algorithms invalid operation`, t => {
t.true(k.algorithms().has('PS384'))
})
- if (oaepHash) {
+ if (oaepHashSupported) {
test('RSA key >= 896 bits can do RSA-OAEP-256', t => {
const k = generateSync('RSA', 896)
t.true(k.algorithms().has('RSA-OAEP-256'))
diff --git a/test/jwk/x5c_thumbprints.test.js b/test/jwk/x5c_thumbprints.test.js
index dd58bb03..6d2a6e86 100644
--- a/test/jwk/x5c_thumbprints.test.js
+++ b/test/jwk/x5c_thumbprints.test.js
@@ -1,5 +1,9 @@
const test = require('ava')
+const { keyObjectSupported } = require('../../lib/help/node_support')
+
+if (!keyObjectSupported) return
+
const errors = require('../../lib/errors')
const { JWK: { asKey } } = require('../..')
diff --git a/test/jws/smoke.test.js b/test/jws/smoke.test.js
index 94473589..35b8beda 100644
--- a/test/jws/smoke.test.js
+++ b/test/jws/smoke.test.js
@@ -1,5 +1,6 @@
const test = require('ava')
+const { edDSASupported } = require('../../lib/help/node_support')
const { JWK: { asKey, generateSync } } = require('../..')
const fixtures = require('../fixtures')
@@ -9,6 +10,7 @@ const { JWS: { success, failure } } = require('../macros')
Object.entries(fixtures.PEM).forEach(([type, { private: key, public: pub }]) => {
if (type === 'P-256K') return
if ('electron' in process.versions && (type.startsWith('X') || type === 'Ed448' || type === 'secp256k1')) return
+ if (!edDSASupported && (type.startsWith('Ed') || type.startsWith('X'))) return
const sKey = asKey(key)
const vKey = asKey(pub)
diff --git a/test/macros/generate.js b/test/macros/generate.js
new file mode 100644
index 00000000..e7903141
--- /dev/null
+++ b/test/macros/generate.js
@@ -0,0 +1,20 @@
+const { generateKeyPairSync } = require('crypto')
+
+const { keyObjectSupported } = require('../../lib/help/node_support')
+const { createPublicKey, createPrivateKey } = require('../../lib/help/key_object')
+
+module.exports = {
+ generateKeyPairSync (type, options) {
+ if (keyObjectSupported) {
+ return generateKeyPairSync(type, options)
+ }
+
+ const { privateKey, publicKey } = generateKeyPairSync(type, {
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
+ ...options
+ })
+
+ return { privateKey: createPrivateKey(privateKey), publicKey: createPublicKey(publicKey) }
+ }
+}