@atproto/jwk-jose 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE.txt +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/jose-key.d.ts +23 -0
- package/dist/jose-key.d.ts.map +1 -0
- package/dist/jose-key.js +128 -0
- package/dist/jose-key.js.map +1 -0
- package/dist/util.d.ts +2 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +11 -0
- package/dist/util.js.map +1 -0
- package/package.json +36 -0
- package/src/index.ts +1 -0
- package/src/jose-key.ts +194 -0
- package/src/util.ts +9 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @atproto/jwk-jose
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2482](https://github.com/bluesky-social/atproto/pull/2482) [`a8d6c1123`](https://github.com/bluesky-social/atproto/commit/a8d6c112359f5c4c0cfbe2df63443ed275f2a646) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add OAuth provider capability & support for DPoP signed tokens
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`a8d6c1123`](https://github.com/bluesky-social/atproto/commit/a8d6c112359f5c4c0cfbe2df63443ed275f2a646)]:
|
|
12
|
+
- @atproto/jwk@0.1.0
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Dual MIT/Apache-2.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2024 Bluesky PBC, and Contributors
|
|
4
|
+
|
|
5
|
+
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
|
+
|
|
7
|
+
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./jose-key.js"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA6B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type GenerateKeyPairOptions, type GenerateKeyPairResult, type KeyLike } from 'jose';
|
|
2
|
+
import { Jwk, JwtHeader, JwtPayload, Key, SignedJwt, VerifyOptions, VerifyPayload, VerifyResult } from '@atproto/jwk';
|
|
3
|
+
export type Importable = string | KeyLike | Jwk;
|
|
4
|
+
export type { GenerateKeyPairOptions, GenerateKeyPairResult };
|
|
5
|
+
export declare class JoseKey extends Key {
|
|
6
|
+
#private;
|
|
7
|
+
protected getKey(): Promise<KeyLike | Uint8Array>;
|
|
8
|
+
createJwt(header: JwtHeader, payload: JwtPayload): Promise<`${string}.${string}.${string}`>;
|
|
9
|
+
verifyJwt<P extends VerifyPayload = JwtPayload, C extends string = string>(token: SignedJwt, options?: VerifyOptions<C>): Promise<VerifyResult<P, C>>;
|
|
10
|
+
static generateKeyPair(allowedAlgos?: readonly string[], options?: GenerateKeyPairOptions): Promise<GenerateKeyPairResult<KeyLike>>;
|
|
11
|
+
static generate(allowedAlgos?: string[], kid?: string, options?: Omit<GenerateKeyPairOptions, 'extractable'>): Promise<JoseKey>;
|
|
12
|
+
static fromImportable(input: Importable, kid?: string): Promise<JoseKey>;
|
|
13
|
+
/**
|
|
14
|
+
* @see {@link exportJWK}
|
|
15
|
+
*/
|
|
16
|
+
static fromKeyLike(keyLike: KeyLike | Uint8Array, kid?: string, alg?: string): Promise<JoseKey>;
|
|
17
|
+
/**
|
|
18
|
+
* @see {@link importPKCS8}
|
|
19
|
+
*/
|
|
20
|
+
static fromPKCS8(pem: string, alg: string, kid?: string): Promise<JoseKey>;
|
|
21
|
+
static fromJWK(input: string | Record<string, unknown>, inputKid?: string): Promise<JoseKey>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=jose-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jose-key.d.ts","sourceRoot":"","sources":["../src/jose-key.ts"],"names":[],"mappings":"AACA,OAAO,EAOL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAG1B,KAAK,OAAO,EACb,MAAM,MAAM,CAAA;AAGb,OAAO,EACL,GAAG,EAGH,SAAS,EACT,UAAU,EACV,GAAG,EACH,SAAS,EACT,aAAa,EACb,aAAa,EACb,YAAY,EAEb,MAAM,cAAc,CAAA;AAGrB,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,GAAG,CAAA;AAE/C,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,CAAA;AAE7D,qBAAa,OAAQ,SAAQ,GAAG;;cAGd,MAAM;IAQhB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU;IAmBhD,SAAS,CACb,CAAC,SAAS,aAAa,GAAG,UAAU,EACpC,CAAC,SAAS,MAAM,GAAG,MAAM,EACzB,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;WAkB/D,eAAe,CAC1B,YAAY,GAAE,SAAS,MAAM,EAAc,EAC3C,OAAO,CAAC,EAAE,sBAAsB;WAoBrB,QAAQ,CACnB,YAAY,GAAE,MAAM,EAAc,EAClC,GAAG,CAAC,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC;WAS1C,cAAc,CACzB,KAAK,EAAE,UAAU,EACjB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC;IA8BnB;;OAEG;WACU,WAAW,CACtB,OAAO,EAAE,OAAO,GAAG,UAAU,EAC7B,GAAG,CAAC,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC;IASnB;;OAEG;WACU,SAAS,CACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC;WAKN,OAAO,CAClB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;CASpB"}
|
package/dist/jose-key.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JoseKey = void 0;
|
|
4
|
+
const jwk_1 = require("@atproto/jwk");
|
|
5
|
+
const jose_1 = require("jose");
|
|
6
|
+
const errors_1 = require("jose/errors");
|
|
7
|
+
const jwk_2 = require("@atproto/jwk");
|
|
8
|
+
const util_1 = require("./util");
|
|
9
|
+
class JoseKey extends jwk_2.Key {
|
|
10
|
+
#keyObj;
|
|
11
|
+
async getKey() {
|
|
12
|
+
try {
|
|
13
|
+
return (this.#keyObj ||= await (0, jose_1.importJWK)(this.jwk));
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
throw new jwk_2.JwkError('Failed to import JWK', undefined, { cause });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async createJwt(header, payload) {
|
|
20
|
+
if (header.kid && header.kid !== this.kid) {
|
|
21
|
+
throw new jwk_2.JwtCreateError(`Invalid "kid" (${header.kid}) used to sign with key "${this.kid}"`);
|
|
22
|
+
}
|
|
23
|
+
if (!header.alg || !this.algorithms.includes(header.alg)) {
|
|
24
|
+
throw new jwk_2.JwtCreateError(`Invalid "alg" (${header.alg}) used to sign with key "${this.kid}"`);
|
|
25
|
+
}
|
|
26
|
+
const keyObj = await this.getKey();
|
|
27
|
+
return new jose_1.SignJWT(payload)
|
|
28
|
+
.setProtectedHeader({ ...header, kid: this.kid })
|
|
29
|
+
.sign(keyObj);
|
|
30
|
+
}
|
|
31
|
+
async verifyJwt(token, options) {
|
|
32
|
+
try {
|
|
33
|
+
const keyObj = await this.getKey();
|
|
34
|
+
const result = await (0, jose_1.jwtVerify)(token, keyObj, {
|
|
35
|
+
...options,
|
|
36
|
+
algorithms: this.algorithms,
|
|
37
|
+
});
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof errors_1.JOSEError) {
|
|
42
|
+
throw new jwk_1.JwtVerifyError(error.message, error.code, { cause: error });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
throw jwk_1.JwtVerifyError.from(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
static async generateKeyPair(allowedAlgos = ['ES256'], options) {
|
|
50
|
+
if (!allowedAlgos.length) {
|
|
51
|
+
throw new jwk_2.JwkError('No algorithms provided for key generation');
|
|
52
|
+
}
|
|
53
|
+
const errors = [];
|
|
54
|
+
for (const alg of allowedAlgos) {
|
|
55
|
+
try {
|
|
56
|
+
return await (0, jose_1.generateKeyPair)(alg, options);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
errors.push(err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new jwk_2.JwkError('Failed to generate key pair', undefined, {
|
|
63
|
+
cause: new AggregateError(errors, 'None of the algorithms worked'),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
static async generate(allowedAlgos = ['ES256'], kid, options) {
|
|
67
|
+
const kp = await this.generateKeyPair(allowedAlgos, {
|
|
68
|
+
...options,
|
|
69
|
+
extractable: true,
|
|
70
|
+
});
|
|
71
|
+
return this.fromImportable(kp.privateKey, kid);
|
|
72
|
+
}
|
|
73
|
+
static async fromImportable(input, kid) {
|
|
74
|
+
if (typeof input === 'string') {
|
|
75
|
+
// PKCS8
|
|
76
|
+
if (input.startsWith('-----')) {
|
|
77
|
+
// The "alg" is only needed in WebCrypto (NodeJS will be fine)
|
|
78
|
+
return this.fromPKCS8(input, '', kid);
|
|
79
|
+
}
|
|
80
|
+
// Jwk (string)
|
|
81
|
+
if (input.startsWith('{')) {
|
|
82
|
+
return this.fromJWK(input, kid);
|
|
83
|
+
}
|
|
84
|
+
throw new jwk_2.JwkError('Invalid input');
|
|
85
|
+
}
|
|
86
|
+
if (typeof input === 'object') {
|
|
87
|
+
// Jwk
|
|
88
|
+
if ('kty' in input || 'alg' in input) {
|
|
89
|
+
return this.fromJWK(input, kid);
|
|
90
|
+
}
|
|
91
|
+
// KeyLike
|
|
92
|
+
if (!kid)
|
|
93
|
+
throw new jwk_2.JwkError('Missing "kid" for KeyLike key');
|
|
94
|
+
return this.fromKeyLike(input, kid);
|
|
95
|
+
}
|
|
96
|
+
throw new jwk_2.JwkError('Invalid input');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @see {@link exportJWK}
|
|
100
|
+
*/
|
|
101
|
+
static async fromKeyLike(keyLike, kid, alg) {
|
|
102
|
+
const jwk = await (0, jose_1.exportJWK)(keyLike);
|
|
103
|
+
if (alg) {
|
|
104
|
+
if (!jwk.alg)
|
|
105
|
+
jwk.alg = alg;
|
|
106
|
+
else if (jwk.alg !== alg)
|
|
107
|
+
throw new jwk_2.JwkError('Invalid "alg" in JWK');
|
|
108
|
+
}
|
|
109
|
+
return this.fromJWK(jwk, kid);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* @see {@link importPKCS8}
|
|
113
|
+
*/
|
|
114
|
+
static async fromPKCS8(pem, alg, kid) {
|
|
115
|
+
const keyLike = await (0, jose_1.importPKCS8)(pem, alg, { extractable: true });
|
|
116
|
+
return this.fromKeyLike(keyLike, kid);
|
|
117
|
+
}
|
|
118
|
+
static async fromJWK(input, inputKid) {
|
|
119
|
+
const jwk = typeof input === 'string' ? JSON.parse(input) : input;
|
|
120
|
+
if (!jwk || typeof jwk !== 'object')
|
|
121
|
+
throw new jwk_2.JwkError('Invalid JWK');
|
|
122
|
+
const kid = (0, util_1.either)(jwk.kid, inputKid);
|
|
123
|
+
const use = jwk.use || 'sig';
|
|
124
|
+
return new JoseKey(jwk_2.jwkValidator.parse({ ...jwk, kid, use }));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.JoseKey = JoseKey;
|
|
128
|
+
//# sourceMappingURL=jose-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jose-key.js","sourceRoot":"","sources":["../src/jose-key.ts"],"names":[],"mappings":"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"}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,OAAO,EACxD,CAAC,CAAC,EAAE,CAAC,EACL,CAAC,CAAC,EAAE,CAAC,GACJ,CAAC,GAAG,SAAS,CAKf"}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.either = void 0;
|
|
4
|
+
function either(a, b) {
|
|
5
|
+
if (a != null && b != null && a !== b) {
|
|
6
|
+
throw new TypeError(`Expected "${b}", got "${a}"`);
|
|
7
|
+
}
|
|
8
|
+
return a ?? b ?? undefined;
|
|
9
|
+
}
|
|
10
|
+
exports.either = either;
|
|
11
|
+
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAA,SAAgB,MAAM,CACpB,CAAK,EACL,CAAK;IAEL,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,SAAS,CAAC,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,IAAI,SAAS,CAAA;AAC5B,CAAC;AARD,wBAQC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atproto/jwk-jose",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "`jose` based implementation of @atproto/jwk Key's",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"atproto",
|
|
8
|
+
"jwk",
|
|
9
|
+
"jose"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://atproto.com",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/bluesky-social/atproto",
|
|
15
|
+
"directory": "packages/oauth/jwk-jose"
|
|
16
|
+
},
|
|
17
|
+
"type": "commonjs",
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"jose": "^5.2.0",
|
|
28
|
+
"@atproto/jwk": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.3.3"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc --build tsconfig.json"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './jose-key.js'
|
package/src/jose-key.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { JwtVerifyError } from '@atproto/jwk'
|
|
2
|
+
import {
|
|
3
|
+
SignJWT,
|
|
4
|
+
exportJWK,
|
|
5
|
+
generateKeyPair,
|
|
6
|
+
importJWK,
|
|
7
|
+
importPKCS8,
|
|
8
|
+
jwtVerify,
|
|
9
|
+
type GenerateKeyPairOptions,
|
|
10
|
+
type GenerateKeyPairResult,
|
|
11
|
+
type JWK,
|
|
12
|
+
type JWTVerifyOptions,
|
|
13
|
+
type KeyLike,
|
|
14
|
+
} from 'jose'
|
|
15
|
+
import { JOSEError } from 'jose/errors'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
Jwk,
|
|
19
|
+
JwkError,
|
|
20
|
+
JwtCreateError,
|
|
21
|
+
JwtHeader,
|
|
22
|
+
JwtPayload,
|
|
23
|
+
Key,
|
|
24
|
+
SignedJwt,
|
|
25
|
+
VerifyOptions,
|
|
26
|
+
VerifyPayload,
|
|
27
|
+
VerifyResult,
|
|
28
|
+
jwkValidator,
|
|
29
|
+
} from '@atproto/jwk'
|
|
30
|
+
import { either } from './util'
|
|
31
|
+
|
|
32
|
+
export type Importable = string | KeyLike | Jwk
|
|
33
|
+
|
|
34
|
+
export type { GenerateKeyPairOptions, GenerateKeyPairResult }
|
|
35
|
+
|
|
36
|
+
export class JoseKey extends Key {
|
|
37
|
+
#keyObj?: KeyLike | Uint8Array
|
|
38
|
+
|
|
39
|
+
protected async getKey() {
|
|
40
|
+
try {
|
|
41
|
+
return (this.#keyObj ||= await importJWK(this.jwk as JWK))
|
|
42
|
+
} catch (cause) {
|
|
43
|
+
throw new JwkError('Failed to import JWK', undefined, { cause })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async createJwt(header: JwtHeader, payload: JwtPayload) {
|
|
48
|
+
if (header.kid && header.kid !== this.kid) {
|
|
49
|
+
throw new JwtCreateError(
|
|
50
|
+
`Invalid "kid" (${header.kid}) used to sign with key "${this.kid}"`,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!header.alg || !this.algorithms.includes(header.alg)) {
|
|
55
|
+
throw new JwtCreateError(
|
|
56
|
+
`Invalid "alg" (${header.alg}) used to sign with key "${this.kid}"`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const keyObj = await this.getKey()
|
|
61
|
+
return new SignJWT(payload)
|
|
62
|
+
.setProtectedHeader({ ...header, kid: this.kid })
|
|
63
|
+
.sign(keyObj) as Promise<SignedJwt>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async verifyJwt<
|
|
67
|
+
P extends VerifyPayload = JwtPayload,
|
|
68
|
+
C extends string = string,
|
|
69
|
+
>(token: SignedJwt, options?: VerifyOptions<C>): Promise<VerifyResult<P, C>> {
|
|
70
|
+
try {
|
|
71
|
+
const keyObj = await this.getKey()
|
|
72
|
+
const result = await jwtVerify(token, keyObj, {
|
|
73
|
+
...options,
|
|
74
|
+
algorithms: this.algorithms,
|
|
75
|
+
} as JWTVerifyOptions)
|
|
76
|
+
|
|
77
|
+
return result as VerifyResult<P, C>
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof JOSEError) {
|
|
80
|
+
throw new JwtVerifyError(error.message, error.code, { cause: error })
|
|
81
|
+
} else {
|
|
82
|
+
throw JwtVerifyError.from(error)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static async generateKeyPair(
|
|
88
|
+
allowedAlgos: readonly string[] = ['ES256'],
|
|
89
|
+
options?: GenerateKeyPairOptions,
|
|
90
|
+
) {
|
|
91
|
+
if (!allowedAlgos.length) {
|
|
92
|
+
throw new JwkError('No algorithms provided for key generation')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const errors: unknown[] = []
|
|
96
|
+
for (const alg of allowedAlgos) {
|
|
97
|
+
try {
|
|
98
|
+
return await generateKeyPair(alg, options)
|
|
99
|
+
} catch (err) {
|
|
100
|
+
errors.push(err)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw new JwkError('Failed to generate key pair', undefined, {
|
|
105
|
+
cause: new AggregateError(errors, 'None of the algorithms worked'),
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static async generate(
|
|
110
|
+
allowedAlgos: string[] = ['ES256'],
|
|
111
|
+
kid?: string,
|
|
112
|
+
options?: Omit<GenerateKeyPairOptions, 'extractable'>,
|
|
113
|
+
) {
|
|
114
|
+
const kp = await this.generateKeyPair(allowedAlgos, {
|
|
115
|
+
...options,
|
|
116
|
+
extractable: true,
|
|
117
|
+
})
|
|
118
|
+
return this.fromImportable(kp.privateKey, kid)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static async fromImportable(
|
|
122
|
+
input: Importable,
|
|
123
|
+
kid?: string,
|
|
124
|
+
): Promise<JoseKey> {
|
|
125
|
+
if (typeof input === 'string') {
|
|
126
|
+
// PKCS8
|
|
127
|
+
if (input.startsWith('-----')) {
|
|
128
|
+
// The "alg" is only needed in WebCrypto (NodeJS will be fine)
|
|
129
|
+
return this.fromPKCS8(input, '', kid)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Jwk (string)
|
|
133
|
+
if (input.startsWith('{')) {
|
|
134
|
+
return this.fromJWK(input, kid)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw new JwkError('Invalid input')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (typeof input === 'object') {
|
|
141
|
+
// Jwk
|
|
142
|
+
if ('kty' in input || 'alg' in input) {
|
|
143
|
+
return this.fromJWK(input, kid)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// KeyLike
|
|
147
|
+
if (!kid) throw new JwkError('Missing "kid" for KeyLike key')
|
|
148
|
+
return this.fromKeyLike(input, kid)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
throw new JwkError('Invalid input')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @see {@link exportJWK}
|
|
156
|
+
*/
|
|
157
|
+
static async fromKeyLike(
|
|
158
|
+
keyLike: KeyLike | Uint8Array,
|
|
159
|
+
kid?: string,
|
|
160
|
+
alg?: string,
|
|
161
|
+
): Promise<JoseKey> {
|
|
162
|
+
const jwk = await exportJWK(keyLike)
|
|
163
|
+
if (alg) {
|
|
164
|
+
if (!jwk.alg) jwk.alg = alg
|
|
165
|
+
else if (jwk.alg !== alg) throw new JwkError('Invalid "alg" in JWK')
|
|
166
|
+
}
|
|
167
|
+
return this.fromJWK(jwk, kid)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @see {@link importPKCS8}
|
|
172
|
+
*/
|
|
173
|
+
static async fromPKCS8(
|
|
174
|
+
pem: string,
|
|
175
|
+
alg: string,
|
|
176
|
+
kid?: string,
|
|
177
|
+
): Promise<JoseKey> {
|
|
178
|
+
const keyLike = await importPKCS8(pem, alg, { extractable: true })
|
|
179
|
+
return this.fromKeyLike(keyLike, kid)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static async fromJWK(
|
|
183
|
+
input: string | Record<string, unknown>,
|
|
184
|
+
inputKid?: string,
|
|
185
|
+
): Promise<JoseKey> {
|
|
186
|
+
const jwk = typeof input === 'string' ? JSON.parse(input) : input
|
|
187
|
+
if (!jwk || typeof jwk !== 'object') throw new JwkError('Invalid JWK')
|
|
188
|
+
|
|
189
|
+
const kid = either(jwk.kid, inputKid)
|
|
190
|
+
const use = jwk.use || 'sig'
|
|
191
|
+
|
|
192
|
+
return new JoseKey(jwkValidator.parse({ ...jwk, kid, use }))
|
|
193
|
+
}
|
|
194
|
+
}
|
package/src/util.ts
ADDED
package/tsconfig.json
ADDED