@oxpulse/intro-protocol 0.2.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/LICENSE +661 -0
- package/README.md +54 -0
- package/SECURITY.md +116 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/intro-crypto.d.ts +216 -0
- package/dist/intro-crypto.d.ts.map +1 -0
- package/dist/intro-crypto.js +378 -0
- package/dist/intro-safety-number.d.ts +42 -0
- package/dist/intro-safety-number.d.ts.map +1 -0
- package/dist/intro-safety-number.js +108 -0
- package/dist/intro-wire.d.ts +149 -0
- package/dist/intro-wire.d.ts.map +1 -0
- package/dist/intro-wire.js +160 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @oxpulse/intro-protocol
|
|
2
|
+
|
|
3
|
+
**EXPERIMENTAL — version 0.1.0.** The L2 introduction protocol as one
|
|
4
|
+
bounded context: Briar-faithful intro crypto (X25519+HKDF+AEAD, MAC/sig),
|
|
5
|
+
a JSON+Zod wire codec for the 6 intro message types, and Signal-style
|
|
6
|
+
safety-number (SAS) derivation.
|
|
7
|
+
|
|
8
|
+
> ⚠️ **EXPERIMENTAL.** This package has not been audited. The crypto
|
|
9
|
+
> invariants are documented in [SECURITY.md](./SECURITY.md) but the
|
|
10
|
+
> package is pre-1.0 and the API may change.
|
|
11
|
+
|
|
12
|
+
## Public surface (flat exports — ADR-003)
|
|
13
|
+
|
|
14
|
+
All exports are flat from the package root
|
|
15
|
+
(`@oxpulse/intro-protocol`); there are no sub-path exports.
|
|
16
|
+
|
|
17
|
+
### intro-crypto
|
|
18
|
+
|
|
19
|
+
- **Labels / version** — `LABEL_SESSION_ID`, `LABEL_MASTER_KEY`,
|
|
20
|
+
`LABEL_ALICE_MAC_KEY`, `LABEL_BOB_MAC_KEY`, `LABEL_AUTH_MAC`,
|
|
21
|
+
`LABEL_AUTH_NONCE`, `LABEL_AUTH_SIGN`, `LABEL_ACTIVATE_MAC`,
|
|
22
|
+
`PROTOCOL_VERSION`
|
|
23
|
+
- **Session ID** — `deriveSessionId`
|
|
24
|
+
- **Role assignment** — `isAliceRole` (constant-time lexicographic compare)
|
|
25
|
+
- **Key derivation** — `deriveMasterKey`, `deriveMacKeys`
|
|
26
|
+
- **Auth transcript** — `buildAuthTranscript`, `TranscriptParty`
|
|
27
|
+
- **AUTH MAC** — `computeAuthMac`, `verifyAuthMac`
|
|
28
|
+
- **AUTH SIG** — `computeAuthSig`, `verifyAuthSig`
|
|
29
|
+
- **ACTIVATE MAC** — `computeActivateMac`, `verifyActivateMac`
|
|
30
|
+
- **AEAD (XChaCha20-Poly1305)** — `sealAead`, `openAead`, `AeadEnvelope`
|
|
31
|
+
|
|
32
|
+
### intro-wire
|
|
33
|
+
|
|
34
|
+
- **Schemas** — `IntroRequestV1Schema`, `IntroAcceptV1Schema`,
|
|
35
|
+
`IntroDeclineV1Schema`, `IntroAuthV1Schema`, `IntroActivateV1Schema`,
|
|
36
|
+
`IntroAbortV1Schema`, `IntroMessageSchema`
|
|
37
|
+
- **Codec** — `encodeIntroMessage`, `decodeIntroMessage`
|
|
38
|
+
- **Session ID redundancy** — `verifySessionIdRedundancy` (CWE-208 fixed)
|
|
39
|
+
- **Types** — `IntroMessage`, `IntroKind`
|
|
40
|
+
|
|
41
|
+
### intro-safety-number
|
|
42
|
+
|
|
43
|
+
- **Safety number** — `deriveSafetyNumber` (60-digit SAS, 12 groups of 5)
|
|
44
|
+
|
|
45
|
+
## Constant-time comparison (CWE-208 invariant)
|
|
46
|
+
|
|
47
|
+
`timingSafeEqual` and `timingSafePubkeyEqualB64u` are imported from
|
|
48
|
+
`@oxpulse/crypto-primitives` (the single public source of truth, ADR-008).
|
|
49
|
+
All attacker-influenced cryptographic comparisons in this package use them.
|
|
50
|
+
See [SECURITY.md](./SECURITY.md) for the full threat model and invariants.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
AGPL-3.0-or-later. See [LICENSE](./LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Security policy — @oxpulse/intro-protocol
|
|
2
|
+
|
|
3
|
+
**EXPERIMENTAL — version 0.1.0.** This package has not been independently
|
|
4
|
+
audited. The invariants below are documented for review and regression
|
|
5
|
+
testing; they are not a substitute for a formal audit.
|
|
6
|
+
|
|
7
|
+
## Threat model
|
|
8
|
+
|
|
9
|
+
This package implements the **L2 introduction protocol**: two parties
|
|
10
|
+
(Alice and Bob) are introduced by a mutual introducer over a QR code /
|
|
11
|
+
introduction channel. The threat model is:
|
|
12
|
+
|
|
13
|
+
- The **introducer** is partially trusted — they choose which two parties
|
|
14
|
+
to introduce but must not be able to forge a session undetected.
|
|
15
|
+
- **Long-term Ed25519 pubkeys** are PUBLIC — they are exchanged over the
|
|
16
|
+
QR code / introduction channel in cleartext. They are not secret material.
|
|
17
|
+
- The **introducer's public key** is attacker-influenced (a malicious
|
|
18
|
+
introducer controls it). The **wire sessionId** is attacker-influenced
|
|
19
|
+
(a malicious introducer or man-in-the-channel can set it).
|
|
20
|
+
- **Ephemeral X25519 keys**, the **master key**, **MAC keys**, and the
|
|
21
|
+
**AEAD key** are secret and never transmitted.
|
|
22
|
+
- The channel is asynchronous (sdk-inbox-bridge sealed payloads); there is
|
|
23
|
+
no live key-agreement handshake, so redundancy checks and MACs/signatures
|
|
24
|
+
carry the integrity guarantee.
|
|
25
|
+
|
|
26
|
+
## CWE-208 fix — `verifySessionIdRedundancy` (ADR-011)
|
|
27
|
+
|
|
28
|
+
**Vulnerable (before):** `verifySessionIdRedundancy` compared the wire
|
|
29
|
+
sessionId to the locally-derived sessionId with plain `===`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
return msg.sessionId === derivedSessionId;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A plain `===` on a crypto-derived base64url string short-circuits on the
|
|
36
|
+
first mismatched character, leaking the first-mismatch byte position via
|
|
37
|
+
timing. Because the wire sessionId is attacker-influenced (a malicious
|
|
38
|
+
introducer controls it), this created a **timing oracle**: an attacker
|
|
39
|
+
could iteratively guess the locally-derived sessionId one byte at a time
|
|
40
|
+
by measuring how long the comparison ran before returning `false`. This
|
|
41
|
+
violates **OWASP ASVS V11.3.1** and is classified as **CWE-208**
|
|
42
|
+
(Observable Timing Discrepancy).
|
|
43
|
+
|
|
44
|
+
**Fixed (now):** The comparison uses `timingSafePubkeyEqualB64u` from
|
|
45
|
+
`@oxpulse/crypto-primitives`, which decodes both base64url strings to
|
|
46
|
+
bytes and compares with `timingSafeEqual` (XOR-reduce over all bytes,
|
|
47
|
+
returns `false` on length mismatch — length is non-secret). No
|
|
48
|
+
short-circuit path based on byte content remains.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
return timingSafePubkeyEqualB64u(msg.sessionId, derivedSessionId);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A regression test (`intro-wire.test.ts`) asserts the fix: two sessionIds
|
|
55
|
+
differing only in the last byte return `false` without throwing, and the
|
|
56
|
+
function returns a boolean for equal, first-char-differing, last-char-
|
|
57
|
+
differing, and different-length inputs.
|
|
58
|
+
|
|
59
|
+
## Constant-time invariants
|
|
60
|
+
|
|
61
|
+
**INVARIANT:** Every comparison of an attacker-influenced cryptographic
|
|
62
|
+
identifier uses `timingSafeEqual` (raw bytes) or
|
|
63
|
+
`timingSafePubkeyEqualB64u` (base64url strings), imported from
|
|
64
|
+
`@oxpulse/crypto-primitives`. Plain `===` is NEVER used on crypto-derived
|
|
65
|
+
b64u strings, MAC tags, signatures, or sessionIds.
|
|
66
|
+
|
|
67
|
+
The functions that enforce this:
|
|
68
|
+
|
|
69
|
+
| Function | Compares | Constant-time? |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `verifySessionIdRedundancy` | wire sessionId vs derived sessionId (b64u) | ✅ `timingSafePubkeyEqualB64u` |
|
|
72
|
+
| `verifyAuthMac` | expected HMAC vs received HMAC (bytes) | ✅ `timingSafeEqual` |
|
|
73
|
+
| `verifyActivateMac` | expected HMAC vs received HMAC (bytes) | ✅ `timingSafeEqual` |
|
|
74
|
+
| `verifyAuthSig` | Ed25519 signature (delegated to `ed25519.verify`) | ✅ (noble verifies in constant time) |
|
|
75
|
+
| `isAliceRole` | local pubkey vs peer pubkey (bytes) | ✅ walks all bytes, branchless decide |
|
|
76
|
+
|
|
77
|
+
## Non-constant-time — safe because public (ADR-012)
|
|
78
|
+
|
|
79
|
+
Two comparisons in this package are intentionally NOT constant-time. This
|
|
80
|
+
is safe because the values being compared are PUBLIC over the QR code /
|
|
81
|
+
introduction channel — they are not secret, so leaking the
|
|
82
|
+
first-differing-byte position via timing reveals nothing an attacker does
|
|
83
|
+
not already know.
|
|
84
|
+
|
|
85
|
+
### `lexLess` (intro-safety-number.ts)
|
|
86
|
+
|
|
87
|
+
`lexLess` orders the two long-term pubkeys (lex-smaller first) before
|
|
88
|
+
CBOR-encoding the safety-number input. It returns early on the first
|
|
89
|
+
differing byte. This is safe because:
|
|
90
|
+
|
|
91
|
+
1. The pubkeys are already public (transmitted in cleartext over the
|
|
92
|
+
QR / introduction channel).
|
|
93
|
+
2. The ordering only determines array position in the CBOR input — it
|
|
94
|
+
does not gate any security decision on secret data.
|
|
95
|
+
3. A constant-time version (`isAliceRole` in intro-crypto.ts) exists where
|
|
96
|
+
the comparison itself IS security-relevant (role assignment).
|
|
97
|
+
|
|
98
|
+
See **ADR-012** for the full reasoning.
|
|
99
|
+
|
|
100
|
+
### SAS human comparison (SECURITY_COST-8)
|
|
101
|
+
|
|
102
|
+
`deriveSafetyNumber` returns a 60-digit Short Authentication String (SAS)
|
|
103
|
+
intended to be read aloud or compared visually by the two human parties
|
|
104
|
+
over an authenticated side channel (in-person, phone call). The comparison
|
|
105
|
+
is performed by **humans**, who are inherently non-constant-time. No
|
|
106
|
+
constant-time comparison is required or meaningful for the SAS value
|
|
107
|
+
itself — the security property comes from the human comparing the full
|
|
108
|
+
string, not from a code-level equality check. The underlying
|
|
109
|
+
`deriveSafetyNumber` computation is deterministic and side-channel-free
|
|
110
|
+
(pure SHA-512 + CBOR, no secret-dependent branches on attacker-controlled
|
|
111
|
+
input beyond the public pubkey ordering).
|
|
112
|
+
|
|
113
|
+
## Reporting a vulnerability
|
|
114
|
+
|
|
115
|
+
Do NOT open a public GitHub issue for a security vulnerability. Email the
|
|
116
|
+
maintainer privately. See the repository root for contact details.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxpulse/intro-protocol — L2 introduction protocol (bounded context).
|
|
3
|
+
*
|
|
4
|
+
* ADR-010: One package containing intro-crypto + intro-wire + intro-safety-number.
|
|
5
|
+
* ADR-003: Flat public re-exports — no sub-path exports.
|
|
6
|
+
*
|
|
7
|
+
* EXPERIMENTAL (0.1.0). See SECURITY.md for the threat model and
|
|
8
|
+
* constant-time invariants.
|
|
9
|
+
*/
|
|
10
|
+
export { LABEL_SESSION_ID, LABEL_MASTER_KEY, LABEL_ALICE_MAC_KEY, LABEL_BOB_MAC_KEY, LABEL_AUTH_MAC, LABEL_AUTH_NONCE, LABEL_AUTH_SIGN, LABEL_ACTIVATE_MAC, PROTOCOL_VERSION, deriveSessionId, isAliceRole, deriveMasterKey, deriveMacKeys, wipeMacKeys, wipe, buildAuthTranscript, computeAuthMac, verifyAuthMac, computeAuthSig, verifyAuthSig, computeActivateMac, verifyActivateMac, sealAead, openAead, envelopeToWireB64u, wireB64uToEnvelope, type TranscriptParty, type AeadEnvelope, type AeadLabel, } from './intro-crypto.ts';
|
|
11
|
+
export { IntroRequestV1Schema, IntroAcceptV1Schema, IntroDeclineV1Schema, IntroAuthV1Schema, IntroActivateV1Schema, IntroAbortV1Schema, IntroMessageSchema, encodeIntroMessage, decodeIntroMessage, verifySessionIdRedundancy, type IntroMessage, type IntroKind, } from './intro-wire.ts';
|
|
12
|
+
export { deriveSafetyNumber } from './intro-safety-number.ts';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,eAAe,EACf,aAAa,EACb,WAAW,EACX,IAAI,EACJ,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxpulse/intro-protocol — L2 introduction protocol (bounded context).
|
|
3
|
+
*
|
|
4
|
+
* ADR-010: One package containing intro-crypto + intro-wire + intro-safety-number.
|
|
5
|
+
* ADR-003: Flat public re-exports — no sub-path exports.
|
|
6
|
+
*
|
|
7
|
+
* EXPERIMENTAL (0.1.0). See SECURITY.md for the threat model and
|
|
8
|
+
* constant-time invariants.
|
|
9
|
+
*/
|
|
10
|
+
// intro-crypto — Briar-faithful crypto primitives (X25519+HKDF+AEAD, MAC/sig)
|
|
11
|
+
export { LABEL_SESSION_ID, LABEL_MASTER_KEY, LABEL_ALICE_MAC_KEY, LABEL_BOB_MAC_KEY, LABEL_AUTH_MAC, LABEL_AUTH_NONCE, LABEL_AUTH_SIGN, LABEL_ACTIVATE_MAC, PROTOCOL_VERSION, deriveSessionId, isAliceRole, deriveMasterKey, deriveMacKeys, wipeMacKeys, wipe, buildAuthTranscript, computeAuthMac, verifyAuthMac, computeAuthSig, verifyAuthSig, computeActivateMac, verifyActivateMac, sealAead, openAead, envelopeToWireB64u, wireB64uToEnvelope, } from "./intro-crypto.js";
|
|
12
|
+
// intro-wire — JSON+Zod wire codec for the 6 intro message types
|
|
13
|
+
export { IntroRequestV1Schema, IntroAcceptV1Schema, IntroDeclineV1Schema, IntroAuthV1Schema, IntroActivateV1Schema, IntroAbortV1Schema, IntroMessageSchema, encodeIntroMessage, decodeIntroMessage, verifySessionIdRedundancy, } from "./intro-wire.js";
|
|
14
|
+
// intro-safety-number — Signal-style safety number (SAS) derivation
|
|
15
|
+
export { deriveSafetyNumber } from "./intro-safety-number.js";
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Briar-faithful crypto primitives for the L2 introduction protocol.
|
|
3
|
+
*
|
|
4
|
+
* All functions are pure — no side-effects beyond the returned values.
|
|
5
|
+
* Implements sub-spec §4.2 (labels, key derivation, MAC/sig, AEAD).
|
|
6
|
+
*
|
|
7
|
+
* Label encoding: UTF-8 at use-site, no NUL terminators (architect nit #2).
|
|
8
|
+
* MAC comparison: always constant-time via timingSafeEqual (W4).
|
|
9
|
+
*
|
|
10
|
+
* ADR-010: This module is part of the @oxpulse/intro-protocol bounded
|
|
11
|
+
* context (intro-crypto + intro-wire + intro-safety-number in ONE package).
|
|
12
|
+
*
|
|
13
|
+
* ADR-008: timingSafeEqual + timingSafePubkeyEqualB64u are imported from
|
|
14
|
+
* @oxpulse/crypto-primitives (the single public source of truth for
|
|
15
|
+
* constant-time comparison). concatBytes + utf8 stay local here — they are
|
|
16
|
+
* internal general-purpose helpers, not part of the crypto-primitives
|
|
17
|
+
* public surface.
|
|
18
|
+
*/
|
|
19
|
+
export declare const LABEL_SESSION_ID = "oxpulse-intro-v1/SESSION_ID";
|
|
20
|
+
export declare const LABEL_MASTER_KEY = "oxpulse-intro-v1/MASTER_KEY";
|
|
21
|
+
export declare const LABEL_ALICE_MAC_KEY = "oxpulse-intro-v1/ALICE_MAC_KEY";
|
|
22
|
+
export declare const LABEL_BOB_MAC_KEY = "oxpulse-intro-v1/BOB_MAC_KEY";
|
|
23
|
+
export declare const LABEL_AUTH_MAC = "oxpulse-intro-v1/AUTH_MAC";
|
|
24
|
+
export declare const LABEL_AUTH_NONCE = "oxpulse-intro-v1/AUTH_NONCE";
|
|
25
|
+
export declare const LABEL_AUTH_SIGN = "oxpulse-intro-v1/AUTH_SIGN";
|
|
26
|
+
export declare const LABEL_ACTIVATE_MAC = "oxpulse-intro-v1/ACTIVATE_MAC";
|
|
27
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort zeroization of secret key material (ADR-013 / #216).
|
|
30
|
+
*
|
|
31
|
+
* Overwrites the buffer with zeros. JS zeroization is best-effort — V8 may
|
|
32
|
+
* copy Uint8Array contents during GC compaction, so this is NOT a guarantee
|
|
33
|
+
* that no copy survives — but it is the recognized hardening baseline and
|
|
34
|
+
* the SECURITY.md threat model already promises these values are secret.
|
|
35
|
+
*
|
|
36
|
+
* Callers MUST wipe:
|
|
37
|
+
* - ephemeral private keys after `deriveMasterKey`
|
|
38
|
+
* - `masterKey` when done with all derived MAC keys + AEAD operations
|
|
39
|
+
* - MAC keys via {@link wipeMacKeys} when the handshake completes/aborts
|
|
40
|
+
*
|
|
41
|
+
* Returns the same Uint8Array reference (now zeroed) for convenience.
|
|
42
|
+
*/
|
|
43
|
+
export declare function wipe(u: Uint8Array): Uint8Array;
|
|
44
|
+
/**
|
|
45
|
+
* Derives a deterministic 16-byte (22-char base64url) session ID.
|
|
46
|
+
*
|
|
47
|
+
* input = utf8(LABEL_SESSION_ID) || introducerPub || firstPub || secondPub
|
|
48
|
+
* result = SHA-256(input)[0..16] encoded as base64url
|
|
49
|
+
*
|
|
50
|
+
* All pubkeys must be 32-byte Ed25519 pubkeys (raw bytes, not base64url).
|
|
51
|
+
* Callers holding b64u strings decode first: `b64uDecodeBytes(b64uStr)`
|
|
52
|
+
* from `@oxpulse/crypto-primitives`.
|
|
53
|
+
*
|
|
54
|
+
* alicePub + bobPub are canonical-ordered internally via {@link isAliceRole}
|
|
55
|
+
* (lex-smaller pubkey first), matching {@link buildAuthTranscript}. This
|
|
56
|
+
* guarantees both parties produce identical sessionIds regardless of which
|
|
57
|
+
* side calls deriveSessionId(introducer, own, peer) — see ADR-013 / #217.
|
|
58
|
+
*
|
|
59
|
+
* @param introducerPub - Introducer long-term Ed25519 pubkey (32 bytes)
|
|
60
|
+
* @param alicePub - Alice ephemeral pubkey (32 bytes) — auto role-ordered
|
|
61
|
+
* @param bobPub - Bob ephemeral pubkey (32 bytes) — auto role-ordered
|
|
62
|
+
*/
|
|
63
|
+
export declare function deriveSessionId(introducerPub: Uint8Array, alicePub: Uint8Array, bobPub: Uint8Array): string;
|
|
64
|
+
/**
|
|
65
|
+
* Constant-time lexicographic compare of two Ed25519/X25519 pubkeys.
|
|
66
|
+
* Returns true if localPubkey < peerPubkey (Alice role).
|
|
67
|
+
* Returns false on equal (tiebreaker: peer is Alice, local is Bob).
|
|
68
|
+
*/
|
|
69
|
+
export declare function isAliceRole(localPubkey: Uint8Array, peerPubkey: Uint8Array): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Derives the shared master key from an ephemeral X25519 DH + HKDF.
|
|
72
|
+
*
|
|
73
|
+
* ikm = X25519(ownEphPriv, peerEphPub) — wiped before return (#216)
|
|
74
|
+
* salt = utf8(LABEL_MASTER_KEY)
|
|
75
|
+
* info = [PROTOCOL_VERSION] || aliceEphPub || bobEphPub
|
|
76
|
+
* return HKDF-SHA256(salt, ikm, info, 32)
|
|
77
|
+
*
|
|
78
|
+
* aliceEphPub + bobEphPub are role-ordered so both parties use identical info.
|
|
79
|
+
* The caller is responsible for wiping the returned masterKey (via {@link wipe})
|
|
80
|
+
* once all MAC keys are derived and AEAD operations complete, and for wiping
|
|
81
|
+
* the ownEphPriv argument after this call returns.
|
|
82
|
+
*/
|
|
83
|
+
export declare function deriveMasterKey(ownEphPriv: Uint8Array, peerEphPub: Uint8Array, aliceEphPub: Uint8Array, bobEphPub: Uint8Array): Uint8Array;
|
|
84
|
+
/**
|
|
85
|
+
* Derives alice and bob MAC keys from masterKey via HKDF-Expand-only.
|
|
86
|
+
*
|
|
87
|
+
* expand(masterKey, utf8(LABEL_ALICE_MAC_KEY), 32)
|
|
88
|
+
* expand(masterKey, utf8(LABEL_BOB_MAC_KEY), 32)
|
|
89
|
+
*
|
|
90
|
+
* Uses `expand` (HKDF-Expand) directly — masterKey is already a PRK.
|
|
91
|
+
*/
|
|
92
|
+
export declare function deriveMacKeys(masterKey: Uint8Array): {
|
|
93
|
+
alice: Uint8Array;
|
|
94
|
+
bob: Uint8Array;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Best-effort zeroize both MAC keys returned by {@link deriveMacKeys} (#216).
|
|
98
|
+
*
|
|
99
|
+
* Call when the handshake completes or aborts and the MAC keys are no longer
|
|
100
|
+
* needed. After this call, `keys.alice` and `keys.bob` are zeroed — further
|
|
101
|
+
* use will produce wrong results / throw, which is the intended fail-closed
|
|
102
|
+
* behaviour.
|
|
103
|
+
*/
|
|
104
|
+
export declare function wipeMacKeys(keys: {
|
|
105
|
+
alice: Uint8Array;
|
|
106
|
+
bob: Uint8Array;
|
|
107
|
+
}): void;
|
|
108
|
+
export interface TranscriptParty {
|
|
109
|
+
longTermPubkey: Uint8Array;
|
|
110
|
+
acceptedAt: number;
|
|
111
|
+
ephPub: Uint8Array;
|
|
112
|
+
transportProps: Record<string, unknown>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Builds the canonical CBOR auth transcript.
|
|
116
|
+
*
|
|
117
|
+
* canonicalCBOR([
|
|
118
|
+
* introducer.longTermPub,
|
|
119
|
+
* [alice.longTermPub, alice.acceptedAt, alice.ephPub, alice.transportProps],
|
|
120
|
+
* [bob.longTermPub, bob.acceptedAt, bob.ephPub, bob.transportProps],
|
|
121
|
+
* ])
|
|
122
|
+
*
|
|
123
|
+
* Parties are canonically ordered: the party whose longTermPubkey is
|
|
124
|
+
* lexicographically smaller (isAliceRole) is always listed first.
|
|
125
|
+
* This ensures both sides produce identical CBOR bytes regardless of
|
|
126
|
+
* which side calls buildAuthTranscript(introducer, own, peer).
|
|
127
|
+
*/
|
|
128
|
+
export declare function buildAuthTranscript(introducerLongTermPub: Uint8Array, ownSide: TranscriptParty, peerSide: TranscriptParty): Uint8Array;
|
|
129
|
+
/**
|
|
130
|
+
* Computes HMAC-SHA256(ownMacKey, utf8(LABEL_AUTH_MAC) || sessionId || transcript).
|
|
131
|
+
*
|
|
132
|
+
* sessionId is mixed in explicitly (ADR-013 / #218 nit #7) so the binding to
|
|
133
|
+
* the introducer's announced sessionId survives future transcript-shape
|
|
134
|
+
* changes. Previously the binding was only transitive (sessionId inputs are
|
|
135
|
+
* also transcript fields); explicit binding is more robust against refactor.
|
|
136
|
+
*/
|
|
137
|
+
export declare function computeAuthMac(ownMacKey: Uint8Array, sessionId: Uint8Array, transcript: Uint8Array): Uint8Array;
|
|
138
|
+
/**
|
|
139
|
+
* Verifies the peer's auth MAC.
|
|
140
|
+
*
|
|
141
|
+
* Recomputes HMAC-SHA256(peerMacKey, utf8(LABEL_AUTH_MAC) || sessionId ||
|
|
142
|
+
* canonicalTranscript) and compares using constant-time equality.
|
|
143
|
+
*
|
|
144
|
+
* With canonical-ordering in buildAuthTranscript, both sides produce identical
|
|
145
|
+
* transcript bytes — the verifier passes its own locally-built transcript, which
|
|
146
|
+
* equals the sender's transcript exactly (the "swap" is identity by design).
|
|
147
|
+
*/
|
|
148
|
+
export declare function verifyAuthMac(peerMacKey: Uint8Array, sessionId: Uint8Array, canonicalTranscript: Uint8Array, receivedMac: Uint8Array): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Computes the auth signature.
|
|
151
|
+
*
|
|
152
|
+
* nonce = HMAC-SHA256(ownMacKey, utf8(LABEL_AUTH_NONCE) || sessionId || transcript)
|
|
153
|
+
* return Ed25519-sign(ownLongTermPriv, utf8(LABEL_AUTH_SIGN) || nonce)
|
|
154
|
+
*
|
|
155
|
+
* sessionId is mixed into the nonce explicitly (ADR-013 / #218 nit #7) for
|
|
156
|
+
* the same reason as in {@link computeAuthMac} — explicit binding survives
|
|
157
|
+
* future transcript-shape changes.
|
|
158
|
+
*/
|
|
159
|
+
export declare function computeAuthSig(ownLongTermPriv: Uint8Array, ownMacKey: Uint8Array, sessionId: Uint8Array, transcript: Uint8Array): Uint8Array;
|
|
160
|
+
/**
|
|
161
|
+
* Verifies the peer's auth signature.
|
|
162
|
+
*
|
|
163
|
+
* nonce = HMAC-SHA256(peerMacKey, utf8(LABEL_AUTH_NONCE) || sessionId ||
|
|
164
|
+
* canonicalTranscript)
|
|
165
|
+
* return Ed25519-verify(peerLongTermPub, utf8(LABEL_AUTH_SIGN) || nonce, receivedSig)
|
|
166
|
+
*
|
|
167
|
+
* With canonical-ordering in buildAuthTranscript, both sides produce identical
|
|
168
|
+
* transcript bytes — pass the locally-built canonical transcript here.
|
|
169
|
+
*/
|
|
170
|
+
export declare function verifyAuthSig(peerLongTermPub: Uint8Array, peerMacKey: Uint8Array, sessionId: Uint8Array, canonicalTranscript: Uint8Array, receivedSig: Uint8Array): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Computes HMAC-SHA256(ownMacKey, utf8(LABEL_ACTIVATE_MAC) || sessionId).
|
|
173
|
+
*/
|
|
174
|
+
export declare function computeActivateMac(ownMacKey: Uint8Array, sessionId: Uint8Array): Uint8Array;
|
|
175
|
+
/**
|
|
176
|
+
* Verifies activate MAC using constant-time comparison.
|
|
177
|
+
*/
|
|
178
|
+
export declare function verifyActivateMac(peerMacKey: Uint8Array, sessionId: Uint8Array, receivedMac: Uint8Array): boolean;
|
|
179
|
+
export interface AeadEnvelope {
|
|
180
|
+
nonce: Uint8Array;
|
|
181
|
+
ciphertext: Uint8Array;
|
|
182
|
+
}
|
|
183
|
+
/** Permitted labels for AEAD operations — prevents cross-context misuse. */
|
|
184
|
+
export type AeadLabel = typeof LABEL_AUTH_MAC | typeof LABEL_ACTIVATE_MAC;
|
|
185
|
+
/**
|
|
186
|
+
* Encrypts plaintext with XChaCha20-Poly1305.
|
|
187
|
+
*
|
|
188
|
+
* nonce = random(24B)
|
|
189
|
+
* AAD = utf8(label) || sessionId
|
|
190
|
+
* cipher = XChaCha20-Poly1305(masterKey, nonce, AAD)
|
|
191
|
+
* return { nonce, ciphertext: cipher.encrypt(plaintext) }
|
|
192
|
+
*/
|
|
193
|
+
export declare function sealAead(masterKey: Uint8Array, label: AeadLabel, sessionId: Uint8Array, plaintext: Uint8Array): AeadEnvelope;
|
|
194
|
+
/**
|
|
195
|
+
* Decrypts an AEAD envelope. Throws on authentication tag mismatch.
|
|
196
|
+
*
|
|
197
|
+
* AAD = utf8(label) || sessionId (must match seal-time values)
|
|
198
|
+
* cipher = XChaCha20-Poly1305(masterKey, env.nonce, AAD)
|
|
199
|
+
* return cipher.decrypt(env.ciphertext)
|
|
200
|
+
*/
|
|
201
|
+
export declare function openAead(masterKey: Uint8Array, label: AeadLabel, sessionId: Uint8Array, env: AeadEnvelope): Uint8Array;
|
|
202
|
+
/**
|
|
203
|
+
* Serialize an AeadEnvelope to the wire-format base64url string
|
|
204
|
+
* `b64u(nonce || ciphertext)`.
|
|
205
|
+
*/
|
|
206
|
+
export declare function envelopeToWireB64u(env: AeadEnvelope): string;
|
|
207
|
+
/**
|
|
208
|
+
* Parse a wire-format base64url string `b64u(nonce || ciphertext)` into an
|
|
209
|
+
* AeadEnvelope. Throws if the input is shorter than the 24-byte nonce.
|
|
210
|
+
*
|
|
211
|
+
* Note: uses `b64uDecodeBytes` from `@oxpulse/crypto-primitives` — for
|
|
212
|
+
* attacker-influenced inputs callers should pre-validate via the wire Zod
|
|
213
|
+
* schema, which constrains the charset to base64url.
|
|
214
|
+
*/
|
|
215
|
+
export declare function wireB64uToEnvelope(s: string): AeadEnvelope;
|
|
216
|
+
//# sourceMappingURL=intro-crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intro-crypto.d.ts","sourceRoot":"","sources":["../src/intro-crypto.ts"],"names":[],"mappings":"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"}
|