Skip to content

Commit 23b874c

Browse files
committed
feat: add JWK key_ops support, fix .algorithms() op returns
BREAKING CHANGE: key.algorithms(op) un+wrapKey was split into correct wrapKey/unwrapKey/deriveKey returns BREAKING CHANGE: keystore.all and keystore.get `operation` option was removed, `key_ops: string[]` supersedes it
1 parent 4ace4be commit 23b874c

40 files changed

+556
-173
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ Won't implement:
8181
- no crypto, no use
8282

8383
Not Planned / PR | Use-Case | Discussion Welcome:
84-
- ◯ automatically adding `kid` reference to JWS / JWE Headers
8584
-`x5c`, `x5t`, `x5t#S256`, `x5u` etc `JWK.Key` fields
8685

8786
</details>

docs/README.md

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ I can continue maintaining it and adding new features carefree. You may also don
2929
- [key.alg](#keyalg)
3030
- [key.use](#keyuse)
3131
- [key.kid](#keykid)
32+
- [key.key_ops](#keykey_ops)
3233
- [key.thumbprint](#keythumbprint)
3334
- [key.type](#keytype)
3435
- [key.public](#keypublic)
@@ -116,6 +117,16 @@ defined in [RFC7638][spec-thumbprint].
116117

117118
---
118119

120+
#### `key.key_ops`
121+
122+
Returns the key's JWK Key Operations Parameter if set. If set the key can only be used for the
123+
specified operations. Supported values are 'sign', 'verify', 'encrypt', 'decrypt', 'wrapKey',
124+
'unwrapKey' and 'deriveKey'.
125+
126+
- `string[]`
127+
128+
---
129+
119130
#### `key.thumbprint`
120131

121132
Returns the key's JWK Key thumbprint calculated using the method defined in [RFC7638][spec-thumbprint].
@@ -415,11 +426,15 @@ Securely generates a new RSA, EC, OKP or oct key.
415426
- `crvOrSize`: `<number>` &vert; `<string>` key's bit size or in case of OKP and EC keys the curve
416427
**Default:** 2048 for RSA, 'P-256' for EC, 'Ed25519' for OKP and 256 for oct.
417428
- `options`: `<Object>`
418-
- `alg`: `<string>` option identifies the algorithm intended for use with the key.
429+
- `alg`: `<string>` Key Algorithm Parameter. It identifies the algorithm intended for use with the
430+
key.
419431
- `kid`: `<string>` Key ID Parameter. When not provided is computed using the method defined in
420-
[RFC7638][spec-thumbprint]
421-
- `use`: `<string>` option indicates whether the key is to be used for encrypting & decrypting
422-
data or signing & verifying data. Must be 'sig' or 'enc'.
432+
[RFC7638][spec-thumbprint].
433+
- `use`: `<string>` Public Key Use Parameter. Indicates whether the key is to be used for
434+
encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'.
435+
- `key_ops`: `string[]` Key Operations Parameter. If set, the key can only be used for the
436+
specified operations. Supported values are 'sign', 'verify', 'encrypt', 'decrypt', 'wrapKey',
437+
'unwrapKey' and 'deriveKey'.
423438
- `private`: `<boolean>` **Default** 'true'. Is the resulting key private or public (when
424439
asymmetrical)
425440
- Returns: `Promise<JWK.RSAKey>` &vert; `Promise<JWK.ECKey>` &vert; `Promise<JWK.OKPKey>` &vert; `Promise<JWK.OctKey>`
@@ -454,11 +469,15 @@ Synchronous version of `JWK.generate()`
454469
- `crvOrSize`: `<number>` &vert; `<string>` key's bit size or in case of OKP and EC keys the curve.
455470
**Default:** 2048 for RSA, 'P-256' for EC, 'Ed25519' for OKP and 256 for oct.
456471
- `options`: `<Object>`
457-
- `alg`: `<string>` option identifies the algorithm intended for use with the key.
458-
- `use`: `<string>` option indicates whether the key is to be used for encrypting & decrypting
459-
data or signing & verifying data. Must be 'sig' or 'enc'.
472+
- `alg`: `<string>` Key Algorithm Parameter. It identifies the algorithm intended for use with the
473+
key.
460474
- `kid`: `<string>` Key ID Parameter. When not provided is computed using the method defined in
461-
[RFC7638][spec-thumbprint]
475+
[RFC7638][spec-thumbprint].
476+
- `use`: `<string>` Public Key Use Parameter. Indicates whether the key is to be used for
477+
encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'.
478+
- `key_ops`: `string[]` Key Operations Parameter. If set, the key can only be used for the
479+
specified operations. Supported values are 'sign', 'verify', 'encrypt', 'decrypt', 'wrapKey',
480+
'unwrapKey' and 'deriveKey'.
462481
- `private`: `<boolean>` **Default** 'true'. Is the resulting key private or public (when
463482
asymmetrical)
464483
- Returns: `<JWK.RSAKey>` &vert; `<JWK.ECKey>` &vert; `<JWK.OKPKey>` &vert; `<JWK.OctKey>`
@@ -551,10 +570,12 @@ specified by the parameters are first.
551570
- `parameters`: `<Object>`
552571
- `kty`: `<string>` Key Type to filter for.
553572
- `alg`: `<string>` Key supported algorithm to filter for.
554-
- `use`: `<string>` Key use to filter for.
555573
- `kid`: `<string>` Key ID to filter for.
556-
- `operation`: `<string>` Further specify the operation a given alg must be valid for. Must be one
557-
of 'encrypt', 'decrypt', 'sign', 'verify', 'wrapKey', 'unwrapKey'
574+
- `use`: `<string>` Filter keys with the specified use defined. Keys missing "use" parameter will
575+
be matched but rank lower then ones with an exact match.
576+
- `key_ops`: `string[]` Filter keys with specified key_ops defined (if key_ops is defined on the
577+
key). Keys missing "key_ops" parameter will be matched but rank lower then ones with matching
578+
entries.
558579
- Returns: `<Key[]>` Array of key instances or an empty array when none are matching the parameters.
559580

560581
---
@@ -567,10 +588,12 @@ parameters is returned.
567588
- `parameters`: `<Object>`
568589
- `kty`: `<string>` Key Type to filter for.
569590
- `alg`: `<string>` Key supported algorithm to filter for.
570-
- `use`: `<string>` Key use to filter for.
571591
- `kid`: `<string>` Key ID to filter for.
572-
- `operation`: `<string>` Further specify the operation a given alg must be valid for. Must be one
573-
of 'encrypt', 'decrypt', 'sign', 'verify', 'wrapKey', 'unwrapKey'
592+
- `use`: `<string>` Filter keys with the specified use defined. Keys missing "use" parameter will
593+
be matched but rank lower then ones with an exact match.
594+
- `key_ops`: `string[]` Filter keys with specified key_ops defined (if key_ops is defined on the
595+
key). Keys missing "key_ops" parameter will be matched but rank lower then ones with matching
596+
entries.
574597
- Returns: `<JWK.RSAKey>` &vert; `<JWK.ECKey>` &vert; `<JWK.OKPKey>` &vert; `<JWK.OctKey>` &vert; `<undefined>`
575598

576599
---
@@ -1206,6 +1229,7 @@ Verifies the provided JWE in either serialization with a given `<JWK.Key>` or `<
12061229
- [Class: &lt;JWEDecryptionFailed&gt;](#class-jwedecryptionfailed)
12071230
- [Class: &lt;JWEInvalid&gt;](#class-jweinvalid)
12081231
- [Class: &lt;JWKImportFailed&gt;](#class-jwkimportfailed)
1232+
- [Class: &lt;JWKKeyInvalid&gt;](#class-jwkkeyinvalid)
12091233
- [Class: &lt;JWKKeySupport&gt;](#class-jwkkeysupport)
12101234
- [Class: &lt;JWKSNoMatchingKey&gt;](#class-jwksnomatchingkey)
12111235
- [Class: &lt;JWSInvalid&gt;](#class-jwsinvalid)
@@ -1311,6 +1335,16 @@ if (err.code === 'ERR_JWK_IMPORT_FAILED') {
13111335
}
13121336
```
13131337

1338+
#### Class: `JWKKeyInvalid`
1339+
1340+
Thrown when key's parameters are invalid, e.g. key_ops and use values are inconsistent.
1341+
1342+
```js
1343+
if (err.code === 'ERR_JWK_INVALID') {
1344+
// ...
1345+
}
1346+
```
1347+
13141348
#### Class: `JWKKeySupport`
13151349

13161350
Thrown when a key does not support the request algorithm.

lib/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const CODES = {
66
JWEDecryptionFailed: 'ERR_JWE_DECRYPTION_FAILED',
77
JWEInvalid: 'ERR_JWE_INVALID',
88
JWKImportFailed: 'ERR_JWK_IMPORT_FAILED',
9+
JWKInvalid: 'ERR_JWK_INVALID',
910
JWKKeySupport: 'ERR_JWK_KEY_SUPPORT',
1011
JWKSNoMatchingKey: 'ERR_JWKS_NO_MATCHING_KEY',
1112
JWSInvalid: 'ERR_JWS_INVALID',
@@ -63,6 +64,7 @@ module.exports.JWEDecryptionFailed = class JWEDecryptionFailed extends JOSEError
6364
module.exports.JWEInvalid = class JWEInvalid extends JOSEError {}
6465

6566
module.exports.JWKImportFailed = class JWKImportFailed extends JOSEError {}
67+
module.exports.JWKInvalid = class JWKInvalid extends JOSEError {}
6668
module.exports.JWKKeySupport = class JWKKeySupport extends JOSEError {}
6769

6870
module.exports.JWKSNoMatchingKey = class JWKSNoMatchingKey extends JOSEError {}

lib/help/consts.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module.exports.KEYOBJECT = Symbol('KEYOBJECT')
2+
module.exports.PRIVATE_MEMBERS = Symbol('PRIVATE_MEMBERS')
3+
module.exports.PUBLIC_MEMBERS = Symbol('PUBLIC_MEMBERS')
4+
module.exports.THUMBPRINT_MATERIAL = Symbol('THUMBPRINT_MATERIAL')
5+
module.exports.JWK_MEMBERS = Symbol('JWK_MEMBERS')
6+
module.exports.KEY_MANAGEMENT_ENCRYPT = Symbol('KEY_MANAGEMENT_ENCRYPT')
7+
module.exports.KEY_MANAGEMENT_DECRYPT = Symbol('KEY_MANAGEMENT_DECRYPT')
8+
9+
const USES_MAPPING = {
10+
sig: new Set(['sign', 'verify']),
11+
enc: new Set(['encrypt', 'decrypt', 'wrapKey', 'unwrapKey', 'deriveKey'])
12+
}
13+
const OPS = new Set([...USES_MAPPING.sig, ...USES_MAPPING.enc])
14+
const USES = new Set(Object.keys(USES_MAPPING))
15+
16+
module.exports.USES_MAPPING = USES_MAPPING
17+
module.exports.OPS = OPS
18+
module.exports.USES = USES
19+
20+
module.exports.OKP_CURVES = new Set(['Ed25519', 'Ed448', 'X25519', 'X448'])
21+
module.exports.EC_CURVES = new Set(['P-256', 'P-384', 'P-521'])
22+
module.exports.ECDH_ALGS = ['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']
23+
24+
module.exports.KEYLENGTHS = {
25+
'A128CBC-HS256': 256,
26+
'A192CBC-HS384': 384,
27+
'A256CBC-HS512': 512,
28+
'A128GCM': 128,
29+
'A192GCM': 192,
30+
'A256GCM': 256
31+
}

lib/help/key_lengths.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

lib/help/key_utils.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ const { createPublicKey } = require('crypto')
33
const base64url = require('./base64url')
44
const errors = require('../errors')
55
const asn1 = require('./asn1')
6-
7-
const EC_CURVES = new Set(['P-256', 'P-384', 'P-521'])
8-
const OKP_CURVES = new Set(['Ed25519', 'Ed448', 'X25519', 'X448'])
6+
const { OKP_CURVES, EC_CURVES } = require('./consts')
97

108
const oidHexToCurve = new Map([
119
['06082a8648ce3d030107', 'P-256'],

lib/help/symbols.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

lib/index.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
import { KeyObject, PrivateKeyInput, PublicKeyInput } from 'crypto'
44

55
type use = 'sig' | 'enc'
6+
type keyOperation = 'sign' | 'verify' | 'encrypt' | 'decrypt' | 'wrapKey' | 'unwrapKey' | 'deriveKey'
67
interface KeyParameters {
78
alg?: string
89
use?: use
910
kid?: string
11+
key_ops?: keyOperation[]
1012
}
1113
type ECCurve = 'P-256' | 'P-384' | 'P-521'
1214
type OKPCurve = 'Ed25519' | 'Ed448' | 'X25519' | 'X448'
1315
type keyType = 'RSA' | 'EC' | 'OKP' | 'oct'
14-
type keyOperation = 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'wrapKey' | 'unwrapKey'
1516
type asymmetricKeyObjectTypes = 'private' | 'public'
1617
type keyObjectTypes = asymmetricKeyObjectTypes | 'secret'
1718

@@ -31,6 +32,7 @@ export namespace JWK {
3132
secret: boolean
3233
alg?: string
3334
use?: use
35+
key_ops?: keyOperation[]
3436
kid: string
3537
thumbprint: string
3638

@@ -144,7 +146,6 @@ export namespace JWK {
144146
export namespace JWKS {
145147
interface KeyQuery extends KeyParameters {
146148
kty: keyType
147-
operation: keyOperation
148149
}
149150

150151
class KeyStore {
@@ -341,6 +342,7 @@ export namespace errors {
341342
export class JWEInvalid extends JOSEError {}
342343

343344
export class JWKImportFailed extends JOSEError {}
345+
export class JWKInvalid extends JOSEError {}
344346
export class JWKKeySupport extends JOSEError {}
345347

346348
export class JWKSNoMatchingKey extends JOSEError {}

lib/jwa/aes_cbc_hmac_sha2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { createCipheriv, createDecipheriv } = require('crypto')
33

44
const uint64be = require('../help/uint64be')
55
const timingSafeEqual = require('../help/timing_safe_equal')
6-
const { KEYOBJECT } = require('../help/symbols')
6+
const { KEYOBJECT } = require('../help/consts')
77
const { JWEInvalid, JWEDecryptionFailed } = require('../errors')
88

99
const checkInput = function (size, iv, tag) {

lib/jwa/aes_gcm.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { strict: assert } = require('assert')
22
const { createCipheriv, createDecipheriv } = require('crypto')
33

4-
const { KEYOBJECT } = require('../help/symbols')
4+
const { KEYOBJECT } = require('../help/consts')
55
const { JWEInvalid, JWEDecryptionFailed } = require('../errors')
66

77
const checkInput = function (size, iv, tag) {

0 commit comments

Comments
 (0)