spd-lib 1.3.6 → 1.3.8
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/index.d.mts +96 -13
- package/index.d.ts +96 -13
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +3 -2
package/index.d.mts
CHANGED
|
@@ -28,6 +28,7 @@ interface SPDPayload {
|
|
|
28
28
|
argon2Time?: number;
|
|
29
29
|
argon2HashLen?: number;
|
|
30
30
|
hashKeyLen?: number;
|
|
31
|
+
argon2Parallelism?: number;
|
|
31
32
|
entryRoles?: Record<string, string[]>;
|
|
32
33
|
maxOpenCount?: number;
|
|
33
34
|
openCount?: number;
|
|
@@ -168,8 +169,8 @@ type SupportedValue = string | number | boolean | unknown[] | TypedArray | Map<u
|
|
|
168
169
|
|
|
169
170
|
declare const ARGON2_MEMORY_HIGH = 524288;
|
|
170
171
|
declare const ARGON2_TIME_HIGH = 8;
|
|
171
|
-
declare const ARGON2_MEMORY_PARANOID =
|
|
172
|
-
declare const ARGON2_TIME_PARANOID =
|
|
172
|
+
declare const ARGON2_MEMORY_PARANOID = 2097152;
|
|
173
|
+
declare const ARGON2_TIME_PARANOID = 16;
|
|
173
174
|
/** Key stretching profile names */
|
|
174
175
|
type SPDKeyProfile = 'standard' | 'high' | 'paranoid';
|
|
175
176
|
declare class SPD {
|
|
@@ -210,6 +211,7 @@ declare class SPD {
|
|
|
210
211
|
private _scryptN;
|
|
211
212
|
private _scryptR;
|
|
212
213
|
private _scryptP;
|
|
214
|
+
private _argon2Parallelism;
|
|
213
215
|
private _wrapArgon2Memory;
|
|
214
216
|
private _wrapArgon2Time;
|
|
215
217
|
private static globalLogger;
|
|
@@ -301,7 +303,7 @@ declare class SPD {
|
|
|
301
303
|
* v27+: hashLen=96, macKeyLen=64 (full SHA3-512 output width = 128-bit PQ forgery resistance)
|
|
302
304
|
* v26: hashLen=64, macKeyLen=32 (legacy — pass these explicitly when reading old files)
|
|
303
305
|
*/
|
|
304
|
-
static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number, kdfChain?: boolean, scryptN?: number, scryptR?: number, scryptP?: number): Promise<{
|
|
306
|
+
static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number, kdfChain?: boolean, scryptN?: number, scryptR?: number, scryptP?: number, parallelism?: number): Promise<{
|
|
305
307
|
aeadKey: Uint8Array;
|
|
306
308
|
macKey: Uint8Array;
|
|
307
309
|
}>;
|
|
@@ -312,6 +314,37 @@ declare class SPD {
|
|
|
312
314
|
* "decrypts" under two different keys.
|
|
313
315
|
*/
|
|
314
316
|
static computeKeyCommitment(aeadKey: Uint8Array): string;
|
|
317
|
+
/**
|
|
318
|
+
* CMT-4 block size: SHA3-256(key || nonce) prepended to every ciphertext.
|
|
319
|
+
* 32 bytes = one SHA3-256 output.
|
|
320
|
+
*/
|
|
321
|
+
private static readonly CMT_BLOCK_BYTES;
|
|
322
|
+
/**
|
|
323
|
+
* CMT-4 encrypt: encrypt plaintext with XChaCha20-Poly1305, then prepend
|
|
324
|
+
* H(key || nonce) so that the correct key is the *only* key that can produce
|
|
325
|
+
* a valid commitment. Closes the USENIX 2022 partitioning oracle attack.
|
|
326
|
+
*
|
|
327
|
+
* Stored layout: [ 32-byte cmt | xchacha20-poly1305 ciphertext ]
|
|
328
|
+
*/
|
|
329
|
+
private static aeadEncrypt;
|
|
330
|
+
/**
|
|
331
|
+
* CMT-4 decrypt: verify the prepended commitment block, then decrypt.
|
|
332
|
+
* Throws on commitment mismatch or AEAD authentication failure.
|
|
333
|
+
*/
|
|
334
|
+
private static aeadDecrypt;
|
|
335
|
+
/**
|
|
336
|
+
* Pad `data` to a multiple of `blockSize` bytes before compression.
|
|
337
|
+
* A 4-byte LE prefix records the original length so the receiver can strip padding.
|
|
338
|
+
* Using random padding bytes ensures the pad bytes themselves don't compress.
|
|
339
|
+
*
|
|
340
|
+
* This defeats CRIME/BREACH-class compression oracle attacks: the AEAD
|
|
341
|
+
* ciphertext length is now quantised to `blockSize`-byte steps regardless of
|
|
342
|
+
* plaintext content, so an attacker who can observe ciphertext lengths learns
|
|
343
|
+
* nothing about the plaintext.
|
|
344
|
+
*/
|
|
345
|
+
private static readonly COMPRESS_PAD_BLOCK;
|
|
346
|
+
private static padForCompression;
|
|
347
|
+
private static unpadAfterDecompression;
|
|
315
348
|
private static computeMAC;
|
|
316
349
|
/**
|
|
317
350
|
* Derives a per-entry 32-byte AEAD key from the master AEAD key using HKDF-SHA3-512.
|
|
@@ -323,7 +356,35 @@ declare class SPD {
|
|
|
323
356
|
* - Zero performance impact: HKDF is ~microseconds vs ~1s for Argon2id
|
|
324
357
|
*/
|
|
325
358
|
static deriveEntryKey(masterAeadKey: Uint8Array, entryName: string): Uint8Array;
|
|
359
|
+
/**
|
|
360
|
+
* Derive a dedicated 32-byte name-encryption key from the master AEAD key.
|
|
361
|
+
* Domain-separated from entry data keys via a distinct info string.
|
|
362
|
+
* Used to encrypt entry names in the binary index so the plaintext index
|
|
363
|
+
* does not leak entry names to an attacker who has the file but not the passcode.
|
|
364
|
+
*/
|
|
365
|
+
static deriveNameKey(masterAeadKey: Uint8Array): Uint8Array;
|
|
366
|
+
/**
|
|
367
|
+
* Encrypt an entry name.
|
|
368
|
+
* Returns [24-byte nonce || XChaCha20-Poly1305+CMT ciphertext].
|
|
369
|
+
*/
|
|
370
|
+
private static _encryptEntryName;
|
|
371
|
+
/**
|
|
372
|
+
* Decrypt an entry name.
|
|
373
|
+
* Expects [24-byte nonce || ciphertext] as produced by _encryptEntryName.
|
|
374
|
+
*/
|
|
375
|
+
private static _decryptEntryName;
|
|
376
|
+
/**
|
|
377
|
+
* Normalize l33t-speak substitutions so common bypasses are caught.
|
|
378
|
+
* Maps: @→a, 0→o, 3→e, 1→i, 5→s, $→s, !→i, 4→a, 7→t
|
|
379
|
+
*/
|
|
380
|
+
private static normalizeLeet;
|
|
326
381
|
checkPasscodeStrength(passcode: string): boolean;
|
|
382
|
+
/**
|
|
383
|
+
* Compute a bigram-frequency penalty factor [0, 0.5].
|
|
384
|
+
* Returns higher values for text heavy in common English bigrams.
|
|
385
|
+
* A factor of 0.3 means the entropy estimate is reduced by 30%.
|
|
386
|
+
*/
|
|
387
|
+
private static _bigramPenalty;
|
|
327
388
|
setPassKey(passcode: string): Promise<void>;
|
|
328
389
|
setHash(hash?: HashAlgorithm): void;
|
|
329
390
|
setCompressionLevel(level?: number): void;
|
|
@@ -762,7 +823,7 @@ declare class SPD {
|
|
|
762
823
|
static verify(filePath: string, passcode: string): Promise<SPDVerifyResult>;
|
|
763
824
|
static loadFromString(data: string, passcode: string): Promise<SPD>;
|
|
764
825
|
static loadFromChunks(chunks: string[], passcode: string): Promise<SPD>;
|
|
765
|
-
static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number): Promise<PBKResult>;
|
|
826
|
+
static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, parallelism?: number): Promise<PBKResult>;
|
|
766
827
|
static decryptSalt(encryptedSalt: number[], saltNonce: number[], wrapSalt: number[], passcode: string, memory?: number, time?: number): Promise<Uint8Array>;
|
|
767
828
|
static encryptSalt(salt: Uint8Array, passcode: string, memory?: number, time?: number): Promise<EncryptedSaltResult>;
|
|
768
829
|
static toBase64(data: Uint8Array | Buffer): string;
|
|
@@ -1026,20 +1087,25 @@ declare class SPDWriter {
|
|
|
1026
1087
|
* ✅ Active MitM — ML-DSA-65 certificate pinning + ECDH server auth
|
|
1027
1088
|
* ✅ Harvest-now-decrypt-later (quantum) — ML-KEM-768 hybrid
|
|
1028
1089
|
* ✅ PSK gives additional session binding layer for high-security deployments
|
|
1090
|
+
* ✅ Key commitment token prevents key mismatch from going undetected
|
|
1091
|
+
* ✅ Sliding window replay protection tolerates mild network reordering (window=64)
|
|
1092
|
+
* ✅ Traffic analysis resistance — optional 64-byte block padding
|
|
1029
1093
|
*
|
|
1030
1094
|
* ## V2 ClientHello wire format (1249 bytes)
|
|
1031
1095
|
*
|
|
1032
|
-
* [1B
|
|
1033
|
-
* [32B
|
|
1034
|
-
* [32B
|
|
1035
|
-
* [1184B kemEphemeralPub] client's ephemeral ML-KEM-768 public key
|
|
1096
|
+
* [1B version ] = TRANSPORT_VERSION_V2 (2)
|
|
1097
|
+
* [32B ecdhEphemeralPub] client's ephemeral Curve25519 public key
|
|
1098
|
+
* [32B sessionSalt ] random HKDF salt (public)
|
|
1099
|
+
* [1184B kemEphemeralPub ] client's ephemeral ML-KEM-768 public key
|
|
1036
1100
|
*
|
|
1037
|
-
* ## V2 ServerHello wire format (1 + 1088 + 3309 =
|
|
1101
|
+
* ## V2 ServerHello wire format (1 + 1088 + 3309 + 32 = 4430 bytes)
|
|
1038
1102
|
*
|
|
1039
1103
|
* [1B version ] = TRANSPORT_VERSION_V2 (2)
|
|
1040
1104
|
* [1088B kemCiphertext ] ML-KEM-768 ciphertext (encapsulated by server to client's kemPub)
|
|
1041
1105
|
* [3309B signature ] ML-DSA-65 signature over (kemCiphertext || sessionSalt || ecdhEphemeralPub)
|
|
1042
1106
|
* — present only if server has a signing key; all zeros if absent
|
|
1107
|
+
* [32B keyCommitment ] HMAC-SHA3-256(c2sKey||s2cKey, "spd-transport-key-commit-v1")
|
|
1108
|
+
* — client verifies this to detect key mismatch / active MitM
|
|
1043
1109
|
*
|
|
1044
1110
|
* ## Usage
|
|
1045
1111
|
*
|
|
@@ -1101,6 +1167,12 @@ interface SPDClientConnectOptions {
|
|
|
1101
1167
|
* Provides an extra secret layer for PSK session resumption.
|
|
1102
1168
|
*/
|
|
1103
1169
|
psk?: Uint8Array;
|
|
1170
|
+
/**
|
|
1171
|
+
* Enable frame padding to hide payload sizes (traffic analysis resistance).
|
|
1172
|
+
* Pads plaintext to the next multiple of 64 bytes before encryption.
|
|
1173
|
+
* Default: false.
|
|
1174
|
+
*/
|
|
1175
|
+
padding?: boolean;
|
|
1104
1176
|
}
|
|
1105
1177
|
/**
|
|
1106
1178
|
* A live, established session between two peers.
|
|
@@ -1109,13 +1181,14 @@ interface SPDClientConnectOptions {
|
|
|
1109
1181
|
interface SPDSession {
|
|
1110
1182
|
/**
|
|
1111
1183
|
* Encrypt `plaintext` for transmission in this session's direction.
|
|
1112
|
-
* Returns a self-contained frame: [1B version][8B counter][
|
|
1184
|
+
* Returns a self-contained frame: [1B version][8B counter][ciphertext+tag]
|
|
1185
|
+
* Nonce is derived from counter (not transmitted) to save 24 bytes per frame.
|
|
1113
1186
|
*/
|
|
1114
1187
|
encrypt(plaintext: Uint8Array | Buffer): Buffer;
|
|
1115
1188
|
/**
|
|
1116
1189
|
* Decrypt a frame received from the remote peer.
|
|
1117
|
-
* Throws if the tag is invalid, the version is wrong, or the counter is
|
|
1118
|
-
*
|
|
1190
|
+
* Throws if the tag is invalid, the version is wrong, or the counter is
|
|
1191
|
+
* outside the 64-entry sliding replay window.
|
|
1119
1192
|
*/
|
|
1120
1193
|
decrypt(frame: Buffer): Buffer;
|
|
1121
1194
|
/**
|
|
@@ -1137,6 +1210,16 @@ interface SPDSession {
|
|
|
1137
1210
|
destroy(): void;
|
|
1138
1211
|
}
|
|
1139
1212
|
declare class SPDTransport {
|
|
1213
|
+
/**
|
|
1214
|
+
* When strict mode is enabled, `serverAccept` will reject any V2 ClientHello
|
|
1215
|
+
* unless the server identity has a signing key pair (ML-DSA-65), and the
|
|
1216
|
+
* server will always include a real signature in the ServerHello.
|
|
1217
|
+
* Clients must pin the signing key; unsigned ServerHellos will be rejected
|
|
1218
|
+
* by the client if it has a pinned signing key (standard behavior).
|
|
1219
|
+
*/
|
|
1220
|
+
private static _strictMode;
|
|
1221
|
+
static setStrictMode(enabled: boolean): void;
|
|
1222
|
+
static isStrictMode(): boolean;
|
|
1140
1223
|
/**
|
|
1141
1224
|
* Generate a long-term Curve25519 server identity key pair.
|
|
1142
1225
|
* Store `privateKey` securely (e.g. in a hardware key store or SPD vault).
|
|
@@ -1189,7 +1272,7 @@ declare class SPDTransport {
|
|
|
1189
1272
|
* @param clientHello The raw bytes sent by the client.
|
|
1190
1273
|
* @param psk Optional PSK (must match client's PSK for session to work).
|
|
1191
1274
|
*/
|
|
1192
|
-
static serverAccept(serverIdentity: SPDServerIdentity, clientHello: Buffer, psk?: Uint8Array): Promise<{
|
|
1275
|
+
static serverAccept(serverIdentity: SPDServerIdentity, clientHello: Buffer, psk?: Uint8Array, padding?: boolean): Promise<{
|
|
1193
1276
|
session: SPDSession;
|
|
1194
1277
|
serverHello?: Buffer;
|
|
1195
1278
|
}>;
|
package/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ interface SPDPayload {
|
|
|
28
28
|
argon2Time?: number;
|
|
29
29
|
argon2HashLen?: number;
|
|
30
30
|
hashKeyLen?: number;
|
|
31
|
+
argon2Parallelism?: number;
|
|
31
32
|
entryRoles?: Record<string, string[]>;
|
|
32
33
|
maxOpenCount?: number;
|
|
33
34
|
openCount?: number;
|
|
@@ -168,8 +169,8 @@ type SupportedValue = string | number | boolean | unknown[] | TypedArray | Map<u
|
|
|
168
169
|
|
|
169
170
|
declare const ARGON2_MEMORY_HIGH = 524288;
|
|
170
171
|
declare const ARGON2_TIME_HIGH = 8;
|
|
171
|
-
declare const ARGON2_MEMORY_PARANOID =
|
|
172
|
-
declare const ARGON2_TIME_PARANOID =
|
|
172
|
+
declare const ARGON2_MEMORY_PARANOID = 2097152;
|
|
173
|
+
declare const ARGON2_TIME_PARANOID = 16;
|
|
173
174
|
/** Key stretching profile names */
|
|
174
175
|
type SPDKeyProfile = 'standard' | 'high' | 'paranoid';
|
|
175
176
|
declare class SPD {
|
|
@@ -210,6 +211,7 @@ declare class SPD {
|
|
|
210
211
|
private _scryptN;
|
|
211
212
|
private _scryptR;
|
|
212
213
|
private _scryptP;
|
|
214
|
+
private _argon2Parallelism;
|
|
213
215
|
private _wrapArgon2Memory;
|
|
214
216
|
private _wrapArgon2Time;
|
|
215
217
|
private static globalLogger;
|
|
@@ -301,7 +303,7 @@ declare class SPD {
|
|
|
301
303
|
* v27+: hashLen=96, macKeyLen=64 (full SHA3-512 output width = 128-bit PQ forgery resistance)
|
|
302
304
|
* v26: hashLen=64, macKeyLen=32 (legacy — pass these explicitly when reading old files)
|
|
303
305
|
*/
|
|
304
|
-
static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number, kdfChain?: boolean, scryptN?: number, scryptR?: number, scryptP?: number): Promise<{
|
|
306
|
+
static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number, kdfChain?: boolean, scryptN?: number, scryptR?: number, scryptP?: number, parallelism?: number): Promise<{
|
|
305
307
|
aeadKey: Uint8Array;
|
|
306
308
|
macKey: Uint8Array;
|
|
307
309
|
}>;
|
|
@@ -312,6 +314,37 @@ declare class SPD {
|
|
|
312
314
|
* "decrypts" under two different keys.
|
|
313
315
|
*/
|
|
314
316
|
static computeKeyCommitment(aeadKey: Uint8Array): string;
|
|
317
|
+
/**
|
|
318
|
+
* CMT-4 block size: SHA3-256(key || nonce) prepended to every ciphertext.
|
|
319
|
+
* 32 bytes = one SHA3-256 output.
|
|
320
|
+
*/
|
|
321
|
+
private static readonly CMT_BLOCK_BYTES;
|
|
322
|
+
/**
|
|
323
|
+
* CMT-4 encrypt: encrypt plaintext with XChaCha20-Poly1305, then prepend
|
|
324
|
+
* H(key || nonce) so that the correct key is the *only* key that can produce
|
|
325
|
+
* a valid commitment. Closes the USENIX 2022 partitioning oracle attack.
|
|
326
|
+
*
|
|
327
|
+
* Stored layout: [ 32-byte cmt | xchacha20-poly1305 ciphertext ]
|
|
328
|
+
*/
|
|
329
|
+
private static aeadEncrypt;
|
|
330
|
+
/**
|
|
331
|
+
* CMT-4 decrypt: verify the prepended commitment block, then decrypt.
|
|
332
|
+
* Throws on commitment mismatch or AEAD authentication failure.
|
|
333
|
+
*/
|
|
334
|
+
private static aeadDecrypt;
|
|
335
|
+
/**
|
|
336
|
+
* Pad `data` to a multiple of `blockSize` bytes before compression.
|
|
337
|
+
* A 4-byte LE prefix records the original length so the receiver can strip padding.
|
|
338
|
+
* Using random padding bytes ensures the pad bytes themselves don't compress.
|
|
339
|
+
*
|
|
340
|
+
* This defeats CRIME/BREACH-class compression oracle attacks: the AEAD
|
|
341
|
+
* ciphertext length is now quantised to `blockSize`-byte steps regardless of
|
|
342
|
+
* plaintext content, so an attacker who can observe ciphertext lengths learns
|
|
343
|
+
* nothing about the plaintext.
|
|
344
|
+
*/
|
|
345
|
+
private static readonly COMPRESS_PAD_BLOCK;
|
|
346
|
+
private static padForCompression;
|
|
347
|
+
private static unpadAfterDecompression;
|
|
315
348
|
private static computeMAC;
|
|
316
349
|
/**
|
|
317
350
|
* Derives a per-entry 32-byte AEAD key from the master AEAD key using HKDF-SHA3-512.
|
|
@@ -323,7 +356,35 @@ declare class SPD {
|
|
|
323
356
|
* - Zero performance impact: HKDF is ~microseconds vs ~1s for Argon2id
|
|
324
357
|
*/
|
|
325
358
|
static deriveEntryKey(masterAeadKey: Uint8Array, entryName: string): Uint8Array;
|
|
359
|
+
/**
|
|
360
|
+
* Derive a dedicated 32-byte name-encryption key from the master AEAD key.
|
|
361
|
+
* Domain-separated from entry data keys via a distinct info string.
|
|
362
|
+
* Used to encrypt entry names in the binary index so the plaintext index
|
|
363
|
+
* does not leak entry names to an attacker who has the file but not the passcode.
|
|
364
|
+
*/
|
|
365
|
+
static deriveNameKey(masterAeadKey: Uint8Array): Uint8Array;
|
|
366
|
+
/**
|
|
367
|
+
* Encrypt an entry name.
|
|
368
|
+
* Returns [24-byte nonce || XChaCha20-Poly1305+CMT ciphertext].
|
|
369
|
+
*/
|
|
370
|
+
private static _encryptEntryName;
|
|
371
|
+
/**
|
|
372
|
+
* Decrypt an entry name.
|
|
373
|
+
* Expects [24-byte nonce || ciphertext] as produced by _encryptEntryName.
|
|
374
|
+
*/
|
|
375
|
+
private static _decryptEntryName;
|
|
376
|
+
/**
|
|
377
|
+
* Normalize l33t-speak substitutions so common bypasses are caught.
|
|
378
|
+
* Maps: @→a, 0→o, 3→e, 1→i, 5→s, $→s, !→i, 4→a, 7→t
|
|
379
|
+
*/
|
|
380
|
+
private static normalizeLeet;
|
|
326
381
|
checkPasscodeStrength(passcode: string): boolean;
|
|
382
|
+
/**
|
|
383
|
+
* Compute a bigram-frequency penalty factor [0, 0.5].
|
|
384
|
+
* Returns higher values for text heavy in common English bigrams.
|
|
385
|
+
* A factor of 0.3 means the entropy estimate is reduced by 30%.
|
|
386
|
+
*/
|
|
387
|
+
private static _bigramPenalty;
|
|
327
388
|
setPassKey(passcode: string): Promise<void>;
|
|
328
389
|
setHash(hash?: HashAlgorithm): void;
|
|
329
390
|
setCompressionLevel(level?: number): void;
|
|
@@ -762,7 +823,7 @@ declare class SPD {
|
|
|
762
823
|
static verify(filePath: string, passcode: string): Promise<SPDVerifyResult>;
|
|
763
824
|
static loadFromString(data: string, passcode: string): Promise<SPD>;
|
|
764
825
|
static loadFromChunks(chunks: string[], passcode: string): Promise<SPD>;
|
|
765
|
-
static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number): Promise<PBKResult>;
|
|
826
|
+
static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, parallelism?: number): Promise<PBKResult>;
|
|
766
827
|
static decryptSalt(encryptedSalt: number[], saltNonce: number[], wrapSalt: number[], passcode: string, memory?: number, time?: number): Promise<Uint8Array>;
|
|
767
828
|
static encryptSalt(salt: Uint8Array, passcode: string, memory?: number, time?: number): Promise<EncryptedSaltResult>;
|
|
768
829
|
static toBase64(data: Uint8Array | Buffer): string;
|
|
@@ -1026,20 +1087,25 @@ declare class SPDWriter {
|
|
|
1026
1087
|
* ✅ Active MitM — ML-DSA-65 certificate pinning + ECDH server auth
|
|
1027
1088
|
* ✅ Harvest-now-decrypt-later (quantum) — ML-KEM-768 hybrid
|
|
1028
1089
|
* ✅ PSK gives additional session binding layer for high-security deployments
|
|
1090
|
+
* ✅ Key commitment token prevents key mismatch from going undetected
|
|
1091
|
+
* ✅ Sliding window replay protection tolerates mild network reordering (window=64)
|
|
1092
|
+
* ✅ Traffic analysis resistance — optional 64-byte block padding
|
|
1029
1093
|
*
|
|
1030
1094
|
* ## V2 ClientHello wire format (1249 bytes)
|
|
1031
1095
|
*
|
|
1032
|
-
* [1B
|
|
1033
|
-
* [32B
|
|
1034
|
-
* [32B
|
|
1035
|
-
* [1184B kemEphemeralPub] client's ephemeral ML-KEM-768 public key
|
|
1096
|
+
* [1B version ] = TRANSPORT_VERSION_V2 (2)
|
|
1097
|
+
* [32B ecdhEphemeralPub] client's ephemeral Curve25519 public key
|
|
1098
|
+
* [32B sessionSalt ] random HKDF salt (public)
|
|
1099
|
+
* [1184B kemEphemeralPub ] client's ephemeral ML-KEM-768 public key
|
|
1036
1100
|
*
|
|
1037
|
-
* ## V2 ServerHello wire format (1 + 1088 + 3309 =
|
|
1101
|
+
* ## V2 ServerHello wire format (1 + 1088 + 3309 + 32 = 4430 bytes)
|
|
1038
1102
|
*
|
|
1039
1103
|
* [1B version ] = TRANSPORT_VERSION_V2 (2)
|
|
1040
1104
|
* [1088B kemCiphertext ] ML-KEM-768 ciphertext (encapsulated by server to client's kemPub)
|
|
1041
1105
|
* [3309B signature ] ML-DSA-65 signature over (kemCiphertext || sessionSalt || ecdhEphemeralPub)
|
|
1042
1106
|
* — present only if server has a signing key; all zeros if absent
|
|
1107
|
+
* [32B keyCommitment ] HMAC-SHA3-256(c2sKey||s2cKey, "spd-transport-key-commit-v1")
|
|
1108
|
+
* — client verifies this to detect key mismatch / active MitM
|
|
1043
1109
|
*
|
|
1044
1110
|
* ## Usage
|
|
1045
1111
|
*
|
|
@@ -1101,6 +1167,12 @@ interface SPDClientConnectOptions {
|
|
|
1101
1167
|
* Provides an extra secret layer for PSK session resumption.
|
|
1102
1168
|
*/
|
|
1103
1169
|
psk?: Uint8Array;
|
|
1170
|
+
/**
|
|
1171
|
+
* Enable frame padding to hide payload sizes (traffic analysis resistance).
|
|
1172
|
+
* Pads plaintext to the next multiple of 64 bytes before encryption.
|
|
1173
|
+
* Default: false.
|
|
1174
|
+
*/
|
|
1175
|
+
padding?: boolean;
|
|
1104
1176
|
}
|
|
1105
1177
|
/**
|
|
1106
1178
|
* A live, established session between two peers.
|
|
@@ -1109,13 +1181,14 @@ interface SPDClientConnectOptions {
|
|
|
1109
1181
|
interface SPDSession {
|
|
1110
1182
|
/**
|
|
1111
1183
|
* Encrypt `plaintext` for transmission in this session's direction.
|
|
1112
|
-
* Returns a self-contained frame: [1B version][8B counter][
|
|
1184
|
+
* Returns a self-contained frame: [1B version][8B counter][ciphertext+tag]
|
|
1185
|
+
* Nonce is derived from counter (not transmitted) to save 24 bytes per frame.
|
|
1113
1186
|
*/
|
|
1114
1187
|
encrypt(plaintext: Uint8Array | Buffer): Buffer;
|
|
1115
1188
|
/**
|
|
1116
1189
|
* Decrypt a frame received from the remote peer.
|
|
1117
|
-
* Throws if the tag is invalid, the version is wrong, or the counter is
|
|
1118
|
-
*
|
|
1190
|
+
* Throws if the tag is invalid, the version is wrong, or the counter is
|
|
1191
|
+
* outside the 64-entry sliding replay window.
|
|
1119
1192
|
*/
|
|
1120
1193
|
decrypt(frame: Buffer): Buffer;
|
|
1121
1194
|
/**
|
|
@@ -1137,6 +1210,16 @@ interface SPDSession {
|
|
|
1137
1210
|
destroy(): void;
|
|
1138
1211
|
}
|
|
1139
1212
|
declare class SPDTransport {
|
|
1213
|
+
/**
|
|
1214
|
+
* When strict mode is enabled, `serverAccept` will reject any V2 ClientHello
|
|
1215
|
+
* unless the server identity has a signing key pair (ML-DSA-65), and the
|
|
1216
|
+
* server will always include a real signature in the ServerHello.
|
|
1217
|
+
* Clients must pin the signing key; unsigned ServerHellos will be rejected
|
|
1218
|
+
* by the client if it has a pinned signing key (standard behavior).
|
|
1219
|
+
*/
|
|
1220
|
+
private static _strictMode;
|
|
1221
|
+
static setStrictMode(enabled: boolean): void;
|
|
1222
|
+
static isStrictMode(): boolean;
|
|
1140
1223
|
/**
|
|
1141
1224
|
* Generate a long-term Curve25519 server identity key pair.
|
|
1142
1225
|
* Store `privateKey` securely (e.g. in a hardware key store or SPD vault).
|
|
@@ -1189,7 +1272,7 @@ declare class SPDTransport {
|
|
|
1189
1272
|
* @param clientHello The raw bytes sent by the client.
|
|
1190
1273
|
* @param psk Optional PSK (must match client's PSK for session to work).
|
|
1191
1274
|
*/
|
|
1192
|
-
static serverAccept(serverIdentity: SPDServerIdentity, clientHello: Buffer, psk?: Uint8Array): Promise<{
|
|
1275
|
+
static serverAccept(serverIdentity: SPDServerIdentity, clientHello: Buffer, psk?: Uint8Array, padding?: boolean): Promise<{
|
|
1193
1276
|
session: SPDSession;
|
|
1194
1277
|
serverHello?: Buffer;
|
|
1195
1278
|
}>;
|