@atproto/jwk-webcrypto 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 ADDED
@@ -0,0 +1,13 @@
1
+ # @atproto/jwk-webcrypto
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-jose@0.1.0
13
+ - @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.
@@ -0,0 +1,2 @@
1
+ export * from './webcrypto-key.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,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("./webcrypto-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,qDAAkC"}
package/dist/util.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export type JWSAlgorithm = 'HS256' | 'HS384' | 'HS512' | 'PS256' | 'PS384' | 'PS512' | 'RS256' | 'RS384' | 'RS512' | 'ES256' | 'ES256K' | 'ES384' | 'ES512' | 'EdDSA';
2
+ export type SubtleAlgorithm = RsaHashedKeyGenParams | EcKeyGenParams;
3
+ export declare function toSubtleAlgorithm(alg: string, crv?: string, options?: {
4
+ modulusLength?: number;
5
+ }): SubtleAlgorithm;
6
+ export declare function fromSubtleAlgorithm(algorithm: KeyAlgorithm): JWSAlgorithm;
7
+ export declare function isCryptoKeyPair(v: unknown, extractable?: boolean): v is CryptoKeyPair;
8
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAEpB,OAAO,GACP,OAAO,GACP,OAAO,GAEP,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,GAEP,OAAO,GACP,QAAQ,GACR,OAAO,GACP,OAAO,GAEP,OAAO,CAAA;AAEX,MAAM,MAAM,eAAe,GAAG,qBAAqB,GAAG,cAAc,CAAA;AAEpE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC,eAAe,CAoCjB;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,YAAY,GAAG,YAAY,CAqCzE;AAED,wBAAgB,eAAe,CAC7B,CAAC,EAAE,OAAO,EACV,WAAW,CAAC,EAAE,OAAO,GACpB,CAAC,IAAI,aAAa,CAepB"}
package/dist/util.js ADDED
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isCryptoKeyPair = exports.fromSubtleAlgorithm = exports.toSubtleAlgorithm = void 0;
4
+ function toSubtleAlgorithm(alg, crv, options) {
5
+ switch (alg) {
6
+ case 'PS256':
7
+ case 'PS384':
8
+ case 'PS512':
9
+ return {
10
+ name: 'RSA-PSS',
11
+ hash: `SHA-${alg.slice(-3)}`,
12
+ modulusLength: options?.modulusLength ?? 2048,
13
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
14
+ };
15
+ case 'RS256':
16
+ case 'RS384':
17
+ case 'RS512':
18
+ return {
19
+ name: 'RSASSA-PKCS1-v1_5',
20
+ hash: `SHA-${alg.slice(-3)}`,
21
+ modulusLength: options?.modulusLength ?? 2048,
22
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
23
+ };
24
+ case 'ES256':
25
+ case 'ES384':
26
+ return {
27
+ name: 'ECDSA',
28
+ namedCurve: `P-${alg.slice(-3)}`,
29
+ };
30
+ case 'ES512':
31
+ return {
32
+ name: 'ECDSA',
33
+ namedCurve: 'P-521',
34
+ };
35
+ default:
36
+ // https://github.com/w3c/webcrypto/issues/82#issuecomment-849856773
37
+ throw new TypeError(`Unsupported alg "${alg}"`);
38
+ }
39
+ }
40
+ exports.toSubtleAlgorithm = toSubtleAlgorithm;
41
+ function fromSubtleAlgorithm(algorithm) {
42
+ switch (algorithm.name) {
43
+ case 'RSA-PSS':
44
+ case 'RSASSA-PKCS1-v1_5': {
45
+ const hash = algorithm.hash.name;
46
+ switch (hash) {
47
+ case 'SHA-256':
48
+ case 'SHA-384':
49
+ case 'SHA-512': {
50
+ const prefix = algorithm.name === 'RSA-PSS' ? 'PS' : 'RS';
51
+ return `${prefix}${hash.slice(-3)}`;
52
+ }
53
+ default:
54
+ throw new TypeError('unsupported RsaHashedKeyAlgorithm hash');
55
+ }
56
+ }
57
+ case 'ECDSA': {
58
+ const namedCurve = algorithm.namedCurve;
59
+ switch (namedCurve) {
60
+ case 'P-256':
61
+ case 'P-384':
62
+ case 'P-512':
63
+ return `ES${namedCurve.slice(-3)}`;
64
+ case 'P-521':
65
+ return 'ES512';
66
+ default:
67
+ throw new TypeError('unsupported EcKeyAlgorithm namedCurve');
68
+ }
69
+ }
70
+ case 'Ed448':
71
+ case 'Ed25519':
72
+ return 'EdDSA';
73
+ default:
74
+ // https://github.com/w3c/webcrypto/issues/82#issuecomment-849856773
75
+ throw new TypeError(`Unexpected algorithm "${algorithm.name}"`);
76
+ }
77
+ }
78
+ exports.fromSubtleAlgorithm = fromSubtleAlgorithm;
79
+ function isCryptoKeyPair(v, extractable) {
80
+ return (typeof v === 'object' &&
81
+ v !== null &&
82
+ 'privateKey' in v &&
83
+ v.privateKey instanceof CryptoKey &&
84
+ v.privateKey.type === 'private' &&
85
+ (extractable == null || v.privateKey.extractable === extractable) &&
86
+ v.privateKey.usages.includes('sign') &&
87
+ 'publicKey' in v &&
88
+ v.publicKey instanceof CryptoKey &&
89
+ v.publicKey.type === 'public' &&
90
+ v.publicKey.extractable === true &&
91
+ v.publicKey.usages.includes('verify'));
92
+ }
93
+ exports.isCryptoKeyPair = isCryptoKeyPair;
94
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { Jwk } from '@atproto/jwk';
2
+ import { GenerateKeyPairOptions, JoseKey } from '@atproto/jwk-jose';
3
+ export declare class WebcryptoKey extends JoseKey {
4
+ readonly cryptoKeyPair: CryptoKeyPair;
5
+ static generate(allowedAlgos?: string[], kid?: string, options?: GenerateKeyPairOptions): Promise<WebcryptoKey>;
6
+ static fromKeypair(cryptoKeyPair: CryptoKeyPair, kid?: string): Promise<WebcryptoKey>;
7
+ constructor(jwk: Jwk, cryptoKeyPair: CryptoKeyPair);
8
+ get isPrivate(): boolean;
9
+ get privateJwk(): Jwk | undefined;
10
+ protected getKey(): Promise<CryptoKey>;
11
+ }
12
+ //# sourceMappingURL=webcrypto-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webcrypto-key.d.ts","sourceRoot":"","sources":["../src/webcrypto-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAa,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAInE,qBAAa,YAAa,SAAQ,OAAO;IA8CrC,QAAQ,CAAC,aAAa,EAAE,aAAa;WA3CjB,QAAQ,CAC5B,YAAY,GAAE,MAAM,EAAc,EAClC,GAAG,GAAE,MAA4B,EACjC,OAAO,CAAC,EAAE,sBAAsB;WAYrB,WAAW,CAAC,aAAa,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,MAAM;gBA2BjE,GAAG,EAAE,GAAG,EACC,aAAa,EAAE,aAAa;IAKvC,IAAI,SAAS,YAEZ;IAED,IAAI,UAAU,IAAI,GAAG,GAAG,SAAS,CAGhC;cAEwB,MAAM;CAGhC"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebcryptoKey = void 0;
4
+ const jwk_1 = require("@atproto/jwk");
5
+ const jwk_jose_1 = require("@atproto/jwk-jose");
6
+ const util_js_1 = require("./util.js");
7
+ class WebcryptoKey extends jwk_jose_1.JoseKey {
8
+ // We need to override the static method generate from JoseKey because
9
+ // the browser needs both the private and public keys
10
+ static async generate(allowedAlgos = ['ES256'], kid = crypto.randomUUID(), options) {
11
+ const keyPair = await this.generateKeyPair(allowedAlgos, options);
12
+ // Type safety only: in the browser, 'jose' always generates a CryptoKeyPair
13
+ if (!(0, util_js_1.isCryptoKeyPair)(keyPair)) {
14
+ throw new TypeError('Invalid CryptoKeyPair');
15
+ }
16
+ return this.fromKeypair(keyPair, kid);
17
+ }
18
+ static async fromKeypair(cryptoKeyPair, kid) {
19
+ // https://datatracker.ietf.org/doc/html/rfc7517
20
+ // > The "use" and "key_ops" JWK members SHOULD NOT be used together; [...]
21
+ // > Applications should specify which of these members they use.
22
+ const { key_ops: _, ...jwk } = await crypto.subtle.exportKey('jwk', cryptoKeyPair.privateKey.extractable
23
+ ? cryptoKeyPair.privateKey
24
+ : cryptoKeyPair.publicKey);
25
+ const use = jwk.use ?? 'sig';
26
+ const alg = jwk.alg ?? (0, util_js_1.fromSubtleAlgorithm)(cryptoKeyPair.privateKey.algorithm);
27
+ if (use !== 'sig') {
28
+ throw new TypeError('Unsupported JWK use');
29
+ }
30
+ return new WebcryptoKey(jwk_1.jwkSchema.parse({ ...jwk, use, kid, alg }), cryptoKeyPair);
31
+ }
32
+ constructor(jwk, cryptoKeyPair) {
33
+ super(jwk);
34
+ Object.defineProperty(this, "cryptoKeyPair", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: cryptoKeyPair
39
+ });
40
+ }
41
+ get isPrivate() {
42
+ return true;
43
+ }
44
+ get privateJwk() {
45
+ if (super.isPrivate)
46
+ return this.jwk;
47
+ throw new Error('Private Webcrypto Key not exportable');
48
+ }
49
+ async getKey() {
50
+ return this.cryptoKeyPair.privateKey;
51
+ }
52
+ }
53
+ exports.WebcryptoKey = WebcryptoKey;
54
+ //# sourceMappingURL=webcrypto-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webcrypto-key.js","sourceRoot":"","sources":["../src/webcrypto-key.ts"],"names":[],"mappings":";;;AAAA,sCAA6C;AAC7C,gDAAmE;AAEnE,uCAAgE;AAEhE,MAAa,YAAa,SAAQ,kBAAO;IACvC,sEAAsE;IACtE,qDAAqD;IACrD,MAAM,CAAU,KAAK,CAAC,QAAQ,CAC5B,eAAyB,CAAC,OAAO,CAAC,EAClC,MAAc,MAAM,CAAC,UAAU,EAAE,EACjC,OAAgC;QAEhC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;QAEjE,4EAA4E;QAC5E,IAAI,CAAC,IAAA,yBAAe,EAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,SAAS,CAAC,uBAAuB,CAAC,CAAA;QAC9C,CAAC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,aAA4B,EAAE,GAAY;QACjE,gDAAgD;QAChD,2EAA2E;QAC3E,iEAAiE;QAEjE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAC1D,KAAK,EACL,aAAa,CAAC,UAAU,CAAC,WAAW;YAClC,CAAC,CAAC,aAAa,CAAC,UAAU;YAC1B,CAAC,CAAC,aAAa,CAAC,SAAS,CAC5B,CAAA;QAED,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,KAAK,CAAA;QAC5B,MAAM,GAAG,GACP,GAAG,CAAC,GAAG,IAAI,IAAA,6BAAmB,EAAC,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAEpE,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAA;QAC5C,CAAC;QAED,OAAO,IAAI,YAAY,CACrB,eAAS,CAAC,KAAK,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAC1C,aAAa,CACd,CAAA;IACH,CAAC;IAED,YACE,GAAQ,EACC,aAA4B;QAErC,KAAK,CAAC,GAAG,CAAC,CAAA;QAFV;;;;mBAAS,aAAa;WAAe;IAGvC,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,UAAU;QACZ,IAAI,KAAK,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,GAAG,CAAA;QACpC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;IACzD,CAAC;IAEkB,KAAK,CAAC,MAAM;QAC7B,OAAO,IAAI,CAAC,aAAa,CAAC,UAAU,CAAA;IACtC,CAAC;CACF;AA/DD,oCA+DC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@atproto/jwk-webcrypto",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "description": "Webcrypto based implementation of @atproto/jwk Key's",
6
+ "keywords": [
7
+ "atproto",
8
+ "jwk",
9
+ "webcrypto"
10
+ ],
11
+ "homepage": "https://atproto.com",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/bluesky-social/atproto",
15
+ "directory": "packages/oauth/jwk-webcrypto"
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
+ "@atproto/jwk": "0.1.0",
28
+ "@atproto/jwk-jose": "0.1.0"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.3.3"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc --build tsconfig.build.json"
35
+ }
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './webcrypto-key.js'
package/src/util.ts ADDED
@@ -0,0 +1,122 @@
1
+ export type JWSAlgorithm =
2
+ // HMAC
3
+ | 'HS256'
4
+ | 'HS384'
5
+ | 'HS512'
6
+ // RSA
7
+ | 'PS256'
8
+ | 'PS384'
9
+ | 'PS512'
10
+ | 'RS256'
11
+ | 'RS384'
12
+ | 'RS512'
13
+ // EC
14
+ | 'ES256'
15
+ | 'ES256K'
16
+ | 'ES384'
17
+ | 'ES512'
18
+ // OKP
19
+ | 'EdDSA'
20
+
21
+ export type SubtleAlgorithm = RsaHashedKeyGenParams | EcKeyGenParams
22
+
23
+ export function toSubtleAlgorithm(
24
+ alg: string,
25
+ crv?: string,
26
+ options?: { modulusLength?: number },
27
+ ): SubtleAlgorithm {
28
+ switch (alg) {
29
+ case 'PS256':
30
+ case 'PS384':
31
+ case 'PS512':
32
+ return {
33
+ name: 'RSA-PSS',
34
+ hash: `SHA-${alg.slice(-3) as '256' | '384' | '512'}`,
35
+ modulusLength: options?.modulusLength ?? 2048,
36
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
37
+ }
38
+ case 'RS256':
39
+ case 'RS384':
40
+ case 'RS512':
41
+ return {
42
+ name: 'RSASSA-PKCS1-v1_5',
43
+ hash: `SHA-${alg.slice(-3) as '256' | '384' | '512'}`,
44
+ modulusLength: options?.modulusLength ?? 2048,
45
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
46
+ }
47
+ case 'ES256':
48
+ case 'ES384':
49
+ return {
50
+ name: 'ECDSA',
51
+ namedCurve: `P-${alg.slice(-3) as '256' | '384'}`,
52
+ }
53
+ case 'ES512':
54
+ return {
55
+ name: 'ECDSA',
56
+ namedCurve: 'P-521',
57
+ }
58
+ default:
59
+ // https://github.com/w3c/webcrypto/issues/82#issuecomment-849856773
60
+
61
+ throw new TypeError(`Unsupported alg "${alg}"`)
62
+ }
63
+ }
64
+
65
+ export function fromSubtleAlgorithm(algorithm: KeyAlgorithm): JWSAlgorithm {
66
+ switch (algorithm.name) {
67
+ case 'RSA-PSS':
68
+ case 'RSASSA-PKCS1-v1_5': {
69
+ const hash = (<RsaHashedKeyAlgorithm>algorithm).hash.name
70
+ switch (hash) {
71
+ case 'SHA-256':
72
+ case 'SHA-384':
73
+ case 'SHA-512': {
74
+ const prefix = algorithm.name === 'RSA-PSS' ? 'PS' : 'RS'
75
+ return `${prefix}${hash.slice(-3) as '256' | '384' | '512'}`
76
+ }
77
+ default:
78
+ throw new TypeError('unsupported RsaHashedKeyAlgorithm hash')
79
+ }
80
+ }
81
+ case 'ECDSA': {
82
+ const namedCurve = (<EcKeyAlgorithm>algorithm).namedCurve
83
+ switch (namedCurve) {
84
+ case 'P-256':
85
+ case 'P-384':
86
+ case 'P-512':
87
+ return `ES${namedCurve.slice(-3) as '256' | '384' | '512'}`
88
+ case 'P-521':
89
+ return 'ES512'
90
+ default:
91
+ throw new TypeError('unsupported EcKeyAlgorithm namedCurve')
92
+ }
93
+ }
94
+ case 'Ed448':
95
+ case 'Ed25519':
96
+ return 'EdDSA'
97
+ default:
98
+ // https://github.com/w3c/webcrypto/issues/82#issuecomment-849856773
99
+
100
+ throw new TypeError(`Unexpected algorithm "${algorithm.name}"`)
101
+ }
102
+ }
103
+
104
+ export function isCryptoKeyPair(
105
+ v: unknown,
106
+ extractable?: boolean,
107
+ ): v is CryptoKeyPair {
108
+ return (
109
+ typeof v === 'object' &&
110
+ v !== null &&
111
+ 'privateKey' in v &&
112
+ v.privateKey instanceof CryptoKey &&
113
+ v.privateKey.type === 'private' &&
114
+ (extractable == null || v.privateKey.extractable === extractable) &&
115
+ v.privateKey.usages.includes('sign') &&
116
+ 'publicKey' in v &&
117
+ v.publicKey instanceof CryptoKey &&
118
+ v.publicKey.type === 'public' &&
119
+ v.publicKey.extractable === true &&
120
+ v.publicKey.usages.includes('verify')
121
+ )
122
+ }
@@ -0,0 +1,69 @@
1
+ import { Jwk, jwkSchema } from '@atproto/jwk'
2
+ import { GenerateKeyPairOptions, JoseKey } from '@atproto/jwk-jose'
3
+
4
+ import { fromSubtleAlgorithm, isCryptoKeyPair } from './util.js'
5
+
6
+ export class WebcryptoKey extends JoseKey {
7
+ // We need to override the static method generate from JoseKey because
8
+ // the browser needs both the private and public keys
9
+ static override async generate(
10
+ allowedAlgos: string[] = ['ES256'],
11
+ kid: string = crypto.randomUUID(),
12
+ options?: GenerateKeyPairOptions,
13
+ ) {
14
+ const keyPair = await this.generateKeyPair(allowedAlgos, options)
15
+
16
+ // Type safety only: in the browser, 'jose' always generates a CryptoKeyPair
17
+ if (!isCryptoKeyPair(keyPair)) {
18
+ throw new TypeError('Invalid CryptoKeyPair')
19
+ }
20
+
21
+ return this.fromKeypair(keyPair, kid)
22
+ }
23
+
24
+ static async fromKeypair(cryptoKeyPair: CryptoKeyPair, kid?: string) {
25
+ // https://datatracker.ietf.org/doc/html/rfc7517
26
+ // > The "use" and "key_ops" JWK members SHOULD NOT be used together; [...]
27
+ // > Applications should specify which of these members they use.
28
+
29
+ const { key_ops: _, ...jwk } = await crypto.subtle.exportKey(
30
+ 'jwk',
31
+ cryptoKeyPair.privateKey.extractable
32
+ ? cryptoKeyPair.privateKey
33
+ : cryptoKeyPair.publicKey,
34
+ )
35
+
36
+ const use = jwk.use ?? 'sig'
37
+ const alg =
38
+ jwk.alg ?? fromSubtleAlgorithm(cryptoKeyPair.privateKey.algorithm)
39
+
40
+ if (use !== 'sig') {
41
+ throw new TypeError('Unsupported JWK use')
42
+ }
43
+
44
+ return new WebcryptoKey(
45
+ jwkSchema.parse({ ...jwk, use, kid, alg }),
46
+ cryptoKeyPair,
47
+ )
48
+ }
49
+
50
+ constructor(
51
+ jwk: Jwk,
52
+ readonly cryptoKeyPair: CryptoKeyPair,
53
+ ) {
54
+ super(jwk)
55
+ }
56
+
57
+ get isPrivate() {
58
+ return true
59
+ }
60
+
61
+ get privateJwk(): Jwk | undefined {
62
+ if (super.isPrivate) return this.jwk
63
+ throw new Error('Private Webcrypto Key not exportable')
64
+ }
65
+
66
+ protected override async getKey() {
67
+ return this.cryptoKeyPair.privateKey
68
+ }
69
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": ["../../../tsconfig/isomorphic.json"],
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["./src"]
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "include": [],
3
+ "references": [{ "path": "./tsconfig.build.json" }]
4
+ }