mirror of
https://github.com/danbulant/jose
synced 2026-05-19 04:18:52 +00:00
feat: added Node.js lts/dubnium support for runtime supported features
This commit is contained in:
parent
1d6d55ff91
commit
67a8601b09
45 changed files with 890 additions and 226 deletions
|
|
@ -1,8 +1,8 @@
|
|||
coverage:
|
||||
status:
|
||||
project: no
|
||||
patch: yes
|
||||
changes: yes
|
||||
project: off
|
||||
patch: off
|
||||
changes: off
|
||||
|
||||
comment:
|
||||
layout: diff
|
||||
|
|
|
|||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 | ✓<sup>*</sup> | RSA-OAEP <sub>(<sup>*</sup>RSA-OAEP-256 is not supported due to its lack of support in Node.JS)</sub> |
|
||||
| RSAES OAEP | ✓<sup>*</sup> | RSA-OAEP <sub>(<sup>*</sup>RSA-OAEP-256 is not supported due to its lack of support in Node.js)</sub> |
|
||||
| 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 |
|
||||
|
|
|
|||
14
README.md
14
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)?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
420
lib/help/key_object.js
Normal file
420
lib/help/key_object.js
Normal file
|
|
@ -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 }
|
||||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
"standard": "^13.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": "^10.13.0 || >=12.0.0"
|
||||
},
|
||||
"ava": {
|
||||
"babel": false,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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('../..')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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')], [])
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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('../..')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
20
test/macros/generate.js
Normal file
20
test/macros/generate.js
Normal file
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue