@atproto/jwk 0.4.0 → 0.6.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/dist/util.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.segmentedStringRefinementFactory = exports.jwtCharsRefinement = exports.cachedGetter = exports.preferredOrderCmp = exports.isDefined = void 0;
4
4
  exports.matchesAny = matchesAny;
5
5
  exports.parseB64uJson = parseB64uJson;
6
+ exports.isLastOccurrence = isLastOccurrence;
6
7
  const base64_1 = require("multiformats/bases/base64");
7
8
  const zod_1 = require("zod");
8
9
  const isDefined = (i) => i !== undefined;
@@ -140,4 +141,7 @@ const segmentedStringRefinementFactory = (count, minPartLength = 2) => {
140
141
  };
141
142
  };
142
143
  exports.segmentedStringRefinementFactory = segmentedStringRefinementFactory;
144
+ function isLastOccurrence(v, i, arr) {
145
+ return arr.indexOf(v, i + 1) === -1;
146
+ }
143
147
  //# sourceMappingURL=util.js.map
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["import { base64url } from 'multiformats/bases/base64'\nimport { RefinementCtx, ZodIssueCode } from 'zod'\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport type Simplify<T> = { [K in keyof T]: T[K] } & {}\nexport type Override<T, V> = Simplify<V & Omit<T, keyof V>>\n\nexport type RequiredKey<T, K extends keyof T = never> = Simplify<\n T & {\n [L in K]-?: unknown extends T[L]\n ? NonNullable<unknown> | null\n : Exclude<T[L], undefined>\n }\n>\n\nexport const isDefined = <T>(i: T | undefined): i is T => i !== undefined\n\nexport const preferredOrderCmp =\n <T>(order: readonly T[]) =>\n (a: T, b: T) => {\n const aIdx = order.indexOf(a)\n const bIdx = order.indexOf(b)\n if (aIdx === bIdx) return 0\n if (aIdx === -1) return 1\n if (bIdx === -1) return -1\n return aIdx - bIdx\n }\n\nexport function matchesAny<T extends string | number | symbol | boolean>(\n value: null | undefined | T | readonly T[],\n): (v: unknown) => v is T {\n return value == null\n ? (v): v is T => true\n : Array.isArray(value)\n ? (v): v is T => value.includes(v)\n : (v): v is T => v === value\n}\n\n/**\n * Decorator to cache the result of a getter on a class instance.\n */\nexport const cachedGetter = <T extends object, V>(\n target: (this: T) => V,\n _context: ClassGetterDecoratorContext<T, V>,\n) => {\n return function (this: T) {\n const value = target.call(this)\n Object.defineProperty(this, target.name, {\n get: () => value,\n enumerable: true,\n configurable: true,\n })\n return value\n }\n}\n\nconst decoder = new TextDecoder()\nexport function parseB64uJson(input: string): unknown {\n const inputBytes = base64url.baseDecode(input)\n const json = decoder.decode(inputBytes)\n return JSON.parse(json)\n}\n\n/**\n * @example\n * ```ts\n * // jwtSchema will only allow base64url chars & \".\" (dot)\n * const jwtSchema = z.string().superRefine(jwtCharsRefinement)\n * ```\n */\nexport const jwtCharsRefinement = (data: string, ctx: RefinementCtx): void => {\n // Note: this is a hot path, let's avoid using a RegExp\n let char\n\n for (let i = 0; i < data.length; i++) {\n char = data.charCodeAt(i)\n\n if (\n // Base64 URL encoding (most frequent)\n (65 <= char && char <= 90) || // A-Z\n (97 <= char && char <= 122) || // a-z\n (48 <= char && char <= 57) || // 0-9\n char === 45 || // -\n char === 95 || // _\n // Boundary (least frequent, check last)\n char === 46 // .\n ) {\n // continue\n } else {\n // Invalid char might be a surrogate pair\n const invalidChar = String.fromCodePoint(data.codePointAt(i)!)\n return ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `Invalid character \"${invalidChar}\" in JWT at position ${i}`,\n })\n }\n }\n}\n\n/**\n * @example\n * ```ts\n * type SegmentedString3 = SegmentedString<3> // `${string}.${string}.${string}`\n * type SegmentedString4 = SegmentedString<4> // `${string}.${string}.${string}.${string}`\n * ```\n *\n * @note\n * This utility only provides one way type safety (A SegmentedString<4> can be\n * assigned to SegmentedString<3> but not vice versa). The purpose of this\n * utility is to improve DX by avoiding as many potential errors as build time.\n * DO NOT rely on this to enforce security or data integrity.\n */\ntype SegmentedString<\n C extends number,\n Acc extends string[] = [string],\n> = Acc['length'] extends C\n ? `${Acc[0]}`\n : `${Acc[0]}.${SegmentedString<C, [string, ...Acc]>}`\n\n/**\n * @example\n * ```ts\n * const jwtSchema = z.string().superRefine(segmentedStringRefinementFactory(3))\n * type Jwt = z.infer<typeof jwtSchema> // `${string}.${string}.${string}`\n * ```\n */\nexport const segmentedStringRefinementFactory = <C extends number>(\n count: C,\n minPartLength = 2,\n) => {\n if (!Number.isFinite(count) || count < 1 || (count | 0) !== count) {\n throw new TypeError(`Count must be a natural number (got ${count})`)\n }\n\n const minTotalLength = count * minPartLength + (count - 1)\n const errorPrefix = `Invalid JWT format`\n\n return (data: string, ctx: RefinementCtx): data is SegmentedString<C> => {\n if (data.length < minTotalLength) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `${errorPrefix}: too short`,\n })\n return false\n }\n let currentStart = 0\n for (let i = 0; i < count - 1; i++) {\n const nextDot = data.indexOf('.', currentStart)\n if (nextDot === -1) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `${errorPrefix}: expected ${count} segments, got ${i + 1}`,\n })\n return false\n }\n if (nextDot - currentStart < minPartLength) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `${errorPrefix}: segment ${i + 1} is too short`,\n })\n return false\n }\n currentStart = nextDot + 1\n }\n if (data.indexOf('.', currentStart) !== -1) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `${errorPrefix}: too many segments`,\n })\n return false\n }\n if (data.length - currentStart < minPartLength) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `${errorPrefix}: last segment is too short`,\n })\n return false\n }\n return true\n }\n}\n\nexport function isLastOccurrence<\n T extends number | boolean | string | null | undefined | symbol | bigint,\n>(v: T, i: number, arr: readonly T[]): boolean {\n return arr.indexOf(v, i + 1) === -1\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/jwk",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
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.",
6
6
  "keywords": [
@@ -33,6 +33,6 @@
33
33
  "typescript": "^5.6.3"
34
34
  },
35
35
  "scripts": {
36
- "build": "tsc --build tsconfig.json"
36
+ "build": "tsc --build tsconfig.build.json"
37
37
  }
38
38
  }
package/src/alg.ts CHANGED
@@ -1,29 +1,29 @@
1
1
  import { JwkError } from './errors.js'
2
- import { Jwk } from './jwk.js'
2
+ import { JwkBase, isEncKeyUsage, isSigKeyUsage } from './jwk.js'
3
3
 
4
4
  // Copy variable to prevent bundlers from automatically polyfilling "process" (e.g. parcel)
5
5
  const { process } = globalThis
6
6
  const IS_NODE_RUNTIME =
7
7
  typeof process !== 'undefined' && typeof process?.versions?.node === 'string'
8
8
 
9
- export function* jwkAlgorithms(jwk: Jwk): Generator<string> {
9
+ export function* jwkAlgorithms(jwk: JwkBase): Generator<string, void, unknown> {
10
10
  // Ed25519, Ed448, and secp256k1 always have "alg"
11
- // OKP always has "use"
12
- if (jwk.alg) {
11
+
12
+ if (typeof jwk.alg === 'string') {
13
13
  yield jwk.alg
14
14
  return
15
15
  }
16
16
 
17
17
  switch (jwk.kty) {
18
18
  case 'EC': {
19
- if (jwk.use === 'enc' || jwk.use === undefined) {
19
+ if (jwkSupportsEnc(jwk)) {
20
20
  yield 'ECDH-ES'
21
21
  yield 'ECDH-ES+A128KW'
22
22
  yield 'ECDH-ES+A192KW'
23
23
  yield 'ECDH-ES+A256KW'
24
24
  }
25
25
 
26
- if (jwk.use === 'sig' || jwk.use === undefined) {
26
+ if (jwkSupportsSig(jwk)) {
27
27
  const crv = 'crv' in jwk ? jwk.crv : undefined
28
28
  switch (crv) {
29
29
  case 'P-256':
@@ -54,7 +54,7 @@ export function* jwkAlgorithms(jwk: Jwk): Generator<string> {
54
54
  }
55
55
 
56
56
  case 'RSA': {
57
- if (jwk.use === 'enc' || jwk.use === undefined) {
57
+ if (jwkSupportsEnc(jwk)) {
58
58
  yield 'RSA-OAEP'
59
59
  yield 'RSA-OAEP-256'
60
60
  yield 'RSA-OAEP-384'
@@ -62,7 +62,7 @@ export function* jwkAlgorithms(jwk: Jwk): Generator<string> {
62
62
  if (IS_NODE_RUNTIME) yield 'RSA1_5'
63
63
  }
64
64
 
65
- if (jwk.use === 'sig' || jwk.use === undefined) {
65
+ if (jwkSupportsSig(jwk)) {
66
66
  yield 'PS256'
67
67
  yield 'PS384'
68
68
  yield 'PS512'
@@ -75,7 +75,7 @@ export function* jwkAlgorithms(jwk: Jwk): Generator<string> {
75
75
  }
76
76
 
77
77
  case 'oct': {
78
- if (jwk.use === 'enc' || jwk.use === undefined) {
78
+ if (jwkSupportsEnc(jwk)) {
79
79
  yield 'A128GCMKW'
80
80
  yield 'A192GCMKW'
81
81
  yield 'A256GCMKW'
@@ -84,7 +84,7 @@ export function* jwkAlgorithms(jwk: Jwk): Generator<string> {
84
84
  yield 'A256KW'
85
85
  }
86
86
 
87
- if (jwk.use === 'sig' || jwk.use === undefined) {
87
+ if (jwkSupportsSig(jwk)) {
88
88
  yield 'HS256'
89
89
  yield 'HS384'
90
90
  yield 'HS512'
@@ -97,3 +97,15 @@ export function* jwkAlgorithms(jwk: Jwk): Generator<string> {
97
97
  throw new JwkError(`Unsupported kty "${jwk.kty}"`)
98
98
  }
99
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/jwk.ts CHANGED
@@ -1,45 +1,96 @@
1
1
  import { z } from 'zod'
2
+ import { isLastOccurrence } from './util'
2
3
 
3
- export const keyUsageSchema = z.enum([
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 = [
4
35
  'sign',
5
- 'verify',
6
- 'encrypt',
7
36
  'decrypt',
8
- 'wrapKey',
9
37
  'unwrapKey',
10
38
  'deriveKey',
11
39
  'deriveBits',
12
- ])
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
+ }
13
46
 
14
- export type KeyUsage = z.infer<typeof keyUsageSchema>
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]
15
50
 
16
51
  /**
17
- * The "use" and "key_ops" JWK members SHOULD NOT be used together;
18
- * however, if both are used, the information they convey MUST be
19
- * consistent. Applications should specify which of these members they
20
- * use, if either is to be used by the application.
21
- *
22
- * @todo Actually check that "use" and "key_ops" are consistent when both are present.
23
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7517#section-4.3}
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}
24
54
  */
25
- export const jwkBaseSchema = z.object({
55
+ const jwkBaseSchema = z.object({
26
56
  kty: z.string().min(1),
27
57
  alg: z.string().min(1).optional(),
28
58
  kid: z.string().min(1).optional(),
29
- ext: z.boolean().optional(),
30
59
  use: z.enum(['sig', 'enc']).optional(),
31
- key_ops: z.array(keyUsageSchema).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(),
32
69
 
33
70
  x5c: z.array(z.string()).optional(), // X.509 Certificate Chain
34
71
  x5t: z.string().min(1).optional(), // X.509 Certificate SHA-1 Thumbprint
35
72
  'x5t#S256': z.string().min(1).optional(), // X.509 Certificate SHA-256 Thumbprint
36
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(),
37
89
  })
38
90
 
39
- /**
40
- * @todo: properly implement this
41
- */
42
- export const jwkRsaKeySchema = jwkBaseSchema.extend({
91
+ export type JwkBase = z.infer<typeof jwkBaseSchema>
92
+
93
+ const jwkRsaKeySchema = jwkBaseSchema.extend({
43
94
  kty: z.literal('RSA'),
44
95
  alg: z
45
96
  .enum(['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512'])
@@ -62,12 +113,11 @@ export const jwkRsaKeySchema = jwkBaseSchema.extend({
62
113
  t: z.string().optional(),
63
114
  }),
64
115
  )
65
- .nonempty()
66
-
116
+ .min(1)
67
117
  .optional(), // Other Primes Info
68
118
  })
69
119
 
70
- export const jwkEcKeySchema = jwkBaseSchema.extend({
120
+ const jwkEcKeySchema = jwkBaseSchema.extend({
71
121
  kty: z.literal('EC'),
72
122
  alg: z.enum(['ES256', 'ES384', 'ES512']).optional(),
73
123
  crv: z.enum(['P-256', 'P-384', 'P-521']),
@@ -78,7 +128,7 @@ export const jwkEcKeySchema = jwkBaseSchema.extend({
78
128
  d: z.string().min(1).optional(), // ECC Private Key
79
129
  })
80
130
 
81
- export const jwkEcSecp256k1KeySchema = jwkBaseSchema.extend({
131
+ const jwkEcSecp256k1KeySchema = jwkBaseSchema.extend({
82
132
  kty: z.literal('EC'),
83
133
  alg: z.enum(['ES256K']).optional(),
84
134
  crv: z.enum(['secp256k1']),
@@ -89,7 +139,7 @@ export const jwkEcSecp256k1KeySchema = jwkBaseSchema.extend({
89
139
  d: z.string().min(1).optional(), // ECC Private Key
90
140
  })
91
141
 
92
- export const jwkOkpKeySchema = jwkBaseSchema.extend({
142
+ const jwkOkpKeySchema = jwkBaseSchema.extend({
93
143
  kty: z.literal('OKP'),
94
144
  alg: z.enum(['EdDSA']).optional(),
95
145
  crv: z.enum(['Ed25519', 'Ed448']),
@@ -98,49 +148,116 @@ export const jwkOkpKeySchema = jwkBaseSchema.extend({
98
148
  d: z.string().min(1).optional(), // ECC Private Key
99
149
  })
100
150
 
101
- export const jwkSymKeySchema = jwkBaseSchema.extend({
151
+ const jwkSymKeySchema = jwkBaseSchema.extend({
102
152
  kty: z.literal('oct'), // Octet Sequence (used to represent symmetric keys)
103
153
  alg: z.enum(['HS256', 'HS384', 'HS512']).optional(),
104
154
 
105
155
  k: z.string(), // Key Value (base64url encoded)
106
156
  })
107
157
 
108
- export const jwkUnknownKeySchema = jwkBaseSchema.extend({
109
- kty: z
110
- .string()
111
- .refine((v) => v !== 'RSA' && v !== 'EC' && v !== 'OKP' && v !== 'oct'),
112
- })
113
-
114
- export const jwkSchema = z.union([
115
- jwkUnknownKeySchema,
116
- jwkRsaKeySchema,
117
- jwkEcKeySchema,
118
- jwkEcSecp256k1KeySchema,
119
- jwkOkpKeySchema,
120
- jwkSymKeySchema,
121
- ])
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
+ )
122
204
 
123
- export type Jwk = z.infer<typeof jwkSchema>
205
+ export type Jwk = z.output<typeof jwkSchema>
124
206
 
207
+ /** @deprecated use {@link jwkSchema} instead */
125
208
  export const jwkValidator = jwkSchema
126
- .refine((k) => k.use != null || k.key_ops != null, 'use or key_ops required')
127
- .refine(
128
- (k) =>
129
- !k.use ||
130
- !k.key_ops ||
131
- k.key_ops.every((o) =>
132
- k.use === 'sig'
133
- ? o === 'sign' || o === 'verify'
134
- : o === 'encrypt' || o === 'decrypt',
135
- ),
136
- 'use and key_ops must be consistent',
137
- )
138
209
 
139
- export const jwkPubSchema = jwkValidator
140
- .refine((k) => k.kid != null, 'kid is required')
141
- .refine((k) => !('k' in k) && !('d' in k), 'private key not allowed')
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
+ }
142
255
 
143
- export const jwkPrivateSchema = jwkValidator.refine(
144
- (k) => ('k' in k && k.k != null) || ('d' in k && k.d != null),
145
- 'private key required',
146
- )
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 CHANGED
@@ -1,14 +1,39 @@
1
1
  import { z } from 'zod'
2
2
  import { jwkPubSchema, jwkSchema } from './jwk.js'
3
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
+ */
4
8
  export const jwksSchema = z.object({
5
- keys: z.array(jwkSchema),
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
+ }),
6
19
  })
7
20
 
8
- export type Jwks = z.infer<typeof jwksSchema>
21
+ export type Jwks = z.output<typeof jwksSchema>
9
22
 
23
+ /**
24
+ * Public JSON Web Key Set schema.
25
+ */
10
26
  export const jwksPubSchema = z.object({
11
- keys: z.array(jwkPubSchema),
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
+ }),
12
37
  })
13
38
 
14
- export type JwksPub = z.infer<typeof jwksPubSchema>
39
+ export type JwksPub = z.output<typeof jwksPubSchema>