@kindgi/crypto 0.1.6 → 0.1.7-rc.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.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Verifying a request a webhook trigger receives, in the scheme its sender
3
+ * signs with (`WebhookSignatureScheme`, `@kindgi/types`):
4
+ *
5
+ * - `hmac-sha256`: HMAC-SHA256 of the raw body under the secret's UTF-8
6
+ * bytes, read from one header (`hex` or `base64`, after an optional
7
+ * prefix). WooCommerce, Shopify, GitHub and Drupal's Webhooks module sign
8
+ * this way.
9
+ * - `standard-webhooks`: {@link verifyWebhook}.
10
+ *
11
+ * The comparison is constant-time. A missing header, a malformed one and a
12
+ * wrong signature are told apart only in the result's `reason`, for the
13
+ * receiver's own record: a receiver answers all of them the same way.
14
+ */
15
+ import { type KeyObject } from 'node:crypto';
16
+ import type { WebhookSignatureScheme } from '@kindgi/types';
17
+ import { type WebhookRequestHeaders } from './webhook.js';
18
+ export interface VerifyInboundSignatureInput {
19
+ readonly scheme: WebhookSignatureScheme;
20
+ /**
21
+ * The shared secret (its UTF-8 bytes for `hmac-sha256`; `whsec_…` for
22
+ * `standard-webhooks`), or the key {@link inboundSigningKey} derived
23
+ * from it for the same scheme.
24
+ */
25
+ readonly secret: string | KeyObject;
26
+ readonly headers: WebhookRequestHeaders;
27
+ /** The raw request body, exactly as received — never a re-serialized JSON value. */
28
+ readonly body: string | Uint8Array;
29
+ /** Clock in milliseconds, for a scheme with a timestamp; default `Date.now`. */
30
+ readonly now?: () => number;
31
+ }
32
+ /**
33
+ * Why a request isn't verified:
34
+ * - `signature-missing`: the scheme's header (or headers) isn't there;
35
+ * - `signature-invalid`: present, but malformed or not matching;
36
+ * - `stale`: the signed timestamp is outside the tolerance;
37
+ * - `secret-invalid`: the secret can't be used with the scheme (empty, or
38
+ * not a `whsec_` key for `standard-webhooks`).
39
+ */
40
+ export type VerifyInboundSignatureFailure = 'signature-missing' | 'signature-invalid' | 'stale' | 'secret-invalid';
41
+ export type VerifyInboundSignatureResult = {
42
+ readonly kind: 'ok';
43
+ /** The id the scheme itself signs (`webhook-id`), when it signs one. */
44
+ readonly signedId?: string;
45
+ } | {
46
+ readonly kind: 'err';
47
+ readonly reason: VerifyInboundSignatureFailure;
48
+ };
49
+ /**
50
+ * A trigger's signing key, derived from its secret for its scheme: the
51
+ * secret's UTF-8 bytes for `hmac-sha256`, the key a `whsec_…` secret
52
+ * encodes for `standard-webhooks`. A `KeyObject`, so a receiver that keeps
53
+ * it for a while holds no string of the secret, and nothing logs or
54
+ * serializes it by accident (`JSON.stringify` gives `{}`).
55
+ */
56
+ export declare function inboundSigningKey(scheme: WebhookSignatureScheme, secret: string): {
57
+ readonly kind: 'ok';
58
+ readonly key: KeyObject;
59
+ } | {
60
+ readonly kind: 'err';
61
+ readonly reason: 'secret-invalid';
62
+ };
63
+ /** Verify a received request against its trigger's signature scheme. */
64
+ export declare function verifyInboundSignature(input: VerifyInboundSignatureInput): VerifyInboundSignatureResult;
65
+ //# sourceMappingURL=inbound-signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-signature.d.ts","sourceRoot":"","sources":["../src/inbound-signature.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAgD,MAAM,aAAa,CAAC;AAE3F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EACL,KAAK,qBAAqB,EAI3B,MAAM,cAAc,CAAC;AAItB,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IACnC,gFAAgF;IAChF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,6BAA6B,GACrC,mBAAmB,GACnB,mBAAmB,GACnB,OAAO,GACP,gBAAgB,CAAC;AAErB,MAAM,MAAM,4BAA4B,GACpC;IACE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAAA;CAAE,CAAC;AAE7E;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,sBAAsB,EAC9B,MAAM,EAAE,MAAM,GAEZ;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAY9D;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,2BAA2B,GACjC,4BAA4B,CA8C9B"}
@@ -0,0 +1,96 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /**
4
+ * Verifying a request a webhook trigger receives, in the scheme its sender
5
+ * signs with (`WebhookSignatureScheme`, `@kindgi/types`):
6
+ *
7
+ * - `hmac-sha256`: HMAC-SHA256 of the raw body under the secret's UTF-8
8
+ * bytes, read from one header (`hex` or `base64`, after an optional
9
+ * prefix). WooCommerce, Shopify, GitHub and Drupal's Webhooks module sign
10
+ * this way.
11
+ * - `standard-webhooks`: {@link verifyWebhook}.
12
+ *
13
+ * The comparison is constant-time. A missing header, a malformed one and a
14
+ * wrong signature are told apart only in the result's `reason`, for the
15
+ * receiver's own record: a receiver answers all of them the same way.
16
+ */
17
+ import { createHmac, createSecretKey, timingSafeEqual } from 'node:crypto';
18
+ import { parseWebhookSecret, readHeader, verifyWebhookWith, } from './webhook.js';
19
+ const SHA256_BYTES = 32;
20
+ /**
21
+ * A trigger's signing key, derived from its secret for its scheme: the
22
+ * secret's UTF-8 bytes for `hmac-sha256`, the key a `whsec_…` secret
23
+ * encodes for `standard-webhooks`. A `KeyObject`, so a receiver that keeps
24
+ * it for a while holds no string of the secret, and nothing logs or
25
+ * serializes it by accident (`JSON.stringify` gives `{}`).
26
+ */
27
+ export function inboundSigningKey(scheme, secret) {
28
+ const bytes = scheme.kind === 'standard-webhooks'
29
+ ? parseWebhookSecret(secret)
30
+ : secret.length === 0
31
+ ? null
32
+ : Buffer.from(secret, 'utf-8');
33
+ if (bytes === null)
34
+ return { kind: 'err', reason: 'secret-invalid' };
35
+ const key = createSecretKey(bytes);
36
+ // The key keeps its own copy.
37
+ bytes.fill(0);
38
+ return { kind: 'ok', key };
39
+ }
40
+ /** Verify a received request against its trigger's signature scheme. */
41
+ export function verifyInboundSignature(input) {
42
+ const { scheme } = input;
43
+ if (scheme.kind === 'standard-webhooks') {
44
+ const verified = verifyWebhookWith({
45
+ headers: input.headers,
46
+ body: input.body,
47
+ ...(scheme.toleranceSeconds !== undefined && {
48
+ toleranceSeconds: scheme.toleranceSeconds,
49
+ }),
50
+ ...(input.now !== undefined && { now: input.now }),
51
+ }, input.secret);
52
+ if (verified.kind === 'ok')
53
+ return { kind: 'ok', signedId: verified.id };
54
+ switch (verified.reason) {
55
+ case 'missing-headers':
56
+ return { kind: 'err', reason: 'signature-missing' };
57
+ case 'timestamp-out-of-tolerance':
58
+ return { kind: 'err', reason: 'stale' };
59
+ case 'invalid-secret':
60
+ return { kind: 'err', reason: 'secret-invalid' };
61
+ default:
62
+ return { kind: 'err', reason: 'signature-invalid' };
63
+ }
64
+ }
65
+ const { secret } = input;
66
+ if (typeof secret === 'string' && secret.length === 0) {
67
+ return { kind: 'err', reason: 'secret-invalid' };
68
+ }
69
+ const raw = readHeader(input.headers, scheme.header.toLowerCase());
70
+ if (raw === undefined)
71
+ return { kind: 'err', reason: 'signature-missing' };
72
+ const prefix = scheme.prefix ?? '';
73
+ if (!raw.startsWith(prefix))
74
+ return { kind: 'err', reason: 'signature-invalid' };
75
+ const provided = decodeDigest(raw.slice(prefix.length).trim(), scheme.encoding);
76
+ if (provided === undefined)
77
+ return { kind: 'err', reason: 'signature-invalid' };
78
+ const expected = createHmac('sha256', typeof secret === 'string' ? Buffer.from(secret, 'utf-8') : secret)
79
+ .update(typeof input.body === 'string' ? Buffer.from(input.body, 'utf-8') : input.body)
80
+ .digest();
81
+ return timingSafeEqual(provided, expected)
82
+ ? { kind: 'ok' }
83
+ : { kind: 'err', reason: 'signature-invalid' };
84
+ }
85
+ /** A 32-byte digest from its `hex` or `base64` form; `undefined` when it isn't one. */
86
+ function decodeDigest(text, encoding) {
87
+ if (encoding === 'hex') {
88
+ return /^[0-9a-fA-F]{64}$/.test(text) ? Buffer.from(text, 'hex') : undefined;
89
+ }
90
+ // Base64 of 32 bytes is 44 characters with one `=` of padding.
91
+ if (!/^[A-Za-z0-9+/]{43}=$/.test(text))
92
+ return undefined;
93
+ const bytes = Buffer.from(text, 'base64');
94
+ return bytes.length === SHA256_BYTES ? bytes : undefined;
95
+ }
96
+ //# sourceMappingURL=inbound-signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-signature.js","sourceRoot":"","sources":["../src/inbound-signature.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export type { Ed25519KeyPair } from './ed25519.js';
3
3
  export { hmacSha256Sign, hmacSha256Verify } from './hmac.js';
4
4
  export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, generateWebhookSecret, isStrongWebhookSecret, signWebhook, verifyWebhook, WEBHOOK_HEADERS, WEBHOOK_SECRET_MIN_BYTES, WEBHOOK_SECRET_PREFIX, webhookHeaders, } from './webhook.js';
5
5
  export type { SignWebhookInput, VerifyWebhookFailure, VerifyWebhookInput, VerifyWebhookResult, WebhookRequestHeaders, } from './webhook.js';
6
+ export { inboundSigningKey, verifyInboundSignature } from './inbound-signature.js';
7
+ export type { VerifyInboundSignatureFailure, VerifyInboundSignatureInput, VerifyInboundSignatureResult, } from './inbound-signature.js';
6
8
  export { createInMemorySigningKeyBinding } from './binding.js';
7
9
  export { EXPORT_SIGNING_ALGORITHMS, createEcdsaP256ExportSigner, createEd25519ExportSigner, createExportSignerFromPem, ecdsaDerToP1363, exportSigningKey, } from './export-signing.js';
8
10
  export { parseRetiredExportKeys, withRetiredExportKeys } from './retired-export-keys.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EACL,iCAAiC,EACjC,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,eAAe,EACf,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACzF,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAE9F,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EACL,iCAAiC,EACjC,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACnF,YAAY,EACV,6BAA6B,EAC7B,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,eAAe,EACf,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACzF,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAE9F,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  export { ED25519_SIGNATURE_BYTES, generateEd25519KeyPair, parsePrivateKeyBase64, parsePrivateKeyPem, parsePublicKeyBase64, parsePublicKeyPem, serializePrivateKeyBase64, serializePrivateKeyPem, serializePublicKeyBase64, serializePublicKeyPem, signEd25519, verifyEd25519, } from './ed25519.js';
4
4
  export { hmacSha256Sign, hmacSha256Verify } from './hmac.js';
5
5
  export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, generateWebhookSecret, isStrongWebhookSecret, signWebhook, verifyWebhook, WEBHOOK_HEADERS, WEBHOOK_SECRET_MIN_BYTES, WEBHOOK_SECRET_PREFIX, webhookHeaders, } from './webhook.js';
6
+ export { inboundSigningKey, verifyInboundSignature } from './inbound-signature.js';
6
7
  export { createInMemorySigningKeyBinding } from './binding.js';
7
8
  export { EXPORT_SIGNING_ALGORITHMS, createEcdsaP256ExportSigner, createEd25519ExportSigner, createExportSignerFromPem, ecdsaDerToP1363, exportSigningKey, } from './export-signing.js';
8
9
  export { parseRetiredExportKeys, withRetiredExportKeys } from './retired-export-keys.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EACL,iCAAiC,EACjC,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAStB,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,eAAe,EACf,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAiBzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EACL,iCAAiC,EACjC,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAStB,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAOnF,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,eAAe,EACf,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAiBzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
package/dist/webhook.d.ts CHANGED
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Webhook signatures in the Standard Webhooks format
3
+ * (https://www.standardwebhooks.com), symmetric variant `v1`:
4
+ *
5
+ * webhook-id: the message id (the same on every retry)
6
+ * webhook-timestamp: Unix seconds when the message was signed
7
+ * webhook-signature: `v1,<base64 HMAC-SHA256>`, space-separated when
8
+ * several secrets sign (secret rotation)
9
+ *
10
+ * The signed content is `${id}.${timestamp}.${body}`, with `body` the raw
11
+ * request body exactly as sent. Secrets are `whsec_` + base64 of the key
12
+ * bytes. A receiver can verify with this module or with any Standard
13
+ * Webhooks library.
14
+ */
15
+ import { type KeyObject } from 'node:crypto';
1
16
  import type { Result } from '@kindgi/types';
2
17
  import type { MalformedKeyError } from './errors.js';
3
18
  /** Prefix of a webhook signing secret. */
@@ -71,4 +86,14 @@ export type VerifyWebhookResult = {
71
86
  * constant-time. On `ok`, deduplicate on `id` — delivery is at least once.
72
87
  */
73
88
  export declare function verifyWebhook(input: VerifyWebhookInput): VerifyWebhookResult;
89
+ /**
90
+ * {@link verifyWebhook} with the secret as given, or as the key already
91
+ * decoded from it (a `KeyObject`, which a receiver can keep without holding
92
+ * the secret as a string). Within the package.
93
+ */
94
+ export declare function verifyWebhookWith(input: Omit<VerifyWebhookInput, 'secret'>, secret: string | KeyObject): VerifyWebhookResult;
95
+ /** Key bytes of a `whsec_…` (or bare base64) secret; `null` when malformed. Within the package. */
96
+ export declare function parseWebhookSecret(secret: string): Buffer | null;
97
+ /** A header's first value (`name` in lower case, matched case-insensitively); `undefined` when absent or empty. */
98
+ export declare function readHeader(headers: WebhookRequestHeaders, name: string): string | undefined;
74
99
  //# sourceMappingURL=webhook.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,0DAA0D;AAC1D,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,gFAAgF;AAChF,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAKrD,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,yEAAyE;AACzE,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC5C,oEAAoE;IACpE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CActF;AAED,6DAA6D;AAC7D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,GACtB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAW7D;AAED,4EAA4E;AAC5E,MAAM,MAAM,qBAAqB,GAC7B;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GACpC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IACnC,yDAAyD;IACzD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,iDAAiD;IACjD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAC5B,iBAAiB,GACjB,mBAAmB,GACnB,4BAA4B,GAC5B,gBAAgB,GAChB,uBAAuB,CAAC;AAE5B,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CA2B5E"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"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"}
package/dist/webhook.js CHANGED
@@ -39,7 +39,7 @@ export function generateWebhookSecret() {
39
39
  * base64) of at least {@link WEBHOOK_SECRET_MIN_BYTES} bytes.
40
40
  */
41
41
  export function isStrongWebhookSecret(secret) {
42
- const key = parseSecret(secret);
42
+ const key = parseWebhookSecret(secret);
43
43
  return key !== null && key.length >= WEBHOOK_SECRET_MIN_BYTES;
44
44
  }
45
45
  /**
@@ -58,7 +58,7 @@ export function signWebhook(input) {
58
58
  const content = signedContent(input.id, String(input.timestamp), input.body);
59
59
  const signatures = [];
60
60
  for (const secret of secrets) {
61
- const key = parseSecret(secret);
61
+ const key = parseWebhookSecret(secret);
62
62
  if (key === null)
63
63
  return malformedKey('webhook secret must be `whsec_` + base64 key bytes');
64
64
  signatures.push(`${SIGNATURE_VERSION},${hmac(key, content).toString('base64')}`);
@@ -85,6 +85,14 @@ export function webhookHeaders(input) {
85
85
  * constant-time. On `ok`, deduplicate on `id` — delivery is at least once.
86
86
  */
87
87
  export function verifyWebhook(input) {
88
+ return verifyWebhookWith(input, input.secret);
89
+ }
90
+ /**
91
+ * {@link verifyWebhook} with the secret as given, or as the key already
92
+ * decoded from it (a `KeyObject`, which a receiver can keep without holding
93
+ * the secret as a string). Within the package.
94
+ */
95
+ export function verifyWebhookWith(input, secret) {
88
96
  const id = readHeader(input.headers, WEBHOOK_HEADERS.id);
89
97
  const timestampRaw = readHeader(input.headers, WEBHOOK_HEADERS.timestamp);
90
98
  const signatureHeader = readHeader(input.headers, WEBHOOK_HEADERS.signature);
@@ -99,7 +107,7 @@ export function verifyWebhook(input) {
99
107
  if (Math.abs(nowSeconds - timestamp) > tolerance) {
100
108
  return { kind: 'err', reason: 'timestamp-out-of-tolerance' };
101
109
  }
102
- const key = parseSecret(input.secret);
110
+ const key = typeof secret === 'string' ? parseWebhookSecret(secret) : secret;
103
111
  if (key === null)
104
112
  return { kind: 'err', reason: 'invalid-secret' };
105
113
  const expected = hmac(key, signedContent(id, timestampRaw, input.body));
@@ -122,8 +130,8 @@ function signedContent(id, timestamp, body) {
122
130
  function hmac(key, content) {
123
131
  return createHmac('sha256', key).update(content).digest();
124
132
  }
125
- /** Key bytes of a `whsec_…` (or bare base64) secret; `null` when malformed. */
126
- function parseSecret(secret) {
133
+ /** Key bytes of a `whsec_…` (or bare base64) secret; `null` when malformed. Within the package. */
134
+ export function parseWebhookSecret(secret) {
127
135
  const encoded = secret.startsWith(WEBHOOK_SECRET_PREFIX)
128
136
  ? secret.slice(WEBHOOK_SECRET_PREFIX.length)
129
137
  : secret;
@@ -132,7 +140,8 @@ function parseSecret(secret) {
132
140
  const key = Buffer.from(encoded, 'base64');
133
141
  return key.length === 0 ? null : key;
134
142
  }
135
- function readHeader(headers, name) {
143
+ /** A header's first value (`name` in lower case, matched case-insensitively); `undefined` when absent or empty. */
144
+ export function readHeader(headers, name) {
136
145
  if (typeof headers.get === 'function') {
137
146
  const value = headers.get(name);
138
147
  return value === null || value.length === 0 ? undefined : value;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kindgi/crypto",
3
- "version": "0.1.6",
3
+ "version": "0.1.7-rc.0",
4
4
  "description": "Kindgi™ cryptographic primitives. Ed25519 signatures for asymmetric use (signed audit bundles, provenance exports, deployment attestations) and HMAC-SHA256 for symmetric use (webhook payloads). Includes SigningKeyBinding interface + in-memory reference impl, PEM/base64 key format converters, and typed error interfaces. Pure Node built-ins, zero runtime deps. Deployments plug their own key store (KMS, Vault, env-var) via SigningKeyBinding.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "README.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@kindgi/types": "0.1.6"
31
+ "@kindgi/types": "0.1.7-rc.0"
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=22.12.0"
@@ -0,0 +1,155 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ /**
5
+ * Verifying a request a webhook trigger receives, in the scheme its sender
6
+ * signs with (`WebhookSignatureScheme`, `@kindgi/types`):
7
+ *
8
+ * - `hmac-sha256`: HMAC-SHA256 of the raw body under the secret's UTF-8
9
+ * bytes, read from one header (`hex` or `base64`, after an optional
10
+ * prefix). WooCommerce, Shopify, GitHub and Drupal's Webhooks module sign
11
+ * this way.
12
+ * - `standard-webhooks`: {@link verifyWebhook}.
13
+ *
14
+ * The comparison is constant-time. A missing header, a malformed one and a
15
+ * wrong signature are told apart only in the result's `reason`, for the
16
+ * receiver's own record: a receiver answers all of them the same way.
17
+ */
18
+
19
+ import { type KeyObject, createHmac, createSecretKey, timingSafeEqual } from 'node:crypto';
20
+
21
+ import type { WebhookSignatureScheme } from '@kindgi/types';
22
+
23
+ import {
24
+ type WebhookRequestHeaders,
25
+ parseWebhookSecret,
26
+ readHeader,
27
+ verifyWebhookWith,
28
+ } from './webhook.js';
29
+
30
+ const SHA256_BYTES = 32;
31
+
32
+ export interface VerifyInboundSignatureInput {
33
+ readonly scheme: WebhookSignatureScheme;
34
+ /**
35
+ * The shared secret (its UTF-8 bytes for `hmac-sha256`; `whsec_…` for
36
+ * `standard-webhooks`), or the key {@link inboundSigningKey} derived
37
+ * from it for the same scheme.
38
+ */
39
+ readonly secret: string | KeyObject;
40
+ readonly headers: WebhookRequestHeaders;
41
+ /** The raw request body, exactly as received — never a re-serialized JSON value. */
42
+ readonly body: string | Uint8Array;
43
+ /** Clock in milliseconds, for a scheme with a timestamp; default `Date.now`. */
44
+ readonly now?: () => number;
45
+ }
46
+
47
+ /**
48
+ * Why a request isn't verified:
49
+ * - `signature-missing`: the scheme's header (or headers) isn't there;
50
+ * - `signature-invalid`: present, but malformed or not matching;
51
+ * - `stale`: the signed timestamp is outside the tolerance;
52
+ * - `secret-invalid`: the secret can't be used with the scheme (empty, or
53
+ * not a `whsec_` key for `standard-webhooks`).
54
+ */
55
+ export type VerifyInboundSignatureFailure =
56
+ | 'signature-missing'
57
+ | 'signature-invalid'
58
+ | 'stale'
59
+ | 'secret-invalid';
60
+
61
+ export type VerifyInboundSignatureResult =
62
+ | {
63
+ readonly kind: 'ok';
64
+ /** The id the scheme itself signs (`webhook-id`), when it signs one. */
65
+ readonly signedId?: string;
66
+ }
67
+ | { readonly kind: 'err'; readonly reason: VerifyInboundSignatureFailure };
68
+
69
+ /**
70
+ * A trigger's signing key, derived from its secret for its scheme: the
71
+ * secret's UTF-8 bytes for `hmac-sha256`, the key a `whsec_…` secret
72
+ * encodes for `standard-webhooks`. A `KeyObject`, so a receiver that keeps
73
+ * it for a while holds no string of the secret, and nothing logs or
74
+ * serializes it by accident (`JSON.stringify` gives `{}`).
75
+ */
76
+ export function inboundSigningKey(
77
+ scheme: WebhookSignatureScheme,
78
+ secret: string,
79
+ ):
80
+ | { readonly kind: 'ok'; readonly key: KeyObject }
81
+ | { readonly kind: 'err'; readonly reason: 'secret-invalid' } {
82
+ const bytes =
83
+ scheme.kind === 'standard-webhooks'
84
+ ? parseWebhookSecret(secret)
85
+ : secret.length === 0
86
+ ? null
87
+ : Buffer.from(secret, 'utf-8');
88
+ if (bytes === null) return { kind: 'err', reason: 'secret-invalid' };
89
+ const key = createSecretKey(bytes);
90
+ // The key keeps its own copy.
91
+ bytes.fill(0);
92
+ return { kind: 'ok', key };
93
+ }
94
+
95
+ /** Verify a received request against its trigger's signature scheme. */
96
+ export function verifyInboundSignature(
97
+ input: VerifyInboundSignatureInput,
98
+ ): VerifyInboundSignatureResult {
99
+ const { scheme } = input;
100
+ if (scheme.kind === 'standard-webhooks') {
101
+ const verified = verifyWebhookWith(
102
+ {
103
+ headers: input.headers,
104
+ body: input.body,
105
+ ...(scheme.toleranceSeconds !== undefined && {
106
+ toleranceSeconds: scheme.toleranceSeconds,
107
+ }),
108
+ ...(input.now !== undefined && { now: input.now }),
109
+ },
110
+ input.secret,
111
+ );
112
+ if (verified.kind === 'ok') return { kind: 'ok', signedId: verified.id };
113
+ switch (verified.reason) {
114
+ case 'missing-headers':
115
+ return { kind: 'err', reason: 'signature-missing' };
116
+ case 'timestamp-out-of-tolerance':
117
+ return { kind: 'err', reason: 'stale' };
118
+ case 'invalid-secret':
119
+ return { kind: 'err', reason: 'secret-invalid' };
120
+ default:
121
+ return { kind: 'err', reason: 'signature-invalid' };
122
+ }
123
+ }
124
+
125
+ const { secret } = input;
126
+ if (typeof secret === 'string' && secret.length === 0) {
127
+ return { kind: 'err', reason: 'secret-invalid' };
128
+ }
129
+ const raw = readHeader(input.headers, scheme.header.toLowerCase());
130
+ if (raw === undefined) return { kind: 'err', reason: 'signature-missing' };
131
+ const prefix = scheme.prefix ?? '';
132
+ if (!raw.startsWith(prefix)) return { kind: 'err', reason: 'signature-invalid' };
133
+ const provided = decodeDigest(raw.slice(prefix.length).trim(), scheme.encoding);
134
+ if (provided === undefined) return { kind: 'err', reason: 'signature-invalid' };
135
+ const expected = createHmac(
136
+ 'sha256',
137
+ typeof secret === 'string' ? Buffer.from(secret, 'utf-8') : secret,
138
+ )
139
+ .update(typeof input.body === 'string' ? Buffer.from(input.body, 'utf-8') : input.body)
140
+ .digest();
141
+ return timingSafeEqual(provided, expected)
142
+ ? { kind: 'ok' }
143
+ : { kind: 'err', reason: 'signature-invalid' };
144
+ }
145
+
146
+ /** A 32-byte digest from its `hex` or `base64` form; `undefined` when it isn't one. */
147
+ function decodeDigest(text: string, encoding: 'hex' | 'base64'): Buffer | undefined {
148
+ if (encoding === 'hex') {
149
+ return /^[0-9a-fA-F]{64}$/.test(text) ? Buffer.from(text, 'hex') : undefined;
150
+ }
151
+ // Base64 of 32 bytes is 44 characters with one `=` of padding.
152
+ if (!/^[A-Za-z0-9+/]{43}=$/.test(text)) return undefined;
153
+ const bytes = Buffer.from(text, 'base64');
154
+ return bytes.length === SHA256_BYTES ? bytes : undefined;
155
+ }
package/src/index.ts CHANGED
@@ -38,6 +38,13 @@ export type {
38
38
  WebhookRequestHeaders,
39
39
  } from './webhook.js';
40
40
 
41
+ export { inboundSigningKey, verifyInboundSignature } from './inbound-signature.js';
42
+ export type {
43
+ VerifyInboundSignatureFailure,
44
+ VerifyInboundSignatureInput,
45
+ VerifyInboundSignatureResult,
46
+ } from './inbound-signature.js';
47
+
41
48
  export { createInMemorySigningKeyBinding } from './binding.js';
42
49
 
43
50
  export {
package/src/webhook.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * Webhooks library.
17
17
  */
18
18
 
19
- import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
19
+ import { type KeyObject, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
20
20
 
21
21
  import type { Result } from '@kindgi/types';
22
22
 
@@ -52,7 +52,7 @@ export function generateWebhookSecret(): string {
52
52
  * base64) of at least {@link WEBHOOK_SECRET_MIN_BYTES} bytes.
53
53
  */
54
54
  export function isStrongWebhookSecret(secret: string): boolean {
55
- const key = parseSecret(secret);
55
+ const key = parseWebhookSecret(secret);
56
56
  return key !== null && key.length >= WEBHOOK_SECRET_MIN_BYTES;
57
57
  }
58
58
 
@@ -86,7 +86,7 @@ export function signWebhook(input: SignWebhookInput): Result<string, MalformedKe
86
86
  const content = signedContent(input.id, String(input.timestamp), input.body);
87
87
  const signatures: string[] = [];
88
88
  for (const secret of secrets) {
89
- const key = parseSecret(secret);
89
+ const key = parseWebhookSecret(secret);
90
90
  if (key === null) return malformedKey('webhook secret must be `whsec_` + base64 key bytes');
91
91
  signatures.push(`${SIGNATURE_VERSION},${hmac(key, content).toString('base64')}`);
92
92
  }
@@ -142,6 +142,18 @@ export type VerifyWebhookResult =
142
142
  * constant-time. On `ok`, deduplicate on `id` — delivery is at least once.
143
143
  */
144
144
  export function verifyWebhook(input: VerifyWebhookInput): VerifyWebhookResult {
145
+ return verifyWebhookWith(input, input.secret);
146
+ }
147
+
148
+ /**
149
+ * {@link verifyWebhook} with the secret as given, or as the key already
150
+ * decoded from it (a `KeyObject`, which a receiver can keep without holding
151
+ * the secret as a string). Within the package.
152
+ */
153
+ export function verifyWebhookWith(
154
+ input: Omit<VerifyWebhookInput, 'secret'>,
155
+ secret: string | KeyObject,
156
+ ): VerifyWebhookResult {
145
157
  const id = readHeader(input.headers, WEBHOOK_HEADERS.id);
146
158
  const timestampRaw = readHeader(input.headers, WEBHOOK_HEADERS.timestamp);
147
159
  const signatureHeader = readHeader(input.headers, WEBHOOK_HEADERS.signature);
@@ -155,7 +167,7 @@ export function verifyWebhook(input: VerifyWebhookInput): VerifyWebhookResult {
155
167
  if (Math.abs(nowSeconds - timestamp) > tolerance) {
156
168
  return { kind: 'err', reason: 'timestamp-out-of-tolerance' };
157
169
  }
158
- const key = parseSecret(input.secret);
170
+ const key = typeof secret === 'string' ? parseWebhookSecret(secret) : secret;
159
171
  if (key === null) return { kind: 'err', reason: 'invalid-secret' };
160
172
 
161
173
  const expected = hmac(key, signedContent(id, timestampRaw, input.body));
@@ -176,12 +188,12 @@ function signedContent(id: string, timestamp: string, body: string | Uint8Array)
176
188
  return Buffer.concat([prefix, bodyBytes]);
177
189
  }
178
190
 
179
- function hmac(key: Buffer, content: Buffer): Buffer {
191
+ function hmac(key: Buffer | KeyObject, content: Buffer): Buffer {
180
192
  return createHmac('sha256', key).update(content).digest();
181
193
  }
182
194
 
183
- /** Key bytes of a `whsec_…` (or bare base64) secret; `null` when malformed. */
184
- function parseSecret(secret: string): Buffer | null {
195
+ /** Key bytes of a `whsec_…` (or bare base64) secret; `null` when malformed. Within the package. */
196
+ export function parseWebhookSecret(secret: string): Buffer | null {
185
197
  const encoded = secret.startsWith(WEBHOOK_SECRET_PREFIX)
186
198
  ? secret.slice(WEBHOOK_SECRET_PREFIX.length)
187
199
  : secret;
@@ -190,7 +202,8 @@ function parseSecret(secret: string): Buffer | null {
190
202
  return key.length === 0 ? null : key;
191
203
  }
192
204
 
193
- function readHeader(headers: WebhookRequestHeaders, name: string): string | undefined {
205
+ /** A header's first value (`name` in lower case, matched case-insensitively); `undefined` when absent or empty. */
206
+ export function readHeader(headers: WebhookRequestHeaders, name: string): string | undefined {
194
207
  if (typeof (headers as { get?: unknown }).get === 'function') {
195
208
  const value = (headers as { get(name: string): string | null }).get(name);
196
209
  return value === null || value.length === 0 ? undefined : value;