@agentproto/secrets 0.2.6 → 1.1.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/dist/{chunk-Q6K3AT3V.mjs → chunk-2FHFW2AU.mjs} +3 -3
- package/dist/{chunk-Q6K3AT3V.mjs.map → chunk-2FHFW2AU.mjs.map} +1 -1
- package/dist/chunk-3YRYO6LK.mjs +222 -0
- package/dist/chunk-3YRYO6LK.mjs.map +1 -0
- package/dist/chunk-432SC3XS.mjs +100 -0
- package/dist/chunk-432SC3XS.mjs.map +1 -0
- package/dist/chunk-5HUHYBPR.mjs +69 -0
- package/dist/chunk-5HUHYBPR.mjs.map +1 -0
- package/dist/{chunk-NLZ5HXGO.mjs → chunk-7V6U6DII.mjs} +4 -4
- package/dist/{chunk-NLZ5HXGO.mjs.map → chunk-7V6U6DII.mjs.map} +1 -1
- package/dist/chunk-L4LKX6JY.mjs +626 -0
- package/dist/chunk-L4LKX6JY.mjs.map +1 -0
- package/dist/chunk-NQX3KBPW.mjs +50 -0
- package/dist/chunk-NQX3KBPW.mjs.map +1 -0
- package/dist/chunk-PCFPJ47T.mjs +28 -0
- package/dist/chunk-PCFPJ47T.mjs.map +1 -0
- package/dist/cli.mjs +10 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/core-CIn0-z-Y.d.ts +42 -0
- package/dist/core-D8zVNPDb.d.ts +58 -0
- package/dist/identity/index.d.ts +25 -46
- package/dist/identity/index.mjs +76 -1
- package/dist/identity/index.mjs.map +1 -1
- package/dist/pairing/browser.d.ts +18 -0
- package/dist/pairing/browser.mjs +6 -0
- package/dist/pairing/browser.mjs.map +1 -0
- package/dist/pairing/index.d.ts +45 -440
- package/dist/pairing/index.mjs +33 -465
- package/dist/pairing/index.mjs.map +1 -1
- package/dist/provision/index.mjs +5 -2
- package/dist/provision/recipe/index.mjs +6 -3
- package/dist/seal/index.d.ts +21 -31
- package/dist/seal/index.mjs +4 -1
- package/dist/types-B2ZfEhsU.d.ts +57 -0
- package/dist/webcrypto-DEOV2iYE.d.ts +678 -0
- package/package.json +8 -2
- package/dist/chunk-2DL6W33G.mjs +0 -126
- package/dist/chunk-2DL6W33G.mjs.map +0 -1
- package/dist/chunk-MVHOJPML.mjs +0 -132
- package/dist/chunk-MVHOJPML.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pairing/derive.ts","../src/pairing/handshake.ts","../src/pairing/offer-url.ts","../src/pairing/tunnel-handshake.ts","../src/pairing/rendezvous.ts"],"names":["KEY_LEN","x25519PublicKey","transcriptHash","deriveDirectionKeys","isRecord","parseJson"],"mappings":"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","file":"chunk-L4LKX6JY.mjs","sourcesContent":["/**\n * Pairing-derived key material (design: DESIGN §3/§4).\n *\n * All derivations are HKDF-SHA256 over secret material the untrusted\n * rendezvous never sees, so it can't reproduce any of them:\n *\n * - **pair root** `K_pair = HKDF(session, \"pair-root\")`: the long-term shared\n * secret persisted by both sides (daemon `pairings.json`, client\n * `pair-credentials.json`). Everything a reconnect needs derives from it.\n * - **route / auth split** (pair/v2): every pairing secret — the offer secret\n * from the offer URL, and the pair root per epoch — yields TWO one-way\n * outputs under distinct labels:\n * - a **route token**, the ONLY value that goes on the broker upgrade URL\n * (`?side=…&t=<route>`). It is an opaque meeting-point name; it\n * authenticates nothing.\n * - an **auth token**, sent ONLY inside the sealed hello (opened by the\n * daemon's X25519 key alone) and checked by the daemon in constant time.\n * HKDF is one-way, so a broker that logs every route it ever sees still\n * can't compute an auth token. pair/v1 used a single value for both jobs,\n * which let the broker pair as a client (offer) or replay a reconnect.\n *\n * Labels (salt ‖ info), all HKDF-SHA256:\n *\n * | output | IKM | salt | info | len |\n * | ----------------- | ---------------------- | --------------------------- | ------------------------------ | --- |\n * | pair root | sorted session keys | transcript hash | `agentproto/pair-root` | 32 |\n * | offer route | UTF-8 of offer secret | `agentproto/pair-offer` | `agentproto/rv-route` | 16 |\n * | offer auth | UTF-8 of offer secret | `agentproto/pair-offer` | `agentproto/rv-auth` | 32 |\n * | epoch route `e` | pair root (raw bytes) | `agentproto/rv-route-salt` | `agentproto/rv-route` ‖ u64(e) | 16 |\n * | epoch auth `e` | pair root (raw bytes) | `agentproto/rv-auth-salt` | `agentproto/rv-auth` ‖ u64(e) | 32 |\n *\n * Tokens are base64url: a 16-byte route is 22 chars (the broker's width), a\n * 32-byte auth is 43. The epoch route is byte-identical to pair/v1's epoch\n * token on purpose: a legacy (v1) client still meets the daemon at the same\n * place, so the daemon can tell it to re-pair instead of leaving it to time out.\n * That leaks nothing new — the route was always public to the broker.\n *\n * ## Why the pair root is derived order-independently\n *\n * A `PairingSession` exposes `sendKey`/`recvKey`, which are **role-swapped**\n * between the two peers (the client's `sendKey` is the daemon's `recvKey`). To\n * get an identical root on both sides without threading a \"which side am I\"\n * flag, we sort the two keys byte-wise before mixing them: the *set* {sendKey,\n * recvKey} is identical on both sides, so the sorted concatenation — and thus\n * the HKDF output — is identical. Both keys are secret ECDH-derived material the\n * rendezvous never sees, so the root (and every epoch token) stays secret.\n *\n * Every derivation is async (WebCrypto HKDF is) and takes an optional trailing\n * `crypto` provider.\n *\n * ## The pair root as a non-extractable key\n *\n * The epoch derivations accept the pair root either as base64 or as a\n * **non-extractable** WebCrypto HKDF `CryptoKey` (`importPairRootKey`). A\n * browser client can keep that key in IndexedDB, and script can derive tokens\n * with it but never read the root back out. The salts, infos and lengths are\n * the same either way, so the tokens are byte-identical.\n */\n\nimport {\n base64Decode,\n base64Encode,\n base64UrlEncode,\n compareBytes,\n concatBytes,\n u64be,\n utf8Encode,\n} from \"../crypto/bytes.js\"\nimport type { CryptoProvider } from \"../crypto/types.js\"\nimport { webCryptoProvider } from \"../crypto/webcrypto.js\"\nimport type { PairingSession } from \"./handshake.js\"\n\nconst PAIR_ROOT_INFO = \"agentproto/pair-root\"\nconst RV_ROUTE_INFO = \"agentproto/rv-route\"\nconst RV_AUTH_INFO = \"agentproto/rv-auth\"\n/** Fixed salts for the epoch HKDFs — the pair root is the (secret) IKM, the\n * epoch rides in `info`, so each salt is a constant domain tag. */\nconst RV_ROUTE_SALT = \"agentproto/rv-route-salt\"\nconst RV_AUTH_SALT = \"agentproto/rv-auth-salt\"\n/** Salt for both offer derivations (the offer secret is the IKM). */\nconst OFFER_SALT = \"agentproto/pair-offer\"\n\n/** Length of a route token, in bytes (→ 22 base64url chars, the width the\n * broker has always carried). */\nconst ROUTE_TOKEN_LEN = 16\n/** Length of an auth token, in bytes (→ 43 base64url chars). Never on a URL. */\nconst AUTH_TOKEN_LEN = 32\n/** Length of the pair root, in bytes. */\nconst PAIR_ROOT_LEN = 32\n\nconst MS_PER_DAY = 86_400_000\n\n/**\n * Derive the long-term pair root from a completed handshake session. Returns\n * standard base64 (persisted in `pairings.json` / `credentials.json`). Both\n * peers, despite role-swapped direction keys, produce the identical root.\n */\nexport async function derivePairRoot(\n session: Pick<PairingSession, \"sendKey\" | \"recvKey\" | \"transcriptHash\">,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<string> {\n const k1 = session.sendKey\n const k2 = session.recvKey\n // Order-independent: sort the two keys so client and daemon mix them the same\n // way regardless of which is \"send\" for them.\n const [lo, hi] = compareBytes(k1, k2) <= 0 ? [k1, k2] : [k2, k1]\n const ikm = concatBytes(lo, hi)\n const okm = await crypto.hkdfSha256(ikm, session.transcriptHash, utf8Encode(PAIR_ROOT_INFO), PAIR_ROOT_LEN)\n return base64Encode(okm)\n}\n\n/** The current pairing epoch — the UTC day number. Injectable `now` (ms) for\n * tests; defaults to the wall clock. */\nexport function currentEpoch(now: number = Date.now()): number {\n return Math.floor(now / MS_PER_DAY)\n}\n\n/** A route token (broker URL only) and its paired auth token (sealed hello\n * only), both base64url. */\nexport interface RouteAuthTokens {\n route: string\n auth: string\n}\n\n/**\n * Import a base64 pair root as a **non-extractable** WebCrypto HKDF key (see\n * \"The pair root as a non-extractable key\" above). IndexedDB stores it by\n * structured clone, so it stays non-extractable at rest.\n */\nexport async function importPairRootKey(pairRoot: string): Promise<CryptoKey> {\n return globalThis.crypto.subtle.importKey(\n \"raw\",\n Uint8Array.from(base64Decode(pairRoot)),\n \"HKDF\",\n false,\n [\"deriveBits\"],\n )\n}\n\n/** HKDF-SHA256 with the pair root as IKM, in either form. A `CryptoKey` only\n * exists in WebCrypto, so it always derives there (`crypto` is then unused). */\nasync function pairRootHkdf(\n pairRoot: string | CryptoKey,\n salt: Uint8Array,\n info: Uint8Array,\n length: number,\n crypto: CryptoProvider,\n): Promise<Uint8Array> {\n if (typeof pairRoot === \"string\") {\n return crypto.hkdfSha256(base64Decode(pairRoot), salt, info, length)\n }\n const bits = await globalThis.crypto.subtle.deriveBits(\n { name: \"HKDF\", hash: \"SHA-256\", salt: Uint8Array.from(salt), info: Uint8Array.from(info) },\n pairRoot,\n length * 8,\n )\n return new Uint8Array(bits)\n}\n\n/** 8-byte big-endian epoch appended to an info label, so a bit-flip in the\n * epoch can never collide two epochs' tokens. */\nfunction epochInfo(label: string, epoch: number): Uint8Array {\n return concatBytes(utf8Encode(label), u64be(epoch))\n}\n\n/**\n * Derive the rendezvous ROUTE token for a pairing at a given epoch:\n * `HKDF(pairRoot, salt \"agentproto/rv-route-salt\", \"agentproto/rv-route\" ‖ epoch)`.\n * base64url, so it drops straight into a `?t=` upgrade param. This is the only\n * reconnect value the broker sees; it does not authenticate. Deterministic —\n * both sides derive the same token for the same `(pairRoot, epoch)`.\n */\nexport async function deriveEpochRoutingToken(\n pairRoot: string | CryptoKey,\n epoch: number,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<string> {\n const okm = await pairRootHkdf(\n pairRoot,\n utf8Encode(RV_ROUTE_SALT),\n epochInfo(RV_ROUTE_INFO, epoch),\n ROUTE_TOKEN_LEN,\n crypto,\n )\n return base64UrlEncode(okm)\n}\n\n/**\n * Derive the reconnect AUTH token for a pairing at a given epoch:\n * `HKDF(pairRoot, salt \"agentproto/rv-auth-salt\", \"agentproto/rv-auth\" ‖ epoch)`.\n * Carried only inside the sealed hello; the daemon compares it in constant\n * time. Never put it on a URL.\n */\nexport async function deriveEpochAuthToken(\n pairRoot: string | CryptoKey,\n epoch: number,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<string> {\n const okm = await pairRootHkdf(\n pairRoot,\n utf8Encode(RV_AUTH_SALT),\n epochInfo(RV_AUTH_INFO, epoch),\n AUTH_TOKEN_LEN,\n crypto,\n )\n return base64UrlEncode(okm)\n}\n\n/** Route + auth tokens for a pairing at one epoch. */\nexport async function deriveEpochTokens(\n pairRoot: string | CryptoKey,\n epoch: number,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<RouteAuthTokens> {\n return {\n route: await deriveEpochRoutingToken(pairRoot, epoch, crypto),\n auth: await deriveEpochAuthToken(pairRoot, epoch, crypto),\n }\n}\n\n/**\n * Derive the route + auth tokens for a pairing offer from its secret (the\n * offer URL's `s`): the daemon parks on — and the client dials — `route`; the\n * client seals `auth` into its hello. The secret itself never leaves the offer\n * URL.\n */\nexport async function deriveOfferTokens(\n offerSecret: string,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<RouteAuthTokens> {\n const ikm = utf8Encode(offerSecret)\n const salt = utf8Encode(OFFER_SALT)\n const route = await crypto.hkdfSha256(ikm, salt, utf8Encode(RV_ROUTE_INFO), ROUTE_TOKEN_LEN)\n const auth = await crypto.hkdfSha256(ikm, salt, utf8Encode(RV_AUTH_INFO), AUTH_TOKEN_LEN)\n return { route: base64UrlEncode(route), auth: base64UrlEncode(auth) }\n}\n\n/**\n * The route + auth tokens a peer should accept/dial to bridge clock skew\n * around a day boundary: the current epoch and the previous one (design:\n * PLAN \"accept current and previous epoch\"). The daemon parks on both routes\n * so a client whose clock sits on either side of midnight still finds it; the\n * client likewise tries both when reconnecting.\n */\nexport async function epochRoutingTokens(\n pairRoot: string | CryptoKey,\n now: number = Date.now(),\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<({ epoch: number } & RouteAuthTokens)[]> {\n const epoch = currentEpoch(now)\n return [\n { epoch, ...(await deriveEpochTokens(pairRoot, epoch, crypto)) },\n { epoch: epoch - 1, ...(await deriveEpochTokens(pairRoot, epoch - 1, crypto)) },\n ]\n}\n","/**\n * @agentproto/secrets/pairing — the `pair/v2` session handshake\n * (design: DESIGN §4).\n *\n * A Noise-flavoured, two-message handshake that lets a client and a daemon —\n * connected only through an untrusted rendezvous that byte-splices their\n * sockets — derive per-direction AEAD keys such that the rendezvous can neither\n * read nor forge the session:\n *\n * ```\n * client → daemon: e_pub // ephemeral X25519\n * ct₀ = Seal(to = daemon_x25519,\n * {clientPub: e_pub, clientName, auth})\n * daemon → client: d_e_pub, sig = Ed25519(daemon_ed25519,\n * transcript = sha256(e_pub ‖ ct₀ ‖ d_e_pub))\n * both: K = HKDF-SHA256(ECDH(e, d_e) ‖ ECDH(e, daemon_x25519),\n * salt = transcript, info = \"agentproto/pair/v2\")\n * → K_c2d ‖ K_d2c (two AES-256-GCM keys)\n * ```\n *\n * Why this shape:\n * - The client learned the daemon's static public keys out-of-band (the offer\n * URL / QR). Verifying `sig` against the offer's Ed25519 key proves the peer\n * is the daemon the human scanned, not a rendezvous impersonating it — MITM\n * protection without a CA.\n * - The daemon proves the client is authorised by opening `ct₀` (only the\n * daemon's X25519 private key can) and checking the sealed `auth` token in\n * constant time. `auth` is derived from the pairing secret under a label\n * distinct from the broker ROUTE token (./derive.ts), so the one value the\n * broker sees in clear — the route on the upgrade URL — never authenticates.\n * pair/v1 sealed the route itself as the proof, which let a broker that\n * knew the route (all of them) seal its own hello and pair; v1 is retired\n * (see `respondToLegacyHandshake`).\n * - `sig` covers the whole transcript and the transcript salts the key\n * schedule, so any tampering with `e_pub`, `ct₀`, or `d_e_pub` in flight\n * makes either the signature or the derived keys disagree — the session\n * fails closed, never continuing with attacker-chosen material.\n *\n * The primitives come from a `CryptoProvider` (node:crypto or WebCrypto), so this\n * one implementation runs in Node and in a browser; every entry point that does\n * crypto is async and takes an optional trailing `crypto` argument.\n *\n * This module is deliberately **transport-agnostic**: it produces and consumes\n * plain messages (`encode*`/`decode*` give byte arrays). The code that pumps\n * those bytes over a `FrameSink` lives in `@agentproto/acp/tunnel`, which stays\n * free of any dependency on this package — it receives only the derived keys.\n * All crypto stays here so the acp layer never touches key material beyond the\n * two symmetric session keys.\n */\n\nimport {\n base64Decode,\n base64Encode,\n concatBytes,\n utf8Decode,\n utf8Encode,\n} from \"../crypto/bytes.js\"\nimport type { CryptoProvider } from \"../crypto/types.js\"\nimport { webCryptoProvider } from \"../crypto/webcrypto.js\"\nimport { seal, unseal, SealError } from \"../seal/core.js\"\nimport {\n identityFingerprint,\n signTranscript,\n verifyTranscript,\n type DaemonIdentity,\n} from \"../identity/core.js\"\n\n/** Wire version of the handshake. Bumped if the message shape or key schedule\n * changes; both sides refuse a version they don't recognise. v2: the sealed\n * hello carries a dedicated auth token, never the broker route (./derive.ts). */\nexport const PAIR_VERSION = 2 as const\n\n/** The retired pair/v1 wire version. Recognised only to answer it with a\n * re-pair notice (`respondToLegacyHandshake`); never served. */\nexport const LEGACY_PAIR_VERSION = 1 as const\n\n/** What a peer is told when it speaks the retired protocol: pairings made\n * under pair/v1 can't be upgraded in place, so the fix is a fresh pairing. */\nexport const PAIRING_PROTOCOL_OUTDATED_MESSAGE =\n \"this pairing uses the retired pair/v1 protocol, which let the rendezvous broker \" +\n \"authenticate as a client. Upgrade agentproto on both machines and re-pair: run \" +\n \"`agentproto pair offer` on the daemon, then `agentproto pair accept` on the client\"\n\n/** HKDF `info` for a version's key schedule — domain-separates it from every\n * other HKDF use in the codebase, and v2 session keys from v1's. */\nfunction hkdfInfo(version: number): Uint8Array {\n return utf8Encode(`agentproto/pair/v${version}`)\n}\n\n/** Length of each direction key: AES-256 → 32 bytes, two of them → 64. */\nconst KEY_LEN = 32\n\n/** Stable, machine-readable failure codes. Every rejection maps to one of\n * these so callers (and tests) branch on a code, not a message string. */\nexport type PairingErrorCode =\n | \"malformed_hello\"\n | \"malformed_reply\"\n | \"invalid_key\"\n | \"unseal_failed\"\n | \"ephemeral_mismatch\"\n | \"offer_rejected\"\n | \"bad_signature\"\n // P2: offer-URL codec (offer-url.ts) rejection vectors.\n | \"malformed_offer\"\n | \"offer_expired\"\n // The peer (or an offer URL / stored pairing) speaks the retired pair/v1\n // protocol. Actionable: re-pair with `agentproto pair offer`.\n | \"pairing_protocol_outdated\"\n\n/** Raised for every handshake failure. Never carries key material or\n * plaintext; the `code` is the contract, the message is for humans. */\nexport class PairingError extends Error {\n readonly code: PairingErrorCode\n constructor(code: PairingErrorCode, message: string) {\n super(message)\n this.name = \"PairingError\"\n this.code = code\n }\n}\n\n/** Client → daemon. `ePub` is the client ephemeral X25519 public key (base64\n * SPKI DER); `ct0` is the sealed hello payload (a `seal()` envelope string). */\nexport interface PairingHello {\n v: typeof PAIR_VERSION\n ePub: string\n ct0: string\n}\n\n/** Daemon → client. `dePub` is the daemon ephemeral X25519 public key (base64\n * SPKI DER); `sig` is the Ed25519 transcript signature (base64). */\nexport interface PairingReply {\n v: typeof PAIR_VERSION\n dePub: string\n sig: string\n}\n\n/** The sealed hello payload, opened only by the daemon. */\ninterface HelloPayload {\n /** Echo of the client ephemeral public key — bound inside the seal so a\n * rendezvous can't swap `ePub` without breaking the seal. */\n clientPub: string\n /** Human-facing client label the daemon shows on `pair accept`. */\n clientName: string\n /** The auth token (./derive.ts: offer auth, or the epoch auth on a\n * reconnect) — the daemon's proof the client holds the pairing secret.\n * Distinct from, and not derivable from, the route the broker sees. */\n auth: string\n}\n\n/**\n * The result of a completed handshake, on either side. `sendKey`/`recvKey` are\n * already role-adjusted (a client's `sendKey` is a daemon's `recvKey`), so the\n * consumer — `wrapE2E` — never has to know which side it is.\n */\nexport interface PairingSession {\n /** AES-256-GCM key for frames THIS side sends. 32 bytes. */\n sendKey: Uint8Array\n /** AES-256-GCM key for frames THIS side receives. 32 bytes. */\n recvKey: Uint8Array\n /** Fingerprint of the peer's static identity (the daemon's X25519 key on the\n * client side; in P1, the client's ephemeral key on the daemon side, since\n * clients have no persisted identity until P2's pairings store). */\n peerFingerprint: string\n /** `sha256(e_pub ‖ ct₀ ‖ d_e_pub)` — the exact transcript both sides bound\n * to. A later phase channel-binds reconnect tokens to this. */\n transcriptHash: Uint8Array\n /**\n * The human-facing client label carried in the sealed hello. On the daemon\n * side this is the name the peer chose (surfaced in `pairings.json` and on\n * `pair accept`); on the client side it echoes the name this side supplied.\n * Optional so pre-P2 callers constructing a session literal are unaffected —\n * P2 (pairing-registry) reads it to name a persisted pairing without opening\n * the seal a second time. Populated by both handshake entry points below.\n */\n clientName?: string\n}\n\n// ─── key helpers ────────────────────────────────────────────────\n\n/** Decode a base64 SPKI X25519 public key, rejecting anything the provider\n * can't import with `invalid_key`. */\nasync function x25519PublicKey(crypto: CryptoProvider, b64Der: string, what: string): Promise<Uint8Array> {\n const der = base64Decode(b64Der)\n try {\n await crypto.x25519ValidatePublicKey(der)\n } catch {\n throw new PairingError(\"invalid_key\", `invalid ${what} X25519 public key`)\n }\n return der\n}\n\n/**\n * Compute the transcript hash from the raw wire bytes of each message part.\n * Using the wire bytes (not a re-export of the parsed key) guarantees both\n * sides hash identical bytes regardless of any DER canonicalisation.\n */\nfunction transcriptHash(\n crypto: CryptoProvider,\n ePubB64: string,\n ct0: string,\n dePubB64: string,\n): Promise<Uint8Array> {\n return crypto.sha256(concatBytes(base64Decode(ePubB64), utf8Encode(ct0), base64Decode(dePubB64)))\n}\n\n/** Derive the two direction keys from the two ECDH outputs, salted by the\n * transcript. Concatenation order (ECDH(e,d_e) then ECDH(e,static)) and the\n * key split (c2d then d2c) are identical on both sides. */\nasync function deriveDirectionKeys(\n crypto: CryptoProvider,\n version: number,\n ecdhEphemeral: Uint8Array,\n ecdhStatic: Uint8Array,\n transcript: Uint8Array,\n): Promise<{ kc2d: Uint8Array; kd2c: Uint8Array }> {\n const ikm = concatBytes(ecdhEphemeral, ecdhStatic)\n const okm = await crypto.hkdfSha256(ikm, transcript, hkdfInfo(version), KEY_LEN * 2)\n return {\n kc2d: okm.subarray(0, KEY_LEN),\n kd2c: okm.subarray(KEY_LEN, KEY_LEN * 2),\n }\n}\n\n// ─── client side ────────────────────────────────────────────────\n\n/** Everything the client learned from the offer URL, plus its chosen name. */\nexport interface ClientHandshakeParams {\n /** Daemon static X25519 public key (base64 SPKI DER) — the seal recipient\n * and one ECDH input. From the offer's `pk`. */\n daemonX25519Pub: string\n /** Daemon static Ed25519 public key (base64 SPKI DER) — verifies `sig`.\n * From the offer's `sk`. */\n daemonEd25519Pub: string\n /** Auth token sealed into the hello: `deriveOfferTokens(offer.secret).auth`\n * on first contact, `deriveEpochAuthToken(pairRoot, epoch)` on reconnect.\n * NEVER the route token the broker sees. */\n authToken: string\n /** Human-facing client label the daemon displays on accept. */\n clientName: string\n}\n\n/** A started client handshake: send `hello`, then feed the daemon's reply to\n * `complete` to derive the session. */\nexport interface StartedClientHandshake {\n hello: PairingHello\n /** Verify the daemon reply and derive the session. Rejects with\n * `PairingError` on a bad signature, malformed reply, or invalid key —\n * never resolves partial state. */\n complete(reply: PairingReply): Promise<PairingSession>\n}\n\n/**\n * Begin a client handshake. Generates the client ephemeral keypair, seals the\n * hello payload to the daemon's static key, and resolves to the `hello` to send\n * plus a `complete` to run once the daemon replies.\n *\n * `crypto` selects the primitive implementation (default: WebCrypto here,\n * `node:crypto` through the `@agentproto/secrets/pairing` Node entry). The\n * provider is captured for `complete` as well.\n */\nexport async function startClientHandshake(\n params: ClientHandshakeParams,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<StartedClientHandshake> {\n // Validate the daemon keys up front so a bad offer fails before we transmit.\n const daemonStatic = await x25519PublicKey(crypto, params.daemonX25519Pub, \"daemon\")\n\n const ephemeral = await crypto.x25519GenerateKeyPair()\n const ePubB64 = base64Encode(ephemeral.publicKey)\n\n const payload: HelloPayload = {\n clientPub: ePubB64,\n clientName: params.clientName,\n auth: params.authToken,\n }\n const ct0 = await seal(JSON.stringify(payload), params.daemonX25519Pub, crypto)\n const hello: PairingHello = { v: PAIR_VERSION, ePub: ePubB64, ct0 }\n\n const complete = async (reply: PairingReply): Promise<PairingSession> => {\n if (reply.v !== PAIR_VERSION) {\n throw new PairingError(\"malformed_reply\", `unsupported reply version ${reply.v}`)\n }\n const dePub = await x25519PublicKey(crypto, reply.dePub, \"daemon ephemeral\")\n const transcript = await transcriptHash(crypto, ePubB64, ct0, reply.dePub)\n\n if (!(await verifyTranscript(params.daemonEd25519Pub, transcript, reply.sig, crypto))) {\n throw new PairingError(\n \"bad_signature\",\n \"daemon transcript signature did not verify against the offered key\"\n )\n }\n\n const ecdhEphemeral = await crypto.x25519(ephemeral.privateKey, dePub)\n const ecdhStatic = await crypto.x25519(ephemeral.privateKey, daemonStatic)\n const { kc2d, kd2c } = await deriveDirectionKeys(crypto, PAIR_VERSION, ecdhEphemeral, ecdhStatic, transcript)\n\n return {\n sendKey: kc2d,\n recvKey: kd2c,\n peerFingerprint: await identityFingerprint(params.daemonX25519Pub, crypto),\n transcriptHash: transcript,\n clientName: params.clientName,\n }\n }\n\n return { hello, complete }\n}\n\n// ─── daemon side ────────────────────────────────────────────────\n\n/** What the daemon brings to the handshake: its identity, and a predicate that\n * validates (and, for an offer, spends) the sealed auth token. */\nexport interface DaemonHandshakeParams {\n identity: DaemonIdentity\n /**\n * Validate the presented auth token (constant time). Returning false\n * rejects the handshake with `offer_rejected`. The daemon owns single-use +\n * expiry policy here so this module never needs to know about the offer\n * store — a stale, spent, or wrong token simply returns false. May be async.\n */\n verifyAuthToken: (token: string) => boolean | Promise<boolean>\n}\n\n/** A completed daemon handshake: send `reply`, keep `session`. */\nexport interface DaemonHandshakeResult {\n reply: PairingReply\n session: PairingSession\n}\n\n/**\n * Open a hello's sealed payload and check its ephemeral binding. Shared by the\n * v2 responder and the legacy-notice responder.\n */\nasync function openHello(\n crypto: CryptoProvider,\n ePub: string,\n ct0: string,\n identity: DaemonIdentity,\n): Promise<{ clientEphemeral: Uint8Array; payload: Record<string, unknown> }> {\n const clientEphemeral = await x25519PublicKey(crypto, ePub, \"client ephemeral\")\n\n let payloadJson: string\n try {\n payloadJson = await unseal(ct0, identity.x25519.priv, crypto)\n } catch (err) {\n if (err instanceof SealError) {\n throw new PairingError(\n \"unseal_failed\",\n \"could not open sealed hello — wrong daemon key or tampered ct₀\"\n )\n }\n throw err\n }\n let payload: unknown\n try {\n payload = JSON.parse(payloadJson)\n } catch {\n throw new PairingError(\"malformed_hello\", \"sealed hello payload was not valid JSON\")\n }\n if (!isRecord(payload) || typeof payload[\"clientPub\"] !== \"string\") {\n throw new PairingError(\"malformed_hello\", \"sealed hello payload is missing required fields\")\n }\n // The sealed payload echoes the client ephemeral key; it must match the\n // cleartext ePub, or a rendezvous swapped ePub while relaying the intact\n // (opaque) seal. Binds the ephemeral to the sealed proof.\n if (payload[\"clientPub\"] !== ePub) {\n throw new PairingError(\n \"ephemeral_mismatch\",\n \"sealed clientPub does not match the hello ephemeral key\"\n )\n }\n return { clientEphemeral, payload }\n}\n\n/** The daemon half of the key schedule: ephemeral, transcript signature, the\n * two ECDHs, and the role-adjusted direction keys. */\nasync function completeDaemonSide(\n crypto: CryptoProvider,\n version: number,\n identity: DaemonIdentity,\n ePub: string,\n ct0: string,\n clientEphemeral: Uint8Array,\n): Promise<{ dePub: string; sig: string; sendKey: Uint8Array; recvKey: Uint8Array; transcript: Uint8Array }> {\n const daemonEphemeral = await crypto.x25519GenerateKeyPair()\n const dePubB64 = base64Encode(daemonEphemeral.publicKey)\n\n const transcript = await transcriptHash(crypto, ePub, ct0, dePubB64)\n const sig = await signTranscript(identity.ed25519.priv, transcript, crypto)\n\n const daemonStaticPriv = base64Decode(identity.x25519.priv)\n const ecdhEphemeral = await crypto.x25519(daemonEphemeral.privateKey, clientEphemeral)\n let ecdhStatic: Uint8Array\n try {\n ecdhStatic = await crypto.x25519(daemonStaticPriv, clientEphemeral)\n } catch {\n throw new PairingError(\"invalid_key\", \"invalid daemon static X25519 private key\")\n }\n const { kc2d, kd2c } = await deriveDirectionKeys(crypto, version, ecdhEphemeral, ecdhStatic, transcript)\n // Daemon sends on c2d's counterpart: it RECEIVES client→daemon (kc2d) and\n // SENDS daemon→client (kd2c).\n return { dePub: dePubB64, sig, sendKey: kd2c, recvKey: kc2d, transcript }\n}\n\n/**\n * Respond to a client hello. Opens the sealed payload with the daemon's X25519\n * private key, checks the ephemeral-key binding and the auth token, signs the\n * transcript, and derives the session. Rejects with `PairingError` on any\n * failure — a tampered `ct₀`, a swapped `ePub`, a rejected token — before\n * producing any reply, so a rejected client learns nothing and gets no session.\n */\nexport async function respondToHandshake(\n hello: PairingHello,\n params: DaemonHandshakeParams,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<DaemonHandshakeResult> {\n if (hello.v !== PAIR_VERSION) {\n throw new PairingError(\"malformed_hello\", `unsupported hello version ${hello.v}`)\n }\n const { clientEphemeral, payload: raw } = await openHello(crypto, hello.ePub, hello.ct0, params.identity)\n const payload = parseHelloPayload(raw)\n\n if (!(await params.verifyAuthToken(payload.auth))) {\n throw new PairingError(\"offer_rejected\", \"auth token was rejected (unknown, expired, spent, or wrong)\")\n }\n\n const d = await completeDaemonSide(crypto, PAIR_VERSION, params.identity, hello.ePub, hello.ct0, clientEphemeral)\n return {\n reply: { v: PAIR_VERSION, dePub: d.dePub, sig: d.sig },\n session: {\n sendKey: d.sendKey,\n recvKey: d.recvKey,\n peerFingerprint: await identityFingerprint(payload.clientPub, crypto),\n transcriptHash: d.transcript,\n clientName: payload.clientName,\n },\n }\n}\n\n/**\n * Answer a retired pair/v1 hello so its client can be TOLD to re-pair.\n *\n * An unmodified v1 client only surfaces what arrives inside a completed v1\n * channel — anything short of that (a closed socket, a WS close reason, which\n * its transport drops) reads as a timeout. So the daemon completes the v1 key\n * schedule (`agentproto/pair/v1`) and hands back the reply + keys; the caller\n * sends ONLY the outdated notice over them and closes.\n *\n * Deliberately performs NO authorisation: the v1 token proves nothing (it's\n * the broker-visible route), so it isn't even read. The result must never be\n * served — it is a one-way notice channel, and a party that gets one (the\n * broker included) gains nothing but that notice. Rejects with `PairingError`\n * for anything that isn't a well-formed v1 hello sealed to this daemon.\n */\nexport async function respondToLegacyHandshake(\n helloBytes: Uint8Array,\n identity: DaemonIdentity,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<{ reply: Uint8Array; keys: { sendKey: Uint8Array; recvKey: Uint8Array } }> {\n const parsed = parseJson(helloBytes, \"malformed_hello\")\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== LEGACY_PAIR_VERSION ||\n typeof parsed[\"ePub\"] !== \"string\" ||\n typeof parsed[\"ct0\"] !== \"string\"\n ) {\n throw new PairingError(\"malformed_hello\", \"not a pair/v1 hello\")\n }\n const ePub = parsed[\"ePub\"]\n const ct0 = parsed[\"ct0\"]\n const { clientEphemeral, payload } = await openHello(crypto, ePub, ct0, identity)\n // A v1 payload names its proof `offerToken`; a v2 payload relabelled v1 in\n // flight names it `auth` and is refused here, so a v2 hello can't be\n // steered onto this unauthenticated path.\n if (typeof payload[\"offerToken\"] !== \"string\") {\n throw new PairingError(\"malformed_hello\", \"not a pair/v1 hello payload\")\n }\n const d = await completeDaemonSide(crypto, LEGACY_PAIR_VERSION, identity, ePub, ct0, clientEphemeral)\n return {\n reply: utf8Encode(JSON.stringify({ v: LEGACY_PAIR_VERSION, dePub: d.dePub, sig: d.sig })),\n keys: { sendKey: d.sendKey, recvKey: d.recvKey },\n }\n}\n\n// ─── message (de)serialization ──────────────────────────────────\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null\n}\n\nfunction parseHelloPayload(parsed: Record<string, unknown>): HelloPayload {\n if (\n typeof parsed[\"clientPub\"] !== \"string\" ||\n typeof parsed[\"clientName\"] !== \"string\" ||\n typeof parsed[\"auth\"] !== \"string\"\n ) {\n throw new PairingError(\"malformed_hello\", \"sealed hello payload is missing required fields\")\n }\n return {\n clientPub: parsed[\"clientPub\"],\n clientName: parsed[\"clientName\"],\n auth: parsed[\"auth\"],\n }\n}\n\n/** Serialize a handshake message to bytes for transport. */\nexport function encodePairingMessage(message: PairingHello | PairingReply): Uint8Array {\n return utf8Encode(JSON.stringify(message))\n}\n\n/** Parse + validate a client hello from raw bytes. Truncated or malformed\n * input throws `PairingError(\"malformed_hello\")` — never a partial object. A\n * retired pair/v1 hello throws `PairingError(\"pairing_protocol_outdated\")` so\n * the daemon can answer it with `respondToLegacyHandshake`. */\nexport function decodePairingHello(bytes: Uint8Array): PairingHello {\n const parsed = parseJson(bytes, \"malformed_hello\")\n if (isRecord(parsed) && parsed[\"v\"] === LEGACY_PAIR_VERSION) {\n throw new PairingError(\"pairing_protocol_outdated\", PAIRING_PROTOCOL_OUTDATED_MESSAGE)\n }\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== PAIR_VERSION ||\n typeof parsed[\"ePub\"] !== \"string\" ||\n typeof parsed[\"ct0\"] !== \"string\"\n ) {\n throw new PairingError(\"malformed_hello\", \"hello message is malformed\")\n }\n return { v: PAIR_VERSION, ePub: parsed[\"ePub\"], ct0: parsed[\"ct0\"] }\n}\n\n/** Parse + validate a daemon reply from raw bytes. Truncated or malformed\n * input throws `PairingError(\"malformed_reply\")`. */\nexport function decodePairingReply(bytes: Uint8Array): PairingReply {\n const parsed = parseJson(bytes, \"malformed_reply\")\n if (isRecord(parsed) && parsed[\"v\"] === LEGACY_PAIR_VERSION) {\n throw new PairingError(\"pairing_protocol_outdated\", PAIRING_PROTOCOL_OUTDATED_MESSAGE)\n }\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== PAIR_VERSION ||\n typeof parsed[\"dePub\"] !== \"string\" ||\n typeof parsed[\"sig\"] !== \"string\"\n ) {\n throw new PairingError(\"malformed_reply\", \"reply message is malformed\")\n }\n return { v: PAIR_VERSION, dePub: parsed[\"dePub\"], sig: parsed[\"sig\"] }\n}\n\nfunction parseJson(bytes: Uint8Array, code: PairingErrorCode): unknown {\n try {\n return JSON.parse(utf8Decode(bytes))\n } catch {\n throw new PairingError(code, \"message was not valid JSON (truncated or corrupt)\")\n }\n}\n","/**\n * The pairing offer URL codec (design: DESIGN §2).\n *\n * `agentproto pair offer` prints a single URL (also renderable as a QR):\n *\n * ```\n * agentproto://pair?v=2\n * &rv=<rendezvous ws/wss url> // where both sides meet\n * &id=<fingerprint> // daemon identity fingerprint (32 hex)\n * &pk=<b64url x25519 SPKI DER> // daemon static encryption key\n * &sk=<b64url ed25519 SPKI DER> // daemon signing key\n * &s=<one-time offer secret> // derives the route + auth tokens\n * &exp=<unix seconds> // offer expiry\n * &scope=host // optional — see \"Offer scope\" below\n * ```\n *\n * The URL **is** the bootstrap secret. It carries the daemon's public keys, so\n * a client that scans it can pin the daemon and detect a man-in-the-middle\n * rendezvous (verifying the handshake signature against `sk`); and it carries a\n * one-time, short-TTL secret so a stranger who never saw the URL can't pair.\n * The secret itself never goes on the wire: both sides derive from it a ROUTE\n * token for the broker and an AUTH token for the sealed hello\n * (`deriveOfferTokens`, ./derive.ts), so the broker — which sees the route —\n * can't pair.\n *\n * v=1 offers (pair/v1) used their `t` token as both route and proof; they are\n * refused with `pairing_protocol_outdated`.\n *\n * This module is a **pure codec** — it validates structure and echoes bytes; it\n * performs no I/O and no network calls, so it is safe to run on either side\n * (the daemon builds it, the client parses it). It lives in `@agentproto/secrets`\n * beside the handshake so both sides share one authority on the format.\n *\n * ## The web form (phone QR)\n *\n * A phone camera opens `https://` links, not `agentproto://`. For a browser\n * client the same parameters ride in the **fragment** of a web URL:\n *\n * ```\n * https://<fingerprint>.agentproto.cloud/pair#v=2&rv=…&id=…&pk=…&sk=…&s=…&exp=…\n * ```\n *\n * i.e. the query string of the `agentproto://` URL, verbatim, after the `#`.\n * A fragment is never sent to a server (not in the request line, not in\n * `Referer`), so the page host never sees the token. `encodeOfferWebUrl` builds\n * it; `parseOfferUrl` accepts both forms and validates them identically.\n *\n * Key material travels **base64url** in the URL (no `+`/`/`/`=` to percent-\n * escape). The handshake, however, speaks standard base64 SPKI DER, so\n * `parseOfferUrl` returns `daemonX25519Pub`/`daemonEd25519Pub` already converted\n * back to standard base64 — feed them straight into `startClientHandshake`.\n *\n * ## Offer scope\n *\n * `scope` is an optional, purely additive param: absent (the default) is\n * today's plain \"remote-control\" offer, byte-identical to before this field\n * existed — `encodeOfferUrl` only emits `&scope=host` when the caller sets\n * `offer.scope === \"host\"`. A host-scoped offer is what lets the accepting\n * side register the offering daemon as a driveable HOST (`agentproto devices\n * add`), rather than just remote-controlling it as a client.\n *\n * `scope` rides in the plaintext query string — it is metadata, not part of\n * the sealed handshake (`handshake.ts` never sees it). A relay could tamper\n * it in transit; that changes nothing about actual technical capability (a\n * pair/v2 client and host offer are cryptographically identical in what they\n * grant), it can only mislead the ACCEPTING side's own bookkeeping about what\n * it thinks it registered. The daemon that MINTED the offer records its own\n * intended scope server-side (`PairingRegistry`'s `OfferEntry`/\n * `PairingRecord`) independently of anything the URL says by the time a\n * client presents it — that server-side record, never the URL text, is what\n * is authoritative for what the offering daemon believes it granted.\n */\n\nimport type { CryptoProvider } from \"../crypto/types.js\"\nimport { webCryptoProvider } from \"../crypto/webcrypto.js\"\nimport { identityFingerprint } from \"../identity/core.js\"\nimport { PairingError } from \"./handshake.js\"\n\n/** URL scheme + host for offer URLs. */\nexport const OFFER_URL_SCHEME = \"agentproto:\" as const\nexport const OFFER_URL_HOST = \"pair\" as const\n/** Offer-format version. Bumped if the param set changes. v2: `s` (a secret\n * that never goes on the wire) replaces v1's `t` (route-and-proof). */\nexport const OFFER_VERSION = 2 as const\n/** A single shared-origin page for the web form of an offer: every daemon's\n * pairing on one origin (AIP-59 §5.8 fallback, which exposes each pairing to\n * every other paired daemon's UI). Not the default: select it explicitly with\n * `pairing.pairPage` / `--pair-page`. */\nexport const PAIR_WEB_URL = \"https://cli.agentproto.sh/pair\" as const\n/** Placeholder for the daemon fingerprint in a pair-page template. Allowed in\n * the hostname only. */\nexport const PAIR_PAGE_FP_PLACEHOLDER = \"{fp}\" as const\n/**\n * The default pair page: one origin per daemon\n * (`<fingerprint>.agentproto.cloud`, AIP-59 §5.8), so each daemon's page,\n * service worker and stored credential are isolated from every other\n * pairing's by the browser's same-origin policy. Override it with\n * `pairing.pairPage` / `--pair-page` (a self-hosted page, or a plain URL).\n */\nexport const PAIR_WEB_URL_TEMPLATE_CLOUD = \"https://{fp}.agentproto.cloud/pair\" as const\n/** The pair page used when none is configured. */\nexport const DEFAULT_PAIR_PAGE = PAIR_WEB_URL_TEMPLATE_CLOUD\n\n/**\n * A parsed, structurally-valid pairing offer. `daemonX25519Pub` /\n * `daemonEd25519Pub` are standard-base64 SPKI DER (handshake-ready). `secret` is\n * the opaque one-time offer secret verbatim — derive the route + auth tokens\n * from it with `deriveOfferTokens`; never send it anywhere. `exp` is unix\n * **seconds**.\n */\nexport interface PairingOffer {\n v: typeof OFFER_VERSION\n /** Rendezvous endpoint both sides dial (ws:// or wss://). */\n rendezvousUrl: string\n /** Daemon identity fingerprint (32 lowercase hex) — must equal fingerprint(pk). */\n fingerprint: string\n /** Daemon static X25519 public key, standard base64 SPKI DER. */\n daemonX25519Pub: string\n /** Daemon static Ed25519 public key, standard base64 SPKI DER. */\n daemonEd25519Pub: string\n /** One-time offer secret — derives the broker route and the sealed auth\n * token (`deriveOfferTokens`). Never sent on its own. */\n secret: string\n /** Offer expiry, unix seconds. */\n exp: number\n /** Optional advisory scope (see \"Offer scope\" above). `\"host\"` marks an\n * offer minted with `agentproto pair offer --host`; absent/undefined is\n * the default plain remote-control offer. Plaintext, unauthenticated —\n * never treat it as authoritative on its own. */\n scope?: \"host\"\n}\n\n// ─── base64 ⇄ base64url ──────────────────────────────────────────\n\nfunction b64ToB64url(b64: string): string {\n return b64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\")\n}\n\nfunction b64urlToB64(b64url: string): string {\n const replaced = b64url.replace(/-/g, \"+\").replace(/_/g, \"/\")\n const pad = replaced.length % 4\n return pad === 0 ? replaced : replaced + \"=\".repeat(4 - pad)\n}\n\n/** True when `s` is a non-empty base64url string (the alphabet only). */\nfunction isB64url(s: string): boolean {\n return s.length > 0 && /^[A-Za-z0-9_-]+$/.test(s)\n}\n\n// ─── encode ──────────────────────────────────────────────────────\n\n/**\n * Build the offer URL from an offer. The public keys come in as standard\n * base64 (the shape the identity file + handshake use) and are emitted as\n * base64url. `secret` is emitted verbatim (callers mint it as base64url).\n */\nexport function encodeOfferUrl(offer: PairingOffer): string {\n const params = new URLSearchParams()\n params.set(\"v\", String(OFFER_VERSION))\n params.set(\"rv\", offer.rendezvousUrl)\n params.set(\"id\", offer.fingerprint)\n params.set(\"pk\", b64ToB64url(offer.daemonX25519Pub))\n params.set(\"sk\", b64ToB64url(offer.daemonEd25519Pub))\n params.set(\"s\", offer.secret)\n params.set(\"exp\", String(offer.exp))\n if (offer.scope === \"host\") params.set(\"scope\", \"host\")\n return `${OFFER_URL_SCHEME}//${OFFER_URL_HOST}?${params.toString()}`\n}\n\n/** One DNS label: 1–63 of [a-z0-9-], not starting or ending with `-`. */\nconst DNS_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/\n\nfunction badPage(message: string): PairingError {\n return new PairingError(\"malformed_offer\", `pair page: ${message}`)\n}\n\n/**\n * Resolve a pair-page setting for one daemon. `templateOrUrl` is either a plain\n * http(s) URL (returned unchanged) or a template with `{fp}` in its HOSTNAME,\n * e.g. `https://{fp}.agentproto.cloud/pair`, where `{fp}` becomes the daemon\n * identity `fingerprint` (lowercase hex, which must be a valid DNS label).\n * Throws `PairingError(\"malformed_offer\")` for a template with `{fp}` outside\n * the hostname (userinfo, port, path, query, fragment), for any other brace\n * left in the URL, for a non-http(s) URL, or for a URL with a fragment (the\n * offer goes there).\n */\nexport function resolvePairPageUrl(templateOrUrl: string, fingerprint: string): string {\n let resolved = templateOrUrl\n if (templateOrUrl.includes(PAIR_PAGE_FP_PLACEHOLDER)) {\n const m = /^(https?:\\/\\/)([^/?#]*)(.*)$/i.exec(templateOrUrl)\n if (!m) throw badPage(\"a {fp} template must be an http(s) URL\")\n const [, scheme, authority, rest] = m as unknown as [string, string, string, string]\n if (rest.includes(PAIR_PAGE_FP_PLACEHOLDER)) {\n throw badPage(\"{fp} is only allowed in the hostname\")\n }\n if (authority.includes(\"@\")) throw badPage(\"userinfo is not allowed in a pair page URL\")\n // Everything after the host's last \":\" is a port, unless it's inside an\n // IPv6 literal (no placeholder can be there anyway).\n const colon = authority.lastIndexOf(\":\")\n const port = colon >= 0 && !authority.includes(\"]\") ? authority.slice(colon) : \"\"\n if (port.includes(PAIR_PAGE_FP_PLACEHOLDER)) throw badPage(\"{fp} is only allowed in the hostname\")\n const fp = fingerprint.toLowerCase()\n if (!/^[0-9a-f]+$/.test(fp) || !DNS_LABEL.test(fp)) {\n throw badPage(`daemon fingerprint \"${fingerprint}\" is not a valid DNS label`)\n }\n resolved = `${scheme}${authority.split(PAIR_PAGE_FP_PLACEHOLDER).join(fp)}${rest}`\n }\n if (/[{}]/.test(resolved)) {\n throw badPage(\"unknown placeholder (only {fp}, in the hostname, is supported)\")\n }\n let page: URL\n try {\n page = new URL(resolved)\n } catch {\n throw badPage(\"not a valid URL\")\n }\n if ((page.protocol !== \"https:\" && page.protocol !== \"http:\") || page.hash !== \"\" || resolved.includes(\"#\")) {\n throw badPage(\"must be an http(s) URL without a fragment\")\n }\n return resolved\n}\n\n/**\n * The `host` (hostname[:port]) a pair page for `fingerprint` must be served\n * from under `templateOrUrl`. The page compares it with its own\n * `location.host` to refuse an offer meant for another daemon's origin.\n */\nexport function expectedPairHost(templateOrUrl: string, fingerprint: string): string {\n return new URL(resolvePairPageUrl(templateOrUrl, fingerprint)).host\n}\n\n/**\n * Re-wrap an `agentproto://pair?…` offer URL as its web form\n * `<pageUrl>#<query>` (see \"The web form\" above). The parameters are carried\n * byte-for-byte. `pageUrl` is a plain http(s) URL without a fragment, or a\n * `{fp}` template (see `resolvePairPageUrl`) filled in with the offer's daemon\n * fingerprint (`id`).\n */\nexport function encodeOfferWebUrl(offerUrl: string, pageUrl: string = DEFAULT_PAIR_PAGE): string {\n const q = offerUrl.indexOf(\"?\")\n if (!offerUrl.startsWith(`${OFFER_URL_SCHEME}//${OFFER_URL_HOST}?`) || q < 0) {\n throw new PairingError(\"malformed_offer\", `expected an ${OFFER_URL_SCHEME}//${OFFER_URL_HOST}?… offer URL`)\n }\n const query = offerUrl.slice(q + 1)\n const fingerprint = new URLSearchParams(query).get(\"id\") ?? \"\"\n if (pageUrl.includes(PAIR_PAGE_FP_PLACEHOLDER) && !fingerprint) {\n throw new PairingError(\"malformed_offer\", \"offer has no daemon fingerprint (`id`) for the {fp} pair page\")\n }\n return `${resolvePairPageUrl(pageUrl, fingerprint)}#${query}`\n}\n\n/** Map the web form (`http(s)://…#<query>`) onto the `agentproto://pair?`\n * form; anything else is returned unchanged for the strict parser below. */\nfunction fromWebForm(url: string): string {\n if (!/^https?:\\/\\//i.test(url)) return url\n const hash = url.indexOf(\"#\")\n if (hash < 0 || hash === url.length - 1) {\n throw new PairingError(\"malformed_offer\", \"web offer URL carries no offer in its fragment\")\n }\n return `${OFFER_URL_SCHEME}//${OFFER_URL_HOST}?${url.slice(hash + 1)}`\n}\n\n// ─── parse ───────────────────────────────────────────────────────\n\nexport interface ParseOfferOptions {\n /**\n * When set, the parser rejects an offer whose `exp` is at or before this\n * instant (unix **milliseconds**) with `PairingError(\"offer_expired\")`. Omit\n * to parse structure only and let the caller decide when to check expiry\n * (the daemon's offer store is the authoritative single-use + expiry gate).\n */\n now?: number\n}\n\n/**\n * Parse + strictly validate an offer URL. Rejects with `PairingError` — never\n * resolves a partial object — on any structural problem:\n *\n * - `pairing_protocol_outdated`: a v=1 (pair/v1) offer from an older daemon.\n * - `malformed_offer`: wrong scheme/host, unknown version, missing/blank\n * params, non-base64url keys/token, non-integer `exp`, or a `fingerprint`\n * that does not match `fingerprint(pk)` (tamper detection: a rendezvous or\n * link-mangler that swaps the daemon key can't keep `id` consistent).\n * - `offer_expired`: only when `opts.now` is supplied and `exp` has passed.\n *\n * Accepts the `agentproto://pair?…` form and the web form\n * (`https://…/pair#<query>`, see `encodeOfferWebUrl`).\n *\n * Async because the `id` ↔ `fingerprint(pk)` check hashes the key, and\n * WebCrypto's SHA-256 is async; `crypto` selects the provider.\n */\nexport async function parseOfferUrl(\n url: string,\n opts: ParseOfferOptions = {},\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<PairingOffer> {\n let parsed: URL\n try {\n parsed = new URL(fromWebForm(url))\n } catch {\n throw new PairingError(\"malformed_offer\", \"offer is not a valid URL\")\n }\n if (parsed.protocol !== OFFER_URL_SCHEME) {\n throw new PairingError(\n \"malformed_offer\",\n `offer scheme must be \"${OFFER_URL_SCHEME}//\" (got \"${parsed.protocol}//\")`,\n )\n }\n // `agentproto://pair?…` parses with host=\"pair\"; tolerate `agentproto:pair?…`\n // (host=\"\" pathname=\"pair\") too, since some QR scanners/relayers normalise the\n // authority away.\n const host = parsed.host || parsed.pathname.replace(/^\\/+/, \"\")\n if (host !== OFFER_URL_HOST) {\n throw new PairingError(\"malformed_offer\", `offer host must be \"${OFFER_URL_HOST}\"`)\n }\n\n const q = parsed.searchParams\n const v = q.get(\"v\")\n if (v === \"1\") {\n throw new PairingError(\n \"pairing_protocol_outdated\",\n \"this offer uses the retired pair/v1 protocol, which let the rendezvous broker \" +\n \"authenticate as a client. Upgrade agentproto on the daemon and mint a new \" +\n \"offer: run `agentproto pair offer`\",\n )\n }\n if (v !== String(OFFER_VERSION)) {\n throw new PairingError(\"malformed_offer\", `unsupported offer version \"${v ?? \"(absent)\"}\"`)\n }\n\n const rendezvousUrl = req(q, \"rv\")\n // The rendezvous must be a ws/wss URL; reject anything else early so a\n // tampered offer can't point the client at an arbitrary scheme.\n let rvParsed: URL\n try {\n rvParsed = new URL(rendezvousUrl)\n } catch {\n throw new PairingError(\"malformed_offer\", \"offer `rv` is not a valid URL\")\n }\n if (rvParsed.protocol !== \"ws:\" && rvParsed.protocol !== \"wss:\") {\n throw new PairingError(\"malformed_offer\", \"offer `rv` must be a ws:// or wss:// URL\")\n }\n\n const fingerprint = req(q, \"id\")\n if (!/^[0-9a-f]{32}$/.test(fingerprint)) {\n throw new PairingError(\"malformed_offer\", \"offer `id` is not a 32-hex fingerprint\")\n }\n\n const pkUrl = req(q, \"pk\")\n const skUrl = req(q, \"sk\")\n if (!isB64url(pkUrl) || !isB64url(skUrl)) {\n throw new PairingError(\"malformed_offer\", \"offer `pk`/`sk` must be base64url\")\n }\n const daemonX25519Pub = b64urlToB64(pkUrl)\n const daemonEd25519Pub = b64urlToB64(skUrl)\n\n const secret = req(q, \"s\")\n if (!isB64url(secret)) {\n throw new PairingError(\"malformed_offer\", \"offer `s` (secret) must be base64url\")\n }\n\n const expRaw = req(q, \"exp\")\n const exp = Number(expRaw)\n if (!Number.isInteger(exp) || exp <= 0) {\n throw new PairingError(\"malformed_offer\", \"offer `exp` is not a positive unix timestamp\")\n }\n\n // Integrity: `id` must be the fingerprint OF the offered X25519 key. This is\n // what turns the URL into a self-authenticating bootstrap secret — a party\n // that swaps `pk` for their own key can't also produce a matching `id` without\n // it being obviously a different fingerprint the human never saw.\n if ((await identityFingerprint(daemonX25519Pub, crypto)) !== fingerprint) {\n throw new PairingError(\n \"malformed_offer\",\n \"offer `id` does not match fingerprint(pk) — tampered or corrupt offer\",\n )\n }\n\n if (opts.now !== undefined && exp * 1000 <= opts.now) {\n throw new PairingError(\"offer_expired\", \"offer has expired\")\n }\n\n const scope = q.get(\"scope\") === \"host\" ? \"host\" : undefined\n\n return {\n v: OFFER_VERSION,\n rendezvousUrl,\n fingerprint,\n daemonX25519Pub,\n daemonEd25519Pub,\n secret,\n exp,\n ...(scope ? { scope } : {}),\n }\n}\n\nfunction req(q: URLSearchParams, key: string): string {\n const v = q.get(key)\n if (v === null || v === \"\") {\n throw new PairingError(\"malformed_offer\", `offer is missing required param \"${key}\"`)\n }\n return v\n}\n","/**\n * @agentproto/secrets/pairing — the `tunnel-e2e/v1` handshake.\n *\n * The reverse tunnel (`agentproto serve --connect <host>`) is a DIFFERENT trust\n * relationship from the client↔daemon pairing in ./handshake.ts:\n *\n * - There is no offer URL, no QR, no PKI. The daemon and the host already\n * share one pre-provisioned secret — the `apt_` **tunnel token** the daemon\n * presents as its WS bearer. Both ends hold it before the socket opens.\n * - So instead of authenticating with a static-key seal + Ed25519 signature,\n * both ends authenticate by proving knowledge of that shared token, and get\n * forward secrecy from a fresh ephemeral X25519 exchange.\n *\n * ```\n * daemon → host: offer = { ePub_d, mac_d = HMAC(K_auth, \"offer/v1\" ‖ ePub_d) }\n * host → daemon: accept = { ePub_h, mac_h = HMAC(K_auth, \"accept/v1\" ‖ ePub_d ‖ ePub_h) }\n * both: K_auth = HKDF(ikm = utf8(token),\n * salt = sha256(token), info = \"…/auth/v1\") // token-only MAC key\n * K = HKDF(ikm = ECDH(e_d, e_h),\n * salt = sha256(token), info = \"…/v1\") // → K_d2h ‖ K_h2d\n * ```\n *\n * Why this shape:\n * - **Mutual authentication at handshake time.** `mac_d` binds the daemon\n * ephemeral to the token; the host verifies it and refuses (`bad_auth`) if\n * the token differs — no partial session, no plaintext, before the daemon's\n * first byte. `mac_h` binds BOTH ephemerals to the token; the daemon\n * verifies it symmetrically. A man-in-the-middle without the token cannot\n * forge either MAC for its own ephemeral, and cannot reuse a recorded one\n * (it lacks the matching private key to finish the ECDH). So the confirm is\n * a **transcript/confirm step that fails a token mismatch AT handshake time,\n * not mid-stream** — exactly what a wrong `tunnel.token` on one side needs.\n * - **Forward secrecy.** The session keys mix ONLY the ephemeral ECDH output;\n * the long-term token merely salts them. A later token compromise can't\n * decrypt a recorded past session.\n * - **Salt-binds the token.** Because `sha256(token)` salts the session-key\n * HKDF too, even if the MACs were somehow bypassed the derived AEAD keys\n * still disagree under a mismatched token → the channel fails closed.\n *\n * Like ./handshake.ts, the primitives come from a `CryptoProvider` (node:crypto\n * or WebCrypto) and every crypto entry point is async with an optional trailing\n * `crypto` argument. Like ./handshake.ts, this module is also deliberately **transport-agnostic**: it\n * produces and consumes plain byte messages (`encode*`/`decode*`). The code that\n * pumps those bytes over a `FrameSink` and wraps the channel lives in\n * `@agentproto/acp/tunnel`, which never depends on this package — it receives\n * only the two derived symmetric keys. Everything crypto stays here.\n */\n\nimport {\n base64Decode,\n base64Encode,\n concatBytes,\n timingSafeEqualBytes,\n utf8Decode,\n utf8Encode,\n} from \"../crypto/bytes.js\"\nimport type { CryptoProvider } from \"../crypto/types.js\"\nimport { webCryptoProvider } from \"../crypto/webcrypto.js\"\n\n/** Wire version. Bumped if the message shape or key schedule changes; both\n * sides refuse a version they don't recognise. */\nexport const TUNNEL_E2E_VERSION = 1 as const\n\n/** HKDF `info` for the two direction session keys — domain-separates this key\n * schedule from every other HKDF use in the codebase. */\nconst SESSION_INFO = \"agentproto/tunnel-e2e/v1\"\n/** HKDF `info` for the token-only MAC key used to authenticate the handshake. */\nconst AUTH_INFO = \"agentproto/tunnel-e2e/auth/v1\"\n/** Label prefixes bound into each MAC so an offer MAC can never be replayed as\n * an accept MAC (and vice versa). */\nconst OFFER_MAC_LABEL = \"offer/v1\"\nconst ACCEPT_MAC_LABEL = \"accept/v1\"\n\n/** Length of each AES-256 direction key (bytes); two of them → 64. */\nconst KEY_LEN = 32\n/** Length of the token-only MAC key (bytes). */\nconst AUTH_KEY_LEN = 32\n\n/** Stable, machine-readable failure codes. Every rejection maps to one of these\n * so callers (and tests) branch on a code, not a message string. */\nexport type TunnelHandshakeErrorCode =\n | \"malformed_offer\"\n | \"malformed_accept\"\n | \"invalid_key\"\n | \"bad_auth\" // an HMAC did not verify — the two ends hold different tunnel tokens (or a MITM)\n | \"unsupported_version\"\n\n/** Raised for every tunnel-handshake failure. Never carries key material or the\n * token; the `code` is the contract, the message is for humans. */\nexport class TunnelHandshakeError extends Error {\n readonly code: TunnelHandshakeErrorCode\n constructor(code: TunnelHandshakeErrorCode, message: string) {\n super(message)\n this.name = \"TunnelHandshakeError\"\n this.code = code\n }\n}\n\n/** Daemon → host. `ePub` is the daemon ephemeral X25519 public key (base64 SPKI\n * DER); `mac` authenticates it under the shared tunnel token. */\nexport interface TunnelOffer {\n v: typeof TUNNEL_E2E_VERSION\n ePub: string\n mac: string\n}\n\n/** Host → daemon. `ePub` is the host ephemeral X25519 public key (base64 SPKI\n * DER); `mac` authenticates both ephemerals under the shared tunnel token. */\nexport interface TunnelAccept {\n v: typeof TUNNEL_E2E_VERSION\n ePub: string\n mac: string\n}\n\n/**\n * A completed tunnel handshake, on either side. `sendKey`/`recvKey` are already\n * role-adjusted (the daemon's `sendKey` is the host's `recvKey`), so the\n * consumer — `wrapE2E` — never has to know which side it is.\n */\nexport interface TunnelE2ESession {\n /** AES-256-GCM key for frames THIS side sends. 32 bytes. */\n sendKey: Uint8Array\n /** AES-256-GCM key for frames THIS side receives. 32 bytes. */\n recvKey: Uint8Array\n /** `sha256(SESSION_INFO ‖ ePub_d ‖ ePub_h)` — the exact transcript both sides\n * bound to. Exposed for parity with `PairingSession`; not required to use. */\n transcriptHash: Uint8Array\n}\n\n// ─── key + mac helpers ──────────────────────────────────────────\n\n/** Salt shared by both HKDF steps: the SHA-256 of the pre-shared tunnel token.\n * Binding it here is what authenticates the two ends — a peer without the\n * token derives a different salt, so both the MAC key and the session keys\n * disagree and the channel fails closed. */\nfunction tokenSalt(crypto: CryptoProvider, token: string): Promise<Uint8Array> {\n return crypto.sha256(utf8Encode(token))\n}\n\n/** The token-only MAC key. Derived solely from the token (no ephemeral), so\n * each side can compute it independently before/without the peer's ephemeral. */\nfunction deriveAuthKey(crypto: CryptoProvider, token: string, salt: Uint8Array): Promise<Uint8Array> {\n return crypto.hkdfSha256(utf8Encode(token), salt, utf8Encode(AUTH_INFO), AUTH_KEY_LEN)\n}\n\n/** HMAC-SHA256 over `label ‖ <raw parts>` under the token-only auth key. The\n * parts are the raw DER bytes of the ephemeral public keys, so both sides MAC\n * identical bytes regardless of any base64 re-encoding. */\nfunction computeMac(\n crypto: CryptoProvider,\n authKey: Uint8Array,\n label: string,\n ...parts: Uint8Array[]\n): Promise<Uint8Array> {\n return crypto.hmacSha256(authKey, concatBytes(utf8Encode(label), ...parts))\n}\n\n/** Constant-time compare of a received MAC (base64) against the expected MAC.\n * Length-guards first (inside `timingSafeEqualBytes`) so a truncated MAC is\n * simply a mismatch. */\nfunction macMatches(expected: Uint8Array, receivedB64: string): boolean {\n return timingSafeEqualBytes(base64Decode(receivedB64), expected)\n}\n\nasync function x25519PublicKey(crypto: CryptoProvider, b64Der: string): Promise<Uint8Array> {\n const der = base64Decode(b64Der)\n try {\n await crypto.x25519ValidatePublicKey(der)\n } catch {\n throw new TunnelHandshakeError(\"invalid_key\", \"peer ephemeral X25519 public key is invalid\")\n }\n return der\n}\n\n/** The transcript hash: `sha256(SESSION_INFO ‖ ePub_d_raw ‖ ePub_h_raw)`. */\nfunction transcriptHash(crypto: CryptoProvider, ePubDRaw: Uint8Array, ePubHRaw: Uint8Array): Promise<Uint8Array> {\n return crypto.sha256(concatBytes(utf8Encode(SESSION_INFO), ePubDRaw, ePubHRaw))\n}\n\n/** Derive the two direction keys from the ephemeral ECDH output, salted by the\n * token. The key split (d2h then h2d) is identical on both sides. */\nasync function deriveDirectionKeys(\n crypto: CryptoProvider,\n ecdh: Uint8Array,\n salt: Uint8Array,\n): Promise<{ kd2h: Uint8Array; kh2d: Uint8Array }> {\n const okm = await crypto.hkdfSha256(ecdh, salt, utf8Encode(SESSION_INFO), KEY_LEN * 2)\n return { kd2h: okm.subarray(0, KEY_LEN), kh2d: okm.subarray(KEY_LEN, KEY_LEN * 2) }\n}\n\n/** Generate an ephemeral X25519 keypair: the private PKCS#8, plus the public\n * half as base64 SPKI DER (the wire form) and raw DER bytes (the\n * MAC/transcript form). */\nasync function generateEphemeral(\n crypto: CryptoProvider,\n): Promise<{ priv: Uint8Array; pubB64: string; pubRaw: Uint8Array }> {\n const kp = await crypto.x25519GenerateKeyPair()\n return { priv: kp.privateKey, pubB64: base64Encode(kp.publicKey), pubRaw: kp.publicKey }\n}\n\n// ─── daemon (initiator) side ────────────────────────────────────\n\n/** A started daemon handshake: send `offer`, then feed the host's `accept` to\n * `complete` to derive the session. */\nexport interface StartedTunnelHandshake {\n offer: TunnelOffer\n /**\n * Verify the host accept and derive the session. Throws `TunnelHandshakeError`\n * on a bad MAC (`bad_auth` — the host holds a different token), a malformed\n * accept, or an invalid key — never returns partial state.\n */\n complete(accept: TunnelAccept): Promise<TunnelE2ESession>\n}\n\n/**\n * Begin the daemon (initiator) side. Generates the daemon ephemeral keypair,\n * MACs it under the token, and resolves to the `offer` to send plus a\n * `complete` to run once the host replies with its `accept`. `crypto` selects\n * the primitive implementation (captured for `complete` too).\n */\nexport async function startTunnelHandshake(\n token: string,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<StartedTunnelHandshake> {\n const salt = await tokenSalt(crypto, token)\n const authKey = await deriveAuthKey(crypto, token, salt)\n const eph = await generateEphemeral(crypto)\n\n const offer: TunnelOffer = {\n v: TUNNEL_E2E_VERSION,\n ePub: eph.pubB64,\n mac: base64Encode(await computeMac(crypto, authKey, OFFER_MAC_LABEL, eph.pubRaw)),\n }\n\n const complete = async (accept: TunnelAccept): Promise<TunnelE2ESession> => {\n if (accept.v !== TUNNEL_E2E_VERSION) {\n throw new TunnelHandshakeError(\n \"unsupported_version\",\n `unsupported accept version ${String(accept.v)}`,\n )\n }\n const hostPubRaw = await x25519PublicKey(crypto, accept.ePub)\n\n const expected = await computeMac(crypto, authKey, ACCEPT_MAC_LABEL, eph.pubRaw, hostPubRaw)\n if (!macMatches(expected, accept.mac)) {\n throw new TunnelHandshakeError(\n \"bad_auth\",\n \"host accept did not authenticate under the shared tunnel token\",\n )\n }\n\n const ecdh = await crypto.x25519(eph.priv, hostPubRaw)\n const { kd2h, kh2d } = await deriveDirectionKeys(crypto, ecdh, salt)\n return {\n // Daemon SENDS daemon→host (kd2h) and RECEIVES host→daemon (kh2d).\n sendKey: kd2h,\n recvKey: kh2d,\n transcriptHash: await transcriptHash(crypto, eph.pubRaw, hostPubRaw),\n }\n }\n\n return { offer, complete }\n}\n\n// ─── host (responder) side ──────────────────────────────────────\n\n/** A completed host handshake: send `accept`, keep `session`. */\nexport interface TunnelHandshakeResult {\n accept: TunnelAccept\n session: TunnelE2ESession\n}\n\n/**\n * Respond to a daemon offer (host side). Verifies the offer MAC under the shared\n * token, generates the host ephemeral, MACs both ephemerals, and derives the\n * session. Rejects with `TunnelHandshakeError` on a bad MAC (`bad_auth`),\n * malformed offer, or invalid key BEFORE producing any accept — so a mismatched\n * token yields no session and no reply, failing closed at handshake time.\n */\nexport async function respondToTunnelHandshake(\n offer: TunnelOffer,\n token: string,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<TunnelHandshakeResult> {\n if (offer.v !== TUNNEL_E2E_VERSION) {\n throw new TunnelHandshakeError(\n \"unsupported_version\",\n `unsupported offer version ${String(offer.v)}`,\n )\n }\n const salt = await tokenSalt(crypto, token)\n const authKey = await deriveAuthKey(crypto, token, salt)\n\n const daemonPubRaw = await x25519PublicKey(crypto, offer.ePub)\n\n const expected = await computeMac(crypto, authKey, OFFER_MAC_LABEL, daemonPubRaw)\n if (!macMatches(expected, offer.mac)) {\n throw new TunnelHandshakeError(\n \"bad_auth\",\n \"daemon offer did not authenticate under the shared tunnel token\",\n )\n }\n\n const eph = await generateEphemeral(crypto)\n const accept: TunnelAccept = {\n v: TUNNEL_E2E_VERSION,\n ePub: eph.pubB64,\n mac: base64Encode(await computeMac(crypto, authKey, ACCEPT_MAC_LABEL, daemonPubRaw, eph.pubRaw)),\n }\n\n const ecdh = await crypto.x25519(eph.priv, daemonPubRaw)\n const { kd2h, kh2d } = await deriveDirectionKeys(crypto, ecdh, salt)\n return {\n accept,\n session: {\n // Host SENDS host→daemon (kh2d) and RECEIVES daemon→host (kd2h).\n sendKey: kh2d,\n recvKey: kd2h,\n transcriptHash: await transcriptHash(crypto, daemonPubRaw, eph.pubRaw),\n },\n }\n}\n\n// ─── message (de)serialization ──────────────────────────────────\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null\n}\n\n/** Serialize a handshake message to bytes for transport. */\nexport function encodeTunnelMessage(message: TunnelOffer | TunnelAccept): Uint8Array {\n return utf8Encode(JSON.stringify(message))\n}\n\nfunction parseJson(bytes: Uint8Array, code: TunnelHandshakeErrorCode): unknown {\n try {\n return JSON.parse(utf8Decode(bytes))\n } catch {\n throw new TunnelHandshakeError(code, \"message was not valid JSON (truncated or corrupt)\")\n }\n}\n\n/** Parse + validate a daemon offer from raw bytes. Truncated or malformed input\n * throws `TunnelHandshakeError(\"malformed_offer\")` — never a partial object. */\nexport function decodeTunnelOffer(bytes: Uint8Array): TunnelOffer {\n const parsed = parseJson(bytes, \"malformed_offer\")\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== TUNNEL_E2E_VERSION ||\n typeof parsed[\"ePub\"] !== \"string\" ||\n typeof parsed[\"mac\"] !== \"string\"\n ) {\n throw new TunnelHandshakeError(\"malformed_offer\", \"offer message is malformed\")\n }\n return { v: TUNNEL_E2E_VERSION, ePub: parsed[\"ePub\"], mac: parsed[\"mac\"] }\n}\n\n/** Parse + validate a host accept from raw bytes. Truncated or malformed input\n * throws `TunnelHandshakeError(\"malformed_accept\")`. */\nexport function decodeTunnelAccept(bytes: Uint8Array): TunnelAccept {\n const parsed = parseJson(bytes, \"malformed_accept\")\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== TUNNEL_E2E_VERSION ||\n typeof parsed[\"ePub\"] !== \"string\" ||\n typeof parsed[\"mac\"] !== \"string\"\n ) {\n throw new TunnelHandshakeError(\"malformed_accept\", \"accept message is malformed\")\n }\n return { v: TUNNEL_E2E_VERSION, ePub: parsed[\"ePub\"], mac: parsed[\"mac\"] }\n}\n","/**\n * The hosted rendezvous broker — the meeting point `pair offer` defaults to\n * when neither `--rendezvous` nor `pairing.rendezvous` is configured.\n *\n * ## Why it lives here\n *\n * It sits in `@agentproto/secrets/pairing`, beside `offer-url.ts` — the codec\n * that bakes this endpoint into every offer's `rv=` param. The daemon runtime\n * already depends on `@agentproto/secrets`, so it reaches this constant without\n * `@agentproto/runtime` gaining a dependency on `@agentproto/rendezvous`. That\n * matters: the broker package is a deliberately lean, standalone container (its\n * only dep is `ws`), and nothing on the daemon/CLI side should have to pull it\n * in just to learn the default endpoint's URL.\n *\n * ## What the broker sees\n *\n * The hosted broker only ever relays **ciphertext** — it learns the route\n * token (opaque; it authenticates nothing — see ./derive.ts), the peers' IPs,\n * ciphertext sizes, and timing; never plaintext or an auth token, and it\n * cannot pair, inject, or alter frames (the pairing handshake is transcript-bound and\n * every frame is AEAD-sealed). See `docs/cli/concepts/pairing.md` for the full\n * threat model.\n *\n * ## Pointing elsewhere / self-hosting\n *\n * The broker is self-hostable (`agentproto rendezvous serve`). To route through\n * your own instead of the hosted default, set `pairing.rendezvous` in\n * `config.json` (or pass `--rendezvous` for a single offer). To disable the\n * default entirely — so a daemon never reaches the hosted broker unless an\n * endpoint is named explicitly — set `pairing.rendezvous: \"\"` (an explicit\n * opt-out; `pair offer` then requires `--rendezvous`).\n */\nexport const HOSTED_RENDEZVOUS_URL = \"wss://rdv.agentproto.sh/v1\" as const\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { webCryptoProvider, base64Encode, toHex, base64Decode } from './chunk-3YRYO6LK.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @agentproto/secrets v0.1.0-alpha
|
|
5
|
+
* AIP-19 SECRETS.md `defineSecrets` reference implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// src/identity/core.ts
|
|
9
|
+
var IDENTITY_VERSION = 1;
|
|
10
|
+
var IdentityError = class extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "IdentityError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
async function generateIdentity(crypto = webCryptoProvider) {
|
|
17
|
+
const [x, ed] = await Promise.all([crypto.x25519GenerateKeyPair(), crypto.ed25519GenerateKeyPair()]);
|
|
18
|
+
return {
|
|
19
|
+
v: IDENTITY_VERSION,
|
|
20
|
+
x25519: { pub: base64Encode(x.publicKey), priv: base64Encode(x.privateKey) },
|
|
21
|
+
ed25519: { pub: base64Encode(ed.publicKey), priv: base64Encode(ed.privateKey) },
|
|
22
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
var IDENTITY_FINGERPRINT_HEX_LENGTH = 32;
|
|
26
|
+
async function identityFingerprint(x25519Pub, crypto = webCryptoProvider) {
|
|
27
|
+
return toHex(await crypto.sha256(base64Decode(x25519Pub))).slice(0, IDENTITY_FINGERPRINT_HEX_LENGTH);
|
|
28
|
+
}
|
|
29
|
+
async function signTranscript(ed25519Priv, transcript, crypto = webCryptoProvider) {
|
|
30
|
+
let sig;
|
|
31
|
+
try {
|
|
32
|
+
sig = await crypto.ed25519Sign(base64Decode(ed25519Priv), transcript);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new IdentityError("invalid ed25519 private key");
|
|
35
|
+
}
|
|
36
|
+
return base64Encode(sig);
|
|
37
|
+
}
|
|
38
|
+
async function verifyTranscript(ed25519Pub, transcript, signature, crypto = webCryptoProvider) {
|
|
39
|
+
const pub = base64Decode(ed25519Pub);
|
|
40
|
+
try {
|
|
41
|
+
await crypto.ed25519ValidatePublicKey(pub);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new IdentityError("invalid ed25519 public key");
|
|
44
|
+
}
|
|
45
|
+
return crypto.ed25519Verify(pub, transcript, base64Decode(signature));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { IDENTITY_VERSION, IdentityError, generateIdentity, identityFingerprint, signTranscript, verifyTranscript };
|
|
49
|
+
//# sourceMappingURL=chunk-NQX3KBPW.mjs.map
|
|
50
|
+
//# sourceMappingURL=chunk-NQX3KBPW.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/identity/core.ts"],"names":[],"mappings":";;;;;;;;AAcO,IAAM,gBAAA,GAAmB;AAIzB,IAAM,aAAA,GAAN,cAA4B,KAAA,CAAM;AAAA,EACvC,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AAAA,EACd;AACF;AA6BA,eAAsB,gBAAA,CAAiB,SAAyB,iBAAA,EAA4C;AAC1G,EAAA,MAAM,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,CAAO,qBAAA,EAAsB,EAAG,MAAA,CAAO,sBAAA,EAAwB,CAAC,CAAA;AACnG,EAAA,OAAO;AAAA,IACL,CAAA,EAAG,gBAAA;AAAA,IACH,MAAA,EAAQ,EAAE,GAAA,EAAK,YAAA,CAAa,CAAA,CAAE,SAAS,CAAA,EAAG,IAAA,EAAM,YAAA,CAAa,CAAA,CAAE,UAAU,CAAA,EAAE;AAAA,IAC3E,OAAA,EAAS,EAAE,GAAA,EAAK,YAAA,CAAa,EAAA,CAAG,SAAS,CAAA,EAAG,IAAA,EAAM,YAAA,CAAa,EAAA,CAAG,UAAU,CAAA,EAAE;AAAA,IAC9E,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,GACpC;AACF;AAGO,IAAM,+BAAA,GAAkC,EAAA;AAQ/C,eAAsB,mBAAA,CAAoB,SAAA,EAAmB,MAAA,GAAyB,iBAAA,EAAoC;AACxH,EAAA,OAAO,KAAA,CAAM,MAAM,MAAA,CAAO,MAAA,CAAO,YAAA,CAAa,SAAS,CAAC,CAAC,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,+BAA+B,CAAA;AACrG;AAGA,eAAsB,cAAA,CACpB,WAAA,EACA,UAAA,EACA,MAAA,GAAyB,iBAAA,EACR;AACjB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,MAAM,MAAA,CAAO,WAAA,CAAY,YAAA,CAAa,WAAW,GAAG,UAAU,CAAA;AAAA,EACtE,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,cAAc,6BAA6B,CAAA;AAAA,EACvD;AACA,EAAA,OAAO,aAAa,GAAG,CAAA;AACzB;AAIA,eAAsB,gBAAA,CACpB,UAAA,EACA,UAAA,EACA,SAAA,EACA,SAAyB,iBAAA,EACP;AAClB,EAAA,MAAM,GAAA,GAAM,aAAa,UAAU,CAAA;AACnC,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,CAAO,yBAAyB,GAAG,CAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,cAAc,4BAA4B,CAAA;AAAA,EACtD;AACA,EAAA,OAAO,OAAO,aAAA,CAAc,GAAA,EAAK,UAAA,EAAY,YAAA,CAAa,SAAS,CAAC,CAAA;AACtE","file":"chunk-NQX3KBPW.mjs","sourcesContent":["/**\n * The daemon identity's crypto (mint, fingerprint, sign, verify), written once\n * against `CryptoProvider`. Browser-safe: no `node:` import, no `Buffer`. The\n * file-backed `loadOrCreateIdentity` stays in the Node entry (./index.ts).\n *\n * Every function takes an optional trailing `crypto` provider, defaulting to\n * WebCrypto here; the Node entry point (./index.ts) defaults to `node:crypto`.\n */\n\nimport { base64Decode, base64Encode, toHex } from \"../crypto/bytes.js\"\nimport type { CryptoProvider } from \"../crypto/types.js\"\nimport { webCryptoProvider } from \"../crypto/webcrypto.js\"\n\n/** Current identity-file schema version. */\nexport const IDENTITY_VERSION = 1 as const\n\n/** Raised for every identity load/generate/sign/verify failure. The message\n * is safe to surface — it never contains private key material. */\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"IdentityError\"\n }\n}\n\n/** One keypair, both halves base64-DER. Public = SPKI, private = PKCS8. */\nexport interface IdentityKeyPair {\n /** base64 DER (SPKI) public key — publishable. */\n pub: string\n /** base64 DER (PKCS8) private key — secret. */\n priv: string\n}\n\n/**\n * A daemon's persistent identity. Serialized verbatim to\n * `~/.agentproto/identity.json` (0600). The `x25519` public half is the key a\n * client seals its hello to; the `ed25519` public half is the key a client\n * verifies the daemon's transcript signature against. Both public halves are\n * carried in the offer URL.\n */\nexport interface DaemonIdentity {\n v: typeof IDENTITY_VERSION\n /** Encryption / key-agreement keypair. */\n x25519: IdentityKeyPair\n /** Signing / authenticity keypair. */\n ed25519: IdentityKeyPair\n /** ISO-8601 creation timestamp. */\n createdAt: string\n}\n\n/** Mint a fresh daemon identity: one X25519 keypair (encryption) and one\n * Ed25519 keypair (signing). */\nexport async function generateIdentity(crypto: CryptoProvider = webCryptoProvider): Promise<DaemonIdentity> {\n const [x, ed] = await Promise.all([crypto.x25519GenerateKeyPair(), crypto.ed25519GenerateKeyPair()])\n return {\n v: IDENTITY_VERSION,\n x25519: { pub: base64Encode(x.publicKey), priv: base64Encode(x.privateKey) },\n ed25519: { pub: base64Encode(ed.publicKey), priv: base64Encode(ed.privateKey) },\n createdAt: new Date().toISOString(),\n }\n}\n\n/** Hex length of an identity fingerprint: 128 bits. */\nexport const IDENTITY_FINGERPRINT_HEX_LENGTH = 32\n\n/** First 32 lowercase hex (128 bits) of `sha256(x25519 pub DER)`. Not a\n * secret. It names the daemon's browser origin\n * (`<fingerprint>.agentproto.cloud`, AIP-59 §5.8), so it must resist a\n * second-preimage grind: at 64 bits a well-resourced attacker could mint a\n * daemon key whose fingerprint lands on a victim daemon's origin. It is still\n * one DNS label (≤ 63 chars). */\nexport async function identityFingerprint(x25519Pub: string, crypto: CryptoProvider = webCryptoProvider): Promise<string> {\n return toHex(await crypto.sha256(base64Decode(x25519Pub))).slice(0, IDENTITY_FINGERPRINT_HEX_LENGTH)\n}\n\n/** Sign a handshake transcript with an Ed25519 private key → base64 signature. */\nexport async function signTranscript(\n ed25519Priv: string,\n transcript: Uint8Array,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<string> {\n let sig: Uint8Array\n try {\n sig = await crypto.ed25519Sign(base64Decode(ed25519Priv), transcript)\n } catch {\n throw new IdentityError(\"invalid ed25519 private key\")\n }\n return base64Encode(sig)\n}\n\n/** Verify a transcript signature. Resolves false on a bad signature; rejects\n * (IdentityError) only on a structurally invalid public key. */\nexport async function verifyTranscript(\n ed25519Pub: string,\n transcript: Uint8Array,\n signature: string,\n crypto: CryptoProvider = webCryptoProvider,\n): Promise<boolean> {\n const pub = base64Decode(ed25519Pub)\n try {\n await crypto.ed25519ValidatePublicKey(pub)\n } catch {\n throw new IdentityError(\"invalid ed25519 public key\")\n }\n return crypto.ed25519Verify(pub, transcript, base64Decode(signature))\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { nodeCryptoProvider } from './chunk-5HUHYBPR.mjs';
|
|
2
|
+
import { generateSealKeyPair, sealingPublicKey, sealKeyId, seal, unseal } from './chunk-432SC3XS.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @agentproto/secrets v0.1.0-alpha
|
|
6
|
+
* AIP-19 SECRETS.md `defineSecrets` reference implementation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// src/seal/index.ts
|
|
10
|
+
function generateSealKeyPair2(crypto = nodeCryptoProvider) {
|
|
11
|
+
return generateSealKeyPair(crypto);
|
|
12
|
+
}
|
|
13
|
+
function sealingPublicKey2(privateKey, crypto = nodeCryptoProvider) {
|
|
14
|
+
return sealingPublicKey(privateKey, crypto);
|
|
15
|
+
}
|
|
16
|
+
function sealKeyId2(publicKey, crypto = nodeCryptoProvider) {
|
|
17
|
+
return sealKeyId(publicKey, crypto);
|
|
18
|
+
}
|
|
19
|
+
function seal2(plaintext, recipientPublicKey, crypto = nodeCryptoProvider) {
|
|
20
|
+
return seal(plaintext, recipientPublicKey, crypto);
|
|
21
|
+
}
|
|
22
|
+
function unseal2(sealed, privateKey, crypto = nodeCryptoProvider) {
|
|
23
|
+
return unseal(sealed, privateKey, crypto);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { generateSealKeyPair2 as generateSealKeyPair, seal2 as seal, sealKeyId2 as sealKeyId, sealingPublicKey2 as sealingPublicKey, unseal2 as unseal };
|
|
27
|
+
//# sourceMappingURL=chunk-PCFPJ47T.mjs.map
|
|
28
|
+
//# sourceMappingURL=chunk-PCFPJ47T.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seal/index.ts"],"names":["generateSealKeyPair","sealingPublicKey","sealKeyId","seal","unseal"],"mappings":";;;;;;;;;AA0CO,SAASA,oBAAAA,CAAoB,SAAyB,kBAAA,EAA+C;AAC1G,EAAA,OAAY,oBAAoB,MAAM,CAAA;AACxC;AAOO,SAASC,iBAAAA,CAAiB,UAAA,EAAoB,MAAA,GAAyB,kBAAA,EAAqC;AACjH,EAAA,OAAY,gBAAA,CAAiB,YAAY,MAAM,CAAA;AACjD;AAOO,SAASC,UAAAA,CAAU,SAAA,EAAmB,MAAA,GAAyB,kBAAA,EAAqC;AACzG,EAAA,OAAY,SAAA,CAAU,WAAW,MAAM,CAAA;AACzC;AAOO,SAASC,KAAAA,CACd,SAAA,EACA,kBAAA,EACA,MAAA,GAAyB,kBAAA,EACR;AACjB,EAAA,OAAY,IAAA,CAAK,SAAA,EAAW,kBAAA,EAAoB,MAAM,CAAA;AACxD;AAQO,SAASC,OAAAA,CAAO,MAAA,EAAgB,UAAA,EAAoB,MAAA,GAAyB,kBAAA,EAAqC;AACvH,EAAA,OAAY,MAAA,CAAO,MAAA,EAAQ,UAAA,EAAY,MAAM,CAAA;AAC/C","file":"chunk-PCFPJ47T.mjs","sourcesContent":["/**\n * @agentproto/secrets/seal — anonymous public-key sealing for secret values.\n *\n * A \"sealed box\": encrypt a secret TO a recipient's public key such that only\n * the holder of the matching private key can open it. The sender needs only\n * the public key, and the sealed envelope reveals nothing about the sender.\n *\n * The point is custody. A secret value (a CLI subscription token, an API key)\n * can be sealed on the machine that holds it, then handed to ANY intermediary\n * — a relay, an agent, a log — as opaque ciphertext, because only the server's\n * private key can recover the plaintext. The plaintext never has to exist\n * anywhere but the origin machine and, transiently, inside the recipient's\n * unseal boundary.\n *\n * Construction (ECIES, no native dep):\n * - X25519 ephemeral key agreement with the recipient's public key\n * - HKDF-SHA256 over the shared secret, salted by both public keys\n * - AES-256-GCM AEAD for the payload (authenticated; tampering fails closed)\n *\n * This is the same shape as libsodium's `crypto_box_seal`. The construction\n * lives once in ./core.ts against a `CryptoProvider`; this Node entry defaults\n * to `node:crypto`, and `@agentproto/secrets/pairing/browser` re-exports the\n * same code over WebCrypto. Every function is async (WebCrypto is) and takes an\n * optional trailing `crypto` to override the provider.\n *\n * Sealing (confidentiality) is NOT signing (authenticity): this hides the\n * value, it does not prove who sent it. Bind the sender separately (e.g. an\n * authenticated transport) when provenance matters.\n */\n\nimport { nodeCryptoProvider } from \"../crypto/node.js\"\nimport type { CryptoProvider } from \"../crypto/types.js\"\nimport * as core from \"./core.js\"\n\nexport { SEAL_ALG, SEAL_VERSION, SealError, type SealKeyPair } from \"./core.js\"\nexport type { CryptoProvider } from \"../crypto/types.js\"\n\n/**\n * Mint a fresh sealing keypair. The recipient (e.g. a server) generates this\n * once, stores `privateKey` in its own secret store, and publishes\n * `publicKey` for senders to seal against.\n */\nexport function generateSealKeyPair(crypto: CryptoProvider = nodeCryptoProvider): Promise<core.SealKeyPair> {\n return core.generateSealKeyPair(crypto)\n}\n\n/**\n * Derive the publishable public key from a stored private key. Lets a\n * recipient hold only the private half (one secret) and serve the public\n * half on demand — the seal-key a sender needs.\n */\nexport function sealingPublicKey(privateKey: string, crypto: CryptoProvider = nodeCryptoProvider): Promise<string> {\n return core.sealingPublicKey(privateKey, crypto)\n}\n\n/**\n * Stable short identifier for a sealing key, derived from the public key.\n * Lets the seal-key endpoint and the sealed envelope name which key was used\n * so rotation is unambiguous. Not a secret.\n */\nexport function sealKeyId(publicKey: string, crypto: CryptoProvider = nodeCryptoProvider): Promise<string> {\n return core.sealKeyId(publicKey, crypto)\n}\n\n/**\n * Seal a plaintext value to a recipient's public key. Resolves to a single\n * base64 string (the envelope) that only the matching private key can open.\n * The sender needs nothing but the public key.\n */\nexport function seal(\n plaintext: string | Uint8Array,\n recipientPublicKey: string,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<string> {\n return core.seal(plaintext, recipientPublicKey, crypto)\n}\n\n/**\n * Open a sealed envelope with the recipient's private key, resolving to the\n * original UTF-8 plaintext. Rejects with `SealError` on a malformed envelope,\n * an unsupported version/alg, the wrong key, or any tampering (the AEAD tag\n * fails closed — a modified ciphertext never decrypts to garbage, it throws).\n */\nexport function unseal(sealed: string, privateKey: string, crypto: CryptoProvider = nodeCryptoProvider): Promise<string> {\n return core.unseal(sealed, privateKey, crypto)\n}\n"]}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { listRecipeIds, resolveRecipeMethod, resolveSourceSpec } from './chunk-
|
|
2
|
-
import { httpTarget, provisionSealed, resolveCredential } from './chunk-
|
|
3
|
-
import { unseal, seal, generateSealKeyPair, sealKeyId } from './chunk-
|
|
1
|
+
import { listRecipeIds, resolveRecipeMethod, resolveSourceSpec } from './chunk-2FHFW2AU.mjs';
|
|
2
|
+
import { httpTarget, provisionSealed, resolveCredential } from './chunk-7V6U6DII.mjs';
|
|
3
|
+
import { unseal, seal, generateSealKeyPair, sealKeyId } from './chunk-PCFPJ47T.mjs';
|
|
4
|
+
import './chunk-5HUHYBPR.mjs';
|
|
5
|
+
import './chunk-432SC3XS.mjs';
|
|
6
|
+
import './chunk-3YRYO6LK.mjs';
|
|
4
7
|
import { CredentialBroker, getAuthProvider, KeychainStore } from '@agentproto/auth';
|
|
5
8
|
|
|
6
9
|
/**
|
|
@@ -126,10 +129,10 @@ async function main() {
|
|
|
126
129
|
const args = parse(rest);
|
|
127
130
|
switch (cmd) {
|
|
128
131
|
case "keygen": {
|
|
129
|
-
const kp = generateSealKeyPair();
|
|
132
|
+
const kp = await generateSealKeyPair();
|
|
130
133
|
process.stdout.write(
|
|
131
134
|
JSON.stringify(
|
|
132
|
-
{ keyId: sealKeyId(kp.publicKey), publicKey: kp.publicKey, privateKey: kp.privateKey },
|
|
135
|
+
{ keyId: await sealKeyId(kp.publicKey), publicKey: kp.publicKey, privateKey: kp.privateKey },
|
|
133
136
|
null,
|
|
134
137
|
2
|
|
135
138
|
) + "\n"
|
|
@@ -140,14 +143,14 @@ async function main() {
|
|
|
140
143
|
const pubkey = args.flags.pubkey;
|
|
141
144
|
if (!pubkey) return fail("seal: --pubkey <b64> required");
|
|
142
145
|
const value = await readValue(args);
|
|
143
|
-
process.stdout.write(seal(value, pubkey));
|
|
146
|
+
process.stdout.write(await seal(value, pubkey));
|
|
144
147
|
return 0;
|
|
145
148
|
}
|
|
146
149
|
case "unseal": {
|
|
147
150
|
const privkey = args.flags.privkey;
|
|
148
151
|
if (!privkey) return fail("unseal: --privkey <b64> required");
|
|
149
152
|
const blob = args.positionals[0] ?? await resolveCredential(credentialSource(args));
|
|
150
|
-
process.stdout.write(unseal(blob, privkey));
|
|
153
|
+
process.stdout.write(await unseal(blob, privkey));
|
|
151
154
|
return 0;
|
|
152
155
|
}
|
|
153
156
|
case "provision": {
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"names":[],"mappings":"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","file":"cli.mjs","sourcesContent":["/**\n * agentproto-secrets — a small CLI over the seal primitive + provision flow.\n *\n * agentproto-secrets keygen mint a sealing keypair (JSON)\n * agentproto-secrets seal --pubkey <b64> [src] seal a value -> sealed blob\n * agentproto-secrets unseal --privkey <b64> [blob] open a sealed blob -> plaintext\n * agentproto-secrets provision --provider P --method M \\\n * --seal-key-url URL --install-url URL [--header 'K: V']... [src]\n * seal a local credential and\n * install it into a vault,\n * carrying only ciphertext\n *\n * credential source [src]: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n *\n * Vendor-neutral: `provision` takes the server's URLs + auth headers as flags.\n * The plaintext credential is read, sealed, and sent — it is never printed.\n * When `--header` is omitted and `--provider` names an auth provider\n * registered in `@agentproto/auth`, the Authorization header is instead\n * resolved via `CredentialBroker` (this is the only file in this package\n * that imports `@agentproto/auth` — see `ProvisionAuthDeps` below).\n */\n\nimport { CredentialBroker, KeychainStore, getAuthProvider } from \"@agentproto/auth\"\nimport {\n generateSealKeyPair,\n sealKeyId,\n seal,\n unseal,\n} from \"./seal/index.js\"\nimport {\n provisionSealed,\n httpTarget,\n resolveCredential,\n type CredentialSource,\n} from \"./provision/index.js\"\nimport {\n resolveRecipeMethod,\n resolveSourceSpec,\n listRecipeIds,\n} from \"./provision/recipe/index.js\"\n\ninterface Args {\n positionals: string[]\n flags: Record<string, string>\n headers: string[]\n}\n\nfunction parse(argv: string[]): Args {\n const positionals: string[] = []\n const flags: Record<string, string> = {}\n const headers: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!\n if (a.startsWith(\"--\")) {\n const eq = a.indexOf(\"=\")\n let key: string\n let val: string\n if (eq !== -1) {\n key = a.slice(2, eq)\n val = a.slice(eq + 1)\n } else {\n key = a.slice(2)\n const next = argv[i + 1]\n if (next !== undefined && !next.startsWith(\"--\")) {\n val = next\n i++\n } else {\n val = \"true\"\n }\n }\n if (key === \"header\") headers.push(val)\n else flags[key] = val\n } else {\n positionals.push(a)\n }\n }\n return { positionals, flags, headers }\n}\n\nfunction credentialSource(args: Args): CredentialSource {\n return {\n ...(args.flags[\"from-env\"] ? { fromEnv: args.flags[\"from-env\"] } : {}),\n ...(args.flags[\"from-file\"] ? { fromFile: args.flags[\"from-file\"] } : {}),\n ...(args.flags[\"json-path\"] ? { jsonPath: args.flags[\"json-path\"] } : {}),\n }\n}\n\n/** Read a value for seal: a positional, or a credential source. */\nasync function readValue(args: Args): Promise<string> {\n if (args.positionals[0]) return args.positionals[0]\n return resolveCredential(credentialSource(args))\n}\n\n/** Read one line from stdin (for a recipe's `prompt` credential source). */\nasync function promptStdin(prompt: string): Promise<string> {\n const { createInterface } = await import(\"node:readline/promises\")\n const rl = createInterface({ input: process.stdin, output: process.stderr })\n try {\n return (await rl.question(`${prompt}: `)).trim()\n } finally {\n rl.close()\n }\n}\n\n/**\n * Resolve the {methodId, credential, label} a provision installs. A recipe\n * (`--provider <id>`) supplies the auth-method id + where the credential lives,\n * so no source flags are needed; explicit `--from-file/--from-env` override the\n * recipe for ad-hoc / unknown providers (then `--method` is required).\n */\nasync function resolveProvision(\n args: Args,\n): Promise<{ methodId: string; credential: string; label?: string }> {\n const provider = args.flags.provider!\n const explicit = args.flags[\"from-file\"] || args.flags[\"from-env\"]\n if (explicit) {\n const methodId = args.flags.method\n if (!methodId)\n throw new Error(\n \"--method is required when passing an explicit --from-file/--from-env source\",\n )\n return {\n methodId,\n credential: await resolveCredential(credentialSource(args)),\n ...(args.flags.label ? { label: args.flags.label } : {}),\n }\n }\n const { recipe, method } = resolveRecipeMethod(provider, args.flags.method)\n const credential = await resolveSourceSpec(method.source, {\n promptImpl: promptStdin,\n })\n const label = args.flags.label ?? method.label ?? recipe.label\n return { methodId: method.id, credential, ...(label ? { label } : {}) }\n}\n\nfunction parseHeaders(raw: string[]): Record<string, string> {\n const out: Record<string, string> = {}\n for (const h of raw) {\n const idx = h.indexOf(\":\")\n if (idx === -1) continue\n out[h.slice(0, idx).trim()] = h.slice(idx + 1).trim()\n }\n return out\n}\n\n/** Structural shape `CredentialBroker` satisfies — kept local (rather than\n * importing the class as a type) so tests can inject a fake without touching\n * Keychain or the network. */\nexport interface AuthHeaderResolver {\n resolveHeaders(o: {\n path: string\n audience?: string\n server?: string\n signal?: AbortSignal\n }): Promise<Record<string, string>>\n}\n\nexport interface ProvisionAuthDeps {\n /** Whether `providerId` is a known `@agentproto/auth` auth provider. */\n isRegistered(providerId: string): boolean\n resolver: AuthHeaderResolver\n}\n\nfunction defaultProvisionAuthDeps(): ProvisionAuthDeps {\n return {\n isRegistered: id => getAuthProvider(id) !== undefined,\n resolver: new CredentialBroker({\n store: new KeychainStore(),\n getProvider: getAuthProvider,\n }),\n }\n}\n\n/**\n * Resolve the Authorization (etc.) headers a provision request carries.\n * `--header` is an explicit override — when present, the broker is never\n * consulted. Otherwise, a `--provider` registered in `@agentproto/auth`\n * resolves its headers via the broker; an unregistered provider degrades to\n * `explicitHeaders` (empty when none were passed) rather than failing.\n */\nexport async function resolveProvisionHeaders(\n provider: string,\n explicitHeaders: string[],\n deps?: ProvisionAuthDeps,\n): Promise<Record<string, string>> {\n const parsed = parseHeaders(explicitHeaders)\n if (explicitHeaders.length > 0) return parsed\n const { isRegistered, resolver } = deps ?? defaultProvisionAuthDeps()\n if (!isRegistered(provider)) return parsed\n const brokered = await resolver.resolveHeaders({ path: provider, audience: \"api\" })\n return { ...brokered, ...parsed }\n}\n\nconst USAGE = `agentproto-secrets — seal secrets and install them into a vault\n\n keygen mint a sealing keypair (JSON to stdout)\n seal --pubkey <b64> [value|src] seal a value -> sealed blob (stdout)\n unseal --privkey <b64> [blob|src] open a sealed blob -> plaintext (stdout)\n provision --provider P --seal-key-url URL --install-url URL \\\\\n [--method M] [--header 'K: V']... [src]\n seal a local credential and install it,\n carrying only ciphertext. A known\n --provider resolves its method + source\n from a builtin recipe; --method picks a\n flavor; an explicit src overrides.\n\n src: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n builtin providers: ${listRecipeIds().join(\", \")}`\n\nexport async function main(): Promise<number> {\n const [cmd, ...rest] = process.argv.slice(2)\n const args = parse(rest)\n\n switch (cmd) {\n case \"keygen\": {\n const kp = generateSealKeyPair()\n process.stdout.write(\n JSON.stringify(\n { keyId: sealKeyId(kp.publicKey), publicKey: kp.publicKey, privateKey: kp.privateKey },\n null,\n 2\n ) + \"\\n\"\n )\n return 0\n }\n\n case \"seal\": {\n const pubkey = args.flags.pubkey\n if (!pubkey) return fail(\"seal: --pubkey <b64> required\")\n const value = await readValue(args)\n process.stdout.write(seal(value, pubkey))\n return 0\n }\n\n case \"unseal\": {\n const privkey = args.flags.privkey\n if (!privkey) return fail(\"unseal: --privkey <b64> required\")\n const blob =\n args.positionals[0] ?? (await resolveCredential(credentialSource(args)))\n process.stdout.write(unseal(blob, privkey))\n return 0\n }\n\n case \"provision\": {\n const provider = args.flags.provider\n const sealKeyUrl = args.flags[\"seal-key-url\"]\n const installUrl = args.flags[\"install-url\"]\n if (!provider) return fail(\"provision: --provider required\")\n if (!sealKeyUrl || !installUrl)\n return fail(\"provision: --seal-key-url and --install-url required\")\n\n let methodId: string\n let credential: string\n let label: string | undefined\n try {\n ;({ methodId, credential, label } = await resolveProvision(args))\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n\n let headers: Record<string, string>\n try {\n headers = await resolveProvisionHeaders(provider, args.headers)\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n\n const target = httpTarget({ sealKeyUrl, installUrl, headers })\n try {\n const out = await provisionSealed({\n target,\n provider,\n methodId,\n credential,\n ...(label ? { label } : {}),\n })\n process.stdout.write(\n `sealed + installed ${provider} (${methodId})` +\n (out.secretId ? ` — secret ${out.secretId.slice(0, 8)}` : \"\") +\n ` [key ${out.keyId.slice(0, 8)}]\\n` +\n `the plaintext never left this machine; the server unsealed it.\\n`\n )\n return 0\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n }\n\n case \"-h\":\n case \"--help\":\n case \"help\":\n case undefined:\n process.stdout.write(USAGE + \"\\n\")\n return cmd ? 0 : 1\n\n default:\n return fail(`unknown command '${cmd}'\\n\\n${USAGE}`)\n }\n}\n\nfunction fail(message: string): number {\n process.stderr.write(`agentproto-secrets: ${message}\\n`)\n return 1\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"names":[],"mappings":"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","file":"cli.mjs","sourcesContent":["/**\n * agentproto-secrets — a small CLI over the seal primitive + provision flow.\n *\n * agentproto-secrets keygen mint a sealing keypair (JSON)\n * agentproto-secrets seal --pubkey <b64> [src] seal a value -> sealed blob\n * agentproto-secrets unseal --privkey <b64> [blob] open a sealed blob -> plaintext\n * agentproto-secrets provision --provider P --method M \\\n * --seal-key-url URL --install-url URL [--header 'K: V']... [src]\n * seal a local credential and\n * install it into a vault,\n * carrying only ciphertext\n *\n * credential source [src]: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n *\n * Vendor-neutral: `provision` takes the server's URLs + auth headers as flags.\n * The plaintext credential is read, sealed, and sent — it is never printed.\n * When `--header` is omitted and `--provider` names an auth provider\n * registered in `@agentproto/auth`, the Authorization header is instead\n * resolved via `CredentialBroker` (this is the only file in this package\n * that imports `@agentproto/auth` — see `ProvisionAuthDeps` below).\n */\n\nimport { CredentialBroker, KeychainStore, getAuthProvider } from \"@agentproto/auth\"\nimport {\n generateSealKeyPair,\n sealKeyId,\n seal,\n unseal,\n} from \"./seal/index.js\"\nimport {\n provisionSealed,\n httpTarget,\n resolveCredential,\n type CredentialSource,\n} from \"./provision/index.js\"\nimport {\n resolveRecipeMethod,\n resolveSourceSpec,\n listRecipeIds,\n} from \"./provision/recipe/index.js\"\n\ninterface Args {\n positionals: string[]\n flags: Record<string, string>\n headers: string[]\n}\n\nfunction parse(argv: string[]): Args {\n const positionals: string[] = []\n const flags: Record<string, string> = {}\n const headers: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!\n if (a.startsWith(\"--\")) {\n const eq = a.indexOf(\"=\")\n let key: string\n let val: string\n if (eq !== -1) {\n key = a.slice(2, eq)\n val = a.slice(eq + 1)\n } else {\n key = a.slice(2)\n const next = argv[i + 1]\n if (next !== undefined && !next.startsWith(\"--\")) {\n val = next\n i++\n } else {\n val = \"true\"\n }\n }\n if (key === \"header\") headers.push(val)\n else flags[key] = val\n } else {\n positionals.push(a)\n }\n }\n return { positionals, flags, headers }\n}\n\nfunction credentialSource(args: Args): CredentialSource {\n return {\n ...(args.flags[\"from-env\"] ? { fromEnv: args.flags[\"from-env\"] } : {}),\n ...(args.flags[\"from-file\"] ? { fromFile: args.flags[\"from-file\"] } : {}),\n ...(args.flags[\"json-path\"] ? { jsonPath: args.flags[\"json-path\"] } : {}),\n }\n}\n\n/** Read a value for seal: a positional, or a credential source. */\nasync function readValue(args: Args): Promise<string> {\n if (args.positionals[0]) return args.positionals[0]\n return resolveCredential(credentialSource(args))\n}\n\n/** Read one line from stdin (for a recipe's `prompt` credential source). */\nasync function promptStdin(prompt: string): Promise<string> {\n const { createInterface } = await import(\"node:readline/promises\")\n const rl = createInterface({ input: process.stdin, output: process.stderr })\n try {\n return (await rl.question(`${prompt}: `)).trim()\n } finally {\n rl.close()\n }\n}\n\n/**\n * Resolve the {methodId, credential, label} a provision installs. A recipe\n * (`--provider <id>`) supplies the auth-method id + where the credential lives,\n * so no source flags are needed; explicit `--from-file/--from-env` override the\n * recipe for ad-hoc / unknown providers (then `--method` is required).\n */\nasync function resolveProvision(\n args: Args,\n): Promise<{ methodId: string; credential: string; label?: string }> {\n const provider = args.flags.provider!\n const explicit = args.flags[\"from-file\"] || args.flags[\"from-env\"]\n if (explicit) {\n const methodId = args.flags.method\n if (!methodId)\n throw new Error(\n \"--method is required when passing an explicit --from-file/--from-env source\",\n )\n return {\n methodId,\n credential: await resolveCredential(credentialSource(args)),\n ...(args.flags.label ? { label: args.flags.label } : {}),\n }\n }\n const { recipe, method } = resolveRecipeMethod(provider, args.flags.method)\n const credential = await resolveSourceSpec(method.source, {\n promptImpl: promptStdin,\n })\n const label = args.flags.label ?? method.label ?? recipe.label\n return { methodId: method.id, credential, ...(label ? { label } : {}) }\n}\n\nfunction parseHeaders(raw: string[]): Record<string, string> {\n const out: Record<string, string> = {}\n for (const h of raw) {\n const idx = h.indexOf(\":\")\n if (idx === -1) continue\n out[h.slice(0, idx).trim()] = h.slice(idx + 1).trim()\n }\n return out\n}\n\n/** Structural shape `CredentialBroker` satisfies — kept local (rather than\n * importing the class as a type) so tests can inject a fake without touching\n * Keychain or the network. */\nexport interface AuthHeaderResolver {\n resolveHeaders(o: {\n path: string\n audience?: string\n server?: string\n signal?: AbortSignal\n }): Promise<Record<string, string>>\n}\n\nexport interface ProvisionAuthDeps {\n /** Whether `providerId` is a known `@agentproto/auth` auth provider. */\n isRegistered(providerId: string): boolean\n resolver: AuthHeaderResolver\n}\n\nfunction defaultProvisionAuthDeps(): ProvisionAuthDeps {\n return {\n isRegistered: id => getAuthProvider(id) !== undefined,\n resolver: new CredentialBroker({\n store: new KeychainStore(),\n getProvider: getAuthProvider,\n }),\n }\n}\n\n/**\n * Resolve the Authorization (etc.) headers a provision request carries.\n * `--header` is an explicit override — when present, the broker is never\n * consulted. Otherwise, a `--provider` registered in `@agentproto/auth`\n * resolves its headers via the broker; an unregistered provider degrades to\n * `explicitHeaders` (empty when none were passed) rather than failing.\n */\nexport async function resolveProvisionHeaders(\n provider: string,\n explicitHeaders: string[],\n deps?: ProvisionAuthDeps,\n): Promise<Record<string, string>> {\n const parsed = parseHeaders(explicitHeaders)\n if (explicitHeaders.length > 0) return parsed\n const { isRegistered, resolver } = deps ?? defaultProvisionAuthDeps()\n if (!isRegistered(provider)) return parsed\n const brokered = await resolver.resolveHeaders({ path: provider, audience: \"api\" })\n return { ...brokered, ...parsed }\n}\n\nconst USAGE = `agentproto-secrets — seal secrets and install them into a vault\n\n keygen mint a sealing keypair (JSON to stdout)\n seal --pubkey <b64> [value|src] seal a value -> sealed blob (stdout)\n unseal --privkey <b64> [blob|src] open a sealed blob -> plaintext (stdout)\n provision --provider P --seal-key-url URL --install-url URL \\\\\n [--method M] [--header 'K: V']... [src]\n seal a local credential and install it,\n carrying only ciphertext. A known\n --provider resolves its method + source\n from a builtin recipe; --method picks a\n flavor; an explicit src overrides.\n\n src: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n builtin providers: ${listRecipeIds().join(\", \")}`\n\nexport async function main(): Promise<number> {\n const [cmd, ...rest] = process.argv.slice(2)\n const args = parse(rest)\n\n switch (cmd) {\n case \"keygen\": {\n const kp = await generateSealKeyPair()\n process.stdout.write(\n JSON.stringify(\n { keyId: await sealKeyId(kp.publicKey), publicKey: kp.publicKey, privateKey: kp.privateKey },\n null,\n 2\n ) + \"\\n\"\n )\n return 0\n }\n\n case \"seal\": {\n const pubkey = args.flags.pubkey\n if (!pubkey) return fail(\"seal: --pubkey <b64> required\")\n const value = await readValue(args)\n process.stdout.write(await seal(value, pubkey))\n return 0\n }\n\n case \"unseal\": {\n const privkey = args.flags.privkey\n if (!privkey) return fail(\"unseal: --privkey <b64> required\")\n const blob =\n args.positionals[0] ?? (await resolveCredential(credentialSource(args)))\n process.stdout.write(await unseal(blob, privkey))\n return 0\n }\n\n case \"provision\": {\n const provider = args.flags.provider\n const sealKeyUrl = args.flags[\"seal-key-url\"]\n const installUrl = args.flags[\"install-url\"]\n if (!provider) return fail(\"provision: --provider required\")\n if (!sealKeyUrl || !installUrl)\n return fail(\"provision: --seal-key-url and --install-url required\")\n\n let methodId: string\n let credential: string\n let label: string | undefined\n try {\n ;({ methodId, credential, label } = await resolveProvision(args))\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n\n let headers: Record<string, string>\n try {\n headers = await resolveProvisionHeaders(provider, args.headers)\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n\n const target = httpTarget({ sealKeyUrl, installUrl, headers })\n try {\n const out = await provisionSealed({\n target,\n provider,\n methodId,\n credential,\n ...(label ? { label } : {}),\n })\n process.stdout.write(\n `sealed + installed ${provider} (${methodId})` +\n (out.secretId ? ` — secret ${out.secretId.slice(0, 8)}` : \"\") +\n ` [key ${out.keyId.slice(0, 8)}]\\n` +\n `the plaintext never left this machine; the server unsealed it.\\n`\n )\n return 0\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n }\n\n case \"-h\":\n case \"--help\":\n case \"help\":\n case undefined:\n process.stdout.write(USAGE + \"\\n\")\n return cmd ? 0 : 1\n\n default:\n return fail(`unknown command '${cmd}'\\n\\n${USAGE}`)\n }\n}\n\nfunction fail(message: string): number {\n process.stderr.write(`agentproto-secrets: ${message}\\n`)\n return 1\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { C as CryptoProvider } from './types-B2ZfEhsU.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The seal box, written once against `CryptoProvider` — see ./index.ts for the
|
|
5
|
+
* construction and the Node entry, and `@agentproto/secrets/pairing/browser`
|
|
6
|
+
* for the browser one. Browser-safe: no `node:` import, no `Buffer`.
|
|
7
|
+
*
|
|
8
|
+
* Every function takes an optional trailing `crypto` provider, defaulting to
|
|
9
|
+
* WebCrypto here; the Node entry point (./index.ts) defaults to `node:crypto`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Versioned algorithm tag embedded in every envelope so the format can
|
|
13
|
+
* evolve without ambiguity at the unseal site. */
|
|
14
|
+
declare const SEAL_ALG: "x25519-hkdf-sha256-aes256gcm";
|
|
15
|
+
declare const SEAL_VERSION: 1;
|
|
16
|
+
/** Raised for every seal/unseal failure. The message is safe to surface —
|
|
17
|
+
* it never contains key material or plaintext. */
|
|
18
|
+
declare class SealError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/** An X25519 keypair for sealing. Both halves are base64-encoded DER so they
|
|
22
|
+
* travel as plain strings (env vars, JSON, config). The public half is safe
|
|
23
|
+
* to publish; the private half must stay with the unseal boundary. */
|
|
24
|
+
interface SealKeyPair {
|
|
25
|
+
/** base64 DER (SPKI) X25519 public key — publishable. */
|
|
26
|
+
publicKey: string;
|
|
27
|
+
/** base64 DER (PKCS8) X25519 private key — secret. */
|
|
28
|
+
privateKey: string;
|
|
29
|
+
}
|
|
30
|
+
/** Mint a fresh sealing keypair. */
|
|
31
|
+
declare function generateSealKeyPair(crypto?: CryptoProvider): Promise<SealKeyPair>;
|
|
32
|
+
/** Derive the publishable public key from a stored private key. */
|
|
33
|
+
declare function sealingPublicKey(privateKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
34
|
+
/** Stable short identifier for a sealing key: first 16 hex of
|
|
35
|
+
* `sha256(public DER)`. Not a secret. */
|
|
36
|
+
declare function sealKeyId(publicKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
37
|
+
/** Seal a plaintext value to a recipient's public key. */
|
|
38
|
+
declare function seal(plaintext: string | Uint8Array, recipientPublicKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
39
|
+
/** Open a sealed envelope with the recipient's private key. */
|
|
40
|
+
declare function unseal(sealed: string, privateKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
41
|
+
|
|
42
|
+
export { SEAL_ALG as S, SEAL_VERSION as a, SealError as b, type SealKeyPair as c, sealKeyId as d, sealingPublicKey as e, generateSealKeyPair as g, seal as s, unseal as u };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { C as CryptoProvider } from './types-B2ZfEhsU.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The daemon identity's crypto (mint, fingerprint, sign, verify), written once
|
|
5
|
+
* against `CryptoProvider`. Browser-safe: no `node:` import, no `Buffer`. The
|
|
6
|
+
* file-backed `loadOrCreateIdentity` stays in the Node entry (./index.ts).
|
|
7
|
+
*
|
|
8
|
+
* Every function takes an optional trailing `crypto` provider, defaulting to
|
|
9
|
+
* WebCrypto here; the Node entry point (./index.ts) defaults to `node:crypto`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Current identity-file schema version. */
|
|
13
|
+
declare const IDENTITY_VERSION: 1;
|
|
14
|
+
/** Raised for every identity load/generate/sign/verify failure. The message
|
|
15
|
+
* is safe to surface — it never contains private key material. */
|
|
16
|
+
declare class IdentityError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** One keypair, both halves base64-DER. Public = SPKI, private = PKCS8. */
|
|
20
|
+
interface IdentityKeyPair {
|
|
21
|
+
/** base64 DER (SPKI) public key — publishable. */
|
|
22
|
+
pub: string;
|
|
23
|
+
/** base64 DER (PKCS8) private key — secret. */
|
|
24
|
+
priv: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A daemon's persistent identity. Serialized verbatim to
|
|
28
|
+
* `~/.agentproto/identity.json` (0600). The `x25519` public half is the key a
|
|
29
|
+
* client seals its hello to; the `ed25519` public half is the key a client
|
|
30
|
+
* verifies the daemon's transcript signature against. Both public halves are
|
|
31
|
+
* carried in the offer URL.
|
|
32
|
+
*/
|
|
33
|
+
interface DaemonIdentity {
|
|
34
|
+
v: typeof IDENTITY_VERSION;
|
|
35
|
+
/** Encryption / key-agreement keypair. */
|
|
36
|
+
x25519: IdentityKeyPair;
|
|
37
|
+
/** Signing / authenticity keypair. */
|
|
38
|
+
ed25519: IdentityKeyPair;
|
|
39
|
+
/** ISO-8601 creation timestamp. */
|
|
40
|
+
createdAt: string;
|
|
41
|
+
}
|
|
42
|
+
/** Mint a fresh daemon identity: one X25519 keypair (encryption) and one
|
|
43
|
+
* Ed25519 keypair (signing). */
|
|
44
|
+
declare function generateIdentity(crypto?: CryptoProvider): Promise<DaemonIdentity>;
|
|
45
|
+
/** First 32 lowercase hex (128 bits) of `sha256(x25519 pub DER)`. Not a
|
|
46
|
+
* secret. It names the daemon's browser origin
|
|
47
|
+
* (`<fingerprint>.agentproto.cloud`, AIP-59 §5.8), so it must resist a
|
|
48
|
+
* second-preimage grind: at 64 bits a well-resourced attacker could mint a
|
|
49
|
+
* daemon key whose fingerprint lands on a victim daemon's origin. It is still
|
|
50
|
+
* one DNS label (≤ 63 chars). */
|
|
51
|
+
declare function identityFingerprint(x25519Pub: string, crypto?: CryptoProvider): Promise<string>;
|
|
52
|
+
/** Sign a handshake transcript with an Ed25519 private key → base64 signature. */
|
|
53
|
+
declare function signTranscript(ed25519Priv: string, transcript: Uint8Array, crypto?: CryptoProvider): Promise<string>;
|
|
54
|
+
/** Verify a transcript signature. Resolves false on a bad signature; rejects
|
|
55
|
+
* (IdentityError) only on a structurally invalid public key. */
|
|
56
|
+
declare function verifyTranscript(ed25519Pub: string, transcript: Uint8Array, signature: string, crypto?: CryptoProvider): Promise<boolean>;
|
|
57
|
+
|
|
58
|
+
export { type DaemonIdentity as D, IDENTITY_VERSION as I, IdentityError as a, type IdentityKeyPair as b, generateIdentity as g, identityFingerprint as i, signTranscript as s, verifyTranscript as v };
|