UNPKG

@atproto/jwk-jose

Version:

`jose` based implementation of @atproto/jwk Key's

171 lines 6.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JoseKey = void 0; const jose_1 = require("jose"); const jwk_1 = require("@atproto/jwk"); const util_js_1 = require("./util.js"); const { JOSEError } = jose_1.errors; class JoseKey extends jwk_1.Key { /** * Some runtimes (e.g. Bun) require an `alg` second argument to be set when * invoking `importJWK`. In order to be compatible with these runtimes, we * provide the following method to ensure the `alg` is always set. We also * take the opportunity to ensure that the `alg` is compatible with this key. */ async getKeyObj(alg) { if (!this.algorithms.includes(alg)) { throw new jwk_1.JwkError(`Key cannot be used with algorithm "${alg}"`); } try { return await (0, jose_1.importJWK)(this.jwk, alg); } catch (cause) { throw new jwk_1.JwkError('Failed to import JWK', undefined, { cause }); } } async createJwt(header, payload) { try { const { kid } = header; if (kid && kid !== this.kid) { throw new jwk_1.JwtCreateError(`Invalid "kid" (${kid}) used to sign with key "${this.kid}"`); } const { alg } = header; if (!alg) { throw new jwk_1.JwtCreateError('Missing "alg" in JWT header'); } const keyObj = await this.getKeyObj(alg); const jwtBuilder = new jose_1.SignJWT(payload).setProtectedHeader({ ...header, alg, kid: this.kid, }); const signedJwt = await jwtBuilder.sign(keyObj); return signedJwt; } catch (cause) { if (cause instanceof JOSEError) { throw new jwk_1.JwtCreateError(cause.message, cause.code, { cause }); } else { throw jwk_1.JwtCreateError.from(cause); } } } async verifyJwt(token, options) { try { const result = await (0, jose_1.jwtVerify)(token, async ({ alg }) => this.getKeyObj(alg), { ...options, algorithms: this.algorithms }); // @NOTE if all tokens are signed exclusively through createJwt(), then // there should be no need to parse the payload and headers here. But // since the JWT could have been signed with the same key from somewhere // else, let's parse it to ensure the integrity (and type safety) of the // data. const headerParsed = jwk_1.jwtHeaderSchema.safeParse(result.protectedHeader); if (!headerParsed.success) { throw new jwk_1.JwtVerifyError('Invalid JWT header', undefined, { cause: headerParsed.error, }); } const payloadParsed = jwk_1.jwtPayloadSchema.safeParse(result.payload); if (!payloadParsed.success) { throw new jwk_1.JwtVerifyError('Invalid JWT payload', undefined, { cause: payloadParsed.error, }); } return { protectedHeader: headerParsed.data, // "requiredClaims" enforced by jwtVerify() payload: payloadParsed.data, }; } catch (cause) { if (cause instanceof JOSEError) { throw new jwk_1.JwtVerifyError(cause.message, cause.code, { cause }); } else { throw jwk_1.JwtVerifyError.from(cause); } } } static async generateKeyPair(allowedAlgos = ['ES256'], options) { if (!allowedAlgos.length) { throw new jwk_1.JwkError('No algorithms provided for key generation'); } const errors = []; for (const alg of allowedAlgos) { try { return await (0, jose_1.generateKeyPair)(alg, options); } catch (err) { errors.push(err); } } throw new jwk_1.JwkError('Failed to generate key pair', undefined, { cause: new AggregateError(errors, 'None of the algorithms worked'), }); } static async generate(allowedAlgos = ['ES256'], kid, options) { const kp = await this.generateKeyPair(allowedAlgos, { ...options, extractable: true, }); return this.fromKeyLike(kp.privateKey, kid); } static async fromImportable(input, kid) { if (typeof input === 'string') { // PKCS8 if (input.startsWith('-----')) { // The "alg" is only needed in WebCrypto (NodeJS will be fine) return this.fromPKCS8(input, '', kid); } // Jwk (string) if (input.startsWith('{')) { return this.fromJWK(input, kid); } throw new jwk_1.JwkError('Invalid input'); } if (typeof input === 'object') { // Jwk if ('kty' in input || 'alg' in input) { return this.fromJWK(input, kid); } // KeyLike return this.fromKeyLike(input, kid); } throw new jwk_1.JwkError('Invalid input'); } /** * @see {@link exportJWK} */ static async fromKeyLike(keyLike, kid, alg) { const jwk = await (0, jose_1.exportJWK)(keyLike); if (alg) { if (!jwk.alg) jwk.alg = alg; else if (jwk.alg !== alg) throw new jwk_1.JwkError('Invalid "alg" in JWK'); } return this.fromJWK(jwk, kid); } /** * @see {@link importPKCS8} */ static async fromPKCS8(pem, alg, kid) { const keyLike = await (0, jose_1.importPKCS8)(pem, alg, { extractable: true }); return this.fromKeyLike(keyLike, kid); } static async fromJWK(input, inputKid) { const jwk = typeof input === 'string' ? JSON.parse(input) : input; if (!jwk || typeof jwk !== 'object') throw new jwk_1.JwkError('Invalid JWK'); const kid = (0, util_js_1.either)(jwk.kid, inputKid); // Backwards compatibility with old behavior if (jwk.use != null && (0, jwk_1.isPrivateJwk)(jwk)) { console.warn('Deprecation warning: Private JWK with a "use" property will be rejected in the future. Please remove replace "use" with (valid) "key_ops".'); jwk.key_ops ??= jwk.use === 'sig' ? ['sign'] : ['encrypt']; delete jwk.use; } return new JoseKey(jwk_1.jwkSchema.parse({ ...jwk, kid })); } } exports.JoseKey = JoseKey; //# sourceMappingURL=jose-key.js.map