@turnkey/crypto
Advanced tools
| export {}; | ||
| //# sourceMappingURL=crypto-test.d.ts.map |
| {"version":3,"file":"crypto-test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crypto-test.ts"],"names":[],"mappings":""} |
| /** @jest-environment node */ | ||
| export {}; | ||
| //# sourceMappingURL=proof-tests.d.ts.map |
| {"version":3,"file":"proof-tests.d.ts","sourceRoot":"","sources":["../../src/__tests__/proof-tests.ts"],"names":[],"mappings":"AAAA,6BAA6B"} |
| export {}; | ||
| //# sourceMappingURL=webhooks-test.d.ts.map |
| {"version":3,"file":"webhooks-test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhooks-test.ts"],"names":[],"mappings":""} |
| export type TurnkeyWebhookVerificationKey = { | ||
| keyId: string; | ||
| publicKey: string; | ||
| algorithm?: "ed25519"; | ||
| }; | ||
| export type TurnkeyWebhookHeaders = Headers | Record<string, string | string[] | undefined>; | ||
| export type TurnkeyWebhookBody = string | Uint8Array | ArrayBuffer; | ||
| export type VerifyTurnkeyWebhookSignatureParams = { | ||
| headers: TurnkeyWebhookHeaders; | ||
| body: TurnkeyWebhookBody; | ||
| verificationKeys: TurnkeyWebhookVerificationKey[]; | ||
| maxTimestampAgeMs: number; | ||
| nowMs?: number; | ||
| }; | ||
| export declare const TurnkeyWebhookVerificationFailureReasons: { | ||
| readonly InvalidMaxTimestampAge: "invalid_max_timestamp_age"; | ||
| readonly InvalidNow: "invalid_now"; | ||
| readonly MissingHeader: "missing_header"; | ||
| readonly InvalidTimestamp: "invalid_timestamp"; | ||
| readonly StaleTimestamp: "stale_timestamp"; | ||
| readonly UnsupportedSignatureVersion: "unsupported_signature_version"; | ||
| readonly UnsupportedSignatureAlgorithm: "unsupported_signature_algorithm"; | ||
| readonly MissingKey: "missing_key"; | ||
| readonly InvalidVerificationKeyAlgorithm: "invalid_verification_key_algorithm"; | ||
| readonly InvalidVerificationKey: "invalid_verification_key"; | ||
| readonly InvalidSignature: "invalid_signature"; | ||
| }; | ||
| export type TurnkeyWebhookVerificationFailureReason = (typeof TurnkeyWebhookVerificationFailureReasons)[keyof typeof TurnkeyWebhookVerificationFailureReasons]; | ||
| export type TurnkeyWebhookVerificationSuccess = { | ||
| ok: true; | ||
| eventId: string; | ||
| keyId: string; | ||
| timestampMs: number; | ||
| }; | ||
| export type TurnkeyWebhookVerificationFailure = { | ||
| ok: false; | ||
| reason: TurnkeyWebhookVerificationFailureReason; | ||
| headerName?: string; | ||
| }; | ||
| export type TurnkeyWebhookVerificationResult = TurnkeyWebhookVerificationSuccess | TurnkeyWebhookVerificationFailure; | ||
| /** | ||
| * Verifies a Turnkey webhook signature with caller-provided Ed25519 | ||
| * verification keys. | ||
| * | ||
| * Pass the exact raw request body bytes Turnkey sent, the request headers, and | ||
| * a bounded `maxTimestampAgeMs` replay window. The function returns a typed | ||
| * failure result for invalid webhook input instead of throwing, so handlers can | ||
| * reject expected verification failures without exception-driven control flow. | ||
| */ | ||
| export declare function verifyTurnkeyWebhookSignature({ headers, body, verificationKeys, maxTimestampAgeMs, nowMs, }: VerifyTurnkeyWebhookSignatureParams): TurnkeyWebhookVerificationResult; | ||
| export declare function buildSignedInput({ version, algorithm, keyId, timestampMs, eventId, body, }: { | ||
| version: string; | ||
| algorithm: string; | ||
| keyId: string; | ||
| timestampMs: string; | ||
| eventId: string; | ||
| body: TurnkeyWebhookBody; | ||
| }): Uint8Array; | ||
| //# sourceMappingURL=webhooks.d.ts.map |
| {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAC7B,OAAO,GACP,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAElD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC;AAEnE,MAAM,MAAM,mCAAmC,GAAG;IAChD,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,EAAE,kBAAkB,CAAC;IACzB,gBAAgB,EAAE,6BAA6B,EAAE,CAAC;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,wCAAwC;;;;;;;;;;;;CAY3C,CAAC;AAEX,MAAM,MAAM,uCAAuC,GACjD,CAAC,OAAO,wCAAwC,CAAC,CAAC,MAAM,OAAO,wCAAwC,CAAC,CAAC;AAE3G,MAAM,MAAM,iCAAiC,GAAG;IAC9C,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,uCAAuC,CAAC;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACxC,iCAAiC,GACjC,iCAAiC,CAAC;AActC;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,EAC5C,OAAO,EACP,IAAI,EACJ,gBAAgB,EAChB,iBAAiB,EACjB,KAAkB,GACnB,EAAE,mCAAmC,GAAG,gCAAgC,CAwGxE;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,SAAS,EACT,KAAK,EACL,WAAW,EACX,OAAO,EACP,IAAI,GACL,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,kBAAkB,CAAC;CAC1B,GAAG,UAAU,CASb"} |
+200
| 'use strict'; | ||
| var ed25519 = require('@noble/curves/ed25519'); | ||
| var encoding = require('@turnkey/encoding'); | ||
| const TurnkeyWebhookVerificationFailureReasons = { | ||
| InvalidMaxTimestampAge: "invalid_max_timestamp_age", | ||
| InvalidNow: "invalid_now", | ||
| MissingHeader: "missing_header", | ||
| InvalidTimestamp: "invalid_timestamp", | ||
| StaleTimestamp: "stale_timestamp", | ||
| UnsupportedSignatureVersion: "unsupported_signature_version", | ||
| UnsupportedSignatureAlgorithm: "unsupported_signature_algorithm", | ||
| MissingKey: "missing_key", | ||
| InvalidVerificationKeyAlgorithm: "invalid_verification_key_algorithm", | ||
| InvalidVerificationKey: "invalid_verification_key", | ||
| InvalidSignature: "invalid_signature", | ||
| }; | ||
| const HEADER_TIMESTAMP = "x-turnkey-timestamp"; | ||
| const HEADER_EVENT_ID = "x-turnkey-event-id"; | ||
| const HEADER_KEY_ID = "x-turnkey-signature-key-id"; | ||
| const HEADER_ALGORITHM = "x-turnkey-signature-algorithm"; | ||
| const HEADER_VERSION = "x-turnkey-signature-version"; | ||
| const HEADER_SIGNATURE = "x-turnkey-signature"; | ||
| const SUPPORTED_VERSION = "v1"; | ||
| const SUPPORTED_ALGORITHM = "ed25519"; | ||
| const PUBLIC_KEY_BYTE_LENGTH = 32; | ||
| const ED25519_SIGNATURE_BYTE_LENGTH = 64; | ||
| /** | ||
| * Verifies a Turnkey webhook signature with caller-provided Ed25519 | ||
| * verification keys. | ||
| * | ||
| * Pass the exact raw request body bytes Turnkey sent, the request headers, and | ||
| * a bounded `maxTimestampAgeMs` replay window. The function returns a typed | ||
| * failure result for invalid webhook input instead of throwing, so handlers can | ||
| * reject expected verification failures without exception-driven control flow. | ||
| */ | ||
| function verifyTurnkeyWebhookSignature({ headers, body, verificationKeys, maxTimestampAgeMs, nowMs = Date.now(), }) { | ||
| if (!Number.isFinite(maxTimestampAgeMs) || maxTimestampAgeMs < 0) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidMaxTimestampAge); | ||
| } | ||
| if (!Number.isFinite(nowMs)) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidNow); | ||
| } | ||
| const timestampHeader = getRequiredHeader(headers, HEADER_TIMESTAMP); | ||
| if (!timestampHeader.ok) { | ||
| return timestampHeader; | ||
| } | ||
| const eventIdHeader = getRequiredHeader(headers, HEADER_EVENT_ID); | ||
| if (!eventIdHeader.ok) { | ||
| return eventIdHeader; | ||
| } | ||
| const keyIdHeader = getRequiredHeader(headers, HEADER_KEY_ID); | ||
| if (!keyIdHeader.ok) { | ||
| return keyIdHeader; | ||
| } | ||
| const algorithmHeader = getRequiredHeader(headers, HEADER_ALGORITHM); | ||
| if (!algorithmHeader.ok) { | ||
| return algorithmHeader; | ||
| } | ||
| const versionHeader = getRequiredHeader(headers, HEADER_VERSION); | ||
| if (!versionHeader.ok) { | ||
| return versionHeader; | ||
| } | ||
| const signatureHeader = getRequiredHeader(headers, HEADER_SIGNATURE); | ||
| if (!signatureHeader.ok) { | ||
| return signatureHeader; | ||
| } | ||
| const timestampMs = parseTimestampMs(timestampHeader.value); | ||
| if (timestampMs === undefined) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidTimestamp); | ||
| } | ||
| if (Math.abs(nowMs - timestampMs) > maxTimestampAgeMs) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.StaleTimestamp); | ||
| } | ||
| if (versionHeader.value !== SUPPORTED_VERSION) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.UnsupportedSignatureVersion); | ||
| } | ||
| if (algorithmHeader.value !== SUPPORTED_ALGORITHM) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.UnsupportedSignatureAlgorithm); | ||
| } | ||
| const key = verificationKeys.find((candidate) => candidate.keyId === keyIdHeader.value); | ||
| if (!key) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.MissingKey); | ||
| } | ||
| const publicKeyResult = getPublicKeyBytes(key); | ||
| if (!publicKeyResult.ok) { | ||
| return publicKeyResult; | ||
| } | ||
| const signatureResult = hexToBytes(signatureHeader.value, ED25519_SIGNATURE_BYTE_LENGTH, TurnkeyWebhookVerificationFailureReasons.InvalidSignature); | ||
| if (!signatureResult.ok) { | ||
| return signatureResult; | ||
| } | ||
| const signedInput = buildSignedInput({ | ||
| version: versionHeader.value, | ||
| algorithm: algorithmHeader.value, | ||
| keyId: keyIdHeader.value, | ||
| timestampMs: timestampHeader.value, | ||
| eventId: eventIdHeader.value, | ||
| body, | ||
| }); | ||
| let verified = false; | ||
| try { | ||
| verified = ed25519.ed25519.verify(signatureResult.value, signedInput, publicKeyResult.value); | ||
| } | ||
| catch { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidSignature); | ||
| } | ||
| if (!verified) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidSignature); | ||
| } | ||
| return { | ||
| ok: true, | ||
| eventId: eventIdHeader.value, | ||
| keyId: keyIdHeader.value, | ||
| timestampMs, | ||
| }; | ||
| } | ||
| function buildSignedInput({ version, algorithm, keyId, timestampMs, eventId, body, }) { | ||
| const prefix = new TextEncoder().encode(`${version}.${algorithm}.${keyId}.${timestampMs}.${eventId}.`); | ||
| const bodyBytes = bodyToBytes(body); | ||
| const signedInput = new Uint8Array(prefix.length + bodyBytes.length); | ||
| signedInput.set(prefix, 0); | ||
| signedInput.set(bodyBytes, prefix.length); | ||
| return signedInput; | ||
| } | ||
| function bodyToBytes(body) { | ||
| if (typeof body === "string") { | ||
| return new TextEncoder().encode(body); | ||
| } | ||
| if (body instanceof Uint8Array) { | ||
| return body; | ||
| } | ||
| return new Uint8Array(body); | ||
| } | ||
| function getRequiredHeader(headers, name) { | ||
| const value = getHeader(headers, name); | ||
| if (value === undefined || value === "") { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.MissingHeader, { | ||
| headerName: name, | ||
| }); | ||
| } | ||
| return { ok: true, value }; | ||
| } | ||
| function getHeader(headers, name) { | ||
| if (isHeaders(headers)) { | ||
| return headers.get(name) ?? undefined; | ||
| } | ||
| for (const [headerName, headerValue] of Object.entries(headers)) { | ||
| if (headerName.toLowerCase() !== name) { | ||
| continue; | ||
| } | ||
| if (Array.isArray(headerValue)) { | ||
| return headerValue[0]; | ||
| } | ||
| return headerValue; | ||
| } | ||
| return undefined; | ||
| } | ||
| function isHeaders(headers) { | ||
| return typeof Headers !== "undefined" && headers instanceof Headers; | ||
| } | ||
| function parseTimestampMs(timestamp) { | ||
| if (!/^[0-9]+$/.test(timestamp)) { | ||
| return undefined; | ||
| } | ||
| const parsed = Number(timestamp); | ||
| if (!Number.isSafeInteger(parsed)) { | ||
| return undefined; | ||
| } | ||
| return parsed; | ||
| } | ||
| function getPublicKeyBytes(key) { | ||
| if (key.algorithm !== undefined && key.algorithm !== SUPPORTED_ALGORITHM) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidVerificationKeyAlgorithm); | ||
| } | ||
| return hexToBytes(key.publicKey, PUBLIC_KEY_BYTE_LENGTH, TurnkeyWebhookVerificationFailureReasons.InvalidVerificationKey); | ||
| } | ||
| function hexToBytes(input, expectedLength, reason) { | ||
| if (input.length !== expectedLength * 2) { | ||
| return invalid(reason); | ||
| } | ||
| try { | ||
| return { | ||
| ok: true, | ||
| value: encoding.uint8ArrayFromHexString(input), | ||
| }; | ||
| } | ||
| catch { | ||
| return invalid(reason); | ||
| } | ||
| } | ||
| function invalid(reason, details = {}) { | ||
| return { ok: false, reason, ...details }; | ||
| } | ||
| exports.TurnkeyWebhookVerificationFailureReasons = TurnkeyWebhookVerificationFailureReasons; | ||
| exports.buildSignedInput = buildSignedInput; | ||
| exports.verifyTurnkeyWebhookSignature = verifyTurnkeyWebhookSignature; | ||
| //# sourceMappingURL=webhooks.js.map |
| {"version":3,"file":"webhooks.js","sources":["../src/webhooks.ts"],"sourcesContent":[null],"names":["ed25519","uint8ArrayFromHexString"],"mappings":"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"} |
| import { ed25519 } from '@noble/curves/ed25519'; | ||
| import { uint8ArrayFromHexString } from '@turnkey/encoding'; | ||
| const TurnkeyWebhookVerificationFailureReasons = { | ||
| InvalidMaxTimestampAge: "invalid_max_timestamp_age", | ||
| InvalidNow: "invalid_now", | ||
| MissingHeader: "missing_header", | ||
| InvalidTimestamp: "invalid_timestamp", | ||
| StaleTimestamp: "stale_timestamp", | ||
| UnsupportedSignatureVersion: "unsupported_signature_version", | ||
| UnsupportedSignatureAlgorithm: "unsupported_signature_algorithm", | ||
| MissingKey: "missing_key", | ||
| InvalidVerificationKeyAlgorithm: "invalid_verification_key_algorithm", | ||
| InvalidVerificationKey: "invalid_verification_key", | ||
| InvalidSignature: "invalid_signature", | ||
| }; | ||
| const HEADER_TIMESTAMP = "x-turnkey-timestamp"; | ||
| const HEADER_EVENT_ID = "x-turnkey-event-id"; | ||
| const HEADER_KEY_ID = "x-turnkey-signature-key-id"; | ||
| const HEADER_ALGORITHM = "x-turnkey-signature-algorithm"; | ||
| const HEADER_VERSION = "x-turnkey-signature-version"; | ||
| const HEADER_SIGNATURE = "x-turnkey-signature"; | ||
| const SUPPORTED_VERSION = "v1"; | ||
| const SUPPORTED_ALGORITHM = "ed25519"; | ||
| const PUBLIC_KEY_BYTE_LENGTH = 32; | ||
| const ED25519_SIGNATURE_BYTE_LENGTH = 64; | ||
| /** | ||
| * Verifies a Turnkey webhook signature with caller-provided Ed25519 | ||
| * verification keys. | ||
| * | ||
| * Pass the exact raw request body bytes Turnkey sent, the request headers, and | ||
| * a bounded `maxTimestampAgeMs` replay window. The function returns a typed | ||
| * failure result for invalid webhook input instead of throwing, so handlers can | ||
| * reject expected verification failures without exception-driven control flow. | ||
| */ | ||
| function verifyTurnkeyWebhookSignature({ headers, body, verificationKeys, maxTimestampAgeMs, nowMs = Date.now(), }) { | ||
| if (!Number.isFinite(maxTimestampAgeMs) || maxTimestampAgeMs < 0) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidMaxTimestampAge); | ||
| } | ||
| if (!Number.isFinite(nowMs)) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidNow); | ||
| } | ||
| const timestampHeader = getRequiredHeader(headers, HEADER_TIMESTAMP); | ||
| if (!timestampHeader.ok) { | ||
| return timestampHeader; | ||
| } | ||
| const eventIdHeader = getRequiredHeader(headers, HEADER_EVENT_ID); | ||
| if (!eventIdHeader.ok) { | ||
| return eventIdHeader; | ||
| } | ||
| const keyIdHeader = getRequiredHeader(headers, HEADER_KEY_ID); | ||
| if (!keyIdHeader.ok) { | ||
| return keyIdHeader; | ||
| } | ||
| const algorithmHeader = getRequiredHeader(headers, HEADER_ALGORITHM); | ||
| if (!algorithmHeader.ok) { | ||
| return algorithmHeader; | ||
| } | ||
| const versionHeader = getRequiredHeader(headers, HEADER_VERSION); | ||
| if (!versionHeader.ok) { | ||
| return versionHeader; | ||
| } | ||
| const signatureHeader = getRequiredHeader(headers, HEADER_SIGNATURE); | ||
| if (!signatureHeader.ok) { | ||
| return signatureHeader; | ||
| } | ||
| const timestampMs = parseTimestampMs(timestampHeader.value); | ||
| if (timestampMs === undefined) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidTimestamp); | ||
| } | ||
| if (Math.abs(nowMs - timestampMs) > maxTimestampAgeMs) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.StaleTimestamp); | ||
| } | ||
| if (versionHeader.value !== SUPPORTED_VERSION) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.UnsupportedSignatureVersion); | ||
| } | ||
| if (algorithmHeader.value !== SUPPORTED_ALGORITHM) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.UnsupportedSignatureAlgorithm); | ||
| } | ||
| const key = verificationKeys.find((candidate) => candidate.keyId === keyIdHeader.value); | ||
| if (!key) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.MissingKey); | ||
| } | ||
| const publicKeyResult = getPublicKeyBytes(key); | ||
| if (!publicKeyResult.ok) { | ||
| return publicKeyResult; | ||
| } | ||
| const signatureResult = hexToBytes(signatureHeader.value, ED25519_SIGNATURE_BYTE_LENGTH, TurnkeyWebhookVerificationFailureReasons.InvalidSignature); | ||
| if (!signatureResult.ok) { | ||
| return signatureResult; | ||
| } | ||
| const signedInput = buildSignedInput({ | ||
| version: versionHeader.value, | ||
| algorithm: algorithmHeader.value, | ||
| keyId: keyIdHeader.value, | ||
| timestampMs: timestampHeader.value, | ||
| eventId: eventIdHeader.value, | ||
| body, | ||
| }); | ||
| let verified = false; | ||
| try { | ||
| verified = ed25519.verify(signatureResult.value, signedInput, publicKeyResult.value); | ||
| } | ||
| catch { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidSignature); | ||
| } | ||
| if (!verified) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidSignature); | ||
| } | ||
| return { | ||
| ok: true, | ||
| eventId: eventIdHeader.value, | ||
| keyId: keyIdHeader.value, | ||
| timestampMs, | ||
| }; | ||
| } | ||
| function buildSignedInput({ version, algorithm, keyId, timestampMs, eventId, body, }) { | ||
| const prefix = new TextEncoder().encode(`${version}.${algorithm}.${keyId}.${timestampMs}.${eventId}.`); | ||
| const bodyBytes = bodyToBytes(body); | ||
| const signedInput = new Uint8Array(prefix.length + bodyBytes.length); | ||
| signedInput.set(prefix, 0); | ||
| signedInput.set(bodyBytes, prefix.length); | ||
| return signedInput; | ||
| } | ||
| function bodyToBytes(body) { | ||
| if (typeof body === "string") { | ||
| return new TextEncoder().encode(body); | ||
| } | ||
| if (body instanceof Uint8Array) { | ||
| return body; | ||
| } | ||
| return new Uint8Array(body); | ||
| } | ||
| function getRequiredHeader(headers, name) { | ||
| const value = getHeader(headers, name); | ||
| if (value === undefined || value === "") { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.MissingHeader, { | ||
| headerName: name, | ||
| }); | ||
| } | ||
| return { ok: true, value }; | ||
| } | ||
| function getHeader(headers, name) { | ||
| if (isHeaders(headers)) { | ||
| return headers.get(name) ?? undefined; | ||
| } | ||
| for (const [headerName, headerValue] of Object.entries(headers)) { | ||
| if (headerName.toLowerCase() !== name) { | ||
| continue; | ||
| } | ||
| if (Array.isArray(headerValue)) { | ||
| return headerValue[0]; | ||
| } | ||
| return headerValue; | ||
| } | ||
| return undefined; | ||
| } | ||
| function isHeaders(headers) { | ||
| return typeof Headers !== "undefined" && headers instanceof Headers; | ||
| } | ||
| function parseTimestampMs(timestamp) { | ||
| if (!/^[0-9]+$/.test(timestamp)) { | ||
| return undefined; | ||
| } | ||
| const parsed = Number(timestamp); | ||
| if (!Number.isSafeInteger(parsed)) { | ||
| return undefined; | ||
| } | ||
| return parsed; | ||
| } | ||
| function getPublicKeyBytes(key) { | ||
| if (key.algorithm !== undefined && key.algorithm !== SUPPORTED_ALGORITHM) { | ||
| return invalid(TurnkeyWebhookVerificationFailureReasons.InvalidVerificationKeyAlgorithm); | ||
| } | ||
| return hexToBytes(key.publicKey, PUBLIC_KEY_BYTE_LENGTH, TurnkeyWebhookVerificationFailureReasons.InvalidVerificationKey); | ||
| } | ||
| function hexToBytes(input, expectedLength, reason) { | ||
| if (input.length !== expectedLength * 2) { | ||
| return invalid(reason); | ||
| } | ||
| try { | ||
| return { | ||
| ok: true, | ||
| value: uint8ArrayFromHexString(input), | ||
| }; | ||
| } | ||
| catch { | ||
| return invalid(reason); | ||
| } | ||
| } | ||
| function invalid(reason, details = {}) { | ||
| return { ok: false, reason, ...details }; | ||
| } | ||
| export { TurnkeyWebhookVerificationFailureReasons, buildSignedInput, verifyTurnkeyWebhookSignature }; | ||
| //# sourceMappingURL=webhooks.mjs.map |
| {"version":3,"file":"webhooks.mjs","sources":["../src/webhooks.ts"],"sourcesContent":[null],"names":[],"mappings":"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"} |
+11
-0
| # @turnkey/crypto | ||
| ## 2.10.0 | ||
| ### Minor Changes | ||
| - [#1314](https://github.com/tkhq/sdk/pull/1314) [`ad044cd`](https://github.com/tkhq/sdk/commit/ad044cdcd41690c6479a81c56ebcd6d5108581fb) Author [@taylorjdawson](https://github.com/taylorjdawson) - Add a helper for verifying Turnkey webhook signatures. | ||
| ### Patch Changes | ||
| - Updated dependencies [[`bf2c28f`](https://github.com/tkhq/sdk/commit/bf2c28fec690ec31254125405bf15a482139109b), [`f505818`](https://github.com/tkhq/sdk/commit/f505818e121c137d290156a32918a884ba80e5f5)]: | ||
| - @turnkey/sdk-types@1.1.0 | ||
| ## 2.9.0 | ||
@@ -4,0 +15,0 @@ |
+2
-0
| export * from "./crypto"; | ||
| export * from "./turnkey"; | ||
| export * from "./proof"; | ||
| export { TurnkeyWebhookVerificationFailureReasons, verifyTurnkeyWebhookSignature, } from "./webhooks"; | ||
| export type { TurnkeyWebhookBody, TurnkeyWebhookHeaders, TurnkeyWebhookVerificationFailure, TurnkeyWebhookVerificationFailureReason, TurnkeyWebhookVerificationKey, TurnkeyWebhookVerificationResult, TurnkeyWebhookVerificationSuccess, VerifyTurnkeyWebhookSignatureParams, } from "./webhooks"; | ||
| export { PRODUCTION_SIGNER_SIGN_PUBLIC_KEY, PRODUCTION_NOTARIZER_SIGN_PUBLIC_KEY, PRODUCTION_TLS_FETCHER_ENCRYPT_PUBLIC_KEY, PRODUCTION_TLS_FETCHER_SIGN_PUBLIC_KEY, PRODUCTION_ON_RAMP_CREDENTIALS_ENCRYPTION_PUBLIC_KEY, } from "./constants"; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,iCAAiC,EACjC,oCAAoC,EACpC,yCAAyC,EACzC,sCAAsC,EACtC,oDAAoD,GACrD,MAAM,aAAa,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,wCAAwC,EACxC,6BAA6B,GAC9B,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,iCAAiC,EACjC,uCAAuC,EACvC,6BAA6B,EAC7B,gCAAgC,EAChC,iCAAiC,EACjC,mCAAmC,GACpC,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iCAAiC,EACjC,oCAAoC,EACpC,yCAAyC,EACzC,sCAAsC,EACtC,oDAAoD,GACrD,MAAM,aAAa,CAAC"} |
+3
-0
@@ -6,2 +6,3 @@ 'use strict'; | ||
| var proof = require('./proof.js'); | ||
| var webhooks = require('./webhooks.js'); | ||
| var constants = require('./constants.js'); | ||
@@ -43,2 +44,4 @@ | ||
| exports.verifyCoseSign1Sig = proof.verifyCoseSign1Sig; | ||
| exports.TurnkeyWebhookVerificationFailureReasons = webhooks.TurnkeyWebhookVerificationFailureReasons; | ||
| exports.verifyTurnkeyWebhookSignature = webhooks.verifyTurnkeyWebhookSignature; | ||
| exports.PRODUCTION_NOTARIZER_SIGN_PUBLIC_KEY = constants.PRODUCTION_NOTARIZER_SIGN_PUBLIC_KEY; | ||
@@ -45,0 +48,0 @@ exports.PRODUCTION_ON_RAMP_CREDENTIALS_ENCRYPTION_PUBLIC_KEY = constants.PRODUCTION_ON_RAMP_CREDENTIALS_ENCRYPTION_PUBLIC_KEY; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} | ||
| {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} |
+1
-0
| export { buildAdditionalAssociatedData, compressRawPublicKey, extractPrivateKeyFromPKCS8Bytes, formatHpkeBuf, fromDerSignature, generateP256KeyPair, getPublicKey, hpkeAuthEncrypt, hpkeDecrypt, hpkeEncrypt, quorumKeyEncrypt, toDerSignature, uncompressRawPublicKey } from './crypto.mjs'; | ||
| export { Enclave, decryptCredentialBundle, decryptExportBundle, encryptOauth2ClientSecret, encryptOnRampSecret, encryptOtpCodeToBundle, encryptPrivateKeyToBundle, encryptToEnclave, encryptWalletToBundle, verifySessionJwtSignature, verifyStampSignature } from './turnkey.mjs'; | ||
| export { getCryptoInstance, verify, verifyAppProofSignature, verifyCertificateChain, verifyCoseSign1Sig } from './proof.mjs'; | ||
| export { TurnkeyWebhookVerificationFailureReasons, verifyTurnkeyWebhookSignature } from './webhooks.mjs'; | ||
| export { PRODUCTION_NOTARIZER_SIGN_PUBLIC_KEY, PRODUCTION_ON_RAMP_CREDENTIALS_ENCRYPTION_PUBLIC_KEY, PRODUCTION_SIGNER_SIGN_PUBLIC_KEY, PRODUCTION_TLS_FETCHER_ENCRYPT_PUBLIC_KEY, PRODUCTION_TLS_FETCHER_SIGN_PUBLIC_KEY } from './constants.mjs'; | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"} | ||
| {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"} |
+4
-4
| { | ||
| "name": "@turnkey/crypto", | ||
| "version": "2.9.0", | ||
| "version": "2.10.0", | ||
| "main": "./dist/index.js", | ||
@@ -48,4 +48,4 @@ "module": "./dist/index.mjs", | ||
| "cbor-js": "0.1.0", | ||
| "@turnkey/sdk-types": "1.0.0", | ||
| "@turnkey/encoding": "0.6.0" | ||
| "@turnkey/encoding": "0.6.0", | ||
| "@turnkey/sdk-types": "1.1.0" | ||
| }, | ||
@@ -61,4 +61,4 @@ "devDependencies": { | ||
| "test": "jest", | ||
| "typecheck": "tsc -p tsconfig.typecheck.json" | ||
| "typecheck": "tsc --noEmit" | ||
| } | ||
| } |
+36
-0
@@ -36,1 +36,37 @@ # @turnkey/crypto | ||
| ``` | ||
| ## Verifying Turnkey webhooks | ||
| Use `@turnkey/crypto` directly when you want to manage verification-key fetching and caching yourself. Verification must use the exact raw request body bytes that Turnkey sent, the Turnkey signature headers, Turnkey webhook verification keys, and an explicit `maxTimestampAgeMs` replay window. | ||
| Turnkey sends these signature headers with the body: `x-turnkey-timestamp`, `x-turnkey-event-id`, `x-turnkey-signature-key-id`, `x-turnkey-signature-algorithm`, `x-turnkey-signature-version`, and `x-turnkey-signature`. Pass the complete headers object through as received. | ||
| `x-turnkey-event-id` is stable across retry attempts for the same webhook event. Use it as the deduplication or idempotency key after signature verification succeeds. | ||
| ```ts | ||
| import { verifyTurnkeyWebhookSignature } from "@turnkey/crypto"; | ||
| const body = req.body; // Buffer from express.raw(), not parsed JSON | ||
| const verificationKeys = [ | ||
| { | ||
| keyId: process.env.TURNKEY_WEBHOOK_KEY_ID!, | ||
| publicKey: process.env.TURNKEY_WEBHOOK_PUBLIC_KEY!, // Hex-encoded Ed25519 public key | ||
| algorithm: "ed25519", | ||
| }, | ||
| ]; | ||
| const result = verifyTurnkeyWebhookSignature({ | ||
| headers: req.headers, | ||
| body, | ||
| verificationKeys, | ||
| maxTimestampAgeMs: 5 * 60 * 1000, | ||
| }); | ||
| if (!result.ok) { | ||
| throw new Error(`Invalid Turnkey webhook: ${result.reason}`); | ||
| } | ||
| const event = JSON.parse(body.toString("utf8")); | ||
| ``` | ||
| Do not verify a parsed and re-stringified JSON object. Even harmless-looking changes to whitespace or key ordering will change the signed payload. |
299328
12.93%52
30%3521
15.03%72
100%+ Added
- Removed
Updated