-
-
Notifications
You must be signed in to change notification settings - Fork 316
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add ECDH-ES with X25519 and X448 OKP keys
- Loading branch information
Showing
7 changed files
with
230 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
const { KeyObject, sign, verify } = require('crypto') | ||
const { diffieHellman, KeyObject, sign, verify } = require('crypto') | ||
|
||
const [major, minor] = process.version.substr(1).split('.').map(x => parseInt(x, 10)) | ||
|
||
module.exports = { | ||
oaepHashSupported: major > 12 || (major === 12 && minor >= 9), | ||
keyObjectSupported: !!KeyObject && major >= 12, | ||
edDSASupported: !!sign && !!verify, | ||
dsaEncodingSupported: major > 13 || (major === 13 && minor >= 2) || (major === 12 && minor >= 16) | ||
dsaEncodingSupported: major > 13 || (major === 13 && minor >= 2) || (major === 12 && minor >= 16), | ||
improvedDH: !!diffieHellman | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,43 @@ | ||
const { createECDH, constants: { POINT_CONVERSION_UNCOMPRESSED } } = require('crypto') | ||
|
||
const base64url = require('../../help/base64url') | ||
const { name: secp256k1 } = require('../../jwk/key/secp256k1_crv') | ||
|
||
const crvToCurve = (crv) => { | ||
switch (crv) { | ||
case 'P-256': | ||
return 'prime256v1' | ||
case 'P-384': | ||
return 'secp384r1' | ||
case 'P-521': | ||
return 'secp521r1' | ||
case 'secp256k1': | ||
case 'X448': | ||
case 'X25519': | ||
return crv | ||
case secp256k1: | ||
return 'secp256k1' | ||
const { improvedDH } = require('../../help/runtime_support') | ||
|
||
if (improvedDH) { | ||
const { diffieHellman } = require('crypto') | ||
|
||
const { KeyObject } = require('../../help/key_object') | ||
const importKey = require('../../jwk/import') | ||
|
||
module.exports = ({ keyObject: privateKey }, publicKey) => { | ||
if (!(publicKey instanceof KeyObject)) { | ||
({ keyObject: publicKey } = importKey(publicKey)) | ||
} | ||
|
||
return diffieHellman({ privateKey, publicKey }) | ||
} | ||
} | ||
} else { | ||
const { createECDH, constants: { POINT_CONVERSION_UNCOMPRESSED } } = require('crypto') | ||
|
||
const UNCOMPRESSED = Buffer.alloc(1, POINT_CONVERSION_UNCOMPRESSED) | ||
const pubToBuffer = (x, y) => Buffer.concat([UNCOMPRESSED, base64url.decodeToBuffer(x), base64url.decodeToBuffer(y)]) | ||
const base64url = require('../../help/base64url') | ||
|
||
module.exports = ({ crv, d }, { x, y = '' }) => { | ||
const curve = crvToCurve(crv) | ||
const exchange = createECDH(curve) | ||
const crvToCurve = (crv) => { | ||
switch (crv) { | ||
case 'P-256': | ||
return 'prime256v1' | ||
case 'P-384': | ||
return 'secp384r1' | ||
case 'P-521': | ||
return 'secp521r1' | ||
} | ||
} | ||
|
||
const UNCOMPRESSED = Buffer.alloc(1, POINT_CONVERSION_UNCOMPRESSED) | ||
const pubToBuffer = (x, y) => Buffer.concat([UNCOMPRESSED, base64url.decodeToBuffer(x), base64url.decodeToBuffer(y)]) | ||
|
||
exchange.setPrivateKey(base64url.decodeToBuffer(d)) | ||
module.exports = ({ crv, d }, { x, y }) => { | ||
const curve = crvToCurve(crv) | ||
const exchange = createECDH(curve) | ||
|
||
return exchange.computeSecret(pubToBuffer(x, y)) | ||
exchange.setPrivateKey(base64url.decodeToBuffer(d)) | ||
|
||
return exchange.computeSecret(pubToBuffer(x, y)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
const test = require('ava') | ||
|
||
const { createPrivateKey, createPublicKey } = require('crypto') | ||
const { hasProperty, hasNoProperties, hasProperties } = require('../macros') | ||
const fixtures = require('../fixtures') | ||
|
||
const OKPKey = require('../../lib/jwk/key/okp') | ||
|
||
test('OKP key .algorithms invalid operation', t => { | ||
const key = new OKPKey(createPrivateKey(fixtures.PEM.X25519.private)) | ||
t.throws(() => key.algorithms('foo'), { instanceOf: TypeError, message: 'invalid key operation' }) | ||
}) | ||
|
||
Object.entries({ | ||
X25519: 'P-c1F5P-1BckI7vasmrM8384J2IBYaYc_EtEXxOZYuI', | ||
X448: 'a-2MwPMAhM3QY0zU0YBP9lzipRk67tsOY9uUhiT2Fos' | ||
}).forEach(([crv, kid]) => { | ||
const alg = 'ECDH-ES' | ||
|
||
// private | ||
;(() => { | ||
const keyObject = createPrivateKey(fixtures.PEM[crv].private) | ||
const key = new OKPKey(keyObject) | ||
|
||
test(`${crv} OKP Private key (with alg)`, hasProperty, new OKPKey(keyObject, { alg }), 'alg', alg) | ||
test(`${crv} OKP Private key (with kid)`, hasProperty, new OKPKey(keyObject, { kid: 'foobar' }), 'kid', 'foobar') | ||
test(`${crv} OKP Private key (with use)`, hasProperty, new OKPKey(keyObject, { use: 'enc' }), 'use', 'enc') | ||
test(`${crv} OKP Private key`, hasNoProperties, key, 'k', 'e', 'n', 'p', 'q', 'dp', 'dq', 'qi', 'y') | ||
test(`${crv} OKP Private key`, hasProperties, key, 'x', 'd') | ||
test(`${crv} OKP Private key`, hasProperty, key, 'alg', undefined) | ||
test(`${crv} OKP Private key`, hasProperty, key, 'kid', kid) | ||
test(`${crv} OKP Private key`, hasProperty, key, 'kty', 'OKP') | ||
test(`${crv} OKP Private key`, hasProperty, key, 'private', true) | ||
test(`${crv} OKP Private key`, hasProperty, key, 'public', false) | ||
test(`${crv} OKP Private key`, hasProperty, key, 'secret', false) | ||
test(`${crv} OKP Private key`, hasProperty, key, 'type', 'private') | ||
test(`${crv} OKP Private key`, hasProperty, key, 'use', undefined) | ||
|
||
test(`${crv} OKP Private key algorithms (no operation)`, t => { | ||
const result = key.algorithms() | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], [])//, 'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']) | ||
}) | ||
|
||
test(`${crv} OKP Private key algorithms (no operation, w/ alg)`, t => { | ||
const key = new OKPKey(keyObject, { alg }) | ||
const result = key.algorithms() | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], [])// [alg]) | ||
}) | ||
|
||
test(`${crv} OKP Private key does not support sign alg (no use)`, t => { | ||
const result = key.algorithms('sign') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Private key does not support verify alg (no use)`, t => { | ||
const result = key.algorithms('verify') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Private key .algorithms("encrypt")`, t => { | ||
const result = key.algorithms('encrypt') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Private key .algorithms("decrypt")`, t => { | ||
const result = key.algorithms('decrypt') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Private key .algorithms("wrapKey")`, t => { | ||
const result = key.algorithms('wrapKey') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], [])// ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']) | ||
}) | ||
|
||
test(`${crv} OKP Private key .algorithms("wrapKey") when use is sig`, t => { | ||
const sigKey = new OKPKey(keyObject, { use: 'sig' }) | ||
const result = sigKey.algorithms('wrapKey') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Private key .algorithms("unwrapKey")`, t => { | ||
const result = key.algorithms('unwrapKey') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], [])// ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']) | ||
}) | ||
})() | ||
|
||
// public | ||
;(() => { | ||
const keyObject = createPublicKey(fixtures.PEM[crv].public) | ||
const key = new OKPKey(keyObject) | ||
|
||
test(`${crv} OKP Public key (with alg)`, hasProperty, new OKPKey(keyObject, { alg }), 'alg', alg) | ||
test(`${crv} OKP Public key (with kid)`, hasProperty, new OKPKey(keyObject, { kid: 'foobar' }), 'kid', 'foobar') | ||
test(`${crv} OKP Public key (with use)`, hasProperty, new OKPKey(keyObject, { use: 'sig' }), 'use', 'sig') | ||
test(`${crv} OKP Public key`, hasNoProperties, key, 'k', 'e', 'n', 'p', 'q', 'dp', 'dq', 'qi', 'd', 'y') | ||
test(`${crv} OKP Public key`, hasProperties, key, 'x') | ||
test(`${crv} OKP Public key`, hasProperty, key, 'alg', undefined) | ||
test(`${crv} OKP Public key`, hasProperty, key, 'kid', kid) | ||
test(`${crv} OKP Public key`, hasProperty, key, 'kty', 'OKP') | ||
test(`${crv} OKP Public key`, hasProperty, key, 'private', false) | ||
test(`${crv} OKP Public key`, hasProperty, key, 'public', true) | ||
test(`${crv} OKP Public key`, hasProperty, key, 'secret', false) | ||
test(`${crv} OKP Public key`, hasProperty, key, 'type', 'public') | ||
test(`${crv} OKP Public key`, hasProperty, key, 'use', undefined) | ||
|
||
test(`${crv} OKP Public key algorithms (no operation)`, t => { | ||
const result = key.algorithms() | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], [])//, 'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']) | ||
}) | ||
|
||
test(`${crv} OKP Public key algorithms (no operation, w/ alg)`, t => { | ||
const key = new OKPKey(keyObject, { alg }) | ||
const result = key.algorithms() | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], [])// [alg]) | ||
}) | ||
|
||
test(`${crv} OKP Public key cannot sign`, t => { | ||
const result = key.algorithms('sign') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Public key does not support verify alg (no use)`, t => { | ||
const result = key.algorithms('verify') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Public key .algorithms("encrypt")`, t => { | ||
const result = key.algorithms('encrypt') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Public key .algorithms("decrypt")`, t => { | ||
const result = key.algorithms('decrypt') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
|
||
test(`${crv} OKP Public key .algorithms("wrapKey")`, t => { | ||
const result = key.algorithms('wrapKey') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], [])// ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']) | ||
}) | ||
|
||
test(`${crv} OKP Public key .algorithms("unwrapKey")`, t => { | ||
const result = key.algorithms('unwrapKey') | ||
t.is(result.constructor, Set) | ||
t.deepEqual([...result], []) | ||
}) | ||
})() | ||
}) |