@vex-chat/libvex 5.5.2 → 6.0.1
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/Client.d.ts.map +1 -1
- package/dist/Client.js +95 -23
- package/dist/Client.js.map +1 -1
- package/dist/__tests__/harness/memory-storage.d.ts.map +1 -1
- package/dist/__tests__/harness/memory-storage.js +23 -1
- package/dist/__tests__/harness/memory-storage.js.map +1 -1
- package/dist/storage/schema.d.ts +10 -0
- package/dist/storage/schema.d.ts.map +1 -1
- package/dist/storage/sqlite.d.ts +3 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +121 -4
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/crypto.d.ts +11 -0
- package/dist/types/crypto.d.ts.map +1 -1
- package/dist/utils/ratchet.d.ts +73 -0
- package/dist/utils/ratchet.d.ts.map +1 -0
- package/dist/utils/ratchet.js +173 -0
- package/dist/utils/ratchet.js.map +1 -0
- package/dist/utils/sqlSessionToCrypto.d.ts.map +1 -1
- package/dist/utils/sqlSessionToCrypto.js +30 -0
- package/dist/utils/sqlSessionToCrypto.js.map +1 -1
- package/package.json +3 -3
- package/src/Client.ts +133 -33
- package/src/__tests__/harness/memory-storage.ts +23 -1
- package/src/__tests__/harness/shared-suite.ts +74 -0
- package/src/__tests__/ratchet.test.ts +481 -0
- package/src/storage/schema.ts +10 -0
- package/src/storage/sqlite.ts +131 -5
- package/src/types/crypto.ts +11 -0
- package/src/utils/ratchet.ts +287 -0
- package/src/utils/sqlSessionToCrypto.ts +30 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlSessionToCrypto.js","sourceRoot":"","sources":["../../src/utils/sqlSessionToCrypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,MAAM,UAAU,kBAAkB,CAAC,OAAmB;IAClD,OAAO;QACH,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC;QAClD,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,EAAE,OAAO,CAAC,MAAM;
|
|
1
|
+
{"version":3,"file":"sqlSessionToCrypto.js","sourceRoot":"","sources":["../../src/utils/sqlSessionToCrypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,MAAM,UAAU,kBAAkB,CAAC,OAAmB;IAClD,MAAM,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1D,OAAO;QACH,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QACvD,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QACvD,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QACvD,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC;QAChD,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9C,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC;QAClD,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9C,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,WAAW;QACX,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC7B,CAAC;AACN,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACjC,IAAI,CAAC;QACD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAChD,OAAO,EAAE,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACxB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACf,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vex-chat/libvex",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Library for communicating with xchat server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"msgpackr": "^1.11.9",
|
|
94
94
|
"uuid": "^14.0.0",
|
|
95
95
|
"zod": "^4.3.6",
|
|
96
|
-
"@vex-chat/
|
|
97
|
-
"@vex-chat/
|
|
96
|
+
"@vex-chat/types": "^3.0.0",
|
|
97
|
+
"@vex-chat/crypto": "^3.0.0"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
100
|
"better-sqlite3": ">=11.0.0"
|
package/src/Client.ts
CHANGED
|
@@ -80,14 +80,23 @@ import { z } from "zod/v4";
|
|
|
80
80
|
import { WebSocketAdapter } from "./transport/websocket.js";
|
|
81
81
|
import {
|
|
82
82
|
decodeFipsInitialExtraV1,
|
|
83
|
-
decodeFipsSubsequentExtraV1,
|
|
84
83
|
encodeFipsInitialExtraV1,
|
|
85
|
-
encodeFipsSubsequentExtraV1,
|
|
86
84
|
fipsP256AdFromIdentityPubs,
|
|
87
85
|
fipsP256PreKeySignPayload,
|
|
88
86
|
isFipsInitialExtraV1,
|
|
89
|
-
isFipsSubsequentExtraV1,
|
|
90
87
|
} from "./utils/fipsMailExtra.js";
|
|
88
|
+
import {
|
|
89
|
+
decodeRatchetHeader,
|
|
90
|
+
deriveBootstrapSendChain,
|
|
91
|
+
encodeRatchetHeader,
|
|
92
|
+
hasRemoteDhChanged,
|
|
93
|
+
initRatchetSession,
|
|
94
|
+
ratchetStepReceive,
|
|
95
|
+
ratchetStepSend,
|
|
96
|
+
sessionToSqlPatch,
|
|
97
|
+
takeReceiveMessageKey,
|
|
98
|
+
takeSendMessageKey,
|
|
99
|
+
} from "./utils/ratchet.js";
|
|
91
100
|
|
|
92
101
|
function debugLibvexDm(
|
|
93
102
|
msg: string,
|
|
@@ -1309,9 +1318,6 @@ export class Client {
|
|
|
1309
1318
|
return [signKey, ephKey, ad, index];
|
|
1310
1319
|
}
|
|
1311
1320
|
case MailType.subsequent:
|
|
1312
|
-
if (isFipsSubsequentExtraV1(extra)) {
|
|
1313
|
-
return [decodeFipsSubsequentExtraV1(extra)];
|
|
1314
|
-
}
|
|
1315
1321
|
return [extra];
|
|
1316
1322
|
default:
|
|
1317
1323
|
return [];
|
|
@@ -2094,7 +2100,9 @@ export class Client {
|
|
|
2094
2100
|
// discard the ephemeral keys
|
|
2095
2101
|
await this.newEphemeralKeys();
|
|
2096
2102
|
|
|
2103
|
+
const ratchet = await initRatchetSession(SK, "initiator");
|
|
2097
2104
|
const sessionEntry: SessionSQL = {
|
|
2105
|
+
...ratchet,
|
|
2098
2106
|
deviceID: device.deviceID,
|
|
2099
2107
|
fingerprint: XUtils.encodeHex(AD),
|
|
2100
2108
|
lastUsed: new Date().toISOString(),
|
|
@@ -2114,6 +2122,7 @@ export class Client {
|
|
|
2114
2122
|
const forwardedMsg = forward
|
|
2115
2123
|
? messageSchema.parse(msgpack.decode(message))
|
|
2116
2124
|
: null;
|
|
2125
|
+
const shouldEmitHandshakeMessage = forward || message.length > 0;
|
|
2117
2126
|
const emitMsg: Message = forwardedMsg
|
|
2118
2127
|
? { ...forwardedMsg, forward: true }
|
|
2119
2128
|
: {
|
|
@@ -2130,7 +2139,9 @@ export class Client {
|
|
|
2130
2139
|
sender: mail.sender,
|
|
2131
2140
|
timestamp: new Date().toISOString(),
|
|
2132
2141
|
};
|
|
2133
|
-
|
|
2142
|
+
if (shouldEmitHandshakeMessage) {
|
|
2143
|
+
this.emitter.emit("message", emitMsg);
|
|
2144
|
+
}
|
|
2134
2145
|
|
|
2135
2146
|
// send mail and wait for response
|
|
2136
2147
|
await new Promise((res, rej) => {
|
|
@@ -3220,7 +3231,11 @@ export class Client {
|
|
|
3220
3231
|
timestamp: timestamp,
|
|
3221
3232
|
};
|
|
3222
3233
|
|
|
3223
|
-
|
|
3234
|
+
const shouldEmitIncomingInitial =
|
|
3235
|
+
mail.forward || plaintext.length > 0;
|
|
3236
|
+
if (shouldEmitIncomingInitial) {
|
|
3237
|
+
this.emitter.emit("message", message);
|
|
3238
|
+
}
|
|
3224
3239
|
if (libvexDebugDmEnabled()) {
|
|
3225
3240
|
try {
|
|
3226
3241
|
debugLibvexDm(
|
|
@@ -3271,7 +3286,12 @@ export class Client {
|
|
|
3271
3286
|
deviceEntry;
|
|
3272
3287
|
|
|
3273
3288
|
// save session
|
|
3289
|
+
const ratchet = await initRatchetSession(
|
|
3290
|
+
SK,
|
|
3291
|
+
"receiver",
|
|
3292
|
+
);
|
|
3274
3293
|
const newSession: SessionSQL = {
|
|
3294
|
+
...ratchet,
|
|
3275
3295
|
deviceID: mail.sender,
|
|
3276
3296
|
fingerprint: XUtils.encodeHex(AD),
|
|
3277
3297
|
lastUsed: new Date().toISOString(),
|
|
@@ -3305,19 +3325,12 @@ export class Client {
|
|
|
3305
3325
|
}
|
|
3306
3326
|
break;
|
|
3307
3327
|
case MailType.subsequent: {
|
|
3308
|
-
const
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
)[0];
|
|
3315
|
-
if (!publicKey) {
|
|
3316
|
-
throw new Error(
|
|
3317
|
-
"Malformed subsequent mail extra: missing publicKey",
|
|
3318
|
-
);
|
|
3319
|
-
}
|
|
3320
|
-
let session = await this.getSessionByPubkey(publicKey);
|
|
3328
|
+
const ratchetHeader = decodeRatchetHeader(
|
|
3329
|
+
new Uint8Array(mail.extra),
|
|
3330
|
+
);
|
|
3331
|
+
let session = await this.database.getSessionByDeviceID(
|
|
3332
|
+
mail.sender,
|
|
3333
|
+
);
|
|
3321
3334
|
let retries = 0;
|
|
3322
3335
|
while (!session) {
|
|
3323
3336
|
if (retries >= 3) {
|
|
@@ -3325,14 +3338,44 @@ export class Client {
|
|
|
3325
3338
|
}
|
|
3326
3339
|
await sleep(100 * 2 ** retries);
|
|
3327
3340
|
retries++;
|
|
3328
|
-
session = await this.
|
|
3341
|
+
session = await this.database.getSessionByDeviceID(
|
|
3342
|
+
mail.sender,
|
|
3343
|
+
);
|
|
3329
3344
|
}
|
|
3330
3345
|
|
|
3331
3346
|
if (!session) {
|
|
3332
3347
|
void healSession();
|
|
3333
3348
|
return;
|
|
3334
3349
|
}
|
|
3335
|
-
|
|
3350
|
+
|
|
3351
|
+
const firstInboundFromSubsequent = !session.DHr;
|
|
3352
|
+
if (firstInboundFromSubsequent) {
|
|
3353
|
+
session.DHr = ratchetHeader.dhPub;
|
|
3354
|
+
// First inbound after X3DH initial mail has no prior DH ratchet.
|
|
3355
|
+
// If this side has no receiving chain yet (initiator path),
|
|
3356
|
+
// derive the bootstrap receive chain to match peer's first
|
|
3357
|
+
// bootstrap send chain.
|
|
3358
|
+
if (!session.CKr) {
|
|
3359
|
+
session.CKr = deriveBootstrapSendChain(
|
|
3360
|
+
session.RK,
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
} else if (
|
|
3364
|
+
hasRemoteDhChanged(session.DHr, ratchetHeader.dhPub)
|
|
3365
|
+
) {
|
|
3366
|
+
await ratchetStepReceive(
|
|
3367
|
+
session,
|
|
3368
|
+
ratchetHeader.dhPub,
|
|
3369
|
+
ratchetHeader.pn,
|
|
3370
|
+
);
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
const messageKey = takeReceiveMessageKey(
|
|
3374
|
+
session,
|
|
3375
|
+
ratchetHeader.dhPub,
|
|
3376
|
+
ratchetHeader.n,
|
|
3377
|
+
);
|
|
3378
|
+
const HMAC = xHMAC(mail, messageKey);
|
|
3336
3379
|
|
|
3337
3380
|
if (!XUtils.bytesEqual(HMAC, header)) {
|
|
3338
3381
|
void healSession();
|
|
@@ -3342,7 +3385,7 @@ export class Client {
|
|
|
3342
3385
|
const decrypted = await xSecretboxOpenAsync(
|
|
3343
3386
|
new Uint8Array(mail.cipher),
|
|
3344
3387
|
new Uint8Array(mail.nonce),
|
|
3345
|
-
|
|
3388
|
+
messageKey,
|
|
3346
3389
|
);
|
|
3347
3390
|
|
|
3348
3391
|
if (decrypted) {
|
|
@@ -3374,9 +3417,34 @@ export class Client {
|
|
|
3374
3417
|
};
|
|
3375
3418
|
this.emitter.emit("message", message);
|
|
3376
3419
|
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3420
|
+
const sqlPatch = sessionToSqlPatch(session);
|
|
3421
|
+
const persisted: SessionSQL = {
|
|
3422
|
+
CKr: sqlPatch.CKr,
|
|
3423
|
+
CKs: sqlPatch.CKs,
|
|
3424
|
+
deviceID: mail.sender,
|
|
3425
|
+
DHr: sqlPatch.DHr,
|
|
3426
|
+
DHsPrivate: sqlPatch.DHsPrivate,
|
|
3427
|
+
DHsPublic: sqlPatch.DHsPublic,
|
|
3428
|
+
fingerprint: XUtils.encodeHex(
|
|
3429
|
+
session.fingerprint,
|
|
3430
|
+
),
|
|
3431
|
+
lastUsed: new Date().toISOString(),
|
|
3432
|
+
mode: session.mode,
|
|
3433
|
+
Nr: sqlPatch.Nr,
|
|
3434
|
+
Ns: sqlPatch.Ns,
|
|
3435
|
+
PN: sqlPatch.PN,
|
|
3436
|
+
publicKey: XUtils.encodeHex(session.publicKey),
|
|
3437
|
+
RK: sqlPatch.RK,
|
|
3438
|
+
sessionID: session.sessionID,
|
|
3439
|
+
SK: XUtils.encodeHex(session.SK),
|
|
3440
|
+
skippedKeys: sqlPatch.skippedKeys,
|
|
3441
|
+
userID: session.userID,
|
|
3442
|
+
verified: session.verified,
|
|
3443
|
+
};
|
|
3444
|
+
await this.database.saveSession(persisted);
|
|
3445
|
+
this.sessionRecords[
|
|
3446
|
+
XUtils.encodeHex(session.publicKey)
|
|
3447
|
+
] = session;
|
|
3380
3448
|
} else {
|
|
3381
3449
|
void healSession();
|
|
3382
3450
|
this.emitter.emit("retryRequest", {
|
|
@@ -3718,12 +3786,19 @@ export class Client {
|
|
|
3718
3786
|
});
|
|
3719
3787
|
}
|
|
3720
3788
|
|
|
3789
|
+
if (!session.CKs) {
|
|
3790
|
+
await ratchetStepSend(session);
|
|
3791
|
+
}
|
|
3792
|
+
const { messageKey, n } = takeSendMessageKey(session);
|
|
3793
|
+
const ratchetHeader = {
|
|
3794
|
+
dhPub: session.DHsPublic,
|
|
3795
|
+
n,
|
|
3796
|
+
pn: session.PN,
|
|
3797
|
+
version: 1 as const,
|
|
3798
|
+
};
|
|
3721
3799
|
const nonce = xMakeNonce();
|
|
3722
|
-
const cipher = await xSecretboxAsync(msg, nonce,
|
|
3723
|
-
const extra =
|
|
3724
|
-
this.cryptoProfile === "fips"
|
|
3725
|
-
? encodeFipsSubsequentExtraV1(session.publicKey)
|
|
3726
|
-
: session.publicKey;
|
|
3800
|
+
const cipher = await xSecretboxAsync(msg, nonce, messageKey);
|
|
3801
|
+
const extra = encodeRatchetHeader(ratchetHeader);
|
|
3727
3802
|
|
|
3728
3803
|
const mail: MailWS = {
|
|
3729
3804
|
authorID: this.getUser().userID,
|
|
@@ -3747,7 +3822,7 @@ export class Client {
|
|
|
3747
3822
|
type: "resource",
|
|
3748
3823
|
};
|
|
3749
3824
|
|
|
3750
|
-
const hmac = xHMAC(mail,
|
|
3825
|
+
const hmac = xHMAC(mail, messageKey);
|
|
3751
3826
|
|
|
3752
3827
|
const fwdOut = forward
|
|
3753
3828
|
? messageSchema.parse(msgpack.decode(msg))
|
|
@@ -3770,6 +3845,31 @@ export class Client {
|
|
|
3770
3845
|
};
|
|
3771
3846
|
this.emitter.emit("message", outMsg);
|
|
3772
3847
|
|
|
3848
|
+
const sqlPatch = sessionToSqlPatch(session);
|
|
3849
|
+
const persisted: SessionSQL = {
|
|
3850
|
+
CKr: sqlPatch.CKr,
|
|
3851
|
+
CKs: sqlPatch.CKs,
|
|
3852
|
+
deviceID: device.deviceID,
|
|
3853
|
+
DHr: sqlPatch.DHr,
|
|
3854
|
+
DHsPrivate: sqlPatch.DHsPrivate,
|
|
3855
|
+
DHsPublic: sqlPatch.DHsPublic,
|
|
3856
|
+
fingerprint: XUtils.encodeHex(session.fingerprint),
|
|
3857
|
+
lastUsed: new Date().toISOString(),
|
|
3858
|
+
mode: session.mode,
|
|
3859
|
+
Nr: sqlPatch.Nr,
|
|
3860
|
+
Ns: sqlPatch.Ns,
|
|
3861
|
+
PN: sqlPatch.PN,
|
|
3862
|
+
publicKey: XUtils.encodeHex(session.publicKey),
|
|
3863
|
+
RK: sqlPatch.RK,
|
|
3864
|
+
sessionID: session.sessionID,
|
|
3865
|
+
SK: XUtils.encodeHex(session.SK),
|
|
3866
|
+
skippedKeys: sqlPatch.skippedKeys,
|
|
3867
|
+
userID: session.userID,
|
|
3868
|
+
verified: session.verified,
|
|
3869
|
+
};
|
|
3870
|
+
await this.database.saveSession(persisted);
|
|
3871
|
+
this.sessionRecords[XUtils.encodeHex(session.publicKey)] = session;
|
|
3872
|
+
|
|
3773
3873
|
await new Promise((res, rej) => {
|
|
3774
3874
|
const callback = (packedMsg: Uint8Array) => {
|
|
3775
3875
|
const [_header, receivedMsg] =
|
|
@@ -234,7 +234,12 @@ export class MemoryStorage extends EventEmitter implements Storage {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
saveSession(session: SessionSQL): Promise<void> {
|
|
237
|
-
|
|
237
|
+
const idx = this.sessions.findIndex(
|
|
238
|
+
(s) => s.sessionID === session.sessionID,
|
|
239
|
+
);
|
|
240
|
+
if (idx >= 0) {
|
|
241
|
+
this.sessions[idx] = session;
|
|
242
|
+
} else {
|
|
238
243
|
this.sessions.push(session);
|
|
239
244
|
}
|
|
240
245
|
return Promise.resolve();
|
|
@@ -261,14 +266,31 @@ export class MemoryStorage extends EventEmitter implements Storage {
|
|
|
261
266
|
}
|
|
262
267
|
|
|
263
268
|
private sqlToCrypto(s: SessionSQL): SessionCrypto {
|
|
269
|
+
let skippedKeys: Record<string, string> = {};
|
|
270
|
+
try {
|
|
271
|
+
skippedKeys = JSON.parse(s.skippedKeys) as Record<string, string>;
|
|
272
|
+
} catch {
|
|
273
|
+
skippedKeys = {};
|
|
274
|
+
}
|
|
264
275
|
return {
|
|
276
|
+
CKr: s.CKr ? XUtils.decodeHex(s.CKr) : null,
|
|
277
|
+
CKs: s.CKs ? XUtils.decodeHex(s.CKs) : null,
|
|
278
|
+
DHr: s.DHr ? XUtils.decodeHex(s.DHr) : null,
|
|
279
|
+
DHsPrivate: XUtils.decodeHex(s.DHsPrivate),
|
|
280
|
+
DHsPublic: XUtils.decodeHex(s.DHsPublic),
|
|
265
281
|
fingerprint: XUtils.decodeHex(s.fingerprint),
|
|
266
282
|
lastUsed: s.lastUsed,
|
|
267
283
|
mode: s.mode,
|
|
284
|
+
Nr: s.Nr,
|
|
285
|
+
Ns: s.Ns,
|
|
286
|
+
PN: s.PN,
|
|
268
287
|
publicKey: XUtils.decodeHex(s.publicKey),
|
|
288
|
+
RK: XUtils.decodeHex(s.RK),
|
|
269
289
|
sessionID: s.sessionID,
|
|
270
290
|
SK: XUtils.decodeHex(s.SK),
|
|
291
|
+
skippedKeys,
|
|
271
292
|
userID: s.userID,
|
|
293
|
+
verified: s.verified,
|
|
272
294
|
};
|
|
273
295
|
}
|
|
274
296
|
}
|
|
@@ -126,6 +126,80 @@ export function platformSuite(
|
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
test("two-user DM round-trip decrypts on both clients", async () => {
|
|
130
|
+
const SK1 = await e2eGenerateSecretKey();
|
|
131
|
+
const opts1: ClientOptions = e2eClientOptionsBase();
|
|
132
|
+
const storage1 = await makeStorage(SK1, opts1);
|
|
133
|
+
const client1 = await Client.create(SK1, opts1, storage1);
|
|
134
|
+
const username1 = Client.randomUsername();
|
|
135
|
+
const password1 = "test-pw-1";
|
|
136
|
+
|
|
137
|
+
const SK2 = await e2eGenerateSecretKey();
|
|
138
|
+
const opts2: ClientOptions = e2eClientOptionsBase();
|
|
139
|
+
const storage2 = await makeStorage(SK2, opts2);
|
|
140
|
+
const client2 = await Client.create(SK2, opts2, storage2);
|
|
141
|
+
const username2 = Client.randomUsername();
|
|
142
|
+
const password2 = "test-pw-2";
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const [_user1, regErr1] = await client1.register(
|
|
146
|
+
username1,
|
|
147
|
+
password1,
|
|
148
|
+
);
|
|
149
|
+
expect(regErr1).toBeNull();
|
|
150
|
+
|
|
151
|
+
const loginErr1 = await client1.login(username1, password1);
|
|
152
|
+
expect(loginErr1.ok).toBe(true);
|
|
153
|
+
await connectAndWait(client1, "client1-roundtrip");
|
|
154
|
+
|
|
155
|
+
const [user2, regErr] = await client2.register(
|
|
156
|
+
username2,
|
|
157
|
+
password2,
|
|
158
|
+
);
|
|
159
|
+
expect(regErr).toBeNull();
|
|
160
|
+
|
|
161
|
+
const loginErr = await client2.login(username2, password2);
|
|
162
|
+
expect(loginErr.ok).toBe(true);
|
|
163
|
+
await connectAndWait(client2, "client2-roundtrip");
|
|
164
|
+
|
|
165
|
+
const outbound1 = "roundtrip u1->u2";
|
|
166
|
+
const receiveOnClient2 = waitForMessage(
|
|
167
|
+
client2,
|
|
168
|
+
(m) =>
|
|
169
|
+
m.direction === "incoming" &&
|
|
170
|
+
m.authorID === client1.me.user().userID &&
|
|
171
|
+
m.message === outbound1,
|
|
172
|
+
`[${platformName}] roundtrip receive on client2`,
|
|
173
|
+
15_000,
|
|
174
|
+
);
|
|
175
|
+
await client1.messages.send(user2!.userID, outbound1);
|
|
176
|
+
const inbound2 = await receiveOnClient2;
|
|
177
|
+
expect(inbound2.decrypted).toBe(true);
|
|
178
|
+
expect(inbound2.message).toBe(outbound1);
|
|
179
|
+
|
|
180
|
+
const outbound2 = "roundtrip u2->u1";
|
|
181
|
+
const receiveOnClient1 = waitForMessage(
|
|
182
|
+
client1,
|
|
183
|
+
(m) =>
|
|
184
|
+
m.direction === "incoming" &&
|
|
185
|
+
m.authorID === user2!.userID &&
|
|
186
|
+
m.message === outbound2,
|
|
187
|
+
`[${platformName}] roundtrip receive on client1`,
|
|
188
|
+
15_000,
|
|
189
|
+
);
|
|
190
|
+
await client2.messages.send(
|
|
191
|
+
client1.me.user().userID,
|
|
192
|
+
outbound2,
|
|
193
|
+
);
|
|
194
|
+
const inbound1 = await receiveOnClient1;
|
|
195
|
+
expect(inbound1.decrypted).toBe(true);
|
|
196
|
+
expect(inbound1.message).toBe(outbound2);
|
|
197
|
+
} finally {
|
|
198
|
+
await client1.close().catch(() => {});
|
|
199
|
+
await client2.close().catch(() => {});
|
|
200
|
+
}
|
|
201
|
+
}, 60_000);
|
|
202
|
+
|
|
129
203
|
test("group messaging in channel", async () => {
|
|
130
204
|
const SK2 = await e2eGenerateSecretKey();
|
|
131
205
|
const opts2: ClientOptions = e2eClientOptionsBase();
|