@agenticprimitives/key-custody 0.1.0-alpha.2
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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/aad.d.ts +2 -0
- package/dist/aad.d.ts.map +1 -0
- package/dist/aad.js +19 -0
- package/dist/aad.js.map +1 -0
- package/dist/account.d.ts +23 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +54 -0
- package/dist/account.js.map +1 -0
- package/dist/derive-subject.d.ts +38 -0
- package/dist/derive-subject.d.ts.map +1 -0
- package/dist/derive-subject.js +137 -0
- package/dist/derive-subject.js.map +1 -0
- package/dist/factories.d.ts +30 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +149 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/kms-viem-account.d.ts +4 -0
- package/dist/kms-viem-account.d.ts.map +1 -0
- package/dist/kms-viem-account.js +72 -0
- package/dist/kms-viem-account.js.map +1 -0
- package/dist/providers/aws.d.ts +13 -0
- package/dist/providers/aws.d.ts.map +1 -0
- package/dist/providers/aws.js +14 -0
- package/dist/providers/aws.js.map +1 -0
- package/dist/providers/gcp.d.ts +103 -0
- package/dist/providers/gcp.d.ts.map +1 -0
- package/dist/providers/gcp.js +490 -0
- package/dist/providers/gcp.js.map +1 -0
- package/dist/providers/local.d.ts +60 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +246 -0
- package/dist/providers/local.js.map +1 -0
- package/dist/relay-only.d.ts +3 -0
- package/dist/relay-only.d.ts.map +1 -0
- package/dist/relay-only.js +19 -0
- package/dist/relay-only.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -0
- package/package.json +84 -0
- package/spec.md +6 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// LocalAesProvider / LocalSecp256k1Signer — DEV ONLY backends.
|
|
2
|
+
//
|
|
3
|
+
// Refuses to instantiate when NODE_ENV=production. Production guard per spec.
|
|
4
|
+
//
|
|
5
|
+
// LocalAesProvider:
|
|
6
|
+
// - "Wraps" data keys via HKDF derivation. encryptedDataKey is a random salt;
|
|
7
|
+
// the data key is HKDF(masterSecret, salt, infoBytes(aadContext)).
|
|
8
|
+
// - To "decrypt", re-derives from masterSecret + salt + AAD. Tampering with
|
|
9
|
+
// AAD changes the derived key, which surfaces as a tag mismatch when the
|
|
10
|
+
// caller AES-GCM-decrypts the payload.
|
|
11
|
+
//
|
|
12
|
+
// LocalSecp256k1Signer:
|
|
13
|
+
// - Holds a hardcoded private key from env (A2A_MASTER_PRIVATE_KEY).
|
|
14
|
+
// - secp256k1 signs a 32-byte digest; returns 65-byte (r,s,v) signature.
|
|
15
|
+
import { hmac } from '@noble/hashes/hmac';
|
|
16
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
17
|
+
import { hkdf } from '@noble/hashes/hkdf';
|
|
18
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
19
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
20
|
+
import { hexToBytes, bytesToHex, toHex } from 'viem';
|
|
21
|
+
import { canonicalContextBytes } from '../aad';
|
|
22
|
+
import { buildEvent } from '@agenticprimitives/audit';
|
|
23
|
+
const HKDF_INFO = 'agenticprimitives/local-aes:v1';
|
|
24
|
+
const SALT_BYTES = 16;
|
|
25
|
+
/**
|
|
26
|
+
* Fail-closed default: refuse to start when NODE_ENV=production, but let a
|
|
27
|
+
* deliberate deployment opt into a local in-memory primitive with an explicit
|
|
28
|
+
* acknowledgement env var. Use case: demo / staging stacks that want to
|
|
29
|
+
* run the full A2A flow without standing up a real KMS first.
|
|
30
|
+
*
|
|
31
|
+
* Each override emits a loud one-time warning at boot. Production preflight
|
|
32
|
+
* (`scripts/check-production-deploy.ts`) is the load-bearing check that
|
|
33
|
+
* these opt-ins do not coexist with real-value keys.
|
|
34
|
+
*
|
|
35
|
+
* Two opt-ins exist because the threat models differ:
|
|
36
|
+
* - `A2A_ALLOW_LOCAL_MASTER_KEY` — in-memory secp256k1 signer (relayer,
|
|
37
|
+
* bundler, paymaster envelope). Compromise = forge bundler txs.
|
|
38
|
+
* - `A2A_ALLOW_LOCAL_ENVELOPE_KEY` — HKDF-derived AES-GCM session-data-key
|
|
39
|
+
* wrap. Compromise = decrypt session keypairs at rest = forge delegations.
|
|
40
|
+
*/
|
|
41
|
+
const warnedOptIns = new Set();
|
|
42
|
+
/**
|
|
43
|
+
* H7-B.9 / XPKG-005 — production guard. Previously keyed off
|
|
44
|
+
* `process.env.NODE_ENV !== 'production'` to SKIP — which silently
|
|
45
|
+
* allowed local providers on runtimes that don't set NODE_ENV
|
|
46
|
+
* (Cloudflare Workers, Deno, SES). Fix: inferred environment defaults
|
|
47
|
+
* to `'production'` when NODE_ENV is unreadable or missing, matching
|
|
48
|
+
* `inferEnvironment` in `factories.ts` / sibling-package inferEnvironment functions.
|
|
49
|
+
*/
|
|
50
|
+
function isProductionEnvironment() {
|
|
51
|
+
try {
|
|
52
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV) {
|
|
53
|
+
return process.env.NODE_ENV === 'production';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* SES / Workers may throw on process access */
|
|
58
|
+
}
|
|
59
|
+
// Ambiguous runtime → assume production (fail-closed).
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
function assertLocalProviderAllowedInProduction(label, optInEnvVar) {
|
|
63
|
+
if (!isProductionEnvironment())
|
|
64
|
+
return;
|
|
65
|
+
let optIn = false;
|
|
66
|
+
try {
|
|
67
|
+
optIn = process.env?.[optInEnvVar] === 'true';
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* SES / Workers — opt-in not readable, treat as not-set */
|
|
71
|
+
}
|
|
72
|
+
if (optIn) {
|
|
73
|
+
if (!warnedOptIns.has(optInEnvVar)) {
|
|
74
|
+
warnedOptIns.add(optInEnvVar);
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.warn(`[key-custody] ${label}: running in production via ${optInEnvVar}=true. ` +
|
|
77
|
+
`A managed KMS backend (gcp-kms / aws-kms) MUST replace this before any real-value keys land.`);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`${label} refuses to start in production (or in a runtime where NODE_ENV is unset). ` +
|
|
82
|
+
`Configure a managed KMS backend (gcp-kms / aws-kms), or set ` +
|
|
83
|
+
`${optInEnvVar}=true to acknowledge running with local key material (demo / staging only).`);
|
|
84
|
+
}
|
|
85
|
+
function loadMasterSecret(envOverride) {
|
|
86
|
+
const hex = envOverride ?? process.env.A2A_SESSION_SECRET;
|
|
87
|
+
if (!hex) {
|
|
88
|
+
throw new Error('LocalAesProvider: A2A_SESSION_SECRET (hex) is required. Generate one for dev: openssl rand -hex 32');
|
|
89
|
+
}
|
|
90
|
+
const bytes = hexToBytes(hex.startsWith('0x') ? hex : `0x${hex}`);
|
|
91
|
+
if (bytes.length < 32) {
|
|
92
|
+
throw new Error(`A2A_SESSION_SECRET must be at least 32 bytes (64 hex chars); got ${bytes.length}.`);
|
|
93
|
+
}
|
|
94
|
+
return bytes;
|
|
95
|
+
}
|
|
96
|
+
function loadPrivateKey(envOverride) {
|
|
97
|
+
const hex = envOverride ?? process.env.A2A_MASTER_PRIVATE_KEY;
|
|
98
|
+
if (!hex) {
|
|
99
|
+
throw new Error('LocalSecp256k1Signer: A2A_MASTER_PRIVATE_KEY (0x-prefixed hex) is required.');
|
|
100
|
+
}
|
|
101
|
+
const cleaned = hex.startsWith('0x') ? hex : `0x${hex}`;
|
|
102
|
+
const bytes = hexToBytes(cleaned);
|
|
103
|
+
if (bytes.length !== 32) {
|
|
104
|
+
throw new Error(`A2A_MASTER_PRIVATE_KEY must be 32 bytes; got ${bytes.length}.`);
|
|
105
|
+
}
|
|
106
|
+
return bytes;
|
|
107
|
+
}
|
|
108
|
+
function randomBytes(n) {
|
|
109
|
+
const out = new Uint8Array(n);
|
|
110
|
+
// Node 20+ and modern browsers both expose globalThis.crypto.getRandomValues
|
|
111
|
+
globalThis.crypto.getRandomValues(out);
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
function publicKeyToAddress(pubKey) {
|
|
115
|
+
// pubKey: 64-byte uncompressed (no 0x04 prefix)
|
|
116
|
+
const raw = pubKey.length === 65 ? pubKey.slice(1) : pubKey;
|
|
117
|
+
const hash = keccak_256(raw);
|
|
118
|
+
// viem's bytesToHex already prefixes with 0x — don't double it.
|
|
119
|
+
return bytesToHex(hash.slice(12));
|
|
120
|
+
}
|
|
121
|
+
export class LocalAesProvider {
|
|
122
|
+
keyVersion = 'local-v1';
|
|
123
|
+
master;
|
|
124
|
+
constructor(opts) {
|
|
125
|
+
// Production guard moved from the constructor to the
|
|
126
|
+
// envelope-encryption methods (generateSessionDataKey /
|
|
127
|
+
// decryptSessionDataKey). Rationale: the production posture
|
|
128
|
+
// requires a real KMS for SESSION DATA KEY material (encrypt /
|
|
129
|
+
// decrypt of session payloads) — HKDF-from-a-local-secret is the
|
|
130
|
+
// dev-only path that gets refused in prod. The MAC primitive
|
|
131
|
+
// (generateMac, below) is just HMAC-SHA256 over a wrangler-secret-
|
|
132
|
+
// loaded value, which is a legitimate production pattern for
|
|
133
|
+
// service-to-service auth (audit C1). Splitting the guard lets
|
|
134
|
+
// generateMac work in production without weakening the encryption
|
|
135
|
+
// posture.
|
|
136
|
+
this.master = loadMasterSecret(opts?.sessionSecretHex);
|
|
137
|
+
}
|
|
138
|
+
async generateSessionDataKey(input) {
|
|
139
|
+
assertLocalProviderAllowedInProduction('LocalAesProvider.generateSessionDataKey', 'A2A_ALLOW_LOCAL_ENVELOPE_KEY');
|
|
140
|
+
const salt = randomBytes(SALT_BYTES);
|
|
141
|
+
const info = canonicalContextBytes(input.aadContext);
|
|
142
|
+
// Derive 32-byte data key. encryptedDataKey == salt (the master is held in process memory).
|
|
143
|
+
const dk = hkdf(sha256, this.master, salt, new Uint8Array([...new TextEncoder().encode(HKDF_INFO + '|'), ...info]), 32);
|
|
144
|
+
return {
|
|
145
|
+
plaintextDataKey: dk,
|
|
146
|
+
encryptedDataKey: salt,
|
|
147
|
+
keyId: 'local-master',
|
|
148
|
+
keyVersion: this.keyVersion,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async decryptSessionDataKey(input) {
|
|
152
|
+
assertLocalProviderAllowedInProduction('LocalAesProvider.decryptSessionDataKey', 'A2A_ALLOW_LOCAL_ENVELOPE_KEY');
|
|
153
|
+
if (input.keyVersion !== this.keyVersion) {
|
|
154
|
+
throw new Error(`LocalAesProvider: keyVersion mismatch (got "${input.keyVersion}", expected "${this.keyVersion}").`);
|
|
155
|
+
}
|
|
156
|
+
const info = canonicalContextBytes(input.aadContext);
|
|
157
|
+
const dk = hkdf(sha256, this.master, input.encryptedDataKey, new Uint8Array([...new TextEncoder().encode(HKDF_INFO + '|'), ...info]), 32);
|
|
158
|
+
return dk;
|
|
159
|
+
}
|
|
160
|
+
async generateMac(input) {
|
|
161
|
+
// Permitted in production: this is HMAC-SHA256 with a wrangler-
|
|
162
|
+
// secret-loaded shared key, which is a legitimate production
|
|
163
|
+
// pattern for service-to-service auth. Production should migrate
|
|
164
|
+
// to a managed KMS HMAC key for key rotation + IAM scoping, but
|
|
165
|
+
// the LocalAesProvider MAC path is structurally safe.
|
|
166
|
+
const ctx = `mac|${input.service}|${input.audience}`;
|
|
167
|
+
const subkey = hmac(sha256, this.master, new TextEncoder().encode(ctx));
|
|
168
|
+
const mac = hmac(sha256, subkey, input.canonicalMessage);
|
|
169
|
+
return { mac, keyId: `local-mac:${input.service}:${input.audience}` };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export class LocalSecp256k1Signer {
|
|
173
|
+
provider = 'local-aes';
|
|
174
|
+
priv;
|
|
175
|
+
addr;
|
|
176
|
+
auditSink;
|
|
177
|
+
constructor(opts) {
|
|
178
|
+
assertLocalProviderAllowedInProduction('LocalSecp256k1Signer', 'A2A_ALLOW_LOCAL_MASTER_KEY');
|
|
179
|
+
this.priv = loadPrivateKey(opts?.privateKeyHex);
|
|
180
|
+
const pub = secp256k1.getPublicKey(this.priv, false); // uncompressed (65 bytes)
|
|
181
|
+
this.addr = publicKeyToAddress(pub);
|
|
182
|
+
this.auditSink = opts?.auditSink;
|
|
183
|
+
}
|
|
184
|
+
async signA2AAction(input) {
|
|
185
|
+
if (input.digest.length !== 32) {
|
|
186
|
+
throw new Error(`signA2AAction expects a 32-byte digest; got ${input.digest.length}.`);
|
|
187
|
+
}
|
|
188
|
+
const sig = secp256k1.sign(input.digest, this.priv);
|
|
189
|
+
// viem-compatible signature: r (32) | s (32) | v (1, 27 or 28)
|
|
190
|
+
const out = new Uint8Array(65);
|
|
191
|
+
out.set(numberTo32Bytes(sig.r), 0);
|
|
192
|
+
out.set(numberTo32Bytes(sig.s), 32);
|
|
193
|
+
out[64] = (sig.recovery ?? 0) + 27;
|
|
194
|
+
// Audit emit (C3 pass 3c). Per CLAUDE.md security invariant:
|
|
195
|
+
// "Every Decrypt and signing op emits an audit row with
|
|
196
|
+
// keyVersion, hashed sessionId, optional toolId/actionId.
|
|
197
|
+
// Raw sessionId MUST NEVER be logged."
|
|
198
|
+
if (this.auditSink) {
|
|
199
|
+
const ctx = input.auditContext ?? {};
|
|
200
|
+
try {
|
|
201
|
+
await this.auditSink.write(buildEvent({
|
|
202
|
+
action: 'key-custody.sign',
|
|
203
|
+
outcome: 'success',
|
|
204
|
+
actor: { type: 'system', id: 'local-secp256k1-signer' },
|
|
205
|
+
subject: { type: 'sign-digest', id: bytesToHex(input.digest) },
|
|
206
|
+
context: {
|
|
207
|
+
keyId: 'local-master-secp256k1',
|
|
208
|
+
signerAddress: this.addr,
|
|
209
|
+
toolId: ctx.toolId ?? null,
|
|
210
|
+
actionId: ctx.actionId ?? null,
|
|
211
|
+
// Hash sessionId — never log raw.
|
|
212
|
+
sessionHash: ctx.sessionId
|
|
213
|
+
? toHex(keccak_256(new TextEncoder().encode(ctx.sessionId))).slice(0, 18)
|
|
214
|
+
: null,
|
|
215
|
+
},
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
/* fail-soft */
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
signature: out,
|
|
224
|
+
keyId: 'local-master-secp256k1',
|
|
225
|
+
signerAddress: this.addr,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async getSignerAddress() {
|
|
229
|
+
return this.addr;
|
|
230
|
+
}
|
|
231
|
+
/** Internal: returns the address as a hex string for use by adapters. */
|
|
232
|
+
addressHex() {
|
|
233
|
+
return this.addr;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function numberTo32Bytes(n) {
|
|
237
|
+
const out = new Uint8Array(32);
|
|
238
|
+
let value = n;
|
|
239
|
+
for (let i = 31; i >= 0; i--) {
|
|
240
|
+
out[i] = Number(value & 0xffn);
|
|
241
|
+
value >>= 8n;
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
export { hexToBytes, bytesToHex, toHex };
|
|
246
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/providers/local.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-only.d.ts","sourceRoot":"","sources":["../src/relay-only.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAI5D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,iBAAiB,CAerE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// getRelayOnlySigner — wraps a KmsAccountBackend so any sign call throws.
|
|
2
|
+
// Used in Phase B to guarantee the master signer can only broadcast, never
|
|
3
|
+
// sign on the user's authority.
|
|
4
|
+
import { buildSignerBackend } from './factories';
|
|
5
|
+
export function getRelayOnlySigner(opts) {
|
|
6
|
+
const inner = buildSignerBackend(opts);
|
|
7
|
+
return {
|
|
8
|
+
// Report the wrapped backend's real kind (audit F-6) — the relay-only
|
|
9
|
+
// shim changes capability, not provenance.
|
|
10
|
+
provider: inner.provider,
|
|
11
|
+
async getSignerAddress() {
|
|
12
|
+
return inner.getSignerAddress();
|
|
13
|
+
},
|
|
14
|
+
async signA2AAction() {
|
|
15
|
+
throw new Error('getRelayOnlySigner: signA2AAction called on relay-only signer. The master key is restricted to broadcast operations only.');
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=relay-only.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-only.js","sourceRoot":"","sources":["../src/relay-only.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,2EAA2E;AAC3E,gCAAgC;AAGhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,MAAM,UAAU,kBAAkB,CAAC,IAAe;IAChD,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO;QACL,sEAAsE;QACtE,2CAA2C;QAC3C,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,CAAC,gBAAgB;YACpB,OAAO,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,aAAa;YACjB,MAAM,IAAI,KAAK,CACb,2HAA2H,CAC5H,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Address } from '@agenticprimitives/types';
|
|
2
|
+
import type { AuditSink } from '@agenticprimitives/audit';
|
|
3
|
+
export type KmsBackend = 'local-aes' | 'aws-kms' | 'gcp-kms';
|
|
4
|
+
/**
|
|
5
|
+
* H7-F.5 / PKG-KEY-CUSTODY-005 closure — opaque branded type for
|
|
6
|
+
* sensitive config values (raw private keys, KMS service-account JSON,
|
|
7
|
+
* session secrets, derivation masters, etc.).
|
|
8
|
+
*
|
|
9
|
+
* Previously `BuildOpts.config: Record<string, string>` shipped raw
|
|
10
|
+
* private keys + KMS service-account JSON as plain strings. A consumer
|
|
11
|
+
* who logged `opts.config` for debugging silently dumped the master
|
|
12
|
+
* private key to logs (and worse, to any structured-log backend that
|
|
13
|
+
* indexed it).
|
|
14
|
+
*
|
|
15
|
+
* `Secret<T>` is a `BrandedSecret` wrapper that:
|
|
16
|
+
* - Cannot be `JSON.stringify`d to its underlying value (the brand
|
|
17
|
+
* wins; the value field is non-enumerable + has a custom toJSON
|
|
18
|
+
* that returns `'[redacted secret]'`).
|
|
19
|
+
* - Cannot be `console.log`'d in a useful way (custom `inspect`
|
|
20
|
+
* symbol + `Symbol.toPrimitive` return the redaction marker).
|
|
21
|
+
* - Exposes the underlying value ONLY through {@link unwrapSecret}.
|
|
22
|
+
*
|
|
23
|
+
* Loaders (`loadSecret`, `loadSecretFromEnv`) are the only constructors.
|
|
24
|
+
* Existing `Record<string, string>` config still works (back-compat);
|
|
25
|
+
* new code should use the branded shape via `secretConfig`.
|
|
26
|
+
*/
|
|
27
|
+
declare const SECRET_BRAND: unique symbol;
|
|
28
|
+
export interface Secret<T extends string = string> {
|
|
29
|
+
readonly [SECRET_BRAND]: true;
|
|
30
|
+
/** Phantom — for compile-time discrimination of value shapes. */
|
|
31
|
+
readonly _kind?: T;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wrap a plain string as an opaque secret. The returned object will
|
|
35
|
+
* NOT survive `JSON.stringify`, `console.log`, or `util.inspect`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadSecret<T extends string = string>(value: string): Secret<T>;
|
|
38
|
+
/** Load a secret from a process env var. Throws if the var is missing or empty. */
|
|
39
|
+
export declare function loadSecretFromEnv<T extends string = string>(name: string): Secret<T>;
|
|
40
|
+
/** Unwrap a secret to its underlying string. Use AT THE LAST POSSIBLE MOMENT. */
|
|
41
|
+
export declare function unwrapSecret<T extends string>(s: Secret<T>): string;
|
|
42
|
+
/** Type guard. */
|
|
43
|
+
export declare function isSecret<T extends string = string>(v: unknown): v is Secret<T>;
|
|
44
|
+
export interface BuildOpts {
|
|
45
|
+
/**
|
|
46
|
+
* Backend selection. Recommended explicit value. When omitted, the
|
|
47
|
+
* factory falls back to `A2A_KMS_BACKEND` env, then to `local-aes` in
|
|
48
|
+
* development. In production with neither set, the factory THROWS at
|
|
49
|
+
* construction time (audit H1: no silent local-aes default).
|
|
50
|
+
*/
|
|
51
|
+
backend?: KmsBackend;
|
|
52
|
+
/**
|
|
53
|
+
* Plain config bag (back-compat). H7-F.5 callers should prefer
|
|
54
|
+
* {@link secretConfig} for any value that's a private key, session
|
|
55
|
+
* secret, KMS service-account JSON, or derivation master.
|
|
56
|
+
*/
|
|
57
|
+
config?: Record<string, string>;
|
|
58
|
+
/**
|
|
59
|
+
* H7-F.5 / PKG-KEY-CUSTODY-005 — sensitive config values wrapped in
|
|
60
|
+
* `Secret<T>` so they don't survive logging / JSON.stringify.
|
|
61
|
+
* Factories that need to unwrap call {@link unwrapSecret} at the
|
|
62
|
+
* latest possible moment + never store the unwrapped value.
|
|
63
|
+
*/
|
|
64
|
+
secretConfig?: Record<string, Secret<string>>;
|
|
65
|
+
/**
|
|
66
|
+
* Optional audit sink threaded into signers so every signing op emits
|
|
67
|
+
* `key-custody.sign`. Consumers share one sink across all primitives
|
|
68
|
+
* so rows land in one trail. Fail-soft if the sink throws.
|
|
69
|
+
*/
|
|
70
|
+
auditSink?: AuditSink;
|
|
71
|
+
/**
|
|
72
|
+
* Production-readiness gate (audit H1). Inverted default: factories
|
|
73
|
+
* treat the runtime as `'production'` unless either:
|
|
74
|
+
* - `developmentMode: true` is set explicitly, or
|
|
75
|
+
* - `process.env.NODE_ENV !== 'production'`.
|
|
76
|
+
* In production with no explicit backend AND no `A2A_KMS_BACKEND`
|
|
77
|
+
* env, the factory throws. Pass `environment: 'production'` to force
|
|
78
|
+
* production semantics in tests; pass `'development'` (or
|
|
79
|
+
* `developmentMode: true`) to opt into the dev fallback.
|
|
80
|
+
*/
|
|
81
|
+
environment?: 'production' | 'development';
|
|
82
|
+
/** Shorthand for `environment: 'development'`. */
|
|
83
|
+
developmentMode?: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface A2AKeyProvider {
|
|
86
|
+
readonly keyVersion: string;
|
|
87
|
+
generateSessionDataKey(input: {
|
|
88
|
+
aadContext: Record<string, string>;
|
|
89
|
+
}): Promise<{
|
|
90
|
+
plaintextDataKey: Uint8Array;
|
|
91
|
+
encryptedDataKey: Uint8Array;
|
|
92
|
+
keyId: string;
|
|
93
|
+
keyVersion: string;
|
|
94
|
+
}>;
|
|
95
|
+
decryptSessionDataKey(input: {
|
|
96
|
+
encryptedDataKey: Uint8Array;
|
|
97
|
+
aadContext: Record<string, string>;
|
|
98
|
+
keyId: string;
|
|
99
|
+
keyVersion: string;
|
|
100
|
+
}): Promise<Uint8Array>;
|
|
101
|
+
signA2AAction?(input: {
|
|
102
|
+
digest: Uint8Array;
|
|
103
|
+
auditContext?: {
|
|
104
|
+
toolId?: string;
|
|
105
|
+
sessionId?: string;
|
|
106
|
+
actionId?: string;
|
|
107
|
+
};
|
|
108
|
+
}): Promise<{
|
|
109
|
+
signature: Uint8Array;
|
|
110
|
+
keyId: string;
|
|
111
|
+
signerAddress: Address;
|
|
112
|
+
}>;
|
|
113
|
+
generateMac?(input: {
|
|
114
|
+
canonicalMessage: Uint8Array;
|
|
115
|
+
service: string;
|
|
116
|
+
audience: string;
|
|
117
|
+
}): Promise<{
|
|
118
|
+
mac: Uint8Array;
|
|
119
|
+
keyId: string;
|
|
120
|
+
}>;
|
|
121
|
+
}
|
|
122
|
+
export interface KmsAccountBackend {
|
|
123
|
+
/**
|
|
124
|
+
* The concrete backend kind. `createKmsAccount` reads this so the
|
|
125
|
+
* emitted `provider` / `keyId` reflect the REAL backend (audit
|
|
126
|
+
* provenance), instead of a defaulted `'local-aes'` label that could
|
|
127
|
+
* mislabel a production GCP signer (audit F-6).
|
|
128
|
+
*/
|
|
129
|
+
readonly provider: 'local-aes' | 'aws-kms' | 'gcp-kms';
|
|
130
|
+
signA2AAction: NonNullable<A2AKeyProvider['signA2AAction']>;
|
|
131
|
+
getSignerAddress(): Promise<Address>;
|
|
132
|
+
}
|
|
133
|
+
export {};
|
|
134
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H7-F.5 / PKG-KEY-CUSTODY-005 closure — opaque branded type for
|
|
3
|
+
* sensitive config values (raw private keys, KMS service-account JSON,
|
|
4
|
+
* session secrets, derivation masters, etc.).
|
|
5
|
+
*
|
|
6
|
+
* Previously `BuildOpts.config: Record<string, string>` shipped raw
|
|
7
|
+
* private keys + KMS service-account JSON as plain strings. A consumer
|
|
8
|
+
* who logged `opts.config` for debugging silently dumped the master
|
|
9
|
+
* private key to logs (and worse, to any structured-log backend that
|
|
10
|
+
* indexed it).
|
|
11
|
+
*
|
|
12
|
+
* `Secret<T>` is a `BrandedSecret` wrapper that:
|
|
13
|
+
* - Cannot be `JSON.stringify`d to its underlying value (the brand
|
|
14
|
+
* wins; the value field is non-enumerable + has a custom toJSON
|
|
15
|
+
* that returns `'[redacted secret]'`).
|
|
16
|
+
* - Cannot be `console.log`'d in a useful way (custom `inspect`
|
|
17
|
+
* symbol + `Symbol.toPrimitive` return the redaction marker).
|
|
18
|
+
* - Exposes the underlying value ONLY through {@link unwrapSecret}.
|
|
19
|
+
*
|
|
20
|
+
* Loaders (`loadSecret`, `loadSecretFromEnv`) are the only constructors.
|
|
21
|
+
* Existing `Record<string, string>` config still works (back-compat);
|
|
22
|
+
* new code should use the branded shape via `secretConfig`.
|
|
23
|
+
*/
|
|
24
|
+
const SECRET_BRAND = Symbol.for('agenticprimitives.secret');
|
|
25
|
+
const REDACTED = '[redacted secret]';
|
|
26
|
+
/**
|
|
27
|
+
* Wrap a plain string as an opaque secret. The returned object will
|
|
28
|
+
* NOT survive `JSON.stringify`, `console.log`, or `util.inspect`.
|
|
29
|
+
*/
|
|
30
|
+
export function loadSecret(value) {
|
|
31
|
+
const inner = {
|
|
32
|
+
[SECRET_BRAND]: true,
|
|
33
|
+
__value: value,
|
|
34
|
+
toJSON: () => REDACTED,
|
|
35
|
+
toString: () => REDACTED,
|
|
36
|
+
[Symbol.toPrimitive]: () => REDACTED,
|
|
37
|
+
};
|
|
38
|
+
// Hide `__value` from enumeration so naive iteration (Object.keys,
|
|
39
|
+
// spread, JSON.stringify) cannot reach it.
|
|
40
|
+
Object.defineProperty(inner, '__value', { enumerable: false, writable: false });
|
|
41
|
+
// Node's util.inspect honors this symbol.
|
|
42
|
+
Object.defineProperty(inner, Symbol.for('nodejs.util.inspect.custom'), {
|
|
43
|
+
enumerable: false,
|
|
44
|
+
value: () => REDACTED,
|
|
45
|
+
});
|
|
46
|
+
return inner;
|
|
47
|
+
}
|
|
48
|
+
/** Load a secret from a process env var. Throws if the var is missing or empty. */
|
|
49
|
+
export function loadSecretFromEnv(name) {
|
|
50
|
+
let value;
|
|
51
|
+
try {
|
|
52
|
+
value = process.env?.[name];
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* SES / Workers may throw on process access */
|
|
56
|
+
}
|
|
57
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
58
|
+
throw new Error(`[key-custody] loadSecretFromEnv: env var ${name} is missing or empty`);
|
|
59
|
+
}
|
|
60
|
+
return loadSecret(value);
|
|
61
|
+
}
|
|
62
|
+
/** Unwrap a secret to its underlying string. Use AT THE LAST POSSIBLE MOMENT. */
|
|
63
|
+
export function unwrapSecret(s) {
|
|
64
|
+
return s.__value;
|
|
65
|
+
}
|
|
66
|
+
/** Type guard. */
|
|
67
|
+
export function isSecret(v) {
|
|
68
|
+
return typeof v === 'object' && v !== null && v[SECRET_BRAND] === true;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,YAAY,GAAkB,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAY3E,MAAM,QAAQ,GAAG,mBAA4B,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,UAAU,CAA4B,KAAa;IACjE,MAAM,KAAK,GAAsB;QAC/B,CAAC,YAAY,CAAC,EAAE,IAAI;QACpB,OAAO,EAAE,KAAK;QACd,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;QACtB,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ;QACxB,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC,QAAQ;KAKrC,CAAC;IACF,mEAAmE;IACnE,2CAA2C;IAC3C,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,0CAA0C;IAC1C,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,EAAE;QACrE,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,GAAG,EAAE,CAAC,QAAQ;KACtB,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAA4B,IAAY;IACvE,IAAI,KAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,4CAA4C,IAAI,sBAAsB,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,UAAU,CAAI,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAmB,CAAY;IACzD,OAAQ,CAAuB,CAAC,OAAO,CAAC;AAC1C,CAAC;AAED,kBAAkB;AAClB,MAAM,UAAU,QAAQ,CAA4B,CAAU;IAC5D,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAkC,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;AAC3G,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agenticprimitives/key-custody",
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "Pluggable envelope encryption + signers + HMAC providers (local-AES / AWS KMS / GCP KMS). Pure crypto primitives; no session lifecycle (that's in delegation).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/agentictrustlabs/agenticprimitives.git",
|
|
9
|
+
"directory": "packages/key-custody"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/agentictrustlabs/agenticprimitives/tree/master/packages/key-custody",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/agentictrustlabs/agenticprimitives/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./local": {
|
|
24
|
+
"types": "./dist/providers/local.d.ts",
|
|
25
|
+
"import": "./dist/providers/local.js"
|
|
26
|
+
},
|
|
27
|
+
"./aws": {
|
|
28
|
+
"types": "./dist/providers/aws.d.ts",
|
|
29
|
+
"import": "./dist/providers/aws.js"
|
|
30
|
+
},
|
|
31
|
+
"./gcp": {
|
|
32
|
+
"types": "./dist/providers/gcp.d.ts",
|
|
33
|
+
"import": "./dist/providers/gcp.js"
|
|
34
|
+
},
|
|
35
|
+
"./mac": {
|
|
36
|
+
"types": "./dist/mac.d.ts",
|
|
37
|
+
"import": "./dist/mac.js"
|
|
38
|
+
},
|
|
39
|
+
"./kms-viem": {
|
|
40
|
+
"types": "./dist/kms-viem-account.d.ts",
|
|
41
|
+
"import": "./dist/kms-viem-account.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"dist",
|
|
47
|
+
"spec.md",
|
|
48
|
+
"README.md"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.build.json",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:unit": "vitest run test/unit",
|
|
55
|
+
"test:integration": "vitest run test/integration --passWithNoTests",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"clean": "rm -rf dist"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@noble/curves": "^1.6.0",
|
|
64
|
+
"@noble/hashes": "^1.5.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"@agenticprimitives/audit": "workspace:*",
|
|
68
|
+
"@agenticprimitives/connect-auth": "workspace:*",
|
|
69
|
+
"@agenticprimitives/types": "workspace:*",
|
|
70
|
+
"viem": "^2.50.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"vitest": "^2.1.0"
|
|
74
|
+
},
|
|
75
|
+
"keywords": [
|
|
76
|
+
"kms",
|
|
77
|
+
"aws-kms",
|
|
78
|
+
"gcp-kms",
|
|
79
|
+
"envelope-encryption",
|
|
80
|
+
"secp256k1",
|
|
81
|
+
"hmac",
|
|
82
|
+
"agentic"
|
|
83
|
+
]
|
|
84
|
+
}
|