agent-comms 8.15.8 → 8.15.10
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bridges/user/web/server.d.ts.map +1 -1
- package/dist/bridges/user/web/server.js +44 -24
- package/dist/bridges/user/web/server.js.map +1 -1
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts +5 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js +90 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts +2 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js +75 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js.map +1 -0
- package/dist/bridges/user/web/web-assets.d.ts +28 -0
- package/dist/bridges/user/web/web-assets.d.ts.map +1 -0
- package/dist/bridges/user/web/web-assets.js +67 -0
- package/dist/bridges/user/web/web-assets.js.map +1 -0
- package/package.json +8 -2
- package/src/account-cli.ts +195 -0
- package/src/account-join-cli.ts +191 -0
- package/src/bridges/cc-peer/approval-commands.ts +82 -0
- package/src/bridges/cc-peer/bridge.ts +138 -0
- package/src/bridges/cc-peer/default-front.ts +58 -0
- package/src/bridges/cc-peer/front-controller.ts +160 -0
- package/src/bridges/cc-peer/front-relay.ts +191 -0
- package/src/bridges/cc-peer/front-runtime.ts +167 -0
- package/src/bridges/cc-peer/front.ts +59 -0
- package/src/bridges/cc-peer/reply-aliases.ts +90 -0
- package/src/bridges/cc-peer/run.ts +100 -0
- package/src/bridges/cc-peer/shared-peer.ts +28 -0
- package/src/bridges/claude-code/channel.ts +318 -0
- package/src/bridges/codex/tool.ts +132 -0
- package/src/bridges/mcp/server.ts +137 -0
- package/src/bridges/opencode/plugin.ts +130 -0
- package/src/bridges/pi/extension.ts +330 -0
- package/src/bridges/registry.ts +17 -0
- package/src/bridges/user/cli.ts +87 -0
- package/src/bridges/user/controller.ts +345 -0
- package/src/bridges/user/tui.ts +300 -0
- package/src/bridges/user/web/actions.ts +173 -0
- package/src/bridges/user/web/contract.ts +222 -0
- package/src/bridges/user/web/dist/bundle.js +49 -0
- package/src/bridges/user/web/dist/icons/icon-192x192.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-512x512.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-96x96.svg +9 -0
- package/src/bridges/user/web/dist/index.html +21 -0
- package/src/bridges/user/web/dist/manifest.json +26 -0
- package/src/bridges/user/web/dist/mesh-worker.js +64 -0
- package/src/bridges/user/web/dist/sw.js +1 -0
- package/src/bridges/user/web/event-publisher.ts +114 -0
- package/src/bridges/user/web/port-discovery.ts +49 -0
- package/src/bridges/user/web/reads.ts +35 -0
- package/src/bridges/user/web/router.ts +158 -0
- package/src/bridges/user/web/server.ts +782 -0
- package/src/bridges/user/web/vite.config.ts +71 -0
- package/src/bridges/user/web/web-access.ts +404 -0
- package/src/bridges/user/web/web-assets.ts +90 -0
- package/src/bridges/user/web/web-beacon.ts +69 -0
- package/src/bridges/user/web/web-console-static.ts +90 -0
- package/src/cli-main.ts +635 -0
- package/src/cli.ts +13 -0
- package/src/core/account-bundle.ts +264 -0
- package/src/core/account-join.ts +289 -0
- package/src/core/account-ledger-crypto.ts +154 -0
- package/src/core/account-ledger-store.ts +66 -0
- package/src/core/account-ledger.ts +385 -0
- package/src/core/agent-details.ts +216 -0
- package/src/core/agent-list-format.ts +97 -0
- package/src/core/agent-registry.ts +282 -0
- package/src/core/agent-table.ts +58 -0
- package/src/core/atomic-file.ts +46 -0
- package/src/core/bind-retry.ts +38 -0
- package/src/core/bridge-mesh.ts +176 -0
- package/src/core/bridge.ts +718 -0
- package/src/core/capability-ask.ts +162 -0
- package/src/core/capability-bubble-up.ts +237 -0
- package/src/core/comms-store.ts +130 -0
- package/src/core/connection-approval.ts +134 -0
- package/src/core/connection-code-tool.ts +92 -0
- package/src/core/connection-code.ts +296 -0
- package/src/core/coordinator-role.ts +224 -0
- package/src/core/data-directory.ts +32 -0
- package/src/core/data-frame-routing.ts +309 -0
- package/src/core/default-command.ts +12 -0
- package/src/core/delegation-policy.ts +41 -0
- package/src/core/delivery-engine.ts +649 -0
- package/src/core/device-membership-verification.ts +125 -0
- package/src/core/device-membership.ts +130 -0
- package/src/core/directory-admission.ts +155 -0
- package/src/core/discovery-mdns.ts +247 -0
- package/src/core/discovery-tailscale.ts +224 -0
- package/src/core/discovery.ts +303 -0
- package/src/core/display-name.ts +73 -0
- package/src/core/dm-grant-actions.ts +72 -0
- package/src/core/dm-send-delegation.ts +73 -0
- package/src/core/dm-token-verification.ts +76 -0
- package/src/core/election-sessions.ts +135 -0
- package/src/core/error-reporting.ts +85 -0
- package/src/core/first-contact.ts +242 -0
- package/src/core/gateway-trust-actions.ts +184 -0
- package/src/core/gateway-trust.ts +305 -0
- package/src/core/gossip-directory.ts +276 -0
- package/src/core/gossip-extensions.ts +123 -0
- package/src/core/group-proofs.ts +205 -0
- package/src/core/handshake.ts +354 -0
- package/src/core/hub-forwarding.ts +24 -0
- package/src/core/hub-link.ts +176 -0
- package/src/core/hub-session.ts +374 -0
- package/src/core/identity-store.ts +715 -0
- package/src/core/identity.ts +489 -0
- package/src/core/index.ts +65 -0
- package/src/core/issuer-grouping.ts +165 -0
- package/src/core/issuer-identity-file.ts +187 -0
- package/src/core/lan-web-actions.ts +34 -0
- package/src/core/lan-web-uis.ts +55 -0
- package/src/core/ledger-lock.ts +81 -0
- package/src/core/listener-registry.ts +133 -0
- package/src/core/loopback-address.ts +22 -0
- package/src/core/machine-identity.ts +77 -0
- package/src/core/machine-list-groups.ts +53 -0
- package/src/core/mcp-registry-retry.ts +69 -0
- package/src/core/mcp-registry-version.ts +18 -0
- package/src/core/membership-proof.ts +139 -0
- package/src/core/membership-proofs.ts +149 -0
- package/src/core/mesh-graph-trace-tool.ts +81 -0
- package/src/core/mesh-graph.ts +89 -0
- package/src/core/mesh-network-actions.ts +183 -0
- package/src/core/mesh-store-options.ts +23 -0
- package/src/core/mesh-store-shared.ts +134 -0
- package/src/core/mesh-store.ts +1114 -0
- package/src/core/name-claim.ts +145 -0
- package/src/core/naming-actions.ts +90 -0
- package/src/core/naming.ts +138 -0
- package/src/core/nanoid.ts +20 -0
- package/src/core/network-interfaces.ts +24 -0
- package/src/core/package-version.ts +34 -0
- package/src/core/peer-lifecycle.ts +377 -0
- package/src/core/peer-link-error.ts +41 -0
- package/src/core/peer-versions.ts +98 -0
- package/src/core/pending-connection.ts +53 -0
- package/src/core/petnames.ts +121 -0
- package/src/core/pgp-keyserver.ts +32 -0
- package/src/core/pid-lock-file.ts +122 -0
- package/src/core/push-manager.ts +76 -0
- package/src/core/random-id.ts +9 -0
- package/src/core/receiver-set.ts +42 -0
- package/src/core/relay-role.ts +182 -0
- package/src/core/relay-selection.ts +120 -0
- package/src/core/relay-server.ts +198 -0
- package/src/core/relay-uplink.ts +78 -0
- package/src/core/request-timeouts.ts +37 -0
- package/src/core/room-discovery.ts +48 -0
- package/src/core/room-lifecycle.ts +838 -0
- package/src/core/room-lookup.ts +41 -0
- package/src/core/room-messaging.ts +247 -0
- package/src/core/room-notice-log.ts +61 -0
- package/src/core/room-path.ts +84 -0
- package/src/core/room-protocol.ts +893 -0
- package/src/core/room-router.ts +180 -0
- package/src/core/room-token-verification.ts +85 -0
- package/src/core/room-wire-extensions.ts +60 -0
- package/src/core/self-name-claim.ts +67 -0
- package/src/core/send-outcome.ts +69 -0
- package/src/core/shutdown-signals.ts +57 -0
- package/src/core/stale-agent-checker.ts +108 -0
- package/src/core/store.ts +704 -0
- package/src/core/token-text.ts +40 -0
- package/src/core/tool.ts +853 -0
- package/src/core/transport-events.ts +73 -0
- package/src/core/transport-queries.ts +54 -0
- package/src/core/transport.ts +367 -0
- package/src/core/types.ts +550 -0
- package/src/core/user-identity.ts +290 -0
- package/src/core/vapid.ts +81 -0
- package/src/core/version-check.ts +147 -0
- package/src/core/version-report-actions.ts +79 -0
- package/src/core/web-beacon.ts +31 -0
- package/src/core/web-push.ts +322 -0
- package/src/core/wire-mesh-core-version.ts +25 -0
- package/src/core/wire-mesh-identity.ts +36 -0
- package/src/core/wire-mesh-transport-options.ts +82 -0
- package/src/core/wire-mesh-transport.ts +1007 -0
- package/src/core/wire-protocol.ts +153 -0
- package/src/core/ws-dial.ts +191 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic identity generation for mesh peers.
|
|
3
|
+
*
|
|
4
|
+
* Each MeshStore instance generates an ECDSA P-256 keypair and a self-signed
|
|
5
|
+
* X.509 certificate on first run. The peer ID is derived from the SHA-256
|
|
6
|
+
* fingerprint of the certificate (DER-encoded, hex with colons), replacing
|
|
7
|
+
* the previous nanoid(8) approach.
|
|
8
|
+
*
|
|
9
|
+
* Key material is generated in memory; bridges that need a stable identity
|
|
10
|
+
* across restarts persist it through identity-store.ts, which keeps the
|
|
11
|
+
* fingerprint (and therefore the peer/agent ID) stable.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
createHash,
|
|
16
|
+
createPublicKey,
|
|
17
|
+
createSign,
|
|
18
|
+
generateKeyPairSync,
|
|
19
|
+
randomBytes,
|
|
20
|
+
} from "node:crypto";
|
|
21
|
+
|
|
22
|
+
/** The uncompressed SEC1 point tag byte (RFC 5480 §2.2): 0x04 marks what follows as raw X||Y, never compressed or hybrid encoding. */
|
|
23
|
+
const UNCOMPRESSED_POINT_TAG = 0x04;
|
|
24
|
+
|
|
25
|
+
/** Days in the certificate validity period. */
|
|
26
|
+
const CERTIFICATE_VALIDITY_DAYS = 365;
|
|
27
|
+
/** Hours per day, used to convert the certificate validity period to milliseconds. */
|
|
28
|
+
const HOURS_PER_DAY = 24;
|
|
29
|
+
/** Minutes per hour, used to convert the certificate validity period to milliseconds. */
|
|
30
|
+
const MINUTES_PER_HOUR = 60;
|
|
31
|
+
/** Seconds per minute, used to convert the certificate validity period to milliseconds. */
|
|
32
|
+
const SECONDS_PER_MINUTE = 60;
|
|
33
|
+
/** Milliseconds per second, used to convert the certificate validity period to milliseconds. */
|
|
34
|
+
const MS_PER_SECOND = 1000;
|
|
35
|
+
|
|
36
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/** Self-signed certificate validity. Exported so identity persistence can derive its renewal margin. */
|
|
39
|
+
export const CERTIFICATE_VALIDITY_MS =
|
|
40
|
+
CERTIFICATE_VALIDITY_DAYS *
|
|
41
|
+
HOURS_PER_DAY *
|
|
42
|
+
MINUTES_PER_HOUR *
|
|
43
|
+
SECONDS_PER_MINUTE *
|
|
44
|
+
MS_PER_SECOND;
|
|
45
|
+
|
|
46
|
+
export interface PeerIdentity {
|
|
47
|
+
/** PEM-encoded PKCS#8 private key. */
|
|
48
|
+
privateKey: string;
|
|
49
|
+
/** PEM-encoded self-signed X.509 certificate. */
|
|
50
|
+
certificate: string;
|
|
51
|
+
/** SHA-256 fingerprint of the certificate (DER), hex-encoded with colons. */
|
|
52
|
+
fingerprint: string;
|
|
53
|
+
/**
|
|
54
|
+
* SHA-256 of the raw, uncompressed SEC1 public-key point -- wire-mesh's own device-id derivation (never the certificate's DER encoding, which embeds a serial number and validity window that change on every reissue even for an identical key; this is the exact instability fingerprint already has). Additive: nothing in this codebase reads it yet. `fingerprint` remains what MeshStore.peerId is set to until the substrate migration cuts over to this field instead.
|
|
55
|
+
*/
|
|
56
|
+
deviceId: Uint8Array;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── ASN.1 DER helpers ─────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/** The high (0x80) bit of a byte -- the DER long-form length marker, the OID base-128 continuation bit, and the DER INTEGER sign bit all key off this same bit. */
|
|
62
|
+
const HIGH_BIT = 0x80;
|
|
63
|
+
/** Mask for the low 7 bits of a byte -- the payload bits in a DER long-form length prefix and in a base-128 OID subidentifier byte. */
|
|
64
|
+
const LOW_7_BITS_MASK = 0x7f;
|
|
65
|
+
/** Bits in a byte, used to split a DER length into its high and low bytes. */
|
|
66
|
+
const BITS_PER_BYTE = 8;
|
|
67
|
+
/** Mask for a single byte. */
|
|
68
|
+
const BYTE_MASK = 0xff;
|
|
69
|
+
/** Smallest length that needs a second DER long-form length byte (i.e. no longer fits in one byte). */
|
|
70
|
+
const DER_LENGTH_TWO_BYTE_MIN = 0x100;
|
|
71
|
+
/** DER long-form length prefix: one length byte follows. */
|
|
72
|
+
const DER_LENGTH_PREFIX_1_BYTE = 0x81;
|
|
73
|
+
/** DER long-form length prefix: two length bytes follow. */
|
|
74
|
+
const DER_LENGTH_PREFIX_2_BYTES = 0x82;
|
|
75
|
+
/** ASN.1 universal SEQUENCE tag. */
|
|
76
|
+
const DER_TAG_SEQUENCE = 0x30;
|
|
77
|
+
/** ASN.1 universal SET tag. */
|
|
78
|
+
const DER_TAG_SET = 0x31;
|
|
79
|
+
|
|
80
|
+
/** Encode a DER length field. */
|
|
81
|
+
function derLength(length: number): Buffer {
|
|
82
|
+
if (length < HIGH_BIT) return Buffer.from([length]);
|
|
83
|
+
if (length < DER_LENGTH_TWO_BYTE_MIN)
|
|
84
|
+
return Buffer.from([DER_LENGTH_PREFIX_1_BYTE, length]);
|
|
85
|
+
return Buffer.from([
|
|
86
|
+
DER_LENGTH_PREFIX_2_BYTES,
|
|
87
|
+
(length >> BITS_PER_BYTE) & BYTE_MASK,
|
|
88
|
+
length & BYTE_MASK,
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Wrap content bytes in a DER tag. */
|
|
93
|
+
function derWrap(tag: number, content: Buffer): Buffer {
|
|
94
|
+
return Buffer.concat([
|
|
95
|
+
Buffer.from([tag]),
|
|
96
|
+
derLength(content.length),
|
|
97
|
+
content,
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** DER SEQUENCE. */
|
|
102
|
+
function derSequence(...items: readonly Buffer[]): Buffer {
|
|
103
|
+
return derWrap(DER_TAG_SEQUENCE, Buffer.concat(items));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** DER SET. */
|
|
107
|
+
function derSet(...items: readonly Buffer[]): Buffer {
|
|
108
|
+
return derWrap(DER_TAG_SET, Buffer.concat(items));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** X.690 rule for encoding an OID's first two arcs into a single byte: 40 * first-arc + second-arc. */
|
|
112
|
+
const OID_FIRST_ARC_MULTIPLIER = 40;
|
|
113
|
+
/** Bits carried per byte in an OID subidentifier's base-128 (7-bit group) varint encoding. */
|
|
114
|
+
const BITS_PER_OID_GROUP = 7;
|
|
115
|
+
/** ASN.1 universal OBJECT IDENTIFIER tag. */
|
|
116
|
+
const DER_TAG_OID = 0x06;
|
|
117
|
+
/** ASN.1 universal UTF8String tag. */
|
|
118
|
+
const DER_TAG_UTF8_STRING = 0x0c;
|
|
119
|
+
/** ASN.1 universal INTEGER tag. */
|
|
120
|
+
const DER_TAG_INTEGER = 0x02;
|
|
121
|
+
/** Leading zero byte prepended to a DER INTEGER whose first content byte has its sign bit set, so it is not misread as negative. */
|
|
122
|
+
const DER_INTEGER_PADDING_BYTE = 0x00;
|
|
123
|
+
/** ASN.1 universal BIT STRING tag. */
|
|
124
|
+
const DER_TAG_BIT_STRING = 0x03;
|
|
125
|
+
/** BIT STRING unused-bits prefix: this codec never leaves trailing unused bits. */
|
|
126
|
+
const DER_BIT_STRING_NO_UNUSED_BITS = 0x00;
|
|
127
|
+
/** ASN.1 universal OCTET STRING tag. */
|
|
128
|
+
const DER_TAG_OCTET_STRING = 0x04;
|
|
129
|
+
/** ASN.1 universal BOOLEAN tag. */
|
|
130
|
+
const DER_TAG_BOOLEAN = 0x01;
|
|
131
|
+
/** DER encodes a BOOLEAN true as an all-ones byte (X.690 §8.2.2 in DER mode). */
|
|
132
|
+
const DER_BOOLEAN_TRUE_BYTE = 0xff;
|
|
133
|
+
/** DER encodes a BOOLEAN false as an all-zeros byte. */
|
|
134
|
+
const DER_BOOLEAN_FALSE_BYTE = 0x00;
|
|
135
|
+
/** ASN.1 universal UTCTime tag. */
|
|
136
|
+
const DER_TAG_UTCTIME = 0x17;
|
|
137
|
+
/** UTCTime encodes the year as two digits, so the full year is taken modulo this. */
|
|
138
|
+
const UTCTIME_YEAR_MODULUS = 100;
|
|
139
|
+
|
|
140
|
+
/** DER OBJECT IDENTIFIER from dotted-decimal string. */
|
|
141
|
+
function derOID(oid: string): Buffer {
|
|
142
|
+
const parts = oid.split(".").map(Number);
|
|
143
|
+
const first = parts[0];
|
|
144
|
+
const second = parts[1];
|
|
145
|
+
if (first === undefined || second === undefined) {
|
|
146
|
+
throw new Error(`Invalid OID: ${oid}`);
|
|
147
|
+
}
|
|
148
|
+
const bytes: number[] = [OID_FIRST_ARC_MULTIPLIER * first + second];
|
|
149
|
+
for (let i = 2; i < parts.length; i++) {
|
|
150
|
+
let value = parts[i];
|
|
151
|
+
if (value === undefined) continue;
|
|
152
|
+
if (value < HIGH_BIT) {
|
|
153
|
+
bytes.push(value);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const encoded: number[] = [];
|
|
157
|
+
encoded.push(value & LOW_7_BITS_MASK);
|
|
158
|
+
value >>= BITS_PER_OID_GROUP;
|
|
159
|
+
while (value > 0) {
|
|
160
|
+
encoded.push(HIGH_BIT | (value & LOW_7_BITS_MASK));
|
|
161
|
+
value >>= BITS_PER_OID_GROUP;
|
|
162
|
+
}
|
|
163
|
+
bytes.push(...encoded.reverse());
|
|
164
|
+
}
|
|
165
|
+
return derWrap(DER_TAG_OID, Buffer.from(bytes));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** DER UTF8String. */
|
|
169
|
+
function derUTF8String(value: string): Buffer {
|
|
170
|
+
return derWrap(DER_TAG_UTF8_STRING, Buffer.from(value, "utf8"));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** DER INTEGER from a raw byte buffer (adds leading zero if high bit set). */
|
|
174
|
+
function derIntegerBytes(value: Buffer): Buffer {
|
|
175
|
+
const firstByte = value[0];
|
|
176
|
+
if (firstByte === undefined || firstByte & HIGH_BIT) {
|
|
177
|
+
return derWrap(
|
|
178
|
+
DER_TAG_INTEGER,
|
|
179
|
+
Buffer.concat([Buffer.from([DER_INTEGER_PADDING_BYTE]), value]),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return derWrap(DER_TAG_INTEGER, value);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** DER BIT STRING (with zero unused-bits prefix). */
|
|
186
|
+
function derBitString(content: Buffer): Buffer {
|
|
187
|
+
return derWrap(
|
|
188
|
+
DER_TAG_BIT_STRING,
|
|
189
|
+
Buffer.concat([Buffer.from([DER_BIT_STRING_NO_UNUSED_BITS]), content]),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** DER OCTET STRING. */
|
|
194
|
+
function derOctetString(content: Buffer): Buffer {
|
|
195
|
+
return derWrap(DER_TAG_OCTET_STRING, content);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** DER BOOLEAN. */
|
|
199
|
+
function derBoolean(value: boolean): Buffer {
|
|
200
|
+
return derWrap(
|
|
201
|
+
DER_TAG_BOOLEAN,
|
|
202
|
+
Buffer.from([value ? DER_BOOLEAN_TRUE_BYTE : DER_BOOLEAN_FALSE_BYTE]),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** DER UTCTime from a Date. Format: YYMMDDHHMMSSZ */
|
|
207
|
+
function derUTCTime(date: Readonly<Date>): Buffer {
|
|
208
|
+
const str = [
|
|
209
|
+
String(date.getUTCFullYear() % UTCTIME_YEAR_MODULUS).padStart(2, "0"),
|
|
210
|
+
String(date.getUTCMonth() + 1).padStart(2, "0"),
|
|
211
|
+
String(date.getUTCDate()).padStart(2, "0"),
|
|
212
|
+
String(date.getUTCHours()).padStart(2, "0"),
|
|
213
|
+
String(date.getUTCMinutes()).padStart(2, "0"),
|
|
214
|
+
String(date.getUTCSeconds()).padStart(2, "0"),
|
|
215
|
+
"Z",
|
|
216
|
+
].join("");
|
|
217
|
+
return derWrap(DER_TAG_UTCTIME, Buffer.from(str, "ascii"));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── OID constants ──────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/** ECDSA with SHA-256 signature algorithm. */
|
|
223
|
+
const OID_ECDSA_WITH_SHA256 = "1.2.840.10045.4.3.2";
|
|
224
|
+
/** Common Name attribute. */
|
|
225
|
+
const OID_CN = "2.5.4.3";
|
|
226
|
+
/** Subject Key Identifier extension. */
|
|
227
|
+
const OID_SKI = "2.5.29.14";
|
|
228
|
+
/** Subject Alternative Name extension. */
|
|
229
|
+
const OID_SAN = "2.5.29.17";
|
|
230
|
+
/** Basic Constraints extension. */
|
|
231
|
+
const OID_BASIC_CONSTRAINTS = "2.5.29.19";
|
|
232
|
+
|
|
233
|
+
/** X.509 TBSCertificate `version` field's context-specific tag: `[0] EXPLICIT`. */
|
|
234
|
+
const CONTEXT_TAG_VERSION = 0xa0;
|
|
235
|
+
/** X.509 TBSCertificate `extensions` field's context-specific tag: `[3] EXPLICIT`. */
|
|
236
|
+
const CONTEXT_TAG_EXTENSIONS = 0xa3;
|
|
237
|
+
/** SAN GeneralName `dNSName` choice's context-specific tag: `[2] IMPLICIT`. */
|
|
238
|
+
const SAN_TAG_DNS_NAME = 0x82;
|
|
239
|
+
/** SAN GeneralName `iPAddress` choice's context-specific tag: `[7] IMPLICIT`. */
|
|
240
|
+
const SAN_TAG_IP_ADDRESS = 0x87;
|
|
241
|
+
/** First octet of the IPv4 loopback address (127.0.0.1) encoded into the SAN `iPAddress` extension. */
|
|
242
|
+
const LOCALHOST_IPV4_FIRST_OCTET = 127;
|
|
243
|
+
/** Random bytes hashed to seed the certificate serial number. */
|
|
244
|
+
const SERIAL_SEED_BYTES = 16;
|
|
245
|
+
/** Certificate serial number length in bytes (RFC 5280 recommends no more than 20 octets). */
|
|
246
|
+
const SERIAL_LENGTH_BYTES = 20;
|
|
247
|
+
|
|
248
|
+
// ─── Certificate building ───────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Build a self-signed X.509 v3 certificate in DER format.
|
|
252
|
+
*
|
|
253
|
+
* Structure (RFC 5280 §4.1): `Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue }`
|
|
254
|
+
*/
|
|
255
|
+
function buildCertificateDer(
|
|
256
|
+
tbsCertificate: Buffer,
|
|
257
|
+
signatureAlgorithm: Buffer,
|
|
258
|
+
signature: Buffer,
|
|
259
|
+
): Buffer {
|
|
260
|
+
return derSequence(
|
|
261
|
+
tbsCertificate,
|
|
262
|
+
signatureAlgorithm,
|
|
263
|
+
derBitString(signature),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Build the TBSCertificate DER structure.
|
|
269
|
+
*
|
|
270
|
+
* Structure (RFC 5280 §4.1.2): `version [0] EXPLICIT INTEGER (v3 = 2), serialNumber INTEGER, signature AlgorithmIdentifier, issuer Name, validity { notBefore, notAfter }, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, extensions [3] EXPLICIT Extensions OPTIONAL`
|
|
271
|
+
*/
|
|
272
|
+
function buildTbsCertificate(options: {
|
|
273
|
+
serial: Buffer;
|
|
274
|
+
signatureAlgorithm: Buffer;
|
|
275
|
+
issuerSubject: Buffer;
|
|
276
|
+
validity: Buffer;
|
|
277
|
+
subjectPublicKeyInfoDer: Buffer;
|
|
278
|
+
extensions: Buffer;
|
|
279
|
+
}): Buffer {
|
|
280
|
+
const {
|
|
281
|
+
serial,
|
|
282
|
+
signatureAlgorithm,
|
|
283
|
+
issuerSubject,
|
|
284
|
+
validity,
|
|
285
|
+
subjectPublicKeyInfoDer,
|
|
286
|
+
extensions,
|
|
287
|
+
} = options;
|
|
288
|
+
// version: [0] EXPLICIT { INTEGER 2 } → a0 03 02 01 02
|
|
289
|
+
const version = derWrap(
|
|
290
|
+
CONTEXT_TAG_VERSION,
|
|
291
|
+
derIntegerBytes(Buffer.from([2])),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
return derSequence(
|
|
295
|
+
version,
|
|
296
|
+
derIntegerBytes(serial),
|
|
297
|
+
signatureAlgorithm,
|
|
298
|
+
issuerSubject, // issuer
|
|
299
|
+
validity,
|
|
300
|
+
issuerSubject, // subject (same as issuer for self-signed)
|
|
301
|
+
subjectPublicKeyInfoDer,
|
|
302
|
+
derWrap(CONTEXT_TAG_EXTENSIONS, derSequence(extensions)), // [3] EXPLICIT
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Build the X.509v3 extensions.
|
|
308
|
+
*
|
|
309
|
+
* Includes:
|
|
310
|
+
* - Subject Key Identifier (SHA-1 hash of the public key DER)
|
|
311
|
+
* - Subject Alternative Name (DNS:localhost, IP:127.0.0.1)
|
|
312
|
+
* - Basic Constraints (CA:FALSE, critical)
|
|
313
|
+
*/
|
|
314
|
+
function buildExtensions(publicKeyDer: Buffer): Buffer {
|
|
315
|
+
// Subject Key Identifier
|
|
316
|
+
const ski = createHash("sha1").update(publicKeyDer).digest();
|
|
317
|
+
const skiExtension = derSequence(
|
|
318
|
+
derOID(OID_SKI),
|
|
319
|
+
derOctetString(derOctetString(ski)),
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// Subject Alternative Name
|
|
323
|
+
const sanValue = derSequence(
|
|
324
|
+
derWrap(SAN_TAG_DNS_NAME, Buffer.from("localhost", "ascii")), // dNSName
|
|
325
|
+
derWrap(
|
|
326
|
+
SAN_TAG_IP_ADDRESS,
|
|
327
|
+
Buffer.from([LOCALHOST_IPV4_FIRST_OCTET, 0, 0, 1]),
|
|
328
|
+
), // iPAddress
|
|
329
|
+
);
|
|
330
|
+
const sanExtension = derSequence(derOID(OID_SAN), derOctetString(sanValue));
|
|
331
|
+
|
|
332
|
+
// Basic Constraints (CA:FALSE, critical)
|
|
333
|
+
const bcExtension = derSequence(
|
|
334
|
+
derOID(OID_BASIC_CONSTRAINTS),
|
|
335
|
+
derBoolean(true), // critical
|
|
336
|
+
derOctetString(derSequence()), // empty sequence = CA:FALSE
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
return Buffer.concat([skiExtension, sanExtension, bcExtension]);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Generate a fresh cryptographic identity: ECDSA P-256 keypair and self-signed X.509 certificate. The fingerprint of the certificate serves as the peer ID.
|
|
346
|
+
*
|
|
347
|
+
* Key material is generated in memory; bridges that need a stable identity across restarts persist it through identity-store.ts, which keeps the fingerprint (and therefore the peer/agent ID) stable.
|
|
348
|
+
*/
|
|
349
|
+
export function generateIdentity(): PeerIdentity {
|
|
350
|
+
// When encoding options are specified, generateKeyPairSync returns { publicKey: string, privateKey: string } in PEM format.
|
|
351
|
+
const keyPair = generateKeyPairSync("ec", {
|
|
352
|
+
namedCurve: "P-256",
|
|
353
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
354
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return certifyKeyPair(keyPair.privateKey);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Issue a fresh self-signed X.509 certificate for an already-existing EC private key, rather than generating a new key pair -- what a certificate renewal must call instead of `generateIdentity()`, since device-id is derived from the public key, not the certificate: reusing the same key pair across a re-sign is what keeps device-id stable across renewal.
|
|
362
|
+
*/
|
|
363
|
+
export function certifyKeyPair(privateKeyPem: string): PeerIdentity {
|
|
364
|
+
const publicKeyDer = createPublicKey(privateKeyPem).export({
|
|
365
|
+
type: "spki",
|
|
366
|
+
format: "der",
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Random serial number (SERIAL_LENGTH_BYTES bytes). Clear the high bit to ensure positive.
|
|
370
|
+
const serialBytes = createHash("sha256")
|
|
371
|
+
.update(randomBytes(SERIAL_SEED_BYTES))
|
|
372
|
+
.digest()
|
|
373
|
+
.subarray(0, SERIAL_LENGTH_BYTES);
|
|
374
|
+
const serial = Buffer.from(serialBytes);
|
|
375
|
+
const firstSerialByte = serial[0];
|
|
376
|
+
if (firstSerialByte !== undefined)
|
|
377
|
+
serial[0] = firstSerialByte & LOW_7_BITS_MASK;
|
|
378
|
+
// DER INTEGERs are minimally encoded: a leading zero byte is only legal when the following byte's high bit is set. Clearing the sign bit above can leave 0x00 here, which OpenSSL rejects as illegal padding when the certificate is loaded (tls.createServer then fails despite retries), so pin it to a minimal non-zero value.
|
|
379
|
+
if (serial[0] === 0) serial[0] = 1;
|
|
380
|
+
|
|
381
|
+
// Validity period: now through CERTIFICATE_VALIDITY_MS from now
|
|
382
|
+
const now = new Date();
|
|
383
|
+
const expires = new Date(now.getTime() + CERTIFICATE_VALIDITY_MS);
|
|
384
|
+
|
|
385
|
+
// Subject/Issuer Name: SEQUENCE { SET { SEQUENCE { OID, value } } }
|
|
386
|
+
const subject = derSequence(
|
|
387
|
+
derSet(derSequence(derOID(OID_CN), derUTF8String("agent-comms"))),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Signature algorithm: ecdsa-with-SHA256
|
|
391
|
+
const sigAlgSeq = derSequence(derOID(OID_ECDSA_WITH_SHA256));
|
|
392
|
+
|
|
393
|
+
// Validity
|
|
394
|
+
const validity = derSequence(derUTCTime(now), derUTCTime(expires));
|
|
395
|
+
|
|
396
|
+
// Extensions
|
|
397
|
+
const extensions = buildExtensions(publicKeyDer);
|
|
398
|
+
|
|
399
|
+
// TBSCertificate
|
|
400
|
+
const tbsCert = buildTbsCertificate({
|
|
401
|
+
serial,
|
|
402
|
+
signatureAlgorithm: sigAlgSeq,
|
|
403
|
+
issuerSubject: subject,
|
|
404
|
+
validity,
|
|
405
|
+
subjectPublicKeyInfoDer: publicKeyDer,
|
|
406
|
+
extensions,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Sign the TBSCertificate
|
|
410
|
+
const signer = createSign("SHA256");
|
|
411
|
+
signer.update(tbsCert);
|
|
412
|
+
const signature = signer.sign(privateKeyPem);
|
|
413
|
+
|
|
414
|
+
// Assemble the full certificate
|
|
415
|
+
const certDer = buildCertificateDer(tbsCert, sigAlgSeq, signature);
|
|
416
|
+
|
|
417
|
+
// PEM-encode
|
|
418
|
+
const certificate = derToCertificatePem(certDer);
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
privateKey: privateKeyPem,
|
|
422
|
+
certificate,
|
|
423
|
+
fingerprint: getCertificateFingerprint(certificate),
|
|
424
|
+
deviceId: deriveDeviceId(privateKeyPem),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Compute the SHA-256 fingerprint of a PEM-encoded certificate.
|
|
430
|
+
* Returns hex-encoded with colon separators (standard format): "AB:CD:EF:..."
|
|
431
|
+
*/
|
|
432
|
+
export function getCertificateFingerprint(certificate: string): string {
|
|
433
|
+
const der = pemToDer(certificate);
|
|
434
|
+
return fingerprintDer(der);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Compute the SHA-256 fingerprint of a certificate already presented as raw DER bytes — the shape `tls.TLSSocket.getPeerCertificate().raw` returns for a live connection. Same hashing and formatting as `getCertificateFingerprint`, so a value pinned from a PEM certificate compares equal to the fingerprint of that same certificate presented live over a socket.
|
|
439
|
+
*/
|
|
440
|
+
export function fingerprintDer(der: Buffer): string {
|
|
441
|
+
const hex = createHash("sha256").update(der).digest("hex").toUpperCase();
|
|
442
|
+
const matched = hex.match(/.{2}/g);
|
|
443
|
+
if (matched === null) return "";
|
|
444
|
+
return matched.join(":");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
448
|
+
|
|
449
|
+
/** Decode a PEM-encoded certificate to raw DER bytes. */
|
|
450
|
+
function pemToDer(pem: string): Buffer {
|
|
451
|
+
const b64 = pem
|
|
452
|
+
.replace(/-----BEGIN CERTIFICATE-----/, "")
|
|
453
|
+
.replace(/-----END CERTIFICATE-----/, "")
|
|
454
|
+
.replace(/\s/g, "");
|
|
455
|
+
return Buffer.from(b64, "base64");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** The raw, uncompressed SEC1 public-key point (0x04 || X || Y) for a PEM-encoded EC private key -- what wire-mesh's own device-id and identity-key.public-key are both derived from, never a certificate's DER encoding. */
|
|
459
|
+
export function rawPublicKeyFromPrivateKey(privateKeyPem: string): Uint8Array {
|
|
460
|
+
const publicKey = createPublicKey(privateKeyPem);
|
|
461
|
+
const jwk = publicKey.export({ format: "jwk" });
|
|
462
|
+
if (jwk.x === undefined || jwk.y === undefined) {
|
|
463
|
+
throw new Error("expected an EC JWK with x/y coordinates");
|
|
464
|
+
}
|
|
465
|
+
return Buffer.concat([
|
|
466
|
+
Buffer.from([UNCOMPRESSED_POINT_TAG]),
|
|
467
|
+
Buffer.from(jwk.x, "base64url"),
|
|
468
|
+
Buffer.from(jwk.y, "base64url"),
|
|
469
|
+
]);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Derives wire-mesh's own device-id (SHA-256 of the raw, uncompressed SEC1 public-key point) from a PEM-encoded EC private key. */
|
|
473
|
+
export function deriveDeviceId(privateKeyPem: string): Uint8Array {
|
|
474
|
+
return createHash("sha256")
|
|
475
|
+
.update(rawPublicKeyFromPrivateKey(privateKeyPem))
|
|
476
|
+
.digest();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Encode DER bytes as a PEM certificate string. */
|
|
480
|
+
function derToCertificatePem(der: Buffer): string {
|
|
481
|
+
const b64 = der.toString("base64").match(/.{1,64}/g);
|
|
482
|
+
const lines = b64 ?? [];
|
|
483
|
+
return [
|
|
484
|
+
"-----BEGIN CERTIFICATE-----",
|
|
485
|
+
...lines,
|
|
486
|
+
"-----END CERTIFICATE-----",
|
|
487
|
+
"",
|
|
488
|
+
].join("\n");
|
|
489
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Comms — cross-harness LLM agent communication.
|
|
3
|
+
*
|
|
4
|
+
* Core protocol: types, store interface, tool handler.
|
|
5
|
+
* Bridges (pi extension, Claude Code channel) are in ../bridges/.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type { CommsStore, SendRoomMessageOptions } from "./comms-store.js";
|
|
9
|
+
export { FileStore, CommsError } from "./store.js";
|
|
10
|
+
export { MeshStore } from "./mesh-store.js";
|
|
11
|
+
export { CommsTool } from "./tool.js";
|
|
12
|
+
export { createMeshErrorReporter, formatMeshError } from "./error-reporting.js";
|
|
13
|
+
export {
|
|
14
|
+
installShutdownSignalHandlers,
|
|
15
|
+
type ShutdownDisposition,
|
|
16
|
+
type ShutdownSignalOptions,
|
|
17
|
+
} from "./shutdown-signals.js";
|
|
18
|
+
export {
|
|
19
|
+
createBridgeMesh,
|
|
20
|
+
createBridgeMeshSync,
|
|
21
|
+
createBridgeMeshFromIdentity,
|
|
22
|
+
createBridgeMeshSyncFromIdentity,
|
|
23
|
+
} from "./bridge-mesh.js";
|
|
24
|
+
export type {
|
|
25
|
+
BridgeMesh,
|
|
26
|
+
BridgeMeshOptions,
|
|
27
|
+
BridgeMeshSync,
|
|
28
|
+
BridgeRunOptions,
|
|
29
|
+
} from "./bridge-mesh.js";
|
|
30
|
+
export {
|
|
31
|
+
loadIdentityForFront,
|
|
32
|
+
probeSlotOwner,
|
|
33
|
+
releaseIdentityLock,
|
|
34
|
+
} from "./identity-store.js";
|
|
35
|
+
export type { IdentitySlot } from "./identity-store.js";
|
|
36
|
+
export type { CommsContext, CommsResult } from "./tool.js";
|
|
37
|
+
export {
|
|
38
|
+
buildAction,
|
|
39
|
+
formatDeliveryEvent,
|
|
40
|
+
extractStreamingBehavior,
|
|
41
|
+
isActionableEvent,
|
|
42
|
+
ensureRegistered,
|
|
43
|
+
ensureProjectRoom,
|
|
44
|
+
drainAndFormat,
|
|
45
|
+
MCP_TOOL_PARAMS,
|
|
46
|
+
} from "./bridge.js";
|
|
47
|
+
export type { RegistrationResult } from "./bridge.js";
|
|
48
|
+
export { generateVapidKeys } from "./vapid.js";
|
|
49
|
+
export type { VapidKeys } from "./vapid.js";
|
|
50
|
+
export { PushManager } from "./push-manager.js";
|
|
51
|
+
export type { PushSubscription, PushPayload } from "./web-push.js";
|
|
52
|
+
export { DiscoveryManager } from "./discovery.js";
|
|
53
|
+
export type {
|
|
54
|
+
DiscoveredMesh,
|
|
55
|
+
DiscoveryBackend,
|
|
56
|
+
AdvertiseOptions,
|
|
57
|
+
} from "./discovery.js";
|
|
58
|
+
export { MdnsDiscoveryBackend } from "./discovery-mdns.js";
|
|
59
|
+
export { TailscaleDiscoveryBackend } from "./discovery-tailscale.js";
|
|
60
|
+
export * from "./types.js";
|
|
61
|
+
export type {
|
|
62
|
+
ListenerInfo,
|
|
63
|
+
ListenerPolicy,
|
|
64
|
+
ConnectionHandle,
|
|
65
|
+
} from "./transport.js";
|