@cello-protocol/client 0.0.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/dist/agent-hash-queue.d.ts +206 -0
- package/dist/agent-hash-queue.d.ts.map +1 -0
- package/dist/agent-hash-queue.js +380 -0
- package/dist/agent-hash-queue.js.map +1 -0
- package/dist/backup-key-derivation.d.ts +37 -0
- package/dist/backup-key-derivation.d.ts.map +1 -0
- package/dist/backup-key-derivation.js +48 -0
- package/dist/backup-key-derivation.js.map +1 -0
- package/dist/client-backup.d.ts +144 -0
- package/dist/client-backup.d.ts.map +1 -0
- package/dist/client-backup.js +273 -0
- package/dist/client-backup.js.map +1 -0
- package/dist/client.d.ts +249 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +4664 -0
- package/dist/client.js.map +1 -0
- package/dist/connection-policy.d.ts +163 -0
- package/dist/connection-policy.d.ts.map +1 -0
- package/dist/connection-policy.js +248 -0
- package/dist/connection-policy.js.map +1 -0
- package/dist/db-key-derivation.d.ts +26 -0
- package/dist/db-key-derivation.d.ts.map +1 -0
- package/dist/db-key-derivation.js +37 -0
- package/dist/db-key-derivation.js.map +1 -0
- package/dist/encrypted-file-signing-key-provider.d.ts +92 -0
- package/dist/encrypted-file-signing-key-provider.d.ts.map +1 -0
- package/dist/encrypted-file-signing-key-provider.js +251 -0
- package/dist/encrypted-file-signing-key-provider.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +270 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +1155 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/network-directory-node.d.ts +85 -0
- package/dist/network-directory-node.d.ts.map +1 -0
- package/dist/network-directory-node.js +584 -0
- package/dist/network-directory-node.js.map +1 -0
- package/dist/s3-cloud-storage-provider.d.ts +54 -0
- package/dist/s3-cloud-storage-provider.d.ts.map +1 -0
- package/dist/s3-cloud-storage-provider.js +78 -0
- package/dist/s3-cloud-storage-provider.js.map +1 -0
- package/dist/sqlcipher-client-store.d.ts +68 -0
- package/dist/sqlcipher-client-store.d.ts.map +1 -0
- package/dist/sqlcipher-client-store.js +382 -0
- package/dist/sqlcipher-client-store.js.map +1 -0
- package/dist/types.d.ts +408 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cello-protocol/client — CELLO-CONNPOL-001
|
|
3
|
+
* Connection policy engine.
|
|
4
|
+
*
|
|
5
|
+
* Evaluates an incoming connection package against a SignalRequirementPolicy
|
|
6
|
+
* and a DirectoryContext to produce a ConnectionReport.
|
|
7
|
+
*
|
|
8
|
+
* ──── Phase P: Pseudocode ────
|
|
9
|
+
*
|
|
10
|
+
* evaluateConnectionPackage(validatedPackage, policy, context, current_timestamp_ms):
|
|
11
|
+
*
|
|
12
|
+
* 1. If policy.mode === 'closed'
|
|
13
|
+
* return { verdict: 'auto_reject', reason: 'policy_closed' }
|
|
14
|
+
*
|
|
15
|
+
* 2. If context.is_provisional === true
|
|
16
|
+
* return { verdict: 'auto_reject', reason: 'is_provisional' }
|
|
17
|
+
*
|
|
18
|
+
* 3. If validatedPackage.valid === false
|
|
19
|
+
* return { verdict: 'auto_reject', reason: 'pseudonym_binding_invalid' }
|
|
20
|
+
*
|
|
21
|
+
* 4. If policy.mode === 'open' && policy.review_mode === 'deterministic'
|
|
22
|
+
* return { verdict: 'auto_accept' }
|
|
23
|
+
*
|
|
24
|
+
* 5. If policy.mode === 'open' && policy.review_mode === 'inference'
|
|
25
|
+
* return { verdict: 'pending_agent_review', ... zero unmet requirements ... }
|
|
26
|
+
*
|
|
27
|
+
* 6. For mode 'selective' | 'guarded' (identical in M3):
|
|
28
|
+
* evaluate each requirement → collect met[] and unmet[]
|
|
29
|
+
* if review_mode === 'deterministic':
|
|
30
|
+
* unmet.length === 0 → auto_accept
|
|
31
|
+
* unmet.length > 0 → auto_insufficient
|
|
32
|
+
* if review_mode === 'inference':
|
|
33
|
+
* → pending_agent_review (always, with met/unmet summary)
|
|
34
|
+
*
|
|
35
|
+
* Requirement evaluation (evaluateRequirement):
|
|
36
|
+
*
|
|
37
|
+
* endorsement + min_count:
|
|
38
|
+
* count = endorsements.filter(e => e.validation_status === 'valid').length
|
|
39
|
+
* met iff count >= condition.count
|
|
40
|
+
* unmet: { signal_type: 'endorsement', condition, provided: count }
|
|
41
|
+
*
|
|
42
|
+
* pseudonym_age + min_age_days:
|
|
43
|
+
* age = floor((current_timestamp_ms - pseudonym_binding.created_at) / 86_400_000)
|
|
44
|
+
* met iff age >= condition.days
|
|
45
|
+
* unmet: { signal_type: 'pseudonym_age', condition, provided_days: age }
|
|
46
|
+
*
|
|
47
|
+
* registration_age + min_age_days:
|
|
48
|
+
* age = floor((current_timestamp_ms - context.registered_at) / 86_400_000)
|
|
49
|
+
* met iff age >= condition.days
|
|
50
|
+
* unmet: { signal_type: 'registration_age', condition, provided_days: age }
|
|
51
|
+
*
|
|
52
|
+
* attestation + attestation_type:
|
|
53
|
+
* for each type in condition.required: check at least one valid attestation with that type
|
|
54
|
+
* missing_types = types where no valid attestation exists
|
|
55
|
+
* met iff missing_types.length === 0
|
|
56
|
+
* unmet: { signal_type: 'attestation', condition, missing_types }
|
|
57
|
+
*
|
|
58
|
+
* any requirement with from_shared_contact condition:
|
|
59
|
+
* unsatisfiable in M3 → { signal_type, condition, unsupported_condition: true }
|
|
60
|
+
*
|
|
61
|
+
* References:
|
|
62
|
+
* CELLO-CONNPOL-001 story YAML
|
|
63
|
+
*/
|
|
64
|
+
// ─── Default policies ─────────────────────────────────────────────────────────
|
|
65
|
+
export const OPEN_POLICY = {
|
|
66
|
+
mode: "open",
|
|
67
|
+
review_mode: "inference",
|
|
68
|
+
requirements: [],
|
|
69
|
+
};
|
|
70
|
+
export const SELECTIVE_DEFAULT = {
|
|
71
|
+
mode: "selective",
|
|
72
|
+
review_mode: "inference",
|
|
73
|
+
requirements: [{ signal_type: "endorsement", condition: { type: "min_count", count: 1 } }],
|
|
74
|
+
};
|
|
75
|
+
export const CLOSED_POLICY = {
|
|
76
|
+
mode: "closed",
|
|
77
|
+
review_mode: "deterministic",
|
|
78
|
+
requirements: [],
|
|
79
|
+
};
|
|
80
|
+
// ─── Engine ───────────────────────────────────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Evaluate an incoming connection package against a policy and directory context.
|
|
83
|
+
*
|
|
84
|
+
* Evaluation order is hard-coded and non-negotiable:
|
|
85
|
+
* 1. closed → auto_reject: policy_closed
|
|
86
|
+
* 2. is_provisional → auto_reject: is_provisional
|
|
87
|
+
* 3. package.valid === false → auto_reject: pseudonym_binding_invalid
|
|
88
|
+
* 4. open + deterministic → auto_accept
|
|
89
|
+
* 5. open + inference → pending_agent_review (zero requirements)
|
|
90
|
+
* 6. selective/guarded: evaluate requirements, then:
|
|
91
|
+
* - deterministic + all met → auto_accept
|
|
92
|
+
* - deterministic + any unmet → auto_insufficient
|
|
93
|
+
* - inference → pending_agent_review (with met/unmet lists)
|
|
94
|
+
*/
|
|
95
|
+
export function evaluateConnectionPackage(validatedPackage, policy, context, current_timestamp_ms) {
|
|
96
|
+
// Step 1: closed mode — reject before any other check
|
|
97
|
+
if (policy.mode === "closed") {
|
|
98
|
+
return { verdict: "auto_reject", reason: "policy_closed" };
|
|
99
|
+
}
|
|
100
|
+
// Step 2: provisional agent — cannot receive connections
|
|
101
|
+
if (context.is_provisional) {
|
|
102
|
+
return { verdict: "auto_reject", reason: "is_provisional" };
|
|
103
|
+
}
|
|
104
|
+
// Step 3: invalid package — pseudonym binding failed
|
|
105
|
+
if (!validatedPackage.valid) {
|
|
106
|
+
return { verdict: "auto_reject", reason: "pseudonym_binding_invalid" };
|
|
107
|
+
}
|
|
108
|
+
// From here: validatedPackage is valid (valid: true branch)
|
|
109
|
+
const pkg = validatedPackage;
|
|
110
|
+
// Step 4: open + deterministic → auto_accept
|
|
111
|
+
if (policy.mode === "open" && policy.review_mode === "deterministic") {
|
|
112
|
+
return { verdict: "auto_accept" };
|
|
113
|
+
}
|
|
114
|
+
// Step 5: open + inference → pending_agent_review with zero requirements
|
|
115
|
+
if (policy.mode === "open" && policy.review_mode === "inference") {
|
|
116
|
+
return {
|
|
117
|
+
verdict: "pending_agent_review",
|
|
118
|
+
policy_summary: {
|
|
119
|
+
mode: policy.mode,
|
|
120
|
+
review_mode: "inference",
|
|
121
|
+
requirements_met: [],
|
|
122
|
+
requirements_unmet: [],
|
|
123
|
+
},
|
|
124
|
+
package_summary: buildPackageSummary(pkg, context, current_timestamp_ms),
|
|
125
|
+
is_round_2: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Step 6: selective | guarded — evaluate requirements
|
|
129
|
+
const metRequirements = [];
|
|
130
|
+
const unmetRequirements = [];
|
|
131
|
+
for (const req of policy.requirements) {
|
|
132
|
+
const result = evaluateRequirement(req, pkg, context, current_timestamp_ms);
|
|
133
|
+
if (result === null) {
|
|
134
|
+
metRequirements.push(req);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
unmetRequirements.push(result);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (policy.review_mode === "deterministic") {
|
|
141
|
+
if (unmetRequirements.length === 0) {
|
|
142
|
+
return { verdict: "auto_accept" };
|
|
143
|
+
}
|
|
144
|
+
return { verdict: "auto_insufficient", unmet_requirements: unmetRequirements };
|
|
145
|
+
}
|
|
146
|
+
// review_mode === 'inference'
|
|
147
|
+
return {
|
|
148
|
+
verdict: "pending_agent_review",
|
|
149
|
+
policy_summary: {
|
|
150
|
+
mode: policy.mode,
|
|
151
|
+
review_mode: "inference",
|
|
152
|
+
requirements_met: metRequirements,
|
|
153
|
+
requirements_unmet: unmetRequirements,
|
|
154
|
+
},
|
|
155
|
+
package_summary: buildPackageSummary(pkg, context, current_timestamp_ms),
|
|
156
|
+
is_round_2: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// ─── Requirement evaluation ───────────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* Evaluate a single requirement.
|
|
162
|
+
*
|
|
163
|
+
* Returns null if met, or an UnmetRequirement if not.
|
|
164
|
+
*/
|
|
165
|
+
function evaluateRequirement(req, pkg, context, current_timestamp_ms) {
|
|
166
|
+
const { condition } = req;
|
|
167
|
+
// from_shared_contact is M6-only — always unsatisfiable in M3
|
|
168
|
+
if (condition.type === "from_shared_contact") {
|
|
169
|
+
return { signal_type: req.signal_type, condition, unsupported_condition: true };
|
|
170
|
+
}
|
|
171
|
+
switch (req.signal_type) {
|
|
172
|
+
case "endorsement":
|
|
173
|
+
return evaluateEndorsementRequirement(condition, pkg.endorsements);
|
|
174
|
+
case "pseudonym_age":
|
|
175
|
+
return evaluatePseudonymAgeRequirement(condition, pkg.pseudonym_binding.created_at, current_timestamp_ms);
|
|
176
|
+
case "registration_age":
|
|
177
|
+
return evaluateRegistrationAgeRequirement(condition, context.registered_at, current_timestamp_ms);
|
|
178
|
+
case "attestation":
|
|
179
|
+
return evaluateAttestationRequirement(condition, pkg.attestations);
|
|
180
|
+
default:
|
|
181
|
+
// Unknown signal_type — treat as unsupported
|
|
182
|
+
return { signal_type: req.signal_type, condition, unsupported_condition: true };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function evaluateEndorsementRequirement(condition, endorsements) {
|
|
186
|
+
if (condition.type === "min_count") {
|
|
187
|
+
const validCount = endorsements.filter((e) => e.validation_status === "valid").length;
|
|
188
|
+
if (validCount >= condition.count) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return { signal_type: "endorsement", condition, provided: validCount };
|
|
192
|
+
}
|
|
193
|
+
// Unrecognized condition for endorsement — unsupported
|
|
194
|
+
return { signal_type: "endorsement", condition, unsupported_condition: true };
|
|
195
|
+
}
|
|
196
|
+
function evaluatePseudonymAgeRequirement(condition, created_at, current_timestamp_ms) {
|
|
197
|
+
if (condition.type === "min_age_days") {
|
|
198
|
+
const ageDays = Math.floor((current_timestamp_ms - created_at) / 86_400_000);
|
|
199
|
+
if (ageDays >= condition.days) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return { signal_type: "pseudonym_age", condition, provided_days: ageDays };
|
|
203
|
+
}
|
|
204
|
+
return { signal_type: "pseudonym_age", condition, unsupported_condition: true };
|
|
205
|
+
}
|
|
206
|
+
function evaluateRegistrationAgeRequirement(condition, registered_at, current_timestamp_ms) {
|
|
207
|
+
if (condition.type === "min_age_days") {
|
|
208
|
+
const ageDays = Math.floor((current_timestamp_ms - registered_at) / 86_400_000);
|
|
209
|
+
if (ageDays >= condition.days) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return { signal_type: "registration_age", condition, provided_days: ageDays };
|
|
213
|
+
}
|
|
214
|
+
return { signal_type: "registration_age", condition, unsupported_condition: true };
|
|
215
|
+
}
|
|
216
|
+
function evaluateAttestationRequirement(condition, attestations) {
|
|
217
|
+
if (condition.type === "attestation_type") {
|
|
218
|
+
const validAttestationTypes = new Set(attestations
|
|
219
|
+
.filter((a) => a.validation_status === "valid")
|
|
220
|
+
.map((a) => a.attestation_type));
|
|
221
|
+
const missingTypes = condition.required.filter((t) => !validAttestationTypes.has(t));
|
|
222
|
+
if (missingTypes.length === 0) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return { signal_type: "attestation", condition, missing_types: missingTypes };
|
|
226
|
+
}
|
|
227
|
+
return { signal_type: "attestation", condition, unsupported_condition: true };
|
|
228
|
+
}
|
|
229
|
+
// ─── Package summary builder ──────────────────────────────────────────────────
|
|
230
|
+
function buildPackageSummary(pkg, context, current_timestamp_ms) {
|
|
231
|
+
const pseudonymAgeDays = Math.floor((current_timestamp_ms - pkg.pseudonym_binding.created_at) / 86_400_000);
|
|
232
|
+
const registrationAgeDays = Math.floor((current_timestamp_ms - context.registered_at) / 86_400_000);
|
|
233
|
+
const validEndorsementCount = pkg.endorsements.filter((e) => e.validation_status === "valid").length;
|
|
234
|
+
const validAttestationTypes = [
|
|
235
|
+
...new Set(pkg.attestations
|
|
236
|
+
.filter((a) => a.validation_status === "valid")
|
|
237
|
+
.map((a) => a.attestation_type)),
|
|
238
|
+
];
|
|
239
|
+
return {
|
|
240
|
+
pseudonym_label: pkg.pseudonym_binding.pseudonym_label,
|
|
241
|
+
endorsement_count: validEndorsementCount,
|
|
242
|
+
attestation_types: validAttestationTypes,
|
|
243
|
+
pseudonym_age_days: pseudonymAgeDays,
|
|
244
|
+
registration_age_days: registrationAgeDays,
|
|
245
|
+
is_provisional: context.is_provisional,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=connection-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-policy.js","sourceRoot":"","sources":["../src/connection-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* db-key-derivation.ts — HKDF derivation of the local database encryption key.
|
|
3
|
+
*
|
|
4
|
+
* PERSIST-009: db_key = HKDF-SHA256(ikm=identity_key, salt=none, info='local-db-key' || agent_id, length=32)
|
|
5
|
+
*
|
|
6
|
+
* Security invariants (PERSIST-009 SI-002):
|
|
7
|
+
* - identity_key is passed in by the caller; it is NEVER stored here.
|
|
8
|
+
* - The returned db_key is NEVER logged or persisted by this function.
|
|
9
|
+
* - This function has no side effects — pure transformation only.
|
|
10
|
+
*
|
|
11
|
+
* RFC reference: RFC 5869 (HKDF). Node.js implementation: crypto.hkdfSync.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Derive a 32-byte database encryption key from the agent's identity_key.
|
|
15
|
+
*
|
|
16
|
+
* @param identityKey - The agent's long-term root key (32 bytes). Never stored or logged.
|
|
17
|
+
* @param agentId - The stable agent identifier. Binds the key to a specific agent.
|
|
18
|
+
* @returns - A 32-byte Uint8Array suitable for use as a SQLCipher PRAGMA key.
|
|
19
|
+
*
|
|
20
|
+
* Security: The db_key is deterministic — same inputs always produce the same output.
|
|
21
|
+
* This is intentional: the client must re-derive the key on every startup without
|
|
22
|
+
* storing it, using only the identity_key (which is stored separately, protected
|
|
23
|
+
* by the OS keychain or equivalent).
|
|
24
|
+
*/
|
|
25
|
+
export declare function deriveDbKey(identityKey: Uint8Array, agentId: string): Uint8Array;
|
|
26
|
+
//# sourceMappingURL=db-key-derivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-key-derivation.d.ts","sourceRoot":"","sources":["../src/db-key-derivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAYhF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* db-key-derivation.ts — HKDF derivation of the local database encryption key.
|
|
3
|
+
*
|
|
4
|
+
* PERSIST-009: db_key = HKDF-SHA256(ikm=identity_key, salt=none, info='local-db-key' || agent_id, length=32)
|
|
5
|
+
*
|
|
6
|
+
* Security invariants (PERSIST-009 SI-002):
|
|
7
|
+
* - identity_key is passed in by the caller; it is NEVER stored here.
|
|
8
|
+
* - The returned db_key is NEVER logged or persisted by this function.
|
|
9
|
+
* - This function has no side effects — pure transformation only.
|
|
10
|
+
*
|
|
11
|
+
* RFC reference: RFC 5869 (HKDF). Node.js implementation: crypto.hkdfSync.
|
|
12
|
+
*/
|
|
13
|
+
import { hkdfSync } from "node:crypto";
|
|
14
|
+
/**
|
|
15
|
+
* Derive a 32-byte database encryption key from the agent's identity_key.
|
|
16
|
+
*
|
|
17
|
+
* @param identityKey - The agent's long-term root key (32 bytes). Never stored or logged.
|
|
18
|
+
* @param agentId - The stable agent identifier. Binds the key to a specific agent.
|
|
19
|
+
* @returns - A 32-byte Uint8Array suitable for use as a SQLCipher PRAGMA key.
|
|
20
|
+
*
|
|
21
|
+
* Security: The db_key is deterministic — same inputs always produce the same output.
|
|
22
|
+
* This is intentional: the client must re-derive the key on every startup without
|
|
23
|
+
* storing it, using only the identity_key (which is stored separately, protected
|
|
24
|
+
* by the OS keychain or equivalent).
|
|
25
|
+
*/
|
|
26
|
+
export function deriveDbKey(identityKey, agentId) {
|
|
27
|
+
// info = 'local-db-key' || NUL || agentId (UTF-8 encoded).
|
|
28
|
+
// The null-byte separator prevents a theoretical prefix-collision where two
|
|
29
|
+
// different (literal, agentId) pairs could produce the same concatenation.
|
|
30
|
+
const infoStr = `local-db-key\x00${agentId}`;
|
|
31
|
+
const info = Buffer.from(infoStr, "utf8");
|
|
32
|
+
// HKDF-SHA256: salt=none (empty Buffer), length=32 bytes
|
|
33
|
+
// RFC 5869 §2.2: when salt is not provided, a string of HashLen zeros is used.
|
|
34
|
+
const derived = hkdfSync("sha256", identityKey, Buffer.alloc(0), info, 32);
|
|
35
|
+
return new Uint8Array(derived);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=db-key-derivation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-key-derivation.js","sourceRoot":"","sources":["../src/db-key-derivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,WAAuB,EAAE,OAAe;IAClE,2DAA2D;IAC3D,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,OAAO,GAAG,mBAAmB,OAAO,EAAE,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAE1C,yDAAyD;IACzD,+EAA+E;IAC/E,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAE3E,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EncryptedFileSigningKeyProvider — file-backed SigningKeyProvider for CELLO_ENV=local/dev.
|
|
3
|
+
*
|
|
4
|
+
* PERSIST-010: Reads the Ed25519 private key seed from an encrypted key file at the
|
|
5
|
+
* configured path, decrypts it into memory for signing, and zeroes the in-memory key
|
|
6
|
+
* buffer after each sign() call (SI-002).
|
|
7
|
+
*
|
|
8
|
+
* The private key NEVER crosses the provider boundary (SI-001). The only exported
|
|
9
|
+
* values are the public key (via getPublicKey()) and signatures (via sign()).
|
|
10
|
+
*
|
|
11
|
+
* Key file format (same as FileKeyProvider in @cello-protocol/crypto):
|
|
12
|
+
* Bytes 0–3: Magic [0xCE, 0x11, 0x0E, 0x01]
|
|
13
|
+
* Byte 4: Version (0x01)
|
|
14
|
+
* Bytes 5–36: 32-byte Ed25519 seed
|
|
15
|
+
*
|
|
16
|
+
* This file is NOT exported from packages/client/src/index.ts.
|
|
17
|
+
* It is only imported by the composition root (server.ts).
|
|
18
|
+
*
|
|
19
|
+
* RFC reference: Ed25519 signing per RFC 8032.
|
|
20
|
+
*/
|
|
21
|
+
import type { SigningKeyProvider, SigningPublicKey, SigningSignature, SignOptions } from "@cello-protocol/interfaces";
|
|
22
|
+
import type { Logger } from "@cello-protocol/interfaces";
|
|
23
|
+
export interface EncryptedFileSigningKeyProviderOptions {
|
|
24
|
+
/** Stable agent identifier — used in log events. */
|
|
25
|
+
agentId: string;
|
|
26
|
+
/** Structured logger injected from the composition root. */
|
|
27
|
+
logger: Logger;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* SigningKeyProvider backed by an encrypted key file on disk.
|
|
31
|
+
*
|
|
32
|
+
* Lifecycle: EncryptedFileSigningKeyProvider.load(path, opts) → ready to sign.
|
|
33
|
+
*
|
|
34
|
+
* On each sign() call, the seed is read from the file into a temporary buffer,
|
|
35
|
+
* used for signing, and the buffer is zeroed immediately after — even on throw.
|
|
36
|
+
* This ensures the private key exists in process memory only for the minimum
|
|
37
|
+
* time required to produce the signature.
|
|
38
|
+
*
|
|
39
|
+
* Security invariants:
|
|
40
|
+
* SI-001: No method returns/exports the private key.
|
|
41
|
+
* SI-002: Key buffer zeroed after EVERY sign() call, even on throw (try/finally).
|
|
42
|
+
* SI-003: sign() failure propagates — never falls back to weaker signing.
|
|
43
|
+
*/
|
|
44
|
+
export declare class EncryptedFileSigningKeyProvider implements SigningKeyProvider {
|
|
45
|
+
#private;
|
|
46
|
+
private constructor();
|
|
47
|
+
/**
|
|
48
|
+
* Load the signing key from the key file at the given path.
|
|
49
|
+
*
|
|
50
|
+
* Validates the file format and derives the public key. The seed itself
|
|
51
|
+
* is not retained — it is re-read from the file on each sign() call.
|
|
52
|
+
*
|
|
53
|
+
* Throws SigningKeyProviderError if:
|
|
54
|
+
* - File not found (reason: 'key_file_not_found')
|
|
55
|
+
* - File corrupt (reason: 'key_file_corrupt')
|
|
56
|
+
*
|
|
57
|
+
* @param path - Absolute path to the encrypted key file (SIGNING_KEY_PATH)
|
|
58
|
+
* @param opts - Configuration including agentId and logger
|
|
59
|
+
*/
|
|
60
|
+
static load(path: string, opts: EncryptedFileSigningKeyProviderOptions): Promise<EncryptedFileSigningKeyProvider>;
|
|
61
|
+
/** Return the 32-byte Ed25519 public key. */
|
|
62
|
+
getPublicKey(): Promise<SigningPublicKey>;
|
|
63
|
+
/**
|
|
64
|
+
* Sign data with the Ed25519 private key.
|
|
65
|
+
*
|
|
66
|
+
* Security: On each call, the seed is re-read from the key file into a
|
|
67
|
+
* working buffer, used for signing, and the buffer is zeroed in a finally
|
|
68
|
+
* block — even if signing throws (SI-002).
|
|
69
|
+
*
|
|
70
|
+
* The public key is derived once at load() and cached — it is not sensitive.
|
|
71
|
+
*/
|
|
72
|
+
sign(data: Uint8Array, opts?: SignOptions): Promise<SigningSignature>;
|
|
73
|
+
/**
|
|
74
|
+
* @internal Test seam — returns true if the working buffer is currently all-zeros.
|
|
75
|
+
* This does NOT expose key material. Tests use this to verify SI-002 (zeroing)
|
|
76
|
+
* without ever seeing the private key bytes.
|
|
77
|
+
*/
|
|
78
|
+
isKeyBufferZeroedForTesting(): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* @internal Test seam — corrupts the provider state to trigger sign() failures
|
|
81
|
+
* BEFORE the key file is read. Used to test SI-003 (no fallback).
|
|
82
|
+
* Note: does NOT exercise the try/finally zeroing path — use throwAfterLoadForTesting() for that.
|
|
83
|
+
*/
|
|
84
|
+
corruptForTesting(): void;
|
|
85
|
+
/**
|
|
86
|
+
* @internal Test seam — causes sign() to throw AFTER the key has been loaded into
|
|
87
|
+
* the working buffer but BEFORE ed25519.sign() is called. Used to verify SI-002:
|
|
88
|
+
* the finally block zeroes the key buffer even when sign() throws mid-operation.
|
|
89
|
+
*/
|
|
90
|
+
throwAfterLoadForTesting(): void;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=encrypted-file-signing-key-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypted-file-signing-key-provider.d.ts","sourceRoot":"","sources":["../src/encrypted-file-signing-key-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEtH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAWzD,MAAM,WAAW,sCAAsC;IACrD,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,+BAAgC,YAAW,kBAAkB;;IAiBxE,OAAO;IAYP;;;;;;;;;;;;OAYG;WACU,IAAI,CACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,sCAAsC,GAC3C,OAAO,CAAC,+BAA+B,CAAC;IAwC3C,6CAA6C;IACvC,YAAY,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAI/C;;;;;;;;OAQG;IACG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkE3E;;;;OAIG;IACH,2BAA2B,IAAI,OAAO;IAItC;;;;OAIG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;;;OAIG;IACH,wBAAwB,IAAI,IAAI;CAGjC"}
|