@atproto/jwk 0.7.1 → 0.7.3

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atproto/jwk
2
2
 
3
+ ## 0.7.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
8
+
9
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
10
+
11
+ ## 0.7.2
12
+
13
+ ### Patch Changes
14
+
15
+ - [#5151](https://github.com/bluesky-social/atproto/pull/5151) [`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update dependencies
16
+
3
17
  ## 0.7.1
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/jwk",
3
- "version": "0.7.1",
4
- "engines": {
5
- "node": ">=22"
6
- },
3
+ "version": "0.7.3",
7
4
  "license": "MIT",
8
5
  "description": "A library for working with JSON Web Keys (JWKs) in TypeScript. This is meant to be extended by environment-specific libraries like @atproto/jwk-jose.",
9
6
  "keywords": [
@@ -19,6 +16,10 @@
19
16
  "url": "https://github.com/bluesky-social/atproto",
20
17
  "directory": "packages/oauth/jwk"
21
18
  },
19
+ "files": [
20
+ "./dist",
21
+ "./CHANGELOG.md"
22
+ ],
22
23
  "type": "module",
23
24
  "exports": {
24
25
  ".": {
@@ -26,11 +27,13 @@
26
27
  "default": "./dist/index.js"
27
28
  }
28
29
  },
30
+ "engines": {
31
+ "node": ">=22"
32
+ },
29
33
  "dependencies": {
30
34
  "multiformats": "^13.0.0",
31
35
  "zod": "^3.23.8"
32
36
  },
33
- "devDependencies": {},
34
37
  "scripts": {
35
38
  "build": "tsgo --build tsconfig.build.json"
36
39
  }
package/src/alg.ts DELETED
@@ -1,111 +0,0 @@
1
- import { JwkError } from './errors.js'
2
- import { JwkBase, isEncKeyUsage, isSigKeyUsage } from './jwk.js'
3
-
4
- // Copy variable to prevent bundlers from automatically polyfilling "process" (e.g. parcel)
5
- const { process } = globalThis
6
- const IS_NODE_RUNTIME =
7
- typeof process !== 'undefined' && typeof process?.versions?.node === 'string'
8
-
9
- export function* jwkAlgorithms(jwk: JwkBase): Generator<string, void, unknown> {
10
- // Ed25519, Ed448, and secp256k1 always have "alg"
11
-
12
- if (typeof jwk.alg === 'string') {
13
- yield jwk.alg
14
- return
15
- }
16
-
17
- switch (jwk.kty) {
18
- case 'EC': {
19
- if (jwkSupportsEnc(jwk)) {
20
- yield 'ECDH-ES'
21
- yield 'ECDH-ES+A128KW'
22
- yield 'ECDH-ES+A192KW'
23
- yield 'ECDH-ES+A256KW'
24
- }
25
-
26
- if (jwkSupportsSig(jwk)) {
27
- const crv = 'crv' in jwk ? jwk.crv : undefined
28
- switch (crv) {
29
- case 'P-256':
30
- case 'P-384':
31
- yield `ES${crv.slice(-3)}`
32
- break
33
- case 'P-521':
34
- yield 'ES512'
35
- break
36
- case 'secp256k1':
37
- if (IS_NODE_RUNTIME) yield 'ES256K'
38
- break
39
- default:
40
- throw new JwkError(`Unsupported crv "${crv}"`)
41
- }
42
- }
43
-
44
- return
45
- }
46
-
47
- case 'OKP': {
48
- if (!jwk.use) throw new JwkError('Missing "use" Parameter value')
49
- yield 'ECDH-ES'
50
- yield 'ECDH-ES+A128KW'
51
- yield 'ECDH-ES+A192KW'
52
- yield 'ECDH-ES+A256KW'
53
- return
54
- }
55
-
56
- case 'RSA': {
57
- if (jwkSupportsEnc(jwk)) {
58
- yield 'RSA-OAEP'
59
- yield 'RSA-OAEP-256'
60
- yield 'RSA-OAEP-384'
61
- yield 'RSA-OAEP-512'
62
- if (IS_NODE_RUNTIME) yield 'RSA1_5'
63
- }
64
-
65
- if (jwkSupportsSig(jwk)) {
66
- yield 'PS256'
67
- yield 'PS384'
68
- yield 'PS512'
69
- yield 'RS256'
70
- yield 'RS384'
71
- yield 'RS512'
72
- }
73
-
74
- return
75
- }
76
-
77
- case 'oct': {
78
- if (jwkSupportsEnc(jwk)) {
79
- yield 'A128GCMKW'
80
- yield 'A192GCMKW'
81
- yield 'A256GCMKW'
82
- yield 'A128KW'
83
- yield 'A192KW'
84
- yield 'A256KW'
85
- }
86
-
87
- if (jwkSupportsSig(jwk)) {
88
- yield 'HS256'
89
- yield 'HS384'
90
- yield 'HS512'
91
- }
92
-
93
- return
94
- }
95
-
96
- default:
97
- throw new JwkError(`Unsupported kty "${jwk.kty}"`)
98
- }
99
- }
100
-
101
- function jwkSupportsEnc(jwk: JwkBase): boolean {
102
- return (
103
- jwk.key_ops?.some(isEncKeyUsage) ?? (jwk.use == null || jwk.use === 'enc')
104
- )
105
- }
106
-
107
- function jwkSupportsSig(jwk: JwkBase): boolean {
108
- return (
109
- jwk.key_ops?.some(isSigKeyUsage) ?? (jwk.use == null || jwk.use === 'sig')
110
- )
111
- }
package/src/errors.ts DELETED
@@ -1,56 +0,0 @@
1
- export type ErrorOptions = { cause?: unknown }
2
-
3
- export const ERR_JWKS_NO_MATCHING_KEY = 'ERR_JWKS_NO_MATCHING_KEY'
4
- export const ERR_JWK_INVALID = 'ERR_JWK_INVALID'
5
- export const ERR_JWK_NOT_FOUND = 'ERR_JWK_NOT_FOUND'
6
- export const ERR_JWT_INVALID = 'ERR_JWT_INVALID'
7
- export const ERR_JWT_CREATE = 'ERR_JWT_CREATE'
8
- export const ERR_JWT_VERIFY = 'ERR_JWT_VERIFY'
9
-
10
- export class JwkError extends TypeError {
11
- constructor(
12
- message = 'JWK error',
13
- public readonly code = ERR_JWK_INVALID,
14
- options?: ErrorOptions,
15
- ) {
16
- super(message, options)
17
- }
18
- }
19
-
20
- export class JwtCreateError extends Error {
21
- constructor(
22
- message = 'Unable to create JWT',
23
- public readonly code = ERR_JWT_CREATE,
24
- options?: ErrorOptions,
25
- ) {
26
- super(message, options)
27
- }
28
-
29
- static from(cause: unknown, code?: string, message?: string): JwtCreateError {
30
- if (cause instanceof JwtCreateError) return cause
31
- if (cause instanceof JwkError) {
32
- return new JwtCreateError(message, cause.code, { cause })
33
- }
34
-
35
- return new JwtCreateError(message, code, { cause })
36
- }
37
- }
38
-
39
- export class JwtVerifyError extends Error {
40
- constructor(
41
- message = 'Invalid JWT',
42
- public readonly code = ERR_JWT_VERIFY,
43
- options?: ErrorOptions,
44
- ) {
45
- super(message, options)
46
- }
47
-
48
- static from(cause: unknown, code?: string, message?: string): JwtVerifyError {
49
- if (cause instanceof JwtVerifyError) return cause
50
- if (cause instanceof JwkError) {
51
- return new JwtVerifyError(message, cause.code, { cause })
52
- }
53
-
54
- return new JwtVerifyError(message, code, { cause })
55
- }
56
- }
package/src/index.ts DELETED
@@ -1,14 +0,0 @@
1
- // Since we expose zod schemas, let's expose ZodError (under a generic name) so
2
- // that dependents can catch schema parsing errors without requiring an explicit
3
- // dependency on zod, or risking a conflict in case of mismatching zob versions.
4
- export { ZodError as ValidationError } from 'zod'
5
-
6
- export * from './alg.js'
7
- export * from './errors.js'
8
- export * from './jwk.js'
9
- export * from './jwks.js'
10
- export * from './jwt-decode.js'
11
- export * from './jwt-verify.js'
12
- export * from './jwt.js'
13
- export * from './key.js'
14
- export * from './keyset.js'
package/src/jwk.ts DELETED
@@ -1,263 +0,0 @@
1
- import { z } from 'zod'
2
- import { isLastOccurrence } from './util.js'
3
-
4
- export const PUBLIC_KEY_USAGE = ['verify', 'encrypt', 'wrapKey'] as const
5
- export const publicKeyUsageSchema = z.enum(PUBLIC_KEY_USAGE)
6
- export type PublicKeyUsage = (typeof PUBLIC_KEY_USAGE)[number]
7
- export function isPublicKeyUsage(usage: unknown): usage is PublicKeyUsage {
8
- return (PUBLIC_KEY_USAGE as readonly unknown[]).includes(usage)
9
- }
10
-
11
- /**
12
- * Determines if the given key usage is consistent for "sig" (signature) public
13
- * key use.
14
- */
15
- export function isSigKeyUsage(v: KeyUsage) {
16
- return v === 'verify'
17
- }
18
-
19
- /**
20
- * Determines if the given key usage is consistent for "enc" (encryption) public
21
- * key use.
22
- *
23
- * > When a key is used to wrap another key and a public key use
24
- * > designation for the first key is desired, the "enc" (encryption)
25
- * > key use value is used, since key wrapping is a kind of encryption.
26
- * > The "enc" value is also to be used for public keys used for key
27
- * > agreement operations.
28
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7517#section-4.2}
29
- */
30
- export function isEncKeyUsage(v: KeyUsage) {
31
- return v === 'encrypt' || v === 'wrapKey'
32
- }
33
-
34
- export const PRIVATE_KEY_USAGE = [
35
- 'sign',
36
- 'decrypt',
37
- 'unwrapKey',
38
- 'deriveKey',
39
- 'deriveBits',
40
- ] as const
41
- export const privateKeyUsageSchema = z.enum(PRIVATE_KEY_USAGE)
42
- export type PrivateKeyUsage = (typeof PRIVATE_KEY_USAGE)[number]
43
- export function isPrivateKeyUsage(usage: unknown): usage is PrivateKeyUsage {
44
- return (PRIVATE_KEY_USAGE as readonly unknown[]).includes(usage)
45
- }
46
-
47
- export const KEY_USAGE = [...PRIVATE_KEY_USAGE, ...PUBLIC_KEY_USAGE] as const
48
- export const keyUsageSchema = z.enum(KEY_USAGE)
49
- export type KeyUsage = (typeof KEY_USAGE)[number]
50
-
51
- /**
52
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7517#section-4 JSON Web Key (JWK) Format}
53
- * @see {@link https://www.iana.org/assignments/jose/jose.xhtml#web-key-parameters IANA "JSON Web Key Parameters" registry}
54
- */
55
- const jwkBaseSchema = z.object({
56
- kty: z.string().min(1),
57
- alg: z.string().min(1).optional(),
58
- kid: z.string().min(1).optional(),
59
- use: z.enum(['sig', 'enc']).optional(),
60
- key_ops: z
61
- .array(keyUsageSchema)
62
- .min(1, { message: 'At least one key usage must be specified' })
63
- // https://datatracker.ietf.org/doc/html/rfc7517#section-4.3
64
- // > Duplicate key operation values MUST NOT be present in the array.
65
- .refine((ops) => ops.every(isLastOccurrence), {
66
- message: 'key_ops must not contain duplicates',
67
- })
68
- .optional(),
69
-
70
- x5c: z.array(z.string()).optional(), // X.509 Certificate Chain
71
- x5t: z.string().min(1).optional(), // X.509 Certificate SHA-1 Thumbprint
72
- 'x5t#S256': z.string().min(1).optional(), // X.509 Certificate SHA-256 Thumbprint
73
- x5u: z.string().url().optional(), // X.509 URL
74
-
75
- // https://www.w3.org/TR/webcrypto/
76
- ext: z.boolean().optional(), // Extractable
77
-
78
- // Federation Historical Keys Response
79
- // https://openid.net/specs/openid-federation-1_0.html#name-federation-historical-keys-res
80
- iat: z.number().int().optional(), // Issued At (timestamp)
81
- exp: z.number().int().optional(), // Expiration Time (timestamp)
82
- nbf: z.number().int().optional(), // Not Before (timestamp)
83
- revoked: z // properties of the revocation
84
- .object({
85
- revoked_at: z.number().int(),
86
- reason: z.string().optional(),
87
- })
88
- .optional(),
89
- })
90
-
91
- export type JwkBase = z.infer<typeof jwkBaseSchema>
92
-
93
- const jwkRsaKeySchema = jwkBaseSchema.extend({
94
- kty: z.literal('RSA'),
95
- alg: z
96
- .enum(['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512'])
97
- .optional(),
98
-
99
- n: z.string().min(1), // Modulus
100
- e: z.string().min(1), // Exponent
101
-
102
- d: z.string().min(1).optional(), // Private Exponent
103
- p: z.string().min(1).optional(), // First Prime Factor
104
- q: z.string().min(1).optional(), // Second Prime Factor
105
- dp: z.string().min(1).optional(), // First Factor CRT Exponent
106
- dq: z.string().min(1).optional(), // Second Factor CRT Exponent
107
- qi: z.string().min(1).optional(), // First CRT Coefficient
108
- oth: z
109
- .array(
110
- z.object({
111
- r: z.string().optional(),
112
- d: z.string().optional(),
113
- t: z.string().optional(),
114
- }),
115
- )
116
- .min(1)
117
- .optional(), // Other Primes Info
118
- })
119
-
120
- const jwkEcKeySchema = jwkBaseSchema.extend({
121
- kty: z.literal('EC'),
122
- alg: z.enum(['ES256', 'ES384', 'ES512']).optional(),
123
- crv: z.enum(['P-256', 'P-384', 'P-521']),
124
-
125
- x: z.string().min(1),
126
- y: z.string().min(1),
127
-
128
- d: z.string().min(1).optional(), // ECC Private Key
129
- })
130
-
131
- const jwkEcSecp256k1KeySchema = jwkBaseSchema.extend({
132
- kty: z.literal('EC'),
133
- alg: z.enum(['ES256K']).optional(),
134
- crv: z.enum(['secp256k1']),
135
-
136
- x: z.string().min(1),
137
- y: z.string().min(1),
138
-
139
- d: z.string().min(1).optional(), // ECC Private Key
140
- })
141
-
142
- const jwkOkpKeySchema = jwkBaseSchema.extend({
143
- kty: z.literal('OKP'),
144
- alg: z.enum(['EdDSA']).optional(),
145
- crv: z.enum(['Ed25519', 'Ed448']),
146
-
147
- x: z.string().min(1),
148
- d: z.string().min(1).optional(), // ECC Private Key
149
- })
150
-
151
- const jwkSymKeySchema = jwkBaseSchema.extend({
152
- kty: z.literal('oct'), // Octet Sequence (used to represent symmetric keys)
153
- alg: z.enum(['HS256', 'HS384', 'HS512']).optional(),
154
-
155
- k: z.string(), // Key Value (base64url encoded)
156
- })
157
-
158
- /**
159
- * Zod parser for known JWK types
160
- */
161
- export const jwkSchema = z
162
- .union([
163
- jwkRsaKeySchema,
164
- jwkEcKeySchema,
165
- jwkEcSecp256k1KeySchema,
166
- jwkOkpKeySchema,
167
- jwkSymKeySchema,
168
- ])
169
- // @TODO These rules should be applied to jwkBaseSchema, but Zod 3 doesn't
170
- // support extending refined schemas. Move these to the base schema when we
171
- // upgrade to Zod 4.
172
- .refine(
173
- // https://datatracker.ietf.org/doc/html/rfc7517#section-4.2
174
- // > The "use" (public key use) parameter identifies the intended use of the
175
- // > public key
176
- (k): boolean => k.use == null || isPublicJwk(k),
177
- {
178
- message: '"use" can only be used with public keys',
179
- path: ['use'],
180
- },
181
- )
182
- .refine(
183
- (k): boolean => !k.key_ops?.some(isPrivateKeyUsage) || isPrivateJwk(k),
184
- {
185
- message: 'private key usage not allowed for public keys',
186
- path: ['key_ops'],
187
- },
188
- )
189
- .refine(
190
- // https://datatracker.ietf.org/doc/html/rfc7517#section-4.3
191
- // > The "use" and "key_ops" JWK members SHOULD NOT be used together;
192
- // > however, if both are used, the information they convey MUST be
193
- // > consistent.
194
- (k): boolean =>
195
- k.use == null ||
196
- k.key_ops == null ||
197
- (k.use === 'sig' && k.key_ops.every(isSigKeyUsage)) ||
198
- (k.use === 'enc' && k.key_ops.every(isEncKeyUsage)),
199
- {
200
- message: '"key_ops" must be consistent with "use"',
201
- path: ['key_ops'],
202
- },
203
- )
204
-
205
- export type Jwk = z.output<typeof jwkSchema>
206
-
207
- /** @deprecated use {@link jwkSchema} instead */
208
- export const jwkValidator = jwkSchema
209
-
210
- export const jwkPubSchema = jwkSchema
211
- .refine(hasKid, {
212
- message: '"kid" is required',
213
- path: ['kid'],
214
- })
215
- // @NOTE for legacy reasons, we don't impose the presence of either "use" or "key_ops"
216
- .refine(isPublicJwk, {
217
- message: 'private key not allowed',
218
- })
219
- .refine((k): boolean => !k.key_ops || k.key_ops.every(isPublicKeyUsage), {
220
- message: '"key_ops" must not contain private key usage for public keys',
221
- path: ['key_ops'],
222
- })
223
-
224
- export type PublicJwk = z.output<typeof jwkPubSchema>
225
-
226
- export const jwkPrivateSchema = jwkSchema
227
- // @NOTE we don't impose the presence of "kid"
228
- .refine(isPrivateJwk, {
229
- message: 'private key required',
230
- })
231
-
232
- export type PrivateJwk = z.output<typeof jwkPrivateSchema>
233
-
234
- export function hasKid<J extends object>(
235
- jwk: J,
236
- ): jwk is J & { kid: NonNullable<unknown> } {
237
- return 'kid' in jwk && jwk.kid != null
238
- }
239
-
240
- export function hasSharedSecretJwk<J extends object>(
241
- jwk: J,
242
- ): jwk is J & { k: NonNullable<unknown> } {
243
- return 'k' in jwk && jwk.k != null
244
- }
245
-
246
- export function hasPrivateSecretJwk<J extends object>(
247
- jwk: J,
248
- ): jwk is J & { d: NonNullable<unknown> } {
249
- return 'd' in jwk && jwk.d != null
250
- }
251
-
252
- export function isPrivateJwk<J extends object>(jwk: J) {
253
- return hasPrivateSecretJwk(jwk) || hasSharedSecretJwk(jwk)
254
- }
255
-
256
- export function isPublicJwk<J extends object>(
257
- jwk: J,
258
- ): jwk is Extract<
259
- Exclude<J, { k: NonNullable<unknown> }>,
260
- { d?: NonNullable<unknown> }
261
- > & { d?: never } {
262
- return !hasPrivateSecretJwk(jwk) && !hasSharedSecretJwk(jwk)
263
- }
package/src/jwks.ts DELETED
@@ -1,39 +0,0 @@
1
- import { z } from 'zod'
2
- import { jwkPubSchema, jwkSchema } from './jwk.js'
3
-
4
- /**
5
- * JSON Web Key Set schema. The keys set, in this context, represents a
6
- * collection of JSON Web Keys (JWKs), that can be both public and private.
7
- */
8
- export const jwksSchema = z.object({
9
- keys: z.array(z.unknown()).transform((input) => {
10
- // > Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
11
- // > (key type) values that are not understood by them, that are missing
12
- // > required members, or for which values are out of the supported
13
- // > ranges.
14
- return input
15
- .map((item) => jwkSchema.safeParse(item))
16
- .filter((res) => res.success)
17
- .map((res) => res.data)
18
- }),
19
- })
20
-
21
- export type Jwks = z.output<typeof jwksSchema>
22
-
23
- /**
24
- * Public JSON Web Key Set schema.
25
- */
26
- export const jwksPubSchema = z.object({
27
- keys: z.array(z.unknown()).transform((input) => {
28
- // > Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
29
- // > (key type) values that are not understood by them, that are missing
30
- // > required members, or for which values are out of the supported
31
- // > ranges.
32
- return input
33
- .map((item) => jwkPubSchema.safeParse(item))
34
- .filter((res) => res.success)
35
- .map((res) => res.data)
36
- }),
37
- })
38
-
39
- export type JwksPub = z.output<typeof jwksPubSchema>
package/src/jwt-decode.ts DELETED
@@ -1,27 +0,0 @@
1
- import { ERR_JWT_INVALID, JwtVerifyError } from './errors.js'
2
- import {
3
- JwtHeader,
4
- JwtPayload,
5
- jwtHeaderSchema,
6
- jwtPayloadSchema,
7
- } from './jwt.js'
8
- import { parseB64uJson } from './util.js'
9
-
10
- export function unsafeDecodeJwt(jwt: string): {
11
- header: JwtHeader
12
- payload: JwtPayload
13
- } {
14
- const { 0: headerEnc, 1: payloadEnc, length } = jwt.split('.')
15
- if (length > 3 || length < 2) {
16
- throw new JwtVerifyError(undefined, ERR_JWT_INVALID)
17
- }
18
-
19
- const header = jwtHeaderSchema.parse(parseB64uJson(headerEnc!))
20
- if (length === 2 && header?.alg !== 'none') {
21
- throw new JwtVerifyError(undefined, ERR_JWT_INVALID)
22
- }
23
-
24
- const payload = jwtPayloadSchema.parse(parseB64uJson(payloadEnc!))
25
-
26
- return { header, payload }
27
- }
package/src/jwt-verify.ts DELETED
@@ -1,20 +0,0 @@
1
- import { JwtHeader, JwtPayload } from './jwt.js'
2
- import { RequiredKey } from './util.js'
3
-
4
- export type VerifyOptions<C extends string = never> = {
5
- audience?: string | readonly string[]
6
- /** in seconds */
7
- clockTolerance?: number
8
- issuer?: string | readonly string[]
9
- /** in seconds */
10
- maxTokenAge?: number
11
- subject?: string
12
- typ?: string
13
- currentDate?: Date
14
- requiredClaims?: readonly C[]
15
- }
16
-
17
- export type VerifyResult<C extends string = never> = {
18
- payload: RequiredKey<JwtPayload, C>
19
- protectedHeader: JwtHeader
20
- }