@maestro-js/sign 1.0.0-alpha.30
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/index.d.ts +110 -0
- package/dist/index.js +86 -0
- package/package.json +36 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
interface SignDriver {
|
|
2
|
+
sign(value: string): string;
|
|
3
|
+
verify(options: {
|
|
4
|
+
value: string;
|
|
5
|
+
signature: string;
|
|
6
|
+
}): boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface HmacSha256DriverConfig {
|
|
10
|
+
/** The base64-encoded signing key used to produce new signatures. Decoded to its raw bytes for HMAC. */
|
|
11
|
+
key: string;
|
|
12
|
+
/** Retired base64-encoded keys still accepted on verify, so signatures survive key rotation. @default `[]` */
|
|
13
|
+
previousKeys?: string[];
|
|
14
|
+
}
|
|
15
|
+
declare function hmacSha256SignDriver(config: HmacSha256DriverConfig): SignDriver;
|
|
16
|
+
|
|
17
|
+
interface Ed25519DriverConfig {
|
|
18
|
+
/** PEM-encoded (PKCS#8) private key used to produce signatures. Omit for a verify-only instance. */
|
|
19
|
+
privateKey?: string;
|
|
20
|
+
/**
|
|
21
|
+
* PEM-encoded (SPKI) public keys accepted on verify — for verify-only instances and rotated-out
|
|
22
|
+
* keys. The configured private key's own public key is always accepted, so signers don't repeat it.
|
|
23
|
+
* @default `[]`
|
|
24
|
+
*/
|
|
25
|
+
publicKeys?: string[];
|
|
26
|
+
}
|
|
27
|
+
declare function ed25519SignDriver(config: Ed25519DriverConfig): SignDriver;
|
|
28
|
+
|
|
29
|
+
declare function create(driver: Sign.Driver): Sign.SignService;
|
|
30
|
+
declare function provider(key: Sign.Provider.Key): Sign.SignService;
|
|
31
|
+
type KeysWithFallback = keyof Sign.Provider.Keys extends never ? {
|
|
32
|
+
default: unknown;
|
|
33
|
+
} : Sign.Provider.Keys;
|
|
34
|
+
/**
|
|
35
|
+
* Pluggable signing and verification with driver-based algorithms.
|
|
36
|
+
* Call methods directly on the default instance, or use `Sign.Provider.create()` for named instances.
|
|
37
|
+
*/
|
|
38
|
+
declare const Sign: {
|
|
39
|
+
provider: typeof provider;
|
|
40
|
+
drivers: {
|
|
41
|
+
hmacSha256: typeof hmacSha256SignDriver;
|
|
42
|
+
ed25519: typeof ed25519SignDriver;
|
|
43
|
+
};
|
|
44
|
+
/** Signs a value, returning a bare base64url signature */
|
|
45
|
+
sign: (value: string) => string;
|
|
46
|
+
/** Verifies that the signature was produced by {@link sign} for the value, trying every configured key */
|
|
47
|
+
verify: (options: {
|
|
48
|
+
value: string;
|
|
49
|
+
signature: string;
|
|
50
|
+
}) => boolean;
|
|
51
|
+
Provider: {
|
|
52
|
+
create: typeof create;
|
|
53
|
+
register: (name: Sign.Provider.Key, item: Sign.SignService) => void;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Pluggable signing and verification with driver-based algorithms.
|
|
58
|
+
*
|
|
59
|
+
* Signs a value with a secret and verifies the signature later. Signatures are **bare, standard**
|
|
60
|
+
* base64url — no key id or other proprietary framing — so another party can verify your signed
|
|
61
|
+
* content with stock crypto and no `@maestro-js` dependency. For the `hmacSha256` driver (whose key
|
|
62
|
+
* is base64-encoded, decoded to its raw bytes before signing):
|
|
63
|
+
*
|
|
64
|
+
* ```js
|
|
65
|
+
* const expected = crypto.createHmac('sha256', Buffer.from(key, 'base64')).update(value).digest('base64url')
|
|
66
|
+
* crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(theirSignature))
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* Key rotation is handled on the verifying side: there is deliberately no key id embedded in the
|
|
70
|
+
* signature — that keeps it standard and externally verifiable — so {@link SignService.verify} tries
|
|
71
|
+
* the current key and every retired key still configured (negligible for a small rotation window).
|
|
72
|
+
*
|
|
73
|
+
* The `ed25519` driver signs with a PEM private key and verifies with public keys. Omit the private
|
|
74
|
+
* key for a verify-only instance — one that checks another party's signatures but can never produce
|
|
75
|
+
* its own ({@link SignService.sign} throws).
|
|
76
|
+
*
|
|
77
|
+
* This is signing (a MAC or digital signature), distinct from one-way password hashing
|
|
78
|
+
* (`@maestro-js/hash`) and from reversible encryption (`@maestro-js/crypt`).
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* Sign.Provider.register('default', Sign.Provider.create(Sign.drivers.hmacSha256({ key: process.env.SIGNING_KEY! })))
|
|
83
|
+
*
|
|
84
|
+
* const signature = Sign.sign('order.completed:ord_8k2Xp')
|
|
85
|
+
* Sign.verify({ value: 'order.completed:ord_8k2Xp', signature }) // true
|
|
86
|
+
*
|
|
87
|
+
* // Verify-only Ed25519 instance for a vendor's webhooks
|
|
88
|
+
* Sign.Provider.register('webhooks', Sign.Provider.create(Sign.drivers.ed25519({ publicKeys: [vendorPublicKey] })))
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
declare namespace Sign {
|
|
92
|
+
/** Pluggable signing algorithm — see `Sign.drivers` for built-in options */
|
|
93
|
+
type Driver = SignDriver;
|
|
94
|
+
namespace Provider {
|
|
95
|
+
type Key = keyof KeysWithFallback;
|
|
96
|
+
interface Keys {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
interface SignService {
|
|
100
|
+
/** Signs a value, returning a bare base64url signature */
|
|
101
|
+
sign: (value: string) => string;
|
|
102
|
+
/** Verifies that the signature was produced by {@link sign} for the value, trying every configured key */
|
|
103
|
+
verify: (options: {
|
|
104
|
+
value: string;
|
|
105
|
+
signature: string;
|
|
106
|
+
}) => boolean;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { Sign };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { ServiceRegistry } from "@maestro-js/service-registry";
|
|
3
|
+
|
|
4
|
+
// src/hmac-sha256-sign-driver.ts
|
|
5
|
+
import assert from "assert";
|
|
6
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
7
|
+
function hmacSha256SignDriver(config) {
|
|
8
|
+
assert.ok(config.key, "hmacSha256 driver: a signing key is required");
|
|
9
|
+
const keys = [config.key, ...config.previousKeys ?? []].filter(Boolean).map((key) => {
|
|
10
|
+
const decoded = Buffer.from(key, "base64");
|
|
11
|
+
assert.ok(decoded.length > 0, `hmacSha256 driver: invalid base64 signing key: ${key}`);
|
|
12
|
+
return decoded;
|
|
13
|
+
});
|
|
14
|
+
function sign(value) {
|
|
15
|
+
return hmacSign(keys[0], value);
|
|
16
|
+
}
|
|
17
|
+
function verify(options) {
|
|
18
|
+
const provided = Buffer.from(options.signature);
|
|
19
|
+
return keys.some((key) => {
|
|
20
|
+
const expected = Buffer.from(hmacSign(key, options.value));
|
|
21
|
+
return expected.length === provided.length && timingSafeEqual(expected, provided);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return { sign, verify };
|
|
25
|
+
}
|
|
26
|
+
function hmacSign(key, value) {
|
|
27
|
+
return createHmac("sha256", key).update(value).digest("base64url");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/ed25519-sign-driver.ts
|
|
31
|
+
import assert2 from "assert";
|
|
32
|
+
import crypto from "crypto";
|
|
33
|
+
function ed25519SignDriver(config) {
|
|
34
|
+
assert2.ok(config.privateKey || config.publicKeys?.length, "Ed25519 driver requires a privateKey and/or publicKeys");
|
|
35
|
+
const verifyKeys = [
|
|
36
|
+
...config.privateKey ? [crypto.createPublicKey(config.privateKey)] : [],
|
|
37
|
+
...(config.publicKeys ?? []).map((publicKey) => crypto.createPublicKey(publicKey))
|
|
38
|
+
];
|
|
39
|
+
function sign(value) {
|
|
40
|
+
assert2.ok(config.privateKey, "This signer is verify-only \u2014 configure a privateKey to sign");
|
|
41
|
+
return crypto.sign(null, Buffer.from(value), config.privateKey).toString("base64url");
|
|
42
|
+
}
|
|
43
|
+
function verify(options) {
|
|
44
|
+
const provided = Buffer.from(options.signature, "base64url");
|
|
45
|
+
return verifyKeys.some((publicKey) => {
|
|
46
|
+
try {
|
|
47
|
+
return crypto.verify(null, Buffer.from(options.value), publicKey, provided);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { sign, verify };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/index.ts
|
|
57
|
+
function create(driver) {
|
|
58
|
+
return {
|
|
59
|
+
sign: (value) => driver.sign(value),
|
|
60
|
+
verify: (options) => driver.verify(options)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
var registry = ServiceRegistry.createRegistry(ServiceRegistry.proxyFunctionsForObject);
|
|
64
|
+
var Provider = {
|
|
65
|
+
create,
|
|
66
|
+
register: registry.register
|
|
67
|
+
};
|
|
68
|
+
function provider(key) {
|
|
69
|
+
const service = registry.resolve(key);
|
|
70
|
+
return {
|
|
71
|
+
sign: service.sign,
|
|
72
|
+
verify: service.verify
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
var Sign = {
|
|
76
|
+
Provider,
|
|
77
|
+
...provider("default"),
|
|
78
|
+
provider,
|
|
79
|
+
drivers: {
|
|
80
|
+
hmacSha256: hmacSha256SignDriver,
|
|
81
|
+
ed25519: ed25519SignDriver
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
export {
|
|
85
|
+
Sign
|
|
86
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maestro-js/sign",
|
|
3
|
+
"description": "Signing and verification with driver-based algorithms (HMAC-SHA256, Ed25519) and key rotation for the maestro-js framework. Use when signing and verifying high-entropy values (token verifiers, signed URLs, webhook payloads, signed content another party must verify), rotating signing keys, or verifying a third party's Ed25519 signatures with a verify-only instance. Produces a bare, standard base64url signature — no key id or proprietary framing — that anyone can reproduce with stock crypto (e.g. `crypto.createHmac('sha256', Buffer.from(key, 'base64')).update(msg).digest('base64url')`). Key rotation via try-all-keys verification. This is signing (MAC / digital signature), distinct from password hashing (@maestro-js/hash) and encryption (@maestro-js/crypt).",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@maestro-js/service-registry": "1.0.0-alpha.30"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22.19.11"
|
|
16
|
+
},
|
|
17
|
+
"version": "1.0.0-alpha.30",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"license": "UNLICENSED",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.18.0"
|
|
27
|
+
},
|
|
28
|
+
"repository": "https://github.com/Marcato-Partners/maestro-js",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"test": "beartest ./tests/**/*",
|
|
33
|
+
"format": "prettier --write src/ tests/",
|
|
34
|
+
"lint": "prettier --check src/ tests/"
|
|
35
|
+
}
|
|
36
|
+
}
|