@serfab/cadre-core 0.10.0 → 0.11.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.
- package/README.md +255 -255
- package/dist/cadre-node.d.ts +203 -28
- package/dist/cadre-node.js +445 -84
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +26 -0
- package/dist/control-cohort.js +26 -0
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +82 -117
- package/dist/control-database.js +217 -295
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +8 -7
- package/dist/control-formation-recorder.js +8 -7
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +789 -774
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +15 -5
- package/dist/control-write-retry.js +19 -18
- package/dist/control-write-retry.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/peer-addr-book.d.ts +92 -0
- package/dist/peer-addr-book.js +220 -0
- package/dist/peer-addr-book.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +7 -0
- package/dist/seed-bootstrap.js +15 -13
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/strand-cohort.d.ts +4 -37
- package/dist/strand-cohort.js +4 -15
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +11 -16
- package/dist/strand-database.js +19 -7
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +8 -8
- package/dist/strand-formation-manager.js +15 -18
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +52 -21
- package/dist/strand-instance-manager.js +100 -34
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +6 -4
- package/dist/strand-membership-writer.js +38 -36
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/types.d.ts +39 -31
- package/dist/types.js.map +1 -1
- package/package.json +8 -7
- package/src/arachnode-stub.ts +159 -159
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +172 -172
- package/src/cadre-node.ts +5158 -4771
- package/src/canonical-json.ts +30 -30
- package/src/control-authorization.ts +114 -114
- package/src/control-cohort.ts +130 -103
- package/src/control-database.ts +2406 -2506
- package/src/control-formation-recorder.ts +305 -304
- package/src/control-schema.ts +801 -786
- package/src/control-stream.ts +205 -205
- package/src/control-write-retry.ts +483 -473
- package/src/delegate-admission.ts +302 -302
- package/src/device-token.ts +94 -94
- package/src/diagnostics/connection-path.ts +277 -277
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
- package/src/ed25519-key.ts +134 -134
- package/src/enrollment.ts +199 -199
- package/src/file-durable-slot.ts +61 -61
- package/src/formation-approval.ts +604 -604
- package/src/fs-atomic.ts +107 -107
- package/src/hibernation-manager.ts +437 -437
- package/src/identity-key.ts +109 -109
- package/src/index.ts +1 -0
- package/src/key-store-file.ts +132 -132
- package/src/key-store.ts +99 -99
- package/src/membership-connection-gater.ts +204 -204
- package/src/node-local-snapshot.ts +260 -260
- package/src/peer-addr-book.ts +250 -0
- package/src/peer-authorization.ts +259 -259
- package/src/peer-record.ts +164 -164
- package/src/push-credentials.ts +84 -84
- package/src/push-node.ts +64 -64
- package/src/push-notifier-apns.ts +247 -247
- package/src/push-notifier-fcm.ts +220 -220
- package/src/push-notifier-shared.ts +28 -28
- package/src/push-notifier.ts +52 -52
- package/src/relay-addrs.ts +111 -111
- package/src/relay-reservation.ts +823 -823
- package/src/seed-bootstrap.ts +15 -15
- package/src/seed-trust-policy.ts +140 -140
- package/src/strand-addr-protocol.ts +441 -441
- package/src/strand-backfill.ts +366 -366
- package/src/strand-cohort.ts +38 -77
- package/src/strand-database.ts +202 -204
- package/src/strand-formation-manager.ts +491 -494
- package/src/strand-formation-protocol.ts +692 -692
- package/src/strand-instance-manager.ts +108 -50
- package/src/strand-member-key.ts +39 -39
- package/src/strand-member-registry.ts +182 -182
- package/src/strand-membership-writer.ts +1432 -1430
- package/src/strand-solicitation.ts +472 -472
- package/src/strand-transport-key.ts +58 -58
- package/src/strand-wake-payload.ts +32 -32
- package/src/strand-wake-protocol.ts +314 -314
- package/src/strand-watcher.ts +314 -314
- package/src/trusted-owner-store-file.ts +41 -41
- package/src/trusted-owner-store.ts +171 -171
- package/src/types.ts +1309 -1303
package/src/seed-bootstrap.ts
CHANGED
|
@@ -404,7 +404,7 @@ export class SeedBootstrapService {
|
|
|
404
404
|
insert into CadreControl.DeviceToken (PeerId, Platform, Token, UpdatedAt, Sig, StampId)
|
|
405
405
|
with context OwnerKey = ?, Signature = ?
|
|
406
406
|
values (?, ?, ?, ?, ?, ?)
|
|
407
|
-
`, [this.ownerPublicKey, signature, record.peerId, record.platform, record.token, record.updatedAt, record.sig, stampId]);
|
|
407
|
+
`, [this.ownerPublicKey, signature, record.peerId, record.platform, record.token, record.updatedAt, record.sig, stampId], 'device-token-insert');
|
|
408
408
|
log('Device token inserted (owner-signed): %s', record.peerId);
|
|
409
409
|
}
|
|
410
410
|
|
|
@@ -751,8 +751,8 @@ export class SeedBootstrapService {
|
|
|
751
751
|
// `ApplySeedResult.ownerDialsFailed`). Recovery is not this loop's job —
|
|
752
752
|
// `CadreNode.dialColdStartBootstrap` retries these same addresses on every
|
|
753
753
|
// control-cohort reconcile pass until the control database has siblings.
|
|
754
|
-
// `createSeed` projects
|
|
755
|
-
// minted after it joined finds ITSELF in the owner list. Dialing self always
|
|
754
|
+
// `createSeed` projects every non-revoked CadrePeer row, so an owner applying
|
|
755
|
+
// a seed minted after it joined finds ITSELF in the owner list. Dialing self always
|
|
756
756
|
// throws, which would report a healthy owner as "seeded but stranded".
|
|
757
757
|
// Optional-chained: partial libp2p handles (unit-test doubles) omit `peerId`,
|
|
758
758
|
// and an undefined self simply matches nothing.
|
|
@@ -966,6 +966,13 @@ export class SeedBootstrapService {
|
|
|
966
966
|
* This makes any owner node markable — not just the local one — and ties
|
|
967
967
|
* `isOwner` to the control table rather than to `peerId === self`.
|
|
968
968
|
*
|
|
969
|
+
* Read through {@link ControlDatabase.queryCadrePeers}, not a raw `CadrePeer`
|
|
970
|
+
* select: that reader drops any row whose `StampId` is retired in
|
|
971
|
+
* `CadreControl.Revocation`, so a removed member's addresses are never packed
|
|
972
|
+
* into a seed and pushed into a joiner's peerstore (`applySeed` adds every
|
|
973
|
+
* seed peer's addrs and dials the owner-flagged ones). A revoked peer is off
|
|
974
|
+
* the addressable surface everywhere, and this is one of its exits.
|
|
975
|
+
*
|
|
969
976
|
* NOTE: this is the one owner lookup deliberately left on the REPLICATED
|
|
970
977
|
* table rather than the node-local anchor. `SeedPeer.isOwner` is a dial hint
|
|
971
978
|
* — the receiver dials owner-flagged peers first — not a trust decision, and
|
|
@@ -980,29 +987,22 @@ export class SeedBootstrapService {
|
|
|
980
987
|
}
|
|
981
988
|
|
|
982
989
|
const ownerKeys = await this.controlDatabase.getOwnerKeys();
|
|
983
|
-
const
|
|
984
|
-
const peers: SeedPeer[] = [];
|
|
985
|
-
|
|
986
|
-
// Query CadrePeer table
|
|
987
|
-
for await (const row of db.eval('select PeerId, Multiaddr from CadreControl.CadrePeer')) {
|
|
988
|
-
const peerId = row.PeerId as string;
|
|
989
|
-
const multiaddr = row.Multiaddr as string | null;
|
|
990
|
+
const rows = await this.controlDatabase.queryCadrePeers();
|
|
990
991
|
|
|
992
|
+
return rows.map(({ peerId, multiaddr }) => {
|
|
991
993
|
// Derive the peer's ed25519 key from its PeerId; a non-Ed25519 peer or an
|
|
992
994
|
// unparsable id yields null and is treated as a non-owner rather than
|
|
993
995
|
// failing the whole seed creation.
|
|
994
996
|
const pubKeyB64 = ed25519PublicKeyB64FromPeerId(peerId);
|
|
995
997
|
const isOwner = pubKeyB64 !== null && ownerKeys.has(pubKeyB64);
|
|
996
998
|
|
|
997
|
-
|
|
999
|
+
return {
|
|
998
1000
|
peerId,
|
|
999
1001
|
multiaddrs: multiaddr ? multiaddr.split(',') : [],
|
|
1000
1002
|
isOwner,
|
|
1001
1003
|
...(isOwner ? { publicKey: pubKeyB64 } : {}),
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
return peers;
|
|
1004
|
+
};
|
|
1005
|
+
});
|
|
1006
1006
|
}
|
|
1007
1007
|
|
|
1008
1008
|
/**
|
package/src/seed-trust-policy.ts
CHANGED
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Trust-anchor policy for control-network seeds.
|
|
3
|
-
*
|
|
4
|
-
* A seed carries an ed25519 signature over its body and the `signerKey` that
|
|
5
|
-
* produced it. Verifying that signature only proves the seed is internally
|
|
6
|
-
* consistent — it does NOT prove the signer is an owner, because both the
|
|
7
|
-
* signer key and the seed's own `isOwner` peer flags are attacker-supplied.
|
|
8
|
-
* The trust decision must therefore rest on an anchor that does not come from
|
|
9
|
-
* the seed body:
|
|
10
|
-
*
|
|
11
|
-
* 1. Anchored — keys in the receiver's NODE-LOCAL `TrustedOwnerStore`: the
|
|
12
|
-
* non-replicated, per-party record of owner keys established out of band
|
|
13
|
-
* (founding the party, the `CadreInvite` that enrolled this node, an
|
|
14
|
-
* operator pin). Deliberately NOT the replicated `CadreControl.OwnerKey`
|
|
15
|
-
* table — any connecting node can genesis-insert its own key there and let
|
|
16
|
-
* it replicate, so that table can be made to say "yes" by a stranger.
|
|
17
|
-
* 2. Pinned out-of-band — keys handed to the node for this seed only (carried
|
|
18
|
-
* by a `CadreInvite.ownerKeys`, or pinned by an operator) without having
|
|
19
|
-
* been anchored yet.
|
|
20
|
-
* 3. TOFU (opt-in) — an explicit confirmation callback invoked on first sight
|
|
21
|
-
* of an unknown signer key. Interactive hosts only; off by default.
|
|
22
|
-
*
|
|
23
|
-
* Secure default (`anchoredTrustPolicy`): a node with an empty anchor and no
|
|
24
|
-
* pinned keys rejects the seed — a seed can no longer vouch for its own signer,
|
|
25
|
-
* and neither can a polluted replicated table.
|
|
26
|
-
*
|
|
27
|
-
* A key accepted via anchor 2 or 3 is reported back through
|
|
28
|
-
* {@link SeedTrustDecision.anchorAs} so `applySeed` can persist it into the
|
|
29
|
-
* node-local anchor, and later seeds from the same owner are accepted without
|
|
30
|
-
* re-supplying the invite/confirmation.
|
|
31
|
-
*/
|
|
32
|
-
import type { TrustSource } from './trusted-owner-store.js';
|
|
33
|
-
|
|
34
|
-
export interface SeedTrustContext {
|
|
35
|
-
/** Party the seed claims to belong to. */
|
|
36
|
-
partyId: string;
|
|
37
|
-
/** ed25519 base64url signer key — already signature-verified by `applySeed`. */
|
|
38
|
-
signerKey: string;
|
|
39
|
-
/**
|
|
40
|
-
* The receiver's anchored owner keys, sourced from its node-local
|
|
41
|
-
* `TrustedOwnerStore` — NOT from the seed, and NOT from the replicated
|
|
42
|
-
* `OwnerKey` table. Empty for a node whose anchor was never seeded.
|
|
43
|
-
*/
|
|
44
|
-
knownOwnerKeys: ReadonlySet<string>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface SeedTrustDecision {
|
|
48
|
-
/** Whether the signer key is trusted. */
|
|
49
|
-
trusted: boolean;
|
|
50
|
-
/** Human-readable reason, surfaced as the seed-apply error when not trusted. */
|
|
51
|
-
reason?: string;
|
|
52
|
-
/**
|
|
53
|
-
* Set when the key was trusted via an anchor OUTSIDE the node-local store (a
|
|
54
|
-
* pinned key, a TOFU confirmation): the provenance to record it under, so
|
|
55
|
-
* `applySeed` persists it and the next seed from that owner is anchored
|
|
56
|
-
* without re-supplying the pin. Omitted when the key was already anchored
|
|
57
|
-
* (nothing to persist) or not trusted at all.
|
|
58
|
-
*/
|
|
59
|
-
anchorAs?: Exclude<TrustSource, 'genesis'>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Decides whether a signature-verified seed's signer key should be trusted.
|
|
64
|
-
* May be synchronous (anchored/pinned) or asynchronous (TOFU confirmation).
|
|
65
|
-
*/
|
|
66
|
-
export interface SeedTrustPolicy {
|
|
67
|
-
evaluate(ctx: SeedTrustContext): Promise<SeedTrustDecision> | SeedTrustDecision;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Default policy: trust only keys already in the receiver's node-local
|
|
72
|
-
* trusted-owner anchor. A node whose anchor was never seeded (no genesis, no
|
|
73
|
-
* invite pin, no operator pin) rejects every seed.
|
|
74
|
-
*/
|
|
75
|
-
export function anchoredTrustPolicy(): SeedTrustPolicy {
|
|
76
|
-
return {
|
|
77
|
-
evaluate({ signerKey, knownOwnerKeys }) {
|
|
78
|
-
if (knownOwnerKeys.has(signerKey)) {
|
|
79
|
-
return { trusted: true };
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
trusted: false,
|
|
83
|
-
reason: 'Signer key is not an anchored owner (anchored trust policy)',
|
|
84
|
-
};
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Cold-start policy: trust anchored keys plus a set pinned out-of-band
|
|
91
|
-
* (typically `CadreInvite.ownerKeys` or operator config). Lets an unenrolled
|
|
92
|
-
* invitee accept its first seed without the seed vouching for itself.
|
|
93
|
-
*
|
|
94
|
-
* @param anchorAs - provenance under which a pin-only acceptance is persisted
|
|
95
|
-
* into the node-local anchor ('invite' by default — the invite-redemption
|
|
96
|
-
* case; pass 'operator' for an operator-supplied pin).
|
|
97
|
-
*/
|
|
98
|
-
export function pinnedKeyTrustPolicy(
|
|
99
|
-
pinned: Iterable<string>,
|
|
100
|
-
anchorAs: Exclude<TrustSource, 'genesis'> = 'invite'
|
|
101
|
-
): SeedTrustPolicy {
|
|
102
|
-
const pinnedSet = new Set(pinned);
|
|
103
|
-
return {
|
|
104
|
-
evaluate({ signerKey, knownOwnerKeys }) {
|
|
105
|
-
if (knownOwnerKeys.has(signerKey)) {
|
|
106
|
-
return { trusted: true };
|
|
107
|
-
}
|
|
108
|
-
if (pinnedSet.has(signerKey)) {
|
|
109
|
-
return { trusted: true, anchorAs };
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
trusted: false,
|
|
113
|
-
reason: 'Signer key is neither an anchored nor a pinned owner (pinned-key trust policy)',
|
|
114
|
-
};
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Opt-in interactive policy: trust keys already in the node-local anchor, and
|
|
121
|
-
* on an unknown key invoke `confirm` (e.g. a trust-circle UI prompt). The key is
|
|
122
|
-
* trusted iff `confirm` resolves true, and a confirmed key is persisted into the
|
|
123
|
-
* anchor as an 'operator' pin (a human at the console is the same provenance as
|
|
124
|
-
* an explicit operator pin) so the prompt is not repeated. Not enabled by default.
|
|
125
|
-
*/
|
|
126
|
-
export function tofuTrustPolicy(
|
|
127
|
-
confirm: (ctx: SeedTrustContext) => Promise<boolean>
|
|
128
|
-
): SeedTrustPolicy {
|
|
129
|
-
return {
|
|
130
|
-
async evaluate(ctx) {
|
|
131
|
-
if (ctx.knownOwnerKeys.has(ctx.signerKey)) {
|
|
132
|
-
return { trusted: true };
|
|
133
|
-
}
|
|
134
|
-
const accepted = await confirm(ctx);
|
|
135
|
-
return accepted
|
|
136
|
-
? { trusted: true, anchorAs: 'operator' }
|
|
137
|
-
: { trusted: false, reason: 'TOFU confirmation declined for unknown signer key' };
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Trust-anchor policy for control-network seeds.
|
|
3
|
+
*
|
|
4
|
+
* A seed carries an ed25519 signature over its body and the `signerKey` that
|
|
5
|
+
* produced it. Verifying that signature only proves the seed is internally
|
|
6
|
+
* consistent — it does NOT prove the signer is an owner, because both the
|
|
7
|
+
* signer key and the seed's own `isOwner` peer flags are attacker-supplied.
|
|
8
|
+
* The trust decision must therefore rest on an anchor that does not come from
|
|
9
|
+
* the seed body:
|
|
10
|
+
*
|
|
11
|
+
* 1. Anchored — keys in the receiver's NODE-LOCAL `TrustedOwnerStore`: the
|
|
12
|
+
* non-replicated, per-party record of owner keys established out of band
|
|
13
|
+
* (founding the party, the `CadreInvite` that enrolled this node, an
|
|
14
|
+
* operator pin). Deliberately NOT the replicated `CadreControl.OwnerKey`
|
|
15
|
+
* table — any connecting node can genesis-insert its own key there and let
|
|
16
|
+
* it replicate, so that table can be made to say "yes" by a stranger.
|
|
17
|
+
* 2. Pinned out-of-band — keys handed to the node for this seed only (carried
|
|
18
|
+
* by a `CadreInvite.ownerKeys`, or pinned by an operator) without having
|
|
19
|
+
* been anchored yet.
|
|
20
|
+
* 3. TOFU (opt-in) — an explicit confirmation callback invoked on first sight
|
|
21
|
+
* of an unknown signer key. Interactive hosts only; off by default.
|
|
22
|
+
*
|
|
23
|
+
* Secure default (`anchoredTrustPolicy`): a node with an empty anchor and no
|
|
24
|
+
* pinned keys rejects the seed — a seed can no longer vouch for its own signer,
|
|
25
|
+
* and neither can a polluted replicated table.
|
|
26
|
+
*
|
|
27
|
+
* A key accepted via anchor 2 or 3 is reported back through
|
|
28
|
+
* {@link SeedTrustDecision.anchorAs} so `applySeed` can persist it into the
|
|
29
|
+
* node-local anchor, and later seeds from the same owner are accepted without
|
|
30
|
+
* re-supplying the invite/confirmation.
|
|
31
|
+
*/
|
|
32
|
+
import type { TrustSource } from './trusted-owner-store.js';
|
|
33
|
+
|
|
34
|
+
export interface SeedTrustContext {
|
|
35
|
+
/** Party the seed claims to belong to. */
|
|
36
|
+
partyId: string;
|
|
37
|
+
/** ed25519 base64url signer key — already signature-verified by `applySeed`. */
|
|
38
|
+
signerKey: string;
|
|
39
|
+
/**
|
|
40
|
+
* The receiver's anchored owner keys, sourced from its node-local
|
|
41
|
+
* `TrustedOwnerStore` — NOT from the seed, and NOT from the replicated
|
|
42
|
+
* `OwnerKey` table. Empty for a node whose anchor was never seeded.
|
|
43
|
+
*/
|
|
44
|
+
knownOwnerKeys: ReadonlySet<string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SeedTrustDecision {
|
|
48
|
+
/** Whether the signer key is trusted. */
|
|
49
|
+
trusted: boolean;
|
|
50
|
+
/** Human-readable reason, surfaced as the seed-apply error when not trusted. */
|
|
51
|
+
reason?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Set when the key was trusted via an anchor OUTSIDE the node-local store (a
|
|
54
|
+
* pinned key, a TOFU confirmation): the provenance to record it under, so
|
|
55
|
+
* `applySeed` persists it and the next seed from that owner is anchored
|
|
56
|
+
* without re-supplying the pin. Omitted when the key was already anchored
|
|
57
|
+
* (nothing to persist) or not trusted at all.
|
|
58
|
+
*/
|
|
59
|
+
anchorAs?: Exclude<TrustSource, 'genesis'>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Decides whether a signature-verified seed's signer key should be trusted.
|
|
64
|
+
* May be synchronous (anchored/pinned) or asynchronous (TOFU confirmation).
|
|
65
|
+
*/
|
|
66
|
+
export interface SeedTrustPolicy {
|
|
67
|
+
evaluate(ctx: SeedTrustContext): Promise<SeedTrustDecision> | SeedTrustDecision;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Default policy: trust only keys already in the receiver's node-local
|
|
72
|
+
* trusted-owner anchor. A node whose anchor was never seeded (no genesis, no
|
|
73
|
+
* invite pin, no operator pin) rejects every seed.
|
|
74
|
+
*/
|
|
75
|
+
export function anchoredTrustPolicy(): SeedTrustPolicy {
|
|
76
|
+
return {
|
|
77
|
+
evaluate({ signerKey, knownOwnerKeys }) {
|
|
78
|
+
if (knownOwnerKeys.has(signerKey)) {
|
|
79
|
+
return { trusted: true };
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
trusted: false,
|
|
83
|
+
reason: 'Signer key is not an anchored owner (anchored trust policy)',
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Cold-start policy: trust anchored keys plus a set pinned out-of-band
|
|
91
|
+
* (typically `CadreInvite.ownerKeys` or operator config). Lets an unenrolled
|
|
92
|
+
* invitee accept its first seed without the seed vouching for itself.
|
|
93
|
+
*
|
|
94
|
+
* @param anchorAs - provenance under which a pin-only acceptance is persisted
|
|
95
|
+
* into the node-local anchor ('invite' by default — the invite-redemption
|
|
96
|
+
* case; pass 'operator' for an operator-supplied pin).
|
|
97
|
+
*/
|
|
98
|
+
export function pinnedKeyTrustPolicy(
|
|
99
|
+
pinned: Iterable<string>,
|
|
100
|
+
anchorAs: Exclude<TrustSource, 'genesis'> = 'invite'
|
|
101
|
+
): SeedTrustPolicy {
|
|
102
|
+
const pinnedSet = new Set(pinned);
|
|
103
|
+
return {
|
|
104
|
+
evaluate({ signerKey, knownOwnerKeys }) {
|
|
105
|
+
if (knownOwnerKeys.has(signerKey)) {
|
|
106
|
+
return { trusted: true };
|
|
107
|
+
}
|
|
108
|
+
if (pinnedSet.has(signerKey)) {
|
|
109
|
+
return { trusted: true, anchorAs };
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
trusted: false,
|
|
113
|
+
reason: 'Signer key is neither an anchored nor a pinned owner (pinned-key trust policy)',
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Opt-in interactive policy: trust keys already in the node-local anchor, and
|
|
121
|
+
* on an unknown key invoke `confirm` (e.g. a trust-circle UI prompt). The key is
|
|
122
|
+
* trusted iff `confirm` resolves true, and a confirmed key is persisted into the
|
|
123
|
+
* anchor as an 'operator' pin (a human at the console is the same provenance as
|
|
124
|
+
* an explicit operator pin) so the prompt is not repeated. Not enabled by default.
|
|
125
|
+
*/
|
|
126
|
+
export function tofuTrustPolicy(
|
|
127
|
+
confirm: (ctx: SeedTrustContext) => Promise<boolean>
|
|
128
|
+
): SeedTrustPolicy {
|
|
129
|
+
return {
|
|
130
|
+
async evaluate(ctx) {
|
|
131
|
+
if (ctx.knownOwnerKeys.has(ctx.signerKey)) {
|
|
132
|
+
return { trusted: true };
|
|
133
|
+
}
|
|
134
|
+
const accepted = await confirm(ctx);
|
|
135
|
+
return accepted
|
|
136
|
+
? { trusted: true, anchorAs: 'operator' }
|
|
137
|
+
: { trusted: false, reason: 'TOFU confirmation declined for unknown signer key' };
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|