@serfab/cadre-core 0.8.1 → 0.9.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 +2 -2
- package/dist/cadre-node.d.ts +101 -61
- package/dist/cadre-node.js +165 -124
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +15 -15
- package/dist/control-cohort.js +14 -14
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +51 -34
- package/dist/control-database.js +72 -56
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +2 -2
- package/dist/control-formation-recorder.js +2 -2
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +57 -33
- package/dist/control-schema.js.map +1 -1
- package/dist/device-token.d.ts +1 -1
- package/dist/device-token.js +1 -1
- package/dist/{authority-key.d.ts → ed25519-key.d.ts} +11 -10
- package/dist/{authority-key.js → ed25519-key.js} +9 -10
- package/dist/ed25519-key.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/key-store.d.ts +1 -1
- package/dist/key-store.js +1 -1
- package/dist/peer-authorization.d.ts +28 -6
- package/dist/peer-authorization.js +33 -7
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-record.d.ts +3 -3
- package/dist/peer-record.js +3 -3
- package/dist/push-node.d.ts +38 -0
- package/dist/push-node.js +48 -0
- package/dist/push-node.js.map +1 -0
- package/dist/push-notifier-apns.d.ts +10 -2
- package/dist/push-notifier-apns.js +14 -10
- package/dist/push-notifier-apns.js.map +1 -1
- package/dist/push-notifier-fcm.d.ts +7 -1
- package/dist/push-notifier-fcm.js +9 -10
- package/dist/push-notifier-fcm.js.map +1 -1
- package/dist/push-notifier.d.ts +16 -35
- package/dist/push-notifier.js +16 -44
- package/dist/push-notifier.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +70 -51
- package/dist/seed-bootstrap.js +168 -106
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +11 -11
- package/dist/seed-trust-policy.js +11 -11
- package/dist/seed-trust-policy.js.map +1 -1
- package/dist/strand-database.d.ts +5 -5
- package/dist/strand-database.js +4 -4
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +1 -1
- package/dist/strand-formation-protocol.js +1 -1
- package/dist/strand-instance-manager.d.ts +1 -1
- package/dist/strand-instance-manager.js +1 -1
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +6 -6
- package/dist/strand-member-key.js +6 -6
- package/dist/strand-member-key.js.map +1 -1
- package/dist/strand-member-registry.d.ts +7 -7
- package/dist/strand-member-registry.js +5 -5
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +84 -84
- package/dist/strand-membership-writer.js +90 -90
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/types.d.ts +52 -32
- package/dist/types.js.map +1 -1
- package/package.json +7 -3
- package/src/cadre-node.ts +170 -126
- package/src/control-cohort.ts +17 -17
- package/src/control-database.ts +76 -59
- package/src/control-formation-recorder.ts +2 -2
- package/src/control-schema.ts +57 -33
- package/src/device-token.ts +1 -1
- package/src/{authority-key.ts → ed25519-key.ts} +12 -11
- package/src/index.ts +21 -19
- package/src/key-store.ts +1 -1
- package/src/peer-authorization.ts +35 -7
- package/src/peer-record.ts +3 -3
- package/src/push-node.ts +64 -0
- package/src/push-notifier-apns.ts +16 -12
- package/src/push-notifier-fcm.ts +9 -10
- package/src/push-notifier.ts +16 -57
- package/src/seed-bootstrap.ts +179 -115
- package/src/seed-trust-policy.ts +18 -18
- package/src/strand-database.ts +6 -6
- package/src/strand-formation-protocol.ts +1 -1
- package/src/strand-instance-manager.ts +2 -2
- package/src/strand-member-key.ts +7 -7
- package/src/strand-member-registry.ts +10 -10
- package/src/strand-membership-writer.ts +127 -127
- package/src/types.ts +52 -32
- package/dist/authority-key.js.map +0 -1
package/src/seed-trust-policy.ts
CHANGED
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A seed carries an ed25519 signature over its body and the `signerKey` that
|
|
5
5
|
* produced it. Verifying that signature only proves the seed is internally
|
|
6
|
-
* consistent — it does NOT prove the signer is an
|
|
7
|
-
* signer key and the seed's own `
|
|
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
8
|
* The trust decision must therefore rest on an anchor that does not come from
|
|
9
9
|
* the seed body:
|
|
10
10
|
*
|
|
11
|
-
* 1. DB-anchored — keys already in the receiver's `CadreControl.
|
|
11
|
+
* 1. DB-anchored — keys already in the receiver's `CadreControl.OwnerKey`
|
|
12
12
|
* table (steady state: the node is enrolled / has synced control state).
|
|
13
13
|
* 2. Pinned out-of-band — keys handed to the node from outside the seed
|
|
14
|
-
* (carried by a `CadreInvite.
|
|
14
|
+
* (carried by a `CadreInvite.ownerKeys`, or pinned by an operator).
|
|
15
15
|
* 3. TOFU (opt-in) — an explicit confirmation callback invoked on first sight
|
|
16
16
|
* of an unknown signer key. Interactive hosts only; off by default.
|
|
17
17
|
*
|
|
18
18
|
* Secure default (`dbAnchoredTrustPolicy`): a cold-start node with an empty
|
|
19
|
-
* `
|
|
19
|
+
* `OwnerKey` table and no pinned keys rejects the seed — a seed can no
|
|
20
20
|
* longer vouch for its own signer.
|
|
21
21
|
*/
|
|
22
22
|
export interface SeedTrustContext {
|
|
@@ -25,10 +25,10 @@ export interface SeedTrustContext {
|
|
|
25
25
|
/** ed25519 base64url signer key — already signature-verified by `applySeed`. */
|
|
26
26
|
signerKey: string;
|
|
27
27
|
/**
|
|
28
|
-
* The receiver's known
|
|
28
|
+
* The receiver's known owner keys, sourced from its `OwnerKey`
|
|
29
29
|
* table — NOT from the seed. Empty for a cold-start node.
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
knownOwnerKeys: ReadonlySet<string>;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface SeedTrustDecision {
|
|
@@ -48,45 +48,45 @@ export interface SeedTrustPolicy {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Default policy: trust only keys already present in the receiver's
|
|
51
|
-
* `
|
|
51
|
+
* `OwnerKey` table. A cold-start node (empty table) rejects every seed.
|
|
52
52
|
*/
|
|
53
53
|
export function dbAnchoredTrustPolicy(): SeedTrustPolicy {
|
|
54
54
|
return {
|
|
55
|
-
evaluate({ signerKey,
|
|
56
|
-
if (
|
|
55
|
+
evaluate({ signerKey, knownOwnerKeys }) {
|
|
56
|
+
if (knownOwnerKeys.has(signerKey)) {
|
|
57
57
|
return { trusted: true };
|
|
58
58
|
}
|
|
59
59
|
return {
|
|
60
60
|
trusted: false,
|
|
61
|
-
reason: 'Signer key is not a known
|
|
61
|
+
reason: 'Signer key is not a known owner (DB-anchored trust policy)',
|
|
62
62
|
};
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Cold-start policy: trust keys in the receiver's `
|
|
69
|
-
* set pinned out-of-band (typically `CadreInvite.
|
|
68
|
+
* Cold-start policy: trust keys in the receiver's `OwnerKey` table plus a
|
|
69
|
+
* set pinned out-of-band (typically `CadreInvite.ownerKeys` or operator
|
|
70
70
|
* config). Lets an unenrolled invitee accept its first seed without the seed
|
|
71
71
|
* vouching for itself.
|
|
72
72
|
*/
|
|
73
73
|
export function pinnedKeyTrustPolicy(pinned: Iterable<string>): SeedTrustPolicy {
|
|
74
74
|
const pinnedSet = new Set(pinned);
|
|
75
75
|
return {
|
|
76
|
-
evaluate({ signerKey,
|
|
77
|
-
if (
|
|
76
|
+
evaluate({ signerKey, knownOwnerKeys }) {
|
|
77
|
+
if (knownOwnerKeys.has(signerKey) || pinnedSet.has(signerKey)) {
|
|
78
78
|
return { trusted: true };
|
|
79
79
|
}
|
|
80
80
|
return {
|
|
81
81
|
trusted: false,
|
|
82
|
-
reason: 'Signer key is neither a known nor a pinned
|
|
82
|
+
reason: 'Signer key is neither a known nor a pinned owner (pinned-key trust policy)',
|
|
83
83
|
};
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
* Opt-in interactive policy: trust keys already in the `
|
|
89
|
+
* Opt-in interactive policy: trust keys already in the `OwnerKey` table,
|
|
90
90
|
* and on an unknown key invoke `confirm` (e.g. a trust-circle UI prompt). The
|
|
91
91
|
* key is trusted iff `confirm` resolves true. Not enabled by default.
|
|
92
92
|
*/
|
|
@@ -95,7 +95,7 @@ export function tofuTrustPolicy(
|
|
|
95
95
|
): SeedTrustPolicy {
|
|
96
96
|
return {
|
|
97
97
|
async evaluate(ctx) {
|
|
98
|
-
if (ctx.
|
|
98
|
+
if (ctx.knownOwnerKeys.has(ctx.signerKey)) {
|
|
99
99
|
return { trusted: true };
|
|
100
100
|
}
|
|
101
101
|
const accepted = await confirm(ctx);
|
package/src/strand-database.ts
CHANGED
|
@@ -38,13 +38,13 @@ export interface StrandDatabaseConfig {
|
|
|
38
38
|
/**
|
|
39
39
|
* Strand type (`'o'` open / `'c'` closed) from the control-network strand row.
|
|
40
40
|
* Drives the founder bootstrap: open strands get a `Header` only; closed strands
|
|
41
|
-
* also get the founding `Member`+`
|
|
41
|
+
* also get the founding `Member`+`Manager`.
|
|
42
42
|
*/
|
|
43
43
|
strandType: 'o' | 'c';
|
|
44
44
|
/**
|
|
45
45
|
* The closed-strand `MemberPrivateKey` (base64 protobuf) from the strand row.
|
|
46
46
|
* Required when `founder` is true and `strandType` is `'c'` — it derives the
|
|
47
|
-
* founding `Member.Key`/`
|
|
47
|
+
* founding `Member.Key`/`Manager.MemberKey`. Absent for open strands.
|
|
48
48
|
*/
|
|
49
49
|
memberPrivateKey?: string;
|
|
50
50
|
/**
|
|
@@ -136,8 +136,8 @@ export class StrandDatabase {
|
|
|
136
136
|
* Run the founder membership bootstrap against the freshly-composed strand DB.
|
|
137
137
|
*
|
|
138
138
|
* Derives the founding keypair from the closed-strand `memberPrivateKey` (the
|
|
139
|
-
* `Member.Key`/`
|
|
140
|
-
* `memberPrivateKey` throws, because it could never seat a founding
|
|
139
|
+
* `Member.Key`/`Manager.MemberKey` are its public key); a closed strand with no
|
|
140
|
+
* `memberPrivateKey` throws, because it could never seat a founding manager.
|
|
141
141
|
* Open strands derive no keypair (Header only). Idempotent — see
|
|
142
142
|
* {@link bootstrapFounderMembership}.
|
|
143
143
|
*/
|
|
@@ -157,13 +157,13 @@ export class StrandDatabase {
|
|
|
157
157
|
/**
|
|
158
158
|
* Derive the founding keypair for a closed strand from its `memberPrivateKey`,
|
|
159
159
|
* failing loudly when the key is absent (a closed strand with no founding
|
|
160
|
-
*
|
|
160
|
+
* Manager can never admit anyone).
|
|
161
161
|
*/
|
|
162
162
|
private deriveFounderKeyPair(strandId: string, memberPrivateKey: string | undefined) {
|
|
163
163
|
if (!memberPrivateKey) {
|
|
164
164
|
throw new Error(
|
|
165
165
|
`Cannot found closed strand ${strandId}: the strand row has no MemberPrivateKey. ` +
|
|
166
|
-
'A closed strand needs a founding Member/
|
|
166
|
+
'A closed strand needs a founding Member/Manager derived from that key.',
|
|
167
167
|
);
|
|
168
168
|
}
|
|
169
169
|
return strandMemberKeyPair(memberPrivateKey);
|
|
@@ -174,7 +174,7 @@ const PLACEHOLDER_CADRE_ADDR = /^cadre-[ab]-\d+\.local$/;
|
|
|
174
174
|
* placeholders), and returned a strand vouched for by the responder party with a real id.
|
|
175
175
|
*
|
|
176
176
|
* `createdBy: 'responder'` means "the responder party vouches for / returns this strand."
|
|
177
|
-
* Under provision-then-record that strand was minted
|
|
177
|
+
* Under provision-then-record that strand was minted owner-signed earlier (not created
|
|
178
178
|
* in-session), but it is still the responder party returning its own strand, so the marker
|
|
179
179
|
* — and this structural validator — stay unchanged.
|
|
180
180
|
*
|
|
@@ -48,7 +48,7 @@ export interface StartStrandConfig {
|
|
|
48
48
|
requireSignedSchemas?: boolean;
|
|
49
49
|
/**
|
|
50
50
|
* Whether this node founds the strand (vs. joins it). Forwarded to the
|
|
51
|
-
* StrandDatabase so the founder bootstrap (Header, founding Member/
|
|
51
|
+
* StrandDatabase so the founder bootstrap (Header, founding Member/Manager)
|
|
52
52
|
* runs once at bring-up. Joiners leave this unset and write nothing. See
|
|
53
53
|
* {@link StrandConfig.founder}.
|
|
54
54
|
*/
|
|
@@ -292,7 +292,7 @@ export class StrandInstanceManager {
|
|
|
292
292
|
rawStorage: strandStorage,
|
|
293
293
|
// Founder bootstrap inputs: the strand's type drives which membership rows
|
|
294
294
|
// are written, and the closed-strand MemberPrivateKey derives the founding
|
|
295
|
-
// Member/
|
|
295
|
+
// Member/Manager key. Both come off the control-network strand row.
|
|
296
296
|
strandType: config.strandRow.Type,
|
|
297
297
|
memberPrivateKey: config.strandRow.MemberPrivateKey ?? undefined,
|
|
298
298
|
founder: config.founder
|
package/src/strand-member-key.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateKeyPair, privateKeyToProtobuf, privateKeyFromProtobuf } from '@libp2p/crypto/keys';
|
|
2
2
|
import { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';
|
|
3
|
-
import {
|
|
3
|
+
import { ed25519KeyPairFromLibp2p, type Ed25519KeyPair } from './ed25519-key.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Mint a fresh ed25519 strand member private key, encoded as base64 protobuf.
|
|
@@ -22,18 +22,18 @@ export async function generateStrandMemberKey(): Promise<string> {
|
|
|
22
22
|
* base64url keypair the strand RBAC constraints consume.
|
|
23
23
|
*
|
|
24
24
|
* The `Strand` control-layer `MemberPrivateKey` is the closed-strand read-gating
|
|
25
|
-
* secret; the founding `Member.Key`/`
|
|
25
|
+
* secret; the founding `Member.Key`/`Manager.MemberKey` (the strand RBAC layer)
|
|
26
26
|
* are *derived from it* — they are the `publicKeyB64` of the keypair this returns.
|
|
27
27
|
* Decode the protobuf to a libp2p private key, then reuse
|
|
28
|
-
* {@link
|
|
29
|
-
*
|
|
28
|
+
* {@link ed25519KeyPairFromLibp2p} so the same seed→public derivation used for node
|
|
29
|
+
* owner keys yields a stable `{ privateKeyB64, publicKeyB64 }` whose public
|
|
30
30
|
* key a later strand signature verifies against.
|
|
31
31
|
*
|
|
32
32
|
* @param memberPrivateKey - The strand's `MemberPrivateKey` (base64 protobuf).
|
|
33
|
-
* @returns The base64url seed/public-key pair for founding membership/
|
|
33
|
+
* @returns The base64url seed/public-key pair for founding membership/manager.
|
|
34
34
|
* @throws If the decoded key is not Ed25519 or the raw bytes are malformed.
|
|
35
35
|
*/
|
|
36
|
-
export function strandMemberKeyPair(memberPrivateKey: string):
|
|
36
|
+
export function strandMemberKeyPair(memberPrivateKey: string): Ed25519KeyPair {
|
|
37
37
|
const privateKey = privateKeyFromProtobuf(uint8ArrayFromString(memberPrivateKey, 'base64'));
|
|
38
|
-
return
|
|
38
|
+
return ed25519KeyPairFromLibp2p(privateKey);
|
|
39
39
|
}
|
|
@@ -2,11 +2,11 @@ import debug from 'debug';
|
|
|
2
2
|
import type { Database, SqlValue } from '@quereus/quereus';
|
|
3
3
|
import type { MemberRegistration } from './types.js';
|
|
4
4
|
import type { MemberRegistry, MemberVerifier } from './enrollment.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type { Ed25519KeyPair } from './ed25519-key.js';
|
|
6
6
|
import {
|
|
7
7
|
verifyStrandPayload,
|
|
8
8
|
consumeInvite,
|
|
9
|
-
|
|
9
|
+
addMemberByManager,
|
|
10
10
|
} from './strand-membership-writer.js';
|
|
11
11
|
import { canonicalDatetime } from './canonical-datetime.js';
|
|
12
12
|
|
|
@@ -37,10 +37,10 @@ export type StrandAdmission =
|
|
|
37
37
|
readonly invitePrivateKey: string;
|
|
38
38
|
}
|
|
39
39
|
| {
|
|
40
|
-
/** Admit directly by
|
|
41
|
-
readonly mode: '
|
|
42
|
-
/** The admitting
|
|
43
|
-
readonly
|
|
40
|
+
/** Admit directly by manager signature (the manager-side join). */
|
|
41
|
+
readonly mode: 'manager';
|
|
42
|
+
/** The admitting manager's strand keypair. */
|
|
43
|
+
readonly managerKeyPair: Ed25519KeyPair;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -115,8 +115,8 @@ export class StrandMemberVerifier implements MemberVerifier {
|
|
|
115
115
|
* `Member.Authorized` is taken:
|
|
116
116
|
* - `invite` → {@link consumeInvite} (atomic `Member` + `ConsumedInvite`), the
|
|
117
117
|
* invitee-side flow that redeems a single-use invite.
|
|
118
|
-
* - `
|
|
119
|
-
* seats a member already trusted by
|
|
118
|
+
* - `manager` → {@link addMemberByManager}, the manager-side flow that
|
|
119
|
+
* seats a member already trusted by a manager.
|
|
120
120
|
*
|
|
121
121
|
* Scoped to one strand's db; the `strandId` argument is accepted for interface
|
|
122
122
|
* compatibility but writes target this db's `Strand.*` tables.
|
|
@@ -152,8 +152,8 @@ export class StrandMemberRegistry implements MemberRegistry {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
await
|
|
156
|
-
|
|
155
|
+
await addMemberByManager(this.db, {
|
|
156
|
+
managerKeyPair: this.admission.managerKeyPair,
|
|
157
157
|
memberKey,
|
|
158
158
|
});
|
|
159
159
|
}
|