@freesignal/protocol 0.6.1 → 0.7.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/double-ratchet.d.ts +13 -4
- package/dist/double-ratchet.js +38 -34
- package/dist/index.d.ts +1 -17
- package/dist/index.js +0 -21
- package/dist/node.d.ts +33 -25
- package/dist/node.js +101 -83
- package/dist/types.d.ts +17 -6
- package/dist/types.js +45 -61
- package/dist/x3dh.js +2 -4
- package/package.json +3 -2
- package/dist/test.d.ts +0 -1
- package/dist/test.js +0 -45
package/dist/double-ratchet.d.ts
CHANGED
|
@@ -20,9 +20,11 @@ import { IdentityKey, UserId } from "./types";
|
|
|
20
20
|
export interface ExportedKeySession {
|
|
21
21
|
identityKey: string;
|
|
22
22
|
secretKey: string;
|
|
23
|
-
rootKey
|
|
23
|
+
rootKey: string;
|
|
24
24
|
sendingChain?: ExportedKeyChain;
|
|
25
25
|
receivingChain?: ExportedKeyChain;
|
|
26
|
+
headerKey?: string;
|
|
27
|
+
headerKeys: [string, Uint8Array][];
|
|
26
28
|
previousKeys: [string, Uint8Array][];
|
|
27
29
|
}
|
|
28
30
|
export interface EncryptionKeys {
|
|
@@ -44,19 +46,24 @@ export declare class KeySession {
|
|
|
44
46
|
static readonly maxCount = 65536;
|
|
45
47
|
readonly identityKey: IdentityKey;
|
|
46
48
|
private keyPair;
|
|
47
|
-
private rootKey
|
|
49
|
+
private rootKey;
|
|
48
50
|
private sendingChain?;
|
|
49
51
|
private receivingChain?;
|
|
52
|
+
private readonly headerKeys;
|
|
53
|
+
private headerKey?;
|
|
50
54
|
private nextHeaderKey?;
|
|
51
55
|
private previousKeys;
|
|
52
|
-
constructor({ identityKey, secretKey, remoteKey, rootKey }: {
|
|
56
|
+
constructor({ identityKey, secretKey, remoteKey, rootKey, headerKey, nextHeaderKey }: {
|
|
53
57
|
identityKey: IdentityKey;
|
|
54
58
|
secretKey?: Uint8Array;
|
|
55
59
|
remoteKey?: Uint8Array;
|
|
56
|
-
rootKey
|
|
60
|
+
rootKey: Uint8Array;
|
|
61
|
+
headerKey?: Uint8Array;
|
|
62
|
+
nextHeaderKey?: Uint8Array;
|
|
57
63
|
});
|
|
58
64
|
get userId(): UserId;
|
|
59
65
|
private getChain;
|
|
66
|
+
getHeaderKey(hash?: string): Uint8Array | undefined;
|
|
60
67
|
getSendingKey(): PrivateEncryptionKeys | undefined;
|
|
61
68
|
getReceivingKey(encryptionKeys: EncryptionKeys): Uint8Array | undefined;
|
|
62
69
|
/**
|
|
@@ -83,6 +90,8 @@ interface ExportedKeyChain {
|
|
|
83
90
|
publicKey: string;
|
|
84
91
|
remoteKey: string;
|
|
85
92
|
chainKey: string;
|
|
93
|
+
headerKey?: string;
|
|
94
|
+
nextHeaderKey: string;
|
|
86
95
|
count: number;
|
|
87
96
|
previousCount: number;
|
|
88
97
|
}
|
package/dist/double-ratchet.js
CHANGED
|
@@ -30,41 +30,40 @@ const types_1 = require("./types");
|
|
|
30
30
|
* Used for forward-secure encryption and decryption of messages.
|
|
31
31
|
*/
|
|
32
32
|
class KeySession {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
constructor({ identityKey, secretKey, remoteKey, rootKey, headerKey, nextHeaderKey }) {
|
|
34
|
+
this.headerKeys = new Map();
|
|
35
35
|
this.previousKeys = new KeyMap();
|
|
36
36
|
this.identityKey = identityKey;
|
|
37
37
|
this.keyPair = crypto_1.default.ECDH.keyPair(secretKey);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
this.rootKey = rootKey;
|
|
39
|
+
if (headerKey)
|
|
40
|
+
this.headerKey = headerKey;
|
|
41
|
+
if (nextHeaderKey) {
|
|
42
|
+
this.nextHeaderKey = nextHeaderKey;
|
|
43
|
+
this.headerKeys.set((0, utils_1.decodeBase64)(crypto_1.default.hash(nextHeaderKey)), nextHeaderKey);
|
|
44
|
+
}
|
|
42
45
|
if (remoteKey) {
|
|
43
|
-
this.sendingChain = this.getChain(remoteKey
|
|
46
|
+
this.sendingChain = this.getChain(remoteKey, this.headerKey);
|
|
47
|
+
this.headerKey = undefined;
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
get userId() {
|
|
47
51
|
return this.identityKey.userId;
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
getChain(remoteKey, previousCount) {
|
|
53
|
+
getChain(remoteKey, headerKey, previousCount) {
|
|
51
54
|
const sharedKey = crypto_1.default.ECDH.scalarMult(this.keyPair.secretKey, remoteKey);
|
|
52
55
|
if (!this.rootKey)
|
|
53
56
|
this.rootKey = crypto_1.default.hash(sharedKey);
|
|
54
57
|
const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, KeySession.info, KeySession.keyLength * 3);
|
|
55
58
|
this.rootKey = hashkey.subarray(0, KeySession.keyLength);
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
return new KeyChain(this.publicKey, remoteKey, hashkey.subarray(KeySession.keyLength, KeySession.keyLength * 2), hashkey.subarray(KeySession.keyLength * 2), headerKey, previousCount);
|
|
60
|
+
}
|
|
61
|
+
getHeaderKey(hash) {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
if (!hash)
|
|
64
|
+
return (_a = this.headerKey) !== null && _a !== void 0 ? _a : (_b = this.sendingChain) === null || _b === void 0 ? void 0 : _b.headerKey;
|
|
65
|
+
return this.headerKeys.get(hash);
|
|
58
66
|
}
|
|
59
|
-
/*public getHeaderKeys(): {
|
|
60
|
-
readonly sending?: Uint8Array,
|
|
61
|
-
readonly receiving?: Uint8Array
|
|
62
|
-
} {
|
|
63
|
-
return {
|
|
64
|
-
sending: this.sendingChain?.headerKey,
|
|
65
|
-
receiving: (this.receivingChain?.headerKey ?? this.receivingChain?.nextHeaderKey) ?? this.nextHeaderKey
|
|
66
|
-
}
|
|
67
|
-
}*/
|
|
68
67
|
getSendingKey() {
|
|
69
68
|
if (!this.sendingChain)
|
|
70
69
|
return;
|
|
@@ -78,19 +77,21 @@ class KeySession {
|
|
|
78
77
|
};
|
|
79
78
|
}
|
|
80
79
|
getReceivingKey(encryptionKeys) {
|
|
81
|
-
var _a, _b, _c, _d;
|
|
80
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
82
81
|
if (!this.previousKeys.has((0, utils_1.decodeBase64)(encryptionKeys.publicKey) + encryptionKeys.count.toString())) {
|
|
83
82
|
if (!(0, utils_1.compareBytes)(encryptionKeys.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
|
|
84
83
|
while (this.receivingChain && this.receivingChain.count < encryptionKeys.previous) {
|
|
85
84
|
const key = this.receivingChain.getKey();
|
|
86
85
|
this.previousKeys.set((0, utils_1.decodeBase64)(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
|
|
87
86
|
}
|
|
88
|
-
|
|
89
|
-
this.
|
|
90
|
-
this.nextHeaderKey
|
|
87
|
+
this.receivingChain = this.getChain(encryptionKeys.publicKey, (_c = this.nextHeaderKey) !== null && _c !== void 0 ? _c : (_d = this.receivingChain) === null || _d === void 0 ? void 0 : _d.nextHeaderKey, (_e = this.receivingChain) === null || _e === void 0 ? void 0 : _e.count);
|
|
88
|
+
this.headerKeys.set((0, utils_1.decodeBase64)(crypto_1.default.hash(this.receivingChain.nextHeaderKey)), this.receivingChain.nextHeaderKey);
|
|
89
|
+
if (this.nextHeaderKey)
|
|
90
|
+
this.nextHeaderKey = undefined;
|
|
91
91
|
this.keyPair = crypto_1.default.ECDH.keyPair();
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
this.sendingChain = this.getChain(encryptionKeys.publicKey, (_f = this.headerKey) !== null && _f !== void 0 ? _f : (_g = this.sendingChain) === null || _g === void 0 ? void 0 : _g.nextHeaderKey, (_h = this.sendingChain) === null || _h === void 0 ? void 0 : _h.count);
|
|
93
|
+
if (this.headerKey)
|
|
94
|
+
this.headerKey = undefined;
|
|
94
95
|
}
|
|
95
96
|
if (!this.receivingChain)
|
|
96
97
|
throw new Error("Error initializing receivingChain");
|
|
@@ -117,9 +118,11 @@ class KeySession {
|
|
|
117
118
|
return {
|
|
118
119
|
identityKey: this.identityKey.toString(),
|
|
119
120
|
secretKey: (0, utils_1.decodeBase64)(this.keyPair.secretKey),
|
|
120
|
-
rootKey:
|
|
121
|
+
rootKey: (0, utils_1.decodeBase64)(this.rootKey),
|
|
121
122
|
sendingChain: (_a = this.sendingChain) === null || _a === void 0 ? void 0 : _a.toJSON(),
|
|
122
123
|
receivingChain: (_b = this.receivingChain) === null || _b === void 0 ? void 0 : _b.toJSON(),
|
|
124
|
+
headerKey: this.headerKey ? (0, utils_1.decodeBase64)(this.headerKey) : undefined,
|
|
125
|
+
headerKeys: Array.from(this.headerKeys.entries()),
|
|
123
126
|
previousKeys: Array.from(this.previousKeys.entries())
|
|
124
127
|
};
|
|
125
128
|
}
|
|
@@ -130,7 +133,7 @@ class KeySession {
|
|
|
130
133
|
* @returns session with the state parsed.
|
|
131
134
|
*/
|
|
132
135
|
static from(data) {
|
|
133
|
-
const session = new KeySession({ identityKey: types_1.IdentityKey.from(data.identityKey), secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey:
|
|
136
|
+
const session = new KeySession({ identityKey: types_1.IdentityKey.from(data.identityKey), secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: (0, utils_1.encodeBase64)(data.rootKey) });
|
|
134
137
|
session.sendingChain = data.sendingChain ? KeyChain.from(data.sendingChain) : undefined;
|
|
135
138
|
session.receivingChain = data.receivingChain ? KeyChain.from(data.receivingChain) : undefined;
|
|
136
139
|
session.previousKeys = new KeyMap(data.previousKeys);
|
|
@@ -143,11 +146,12 @@ KeySession.version = 1;
|
|
|
143
146
|
KeySession.info = "/freesignal/double-ratchet/v0." + KeySession.version;
|
|
144
147
|
KeySession.maxCount = 65536;
|
|
145
148
|
class KeyChain {
|
|
146
|
-
|
|
147
|
-
constructor(publicKey, remoteKey, chainKey, previousCount = 0) {
|
|
149
|
+
constructor(publicKey, remoteKey, chainKey, nextHeaderKey, headerKey, previousCount = 0) {
|
|
148
150
|
this.publicKey = publicKey;
|
|
149
151
|
this.remoteKey = remoteKey;
|
|
150
152
|
this.chainKey = chainKey;
|
|
153
|
+
this.nextHeaderKey = nextHeaderKey;
|
|
154
|
+
this.headerKey = headerKey;
|
|
151
155
|
this.previousCount = previousCount;
|
|
152
156
|
this._count = 0;
|
|
153
157
|
}
|
|
@@ -168,16 +172,16 @@ class KeyChain {
|
|
|
168
172
|
return {
|
|
169
173
|
publicKey: (0, utils_1.decodeBase64)(this.publicKey),
|
|
170
174
|
remoteKey: (0, utils_1.decodeBase64)(this.remoteKey),
|
|
175
|
+
headerKey: this.headerKey ? (0, utils_1.decodeBase64)(this.headerKey) : undefined,
|
|
176
|
+
nextHeaderKey: (0, utils_1.decodeBase64)(this.nextHeaderKey),
|
|
171
177
|
chainKey: (0, utils_1.decodeBase64)(this.chainKey),
|
|
172
|
-
//nextHeaderKey: decodeBase64(this.nextHeaderKey),
|
|
173
|
-
//headerKey: this.headerKey ? decodeBase64(this.headerKey) : undefined,
|
|
174
178
|
count: this.count,
|
|
175
179
|
previousCount: this.previousCount
|
|
176
180
|
};
|
|
177
181
|
}
|
|
178
182
|
static from(obj) {
|
|
179
|
-
//
|
|
180
|
-
const chain = new KeyChain((0, utils_1.encodeBase64)(obj.publicKey), (0, utils_1.encodeBase64)(obj.remoteKey), (0, utils_1.encodeBase64)(obj.chainKey), obj.previousCount);
|
|
183
|
+
//
|
|
184
|
+
const chain = new KeyChain((0, utils_1.encodeBase64)(obj.publicKey), (0, utils_1.encodeBase64)(obj.remoteKey), (0, utils_1.encodeBase64)(obj.chainKey), (0, utils_1.encodeBase64)(obj.nextHeaderKey), obj.headerKey ? (0, utils_1.encodeBase64)(obj.headerKey) : undefined, obj.previousCount);
|
|
181
185
|
chain._count = obj.count;
|
|
182
186
|
return chain;
|
|
183
187
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,23 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { LocalStorage, Crypto, Database, KeyExchangeDataBundle } from "@freesignal/interfaces";
|
|
20
20
|
import { ExportedKeySession } from "./double-ratchet";
|
|
21
|
-
import {
|
|
21
|
+
import { PrivateIdentityKey } from "./types";
|
|
22
22
|
import { BootstrapRequest, FreeSignalNode } from "./node";
|
|
23
|
-
/**
|
|
24
|
-
* Creates a new Double Ratchet session for secure message exchange.
|
|
25
|
-
*
|
|
26
|
-
* @param opts - Optional parameters for session initialization.
|
|
27
|
-
* @param opts.secretKey - The local party's secret key as a Uint8Array.
|
|
28
|
-
* @param opts.remoteKey - The remote party's public key as a Uint8Array.
|
|
29
|
-
* @param opts.rootKey - An optional root key to initialize the session.
|
|
30
|
-
* @returns A new instance of {@link KeySession}.
|
|
31
|
-
*/
|
|
32
|
-
/**
|
|
33
|
-
* Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
|
|
34
|
-
*
|
|
35
|
-
* @param storage - Local storage for keys.
|
|
36
|
-
* @returns A new instance of {@link KeyExchange}.
|
|
37
|
-
*/
|
|
38
23
|
/**
|
|
39
24
|
* Generates identity key
|
|
40
25
|
*
|
|
@@ -46,7 +31,6 @@ export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
|
|
|
46
31
|
export declare function createNode(storage: Database<{
|
|
47
32
|
sessions: LocalStorage<string, ExportedKeySession>;
|
|
48
33
|
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
49
|
-
users: LocalStorage<string, IdentityKey>;
|
|
50
34
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
51
35
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
52
36
|
}>, privateIdentityKey?: PrivateIdentityKey): FreeSignalNode;
|
package/dist/index.js
CHANGED
|
@@ -40,27 +40,6 @@ exports.createNode = createNode;
|
|
|
40
40
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
41
41
|
const types_1 = require("./types");
|
|
42
42
|
const node_1 = require("./node");
|
|
43
|
-
/**
|
|
44
|
-
* Creates a new Double Ratchet session for secure message exchange.
|
|
45
|
-
*
|
|
46
|
-
* @param opts - Optional parameters for session initialization.
|
|
47
|
-
* @param opts.secretKey - The local party's secret key as a Uint8Array.
|
|
48
|
-
* @param opts.remoteKey - The remote party's public key as a Uint8Array.
|
|
49
|
-
* @param opts.rootKey - An optional root key to initialize the session.
|
|
50
|
-
* @returns A new instance of {@link KeySession}.
|
|
51
|
-
*/
|
|
52
|
-
/*export function createKeySession(storage: LocalStorage<string, ExportedKeySession>, opts?: { secretKey?: Uint8Array, remoteKey?: Uint8Array, rootKey?: Uint8Array }): KeySession {
|
|
53
|
-
return new KeySession(storage, opts);
|
|
54
|
-
}*/
|
|
55
|
-
/**
|
|
56
|
-
* Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
|
|
57
|
-
*
|
|
58
|
-
* @param storage - Local storage for keys.
|
|
59
|
-
* @returns A new instance of {@link KeyExchange}.
|
|
60
|
-
*/
|
|
61
|
-
/*export function createKeyExchange(storage: { keys: LocalStorage<string, Crypto.KeyPair>, sessions: LocalStorage<string, ExportedKeySession> }, privateIdentityKey?: PrivateIdentityKey): KeyExchange {
|
|
62
|
-
return new KeyExchange(storage, privateIdentityKey);
|
|
63
|
-
}*/
|
|
64
43
|
/**
|
|
65
44
|
* Generates identity key
|
|
66
45
|
*
|
package/dist/node.d.ts
CHANGED
|
@@ -20,16 +20,27 @@ import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData
|
|
|
20
20
|
import { Datagram, DatagramHeader, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
|
|
21
21
|
import { KeyExchange } from "./x3dh";
|
|
22
22
|
import { ExportedKeySession, KeySession } from "./double-ratchet";
|
|
23
|
-
|
|
23
|
+
import EventEmitter, { EventCall } from "easyemitter.ts";
|
|
24
|
+
export declare class BootstrapRequest extends EventEmitter<'change', BootstrapRequest> {
|
|
24
25
|
#private;
|
|
25
26
|
readonly senderId: UserId | string;
|
|
26
|
-
private readonly
|
|
27
|
-
constructor(senderId: UserId | string,
|
|
27
|
+
private readonly keyExchangeData;
|
|
28
|
+
constructor(senderId: UserId | string, keyExchangeData: KeyExchangeData);
|
|
29
|
+
onChange: EventCall<'change', BootstrapRequest>;
|
|
28
30
|
get status(): "pending" | "accepted" | "denied";
|
|
29
|
-
get():
|
|
30
|
-
accept():
|
|
31
|
+
get data(): KeyExchangeData | undefined;
|
|
32
|
+
accept(): void;
|
|
31
33
|
deny(): void;
|
|
32
34
|
}
|
|
35
|
+
type NodeEventData = {
|
|
36
|
+
header: DatagramHeader;
|
|
37
|
+
payload?: Uint8Array;
|
|
38
|
+
datagram?: Datagram;
|
|
39
|
+
};
|
|
40
|
+
type MessageEventData = {
|
|
41
|
+
header: DatagramHeader;
|
|
42
|
+
payload: Uint8Array;
|
|
43
|
+
};
|
|
33
44
|
export declare class FreeSignalNode {
|
|
34
45
|
protected readonly privateIdentityKey: PrivateIdentityKey;
|
|
35
46
|
protected readonly sessions: SessionMap;
|
|
@@ -37,36 +48,33 @@ export declare class FreeSignalNode {
|
|
|
37
48
|
protected readonly keyExchange: KeyExchange;
|
|
38
49
|
protected readonly discovers: Set<string>;
|
|
39
50
|
protected readonly bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
51
|
+
protected readonly emitter: EventEmitter<"message" | "send" | "handshaked" | "ping", NodeEventData>;
|
|
40
52
|
constructor(storage: Database<{
|
|
41
53
|
sessions: LocalStorage<string, ExportedKeySession>;
|
|
42
54
|
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
43
55
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
44
56
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
45
57
|
}>, privateIdentityKey?: PrivateIdentityKey);
|
|
58
|
+
onMessage: (data: MessageEventData) => void;
|
|
59
|
+
onSend: (data: Uint8Array) => void;
|
|
60
|
+
onHandshaked: (userId: UserId) => void;
|
|
61
|
+
waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
|
|
46
62
|
get identityKey(): IdentityKey;
|
|
47
63
|
get userId(): UserId;
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
readonly requests: {
|
|
65
|
+
onRequest: (request: BootstrapRequest) => void;
|
|
66
|
+
getRequest: (userId: string) => Promise<BootstrapRequest | undefined>;
|
|
67
|
+
};
|
|
50
68
|
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
sendHandshake(data: KeyExchangeData): Promise<void>;
|
|
70
|
+
sendHandshake(receiverId: string | UserId): Promise<void>;
|
|
71
|
+
sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
|
|
72
|
+
sendRelay(receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
73
|
+
sendPing(receiverId: string | UserId): Promise<void>;
|
|
74
|
+
sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void>;
|
|
75
|
+
sendBootstrap(receiverId: string | UserId): Promise<void>;
|
|
58
76
|
protected decrypt(datagram: Datagram): Promise<Uint8Array>;
|
|
59
|
-
|
|
60
|
-
* Open the datagram and execute operation of Discover and Handshake.
|
|
61
|
-
*
|
|
62
|
-
* @param datagram
|
|
63
|
-
* @returns Header and decrypted payload
|
|
64
|
-
*/
|
|
65
|
-
open(datagram: Datagram | Uint8Array): Promise<{
|
|
66
|
-
header: DatagramHeader;
|
|
67
|
-
payload?: Uint8Array;
|
|
68
|
-
datagram?: Datagram;
|
|
69
|
-
}>;
|
|
77
|
+
protected open(datagram: Datagram | Uint8Array): Promise<void>;
|
|
70
78
|
}
|
|
71
79
|
declare class SessionMap implements LocalStorage<string, KeySession> {
|
|
72
80
|
readonly storage: LocalStorage<string, ExportedKeySession>;
|
package/dist/node.js
CHANGED
|
@@ -49,31 +49,31 @@ const double_ratchet_1 = require("./double-ratchet");
|
|
|
49
49
|
const _1 = require(".");
|
|
50
50
|
const utils_1 = require("@freesignal/utils");
|
|
51
51
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const easyemitter_ts_1 = __importDefault(require("easyemitter.ts"));
|
|
53
|
+
class BootstrapRequest extends easyemitter_ts_1.default {
|
|
54
|
+
constructor(senderId, keyExchangeData) {
|
|
55
|
+
super();
|
|
54
56
|
this.senderId = senderId;
|
|
55
|
-
this.
|
|
57
|
+
this.keyExchangeData = keyExchangeData;
|
|
56
58
|
_BootstrapRequest_status.set(this, 'pending');
|
|
59
|
+
this.onChange = () => { };
|
|
60
|
+
this.on('change', (data) => this.onChange(data));
|
|
57
61
|
}
|
|
58
62
|
get status() {
|
|
59
63
|
return __classPrivateFieldGet(this, _BootstrapRequest_status, "f");
|
|
60
64
|
}
|
|
61
|
-
get() {
|
|
62
|
-
return
|
|
63
|
-
return __classPrivateFieldGet(this, _BootstrapRequest_status, "f") === 'accepted' ? this.datagram : undefined;
|
|
64
|
-
});
|
|
65
|
+
get data() {
|
|
66
|
+
return __classPrivateFieldGet(this, _BootstrapRequest_status, "f") === 'accepted' ? this.keyExchangeData : undefined;
|
|
65
67
|
}
|
|
66
68
|
accept() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return yield this.get();
|
|
71
|
-
});
|
|
69
|
+
if (this.status === 'pending')
|
|
70
|
+
__classPrivateFieldSet(this, _BootstrapRequest_status, 'accepted', "f");
|
|
71
|
+
this.emit('change', this);
|
|
72
72
|
}
|
|
73
73
|
deny() {
|
|
74
74
|
if (this.status === 'pending')
|
|
75
75
|
__classPrivateFieldSet(this, _BootstrapRequest_status, 'denied', "f");
|
|
76
|
-
|
|
76
|
+
this.emit('change', this);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
exports.BootstrapRequest = BootstrapRequest;
|
|
@@ -81,24 +81,38 @@ _BootstrapRequest_status = new WeakMap();
|
|
|
81
81
|
class FreeSignalNode {
|
|
82
82
|
constructor(storage, privateIdentityKey) {
|
|
83
83
|
this.discovers = new Set();
|
|
84
|
-
this.
|
|
84
|
+
this.emitter = new easyemitter_ts_1.default();
|
|
85
|
+
this.onMessage = () => { };
|
|
86
|
+
this.onSend = () => { };
|
|
87
|
+
this.onHandshaked = () => { };
|
|
88
|
+
this.requests = {
|
|
89
|
+
onRequest: () => { },
|
|
90
|
+
getRequest: (userId) => {
|
|
91
|
+
return this.bootstraps.get(userId);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
85
94
|
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
86
95
|
this.sessions = new SessionMap(storage.sessions);
|
|
87
96
|
this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
|
|
88
97
|
this.bundles = storage.bundles;
|
|
89
98
|
this.bootstraps = storage.bootstraps;
|
|
99
|
+
this.emitter.on('send', (data) => this.onSend(data.data.datagram.toBytes()));
|
|
100
|
+
this.emitter.on('handshaked', (data) => this.onHandshaked(types_1.UserId.from(data.data.header.sender)));
|
|
101
|
+
}
|
|
102
|
+
waitHandshaked(userId, timeout) {
|
|
103
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
var _a;
|
|
105
|
+
if (timeout)
|
|
106
|
+
setTimeout(() => { throw new Error(); }, timeout);
|
|
107
|
+
while (((_a = (yield this.emitter.wait('handshaked', timeout))) === null || _a === void 0 ? void 0 : _a.header.sender) !== userId.toString())
|
|
108
|
+
;
|
|
109
|
+
});
|
|
90
110
|
}
|
|
91
111
|
get identityKey() {
|
|
92
112
|
return this.privateIdentityKey.identityKey;
|
|
93
113
|
}
|
|
94
114
|
get userId() {
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
getRequest(userId) {
|
|
98
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
-
var _a;
|
|
100
|
-
return (_a = (yield this.bootstraps.get(userId))) === null || _a === void 0 ? void 0 : _a.get();
|
|
101
|
-
});
|
|
115
|
+
return this.identityKey.userId;
|
|
102
116
|
}
|
|
103
117
|
encrypt(receiverId, protocol, data) {
|
|
104
118
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -112,35 +126,50 @@ class FreeSignalNode {
|
|
|
112
126
|
return new types_1.Datagram(this.userId.toString(), receiverId, protocol, encrypted).sign(this.privateIdentityKey.signatureKey);
|
|
113
127
|
});
|
|
114
128
|
}
|
|
115
|
-
|
|
129
|
+
sendHandshake(data) {
|
|
116
130
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
131
|
var _a;
|
|
118
132
|
if (typeof data === 'string' || data instanceof types_1.UserId) {
|
|
119
|
-
//console.debug("
|
|
133
|
+
//console.debug("Sending Handshake Ack");
|
|
120
134
|
const userId = data.toString();
|
|
121
135
|
const identityKey = (_a = (yield this.sessions.get(userId))) === null || _a === void 0 ? void 0 : _a.identityKey;
|
|
122
136
|
if (!identityKey)
|
|
123
137
|
throw new Error("Missing user");
|
|
124
|
-
const
|
|
125
|
-
|
|
138
|
+
const datagram = yield this.encrypt(userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey));
|
|
139
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
140
|
+
return;
|
|
126
141
|
}
|
|
127
|
-
//console.debug("
|
|
142
|
+
//console.debug("Sending Handshake Syn");
|
|
128
143
|
const { session, message } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
|
|
129
144
|
yield this.sessions.set(session.userId.toString(), session);
|
|
130
|
-
|
|
145
|
+
const datagram = new types_1.Datagram(this.userId.toString(), types_1.UserId.fromKey(data.identityKey).toString(), types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message)).sign(this.privateIdentityKey.signatureKey);
|
|
146
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
131
147
|
});
|
|
132
148
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
149
|
+
sendData(receiverId, data) {
|
|
150
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
151
|
+
//console.debug("Sending Data");
|
|
152
|
+
const datagram = yield this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
|
|
153
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
154
|
+
});
|
|
136
155
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
sendRelay(receiverId, data) {
|
|
157
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
+
//console.debug("Sending Relay");
|
|
159
|
+
const datagram = yield this.encrypt(receiverId, types_1.Protocols.RELAY, data.toBytes());
|
|
160
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
sendPing(receiverId) {
|
|
164
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
165
|
+
//console.debug("Sending Ping");
|
|
166
|
+
const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.PING);
|
|
167
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
168
|
+
});
|
|
140
169
|
}
|
|
141
|
-
|
|
170
|
+
sendDiscover(receiverId, discoverId) {
|
|
142
171
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
-
//console.debug("
|
|
172
|
+
//console.debug("Sending Discover");
|
|
144
173
|
if (receiverId instanceof types_1.UserId)
|
|
145
174
|
receiverId = receiverId.toString();
|
|
146
175
|
if (discoverId instanceof types_1.UserId)
|
|
@@ -150,19 +179,15 @@ class FreeSignalNode {
|
|
|
150
179
|
discoverId
|
|
151
180
|
};
|
|
152
181
|
this.discovers.add(receiverId);
|
|
153
|
-
|
|
182
|
+
const datagram = yield this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message));
|
|
183
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
154
184
|
});
|
|
155
185
|
}
|
|
156
|
-
|
|
186
|
+
sendBootstrap(receiverId) {
|
|
157
187
|
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
-
//console.debug("
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
packGetBootstrap(receiverId) {
|
|
163
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
-
//console.debug("Packing GetBootstrap");
|
|
165
|
-
return new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP);
|
|
188
|
+
//console.debug("Sending Bootstrap");
|
|
189
|
+
const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
|
|
190
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
166
191
|
});
|
|
167
192
|
}
|
|
168
193
|
decrypt(datagram) {
|
|
@@ -183,20 +208,11 @@ class FreeSignalNode {
|
|
|
183
208
|
return decrypted;
|
|
184
209
|
});
|
|
185
210
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Open the datagram and execute operation of Discover and Handshake.
|
|
188
|
-
*
|
|
189
|
-
* @param datagram
|
|
190
|
-
* @returns Header and decrypted payload
|
|
191
|
-
*/
|
|
192
211
|
open(datagram) {
|
|
193
212
|
return __awaiter(this, void 0, void 0, function* () {
|
|
194
|
-
var _a
|
|
213
|
+
var _a;
|
|
195
214
|
if (datagram instanceof Uint8Array)
|
|
196
215
|
datagram = types_1.Datagram.from(datagram);
|
|
197
|
-
let out = {
|
|
198
|
-
header: types_1.DatagramHeader.from(datagram.header)
|
|
199
|
-
};
|
|
200
216
|
switch (datagram.protocol) {
|
|
201
217
|
case types_1.Protocols.HANDSHAKE:
|
|
202
218
|
if (!datagram.payload)
|
|
@@ -209,7 +225,8 @@ class FreeSignalNode {
|
|
|
209
225
|
throw new Error("Missing user");
|
|
210
226
|
if (!(0, utils_1.compareBytes)(payload, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey)))
|
|
211
227
|
throw new Error("Error validating handshake data");
|
|
212
|
-
|
|
228
|
+
this.emitter.emit('handshaked', { header: datagram.header });
|
|
229
|
+
return;
|
|
213
230
|
}
|
|
214
231
|
//console.debug("Opening Handshake Syn");
|
|
215
232
|
const data = (0, utils_1.decodeData)(datagram.payload);
|
|
@@ -218,18 +235,17 @@ class FreeSignalNode {
|
|
|
218
235
|
const { session, associatedData } = yield this.keyExchange.digestMessage(data);
|
|
219
236
|
yield this.sessions.set(session.userId.toString(), session);
|
|
220
237
|
yield this.bundles.set(session.userId.toString(), (0, utils_1.decodeData)(associatedData));
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return out;
|
|
238
|
+
yield this.sendHandshake(session.userId);
|
|
239
|
+
this.emitter.emit('handshaked', { header: datagram.header });
|
|
240
|
+
return;
|
|
225
241
|
case types_1.Protocols.MESSAGE:
|
|
226
242
|
//console.debug("Opening Message");
|
|
227
|
-
|
|
228
|
-
return
|
|
243
|
+
this.emitter.emit('message', { header: datagram.header, payload: yield this.decrypt(datagram) });
|
|
244
|
+
return;
|
|
229
245
|
case types_1.Protocols.RELAY:
|
|
230
246
|
//console.debug("Opening Relay");
|
|
231
|
-
|
|
232
|
-
return
|
|
247
|
+
this.emitter.emit('send', { header: types_1.Datagram.from(yield this.decrypt(datagram)) });
|
|
248
|
+
return;
|
|
233
249
|
case types_1.Protocols.DISCOVER:
|
|
234
250
|
//console.debug("Opening Discover");
|
|
235
251
|
const message = (0, utils_1.decodeData)(yield this.decrypt(datagram));
|
|
@@ -241,12 +257,12 @@ class FreeSignalNode {
|
|
|
241
257
|
else {
|
|
242
258
|
const bundle = yield this.bundles.get(message.discoverId);
|
|
243
259
|
if (!bundle)
|
|
244
|
-
return
|
|
260
|
+
return;
|
|
245
261
|
const { version, identityKey, signedPreKey, signature } = bundle;
|
|
246
262
|
const onetimePreKey = bundle.onetimePreKeys.shift();
|
|
247
263
|
if (!onetimePreKey) {
|
|
248
264
|
yield this.bundles.delete(message.discoverId);
|
|
249
|
-
return
|
|
265
|
+
return;
|
|
250
266
|
}
|
|
251
267
|
data = {
|
|
252
268
|
version,
|
|
@@ -257,31 +273,33 @@ class FreeSignalNode {
|
|
|
257
273
|
};
|
|
258
274
|
}
|
|
259
275
|
const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
260
|
-
|
|
276
|
+
this.emitter.emit('send', yield this.encrypt(datagram.sender, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response)));
|
|
261
277
|
}
|
|
262
278
|
else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
263
279
|
this.discovers.delete(message.discoverId);
|
|
264
280
|
if (message.data)
|
|
265
|
-
|
|
281
|
+
yield this.sendHandshake(message.data);
|
|
266
282
|
}
|
|
267
|
-
return
|
|
283
|
+
return;
|
|
268
284
|
case types_1.Protocols.BOOTSTRAP:
|
|
269
285
|
//console.debug("Opening Bootstrap");
|
|
270
|
-
if (datagram.payload)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
286
|
+
if (!datagram.payload)
|
|
287
|
+
throw new Error("Invalid Bootstrap");
|
|
288
|
+
const keyExchangeData = (0, utils_1.decodeData)(datagram.payload);
|
|
289
|
+
if (!(0, utils_1.compareBytes)(types_1.UserId.fromKey(keyExchangeData.identityKey).toBytes(), (0, utils_1.encodeBase64)(datagram.sender)))
|
|
290
|
+
new Error("Malicious bootstrap request");
|
|
291
|
+
const request = new BootstrapRequest(datagram.sender, keyExchangeData);
|
|
292
|
+
request.onChange = () => {
|
|
293
|
+
if (!request.data)
|
|
294
|
+
throw new Error("Error sending handshake");
|
|
295
|
+
this.sendHandshake(request.data);
|
|
296
|
+
};
|
|
297
|
+
yield this.bootstraps.set(datagram.sender, request);
|
|
298
|
+
this.requests.onRequest(request);
|
|
299
|
+
return;
|
|
283
300
|
case types_1.Protocols.PING:
|
|
284
|
-
|
|
301
|
+
this.emitter.emit('ping', { header: datagram.header });
|
|
302
|
+
return;
|
|
285
303
|
default:
|
|
286
304
|
throw new Error("Invalid protocol");
|
|
287
305
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -128,7 +128,7 @@ export declare class Datagram implements Encodable, DatagramHeader {
|
|
|
128
128
|
get payload(): Uint8Array | undefined;
|
|
129
129
|
get signature(): string | undefined;
|
|
130
130
|
private get unsigned();
|
|
131
|
-
get header():
|
|
131
|
+
get header(): DatagramHeader;
|
|
132
132
|
toBytes(): Uint8Array;
|
|
133
133
|
sign(secretKey: Uint8Array): SignedDatagram;
|
|
134
134
|
toString(): string;
|
|
@@ -154,20 +154,31 @@ export declare class EncryptionHeader implements EncryptionKeys, Encodable {
|
|
|
154
154
|
static from(data: Uint8Array | EncryptionHeader): EncryptionHeader;
|
|
155
155
|
}
|
|
156
156
|
export declare class EncryptedData implements Encodable {
|
|
157
|
-
readonly header: Uint8Array;
|
|
158
|
-
readonly nonce: Uint8Array;
|
|
159
|
-
readonly payload: Uint8Array;
|
|
160
157
|
static readonly version = 1;
|
|
161
158
|
static readonly nonceLength: number;
|
|
162
159
|
private _version;
|
|
163
|
-
|
|
160
|
+
readonly header: Uint8Array;
|
|
161
|
+
readonly hashkey?: Uint8Array;
|
|
162
|
+
readonly nonce?: Uint8Array;
|
|
163
|
+
readonly payload: Uint8Array;
|
|
164
|
+
constructor(opts: {
|
|
165
|
+
header: Uint8Array;
|
|
166
|
+
payload: Uint8Array;
|
|
167
|
+
});
|
|
168
|
+
constructor(opts: {
|
|
169
|
+
header: Uint8Array;
|
|
170
|
+
hashkey: Uint8Array;
|
|
171
|
+
nonce: Uint8Array;
|
|
172
|
+
payload: Uint8Array;
|
|
173
|
+
});
|
|
164
174
|
get version(): number;
|
|
165
175
|
get length(): number;
|
|
166
176
|
toBytes(): Uint8Array;
|
|
167
177
|
toJSON(): {
|
|
168
178
|
version: number;
|
|
169
179
|
header: string;
|
|
170
|
-
|
|
180
|
+
hashkey?: string;
|
|
181
|
+
nonce?: string;
|
|
171
182
|
payload: string;
|
|
172
183
|
};
|
|
173
184
|
static from(data: Uint8Array | EncryptedData): EncryptedData;
|
package/dist/types.js
CHANGED
|
@@ -35,66 +35,34 @@ exports.encryptData = encryptData;
|
|
|
35
35
|
exports.decryptData = decryptData;
|
|
36
36
|
const utils_1 = require("@freesignal/utils");
|
|
37
37
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
38
|
-
|
|
38
|
+
function encryptData(session, data) {
|
|
39
39
|
const key = session.getSendingKey();
|
|
40
40
|
if (!key)
|
|
41
41
|
throw new Error("Error generating key");
|
|
42
|
-
const nonce =
|
|
43
|
-
const
|
|
44
|
-
const headerKey = session.getHeaderKeys().sending;
|
|
45
|
-
//console.debug(session.userId.toString(), "Sending: ", decodeBase64(headerKey ?? new Uint8Array()))
|
|
42
|
+
const nonce = crypto_1.default.randomBytes(EncryptionHeader.nonceLength);
|
|
43
|
+
const payload = crypto_1.default.box.encrypt(data, nonce, key.secretKey);
|
|
46
44
|
let header = new EncryptionHeader(key, nonce).toBytes();
|
|
47
|
-
const
|
|
45
|
+
const headerKey = session.getHeaderKey();
|
|
46
|
+
if (!headerKey)
|
|
47
|
+
return new EncryptedData({ header, payload });
|
|
48
|
+
const headerNonce = crypto_1.default.randomBytes(EncryptionHeader.nonceLength);
|
|
48
49
|
if (headerKey)
|
|
49
|
-
header =
|
|
50
|
-
|
|
51
|
-
return test;
|
|
50
|
+
header = crypto_1.default.box.encrypt(header, headerNonce, headerKey);
|
|
51
|
+
return new EncryptedData({ hashkey: crypto_1.default.hash(headerKey !== null && headerKey !== void 0 ? headerKey : new Uint8Array(32).fill(0)), header, nonce: headerNonce, payload });
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
export function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array {
|
|
53
|
+
function decryptData(session, encryptedData) {
|
|
55
54
|
const encrypted = EncryptedData.from(encryptedData);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
try {
|
|
55
|
+
let headerData = encrypted.header;
|
|
56
|
+
if (encrypted.hashkey && encrypted.nonce) {
|
|
57
|
+
const headerKey = session.getHeaderKey((0, utils_1.decodeBase64)(encrypted.hashkey));
|
|
60
58
|
if (!headerKey)
|
|
61
|
-
throw new Error("Error
|
|
62
|
-
|
|
63
|
-
if (!
|
|
59
|
+
throw new Error("Error getting key");
|
|
60
|
+
const data = crypto_1.default.box.decrypt(encrypted.header, encrypted.nonce, headerKey);
|
|
61
|
+
if (!data)
|
|
64
62
|
throw new Error("Error calculating header");
|
|
65
|
-
|
|
66
|
-
} catch {
|
|
67
|
-
if (!nextHeaderKey)
|
|
68
|
-
throw new Error("Error generating key");
|
|
69
|
-
headerData = crypto.box.decrypt(encrypted.header, encrypted.nonce, nextHeaderKey);
|
|
70
|
-
if (!headerData) {
|
|
71
|
-
//console.debug(session.toJSON());
|
|
72
|
-
throw new Error("Error calculating header");
|
|
73
|
-
}
|
|
74
|
-
//console.debug(session.userId.toString(), "NextReceiving: ", decodeBase64(session.getHeaderKeys().nextReciving ?? new Uint8Array()))
|
|
63
|
+
headerData = data;
|
|
75
64
|
}
|
|
76
|
-
const header = EncryptionHeader.from(headerData
|
|
77
|
-
const key = session.getReceivingKey(header);
|
|
78
|
-
if (!key)
|
|
79
|
-
throw new Error("Error calculating key");
|
|
80
|
-
const decrypted = crypto.box.decrypt(encrypted.payload, header.nonce, key);
|
|
81
|
-
if (!decrypted)
|
|
82
|
-
throw new Error("Error decrypting data");
|
|
83
|
-
return decrypted;
|
|
84
|
-
}*/
|
|
85
|
-
function encryptData(session, data) {
|
|
86
|
-
const key = session.getSendingKey();
|
|
87
|
-
if (!key)
|
|
88
|
-
throw new Error("Error generating key");
|
|
89
|
-
const nonce = crypto_1.default.randomBytes(EncryptionHeader.nonceLength);
|
|
90
|
-
const ciphertext = crypto_1.default.box.encrypt(data, nonce, key.secretKey);
|
|
91
|
-
let header = new EncryptionHeader(key, nonce).toBytes();
|
|
92
|
-
const test = new EncryptedData(header, crypto_1.default.randomBytes(EncryptionHeader.nonceLength), ciphertext);
|
|
93
|
-
return test;
|
|
94
|
-
}
|
|
95
|
-
function decryptData(session, encryptedData) {
|
|
96
|
-
const encrypted = EncryptedData.from(encryptedData);
|
|
97
|
-
const header = EncryptionHeader.from(encrypted.header);
|
|
65
|
+
const header = EncryptionHeader.from(headerData);
|
|
98
66
|
const key = session.getReceivingKey(header);
|
|
99
67
|
if (!key)
|
|
100
68
|
throw new Error("Error calculating key");
|
|
@@ -318,7 +286,7 @@ class Datagram {
|
|
|
318
286
|
return data.subarray(0, data.length - (this._signature ? crypto_1.default.EdDSA.signatureLength : 0));
|
|
319
287
|
}
|
|
320
288
|
get header() {
|
|
321
|
-
return this.toBytes().slice(0, DatagramHeader.headerLength);
|
|
289
|
+
return DatagramHeader.from(this.toBytes().slice(0, DatagramHeader.headerLength));
|
|
322
290
|
}
|
|
323
291
|
toBytes() {
|
|
324
292
|
var _a, _b, _c;
|
|
@@ -400,11 +368,12 @@ class EncryptionHeader {
|
|
|
400
368
|
static from(data) {
|
|
401
369
|
if (data instanceof EncryptionHeader)
|
|
402
370
|
data = data.toBytes();
|
|
371
|
+
let offset = 0;
|
|
403
372
|
return new EncryptionHeader({
|
|
404
|
-
count: (0, utils_1.bytesToNumber)(data.subarray(
|
|
405
|
-
previous: (0, utils_1.bytesToNumber)(data.subarray(
|
|
406
|
-
publicKey: data.subarray(
|
|
407
|
-
}, data.subarray(
|
|
373
|
+
count: (0, utils_1.bytesToNumber)(data.subarray(offset, offset += EncryptionHeader.countLength)),
|
|
374
|
+
previous: (0, utils_1.bytesToNumber)(data.subarray(offset, offset += EncryptionHeader.countLength)),
|
|
375
|
+
publicKey: data.subarray(offset, offset += EncryptionHeader.keyLength)
|
|
376
|
+
}, data.subarray(offset, offset += EncryptedData.nonceLength));
|
|
408
377
|
}
|
|
409
378
|
}
|
|
410
379
|
exports.EncryptionHeader = EncryptionHeader;
|
|
@@ -412,11 +381,12 @@ EncryptionHeader.keyLength = crypto_1.default.box.keyLength;
|
|
|
412
381
|
EncryptionHeader.nonceLength = crypto_1.default.box.nonceLength;
|
|
413
382
|
EncryptionHeader.countLength = 2;
|
|
414
383
|
class EncryptedData {
|
|
415
|
-
constructor(header, nonce, payload) {
|
|
384
|
+
constructor({ hashkey, header, nonce, payload }) {
|
|
385
|
+
this._version = EncryptedData.version;
|
|
416
386
|
this.header = header;
|
|
387
|
+
this.hashkey = hashkey;
|
|
417
388
|
this.nonce = nonce;
|
|
418
389
|
this.payload = payload;
|
|
419
|
-
this._version = EncryptedData.version;
|
|
420
390
|
}
|
|
421
391
|
get version() {
|
|
422
392
|
return this._version;
|
|
@@ -425,22 +395,36 @@ class EncryptedData {
|
|
|
425
395
|
return this.toBytes().length;
|
|
426
396
|
}
|
|
427
397
|
toBytes() {
|
|
428
|
-
|
|
398
|
+
var _a, _b;
|
|
399
|
+
return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this._version | (this.hashkey && this.nonce ? 128 : 0), 1), (0, utils_1.numberToBytes)(this.header.length, 3), this.header, (_a = this.hashkey) !== null && _a !== void 0 ? _a : new Uint8Array(), (_b = this.nonce) !== null && _b !== void 0 ? _b : new Uint8Array, this.payload);
|
|
429
400
|
}
|
|
430
401
|
toJSON() {
|
|
431
402
|
return {
|
|
432
403
|
version: this._version,
|
|
433
404
|
header: (0, utils_1.decodeBase64)(this.header),
|
|
434
|
-
|
|
405
|
+
hashkey: this.hashkey ? (0, utils_1.decodeBase64)(this.hashkey) : undefined,
|
|
406
|
+
nonce: this.nonce ? (0, utils_1.decodeBase64)(this.nonce) : undefined,
|
|
435
407
|
payload: (0, utils_1.decodeBase64)(this.payload)
|
|
436
408
|
};
|
|
437
409
|
}
|
|
438
410
|
static from(data) {
|
|
439
411
|
if (data instanceof EncryptedData)
|
|
440
412
|
data = data.toBytes();
|
|
413
|
+
const versionByte = (0, utils_1.bytesToNumber)(data.subarray(0, 1));
|
|
441
414
|
const headerLength = (0, utils_1.bytesToNumber)(data.subarray(1, 4));
|
|
442
|
-
|
|
443
|
-
|
|
415
|
+
let offset = 4;
|
|
416
|
+
const header = data.subarray(offset, offset += headerLength);
|
|
417
|
+
let hashkey, nonce;
|
|
418
|
+
if ((versionByte & 128) > 0) {
|
|
419
|
+
hashkey = data.subarray(offset, offset += 32);
|
|
420
|
+
nonce = data.subarray(offset, offset += this.nonceLength);
|
|
421
|
+
}
|
|
422
|
+
const payload = data.subarray(offset);
|
|
423
|
+
if (!hashkey || !nonce)
|
|
424
|
+
var obj = new EncryptedData({ header, payload });
|
|
425
|
+
else
|
|
426
|
+
var obj = new EncryptedData({ header, hashkey, nonce, payload });
|
|
427
|
+
obj._version = versionByte & 127;
|
|
444
428
|
return obj;
|
|
445
429
|
}
|
|
446
430
|
}
|
package/dist/x3dh.js
CHANGED
|
@@ -99,8 +99,7 @@ class KeyExchange {
|
|
|
99
99
|
...crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
|
|
100
100
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
|
|
101
101
|
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength * 3);
|
|
102
|
-
|
|
103
|
-
const session = new double_ratchet_1.KeySession({ identityKey, remoteKey: identityKey.exchangeKey, rootKey: derivedKey.subarray(0, double_ratchet_1.KeySession.keyLength) });
|
|
102
|
+
const session = new double_ratchet_1.KeySession({ identityKey, remoteKey: identityKey.exchangeKey, rootKey: derivedKey.subarray(0, double_ratchet_1.KeySession.keyLength), headerKey: derivedKey.subarray(double_ratchet_1.KeySession.keyLength, double_ratchet_1.KeySession.keyLength * 2), nextHeaderKey: derivedKey.subarray(double_ratchet_1.KeySession.keyLength * 2) });
|
|
104
103
|
const encrypted = (0, types_1.encryptData)(session, (0, utils_1.concatBytes)(crypto_1.default.hash(this.identityKey.toBytes()), crypto_1.default.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
|
|
105
104
|
if (!encrypted)
|
|
106
105
|
throw new Error("Decryption error");
|
|
@@ -135,8 +134,7 @@ class KeyExchange {
|
|
|
135
134
|
...crypto_1.default.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
|
|
136
135
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
|
|
137
136
|
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength * 3);
|
|
138
|
-
|
|
139
|
-
const session = new double_ratchet_1.KeySession({ identityKey, secretKey: this.privateIdentityKey.exchangeKey, rootKey: derivedKey.subarray(0, double_ratchet_1.KeySession.keyLength) });
|
|
137
|
+
const session = new double_ratchet_1.KeySession({ identityKey, secretKey: this.privateIdentityKey.exchangeKey, rootKey: derivedKey.subarray(0, double_ratchet_1.KeySession.keyLength), nextHeaderKey: derivedKey.subarray(double_ratchet_1.KeySession.keyLength, double_ratchet_1.KeySession.keyLength * 2), headerKey: derivedKey.subarray(double_ratchet_1.KeySession.keyLength * 2) });
|
|
140
138
|
const data = (0, types_1.decryptData)(session, (0, utils_1.encodeBase64)(message.associatedData));
|
|
141
139
|
if (!data)
|
|
142
140
|
throw new Error("Error decrypting ACK message");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
@@ -37,9 +37,10 @@
|
|
|
37
37
|
"@freesignal/crypto": "^0.3.0",
|
|
38
38
|
"@freesignal/interfaces": "^0.2.0",
|
|
39
39
|
"@freesignal/utils": "^1.4.1",
|
|
40
|
+
"easyemitter.ts": "^1.0.3",
|
|
40
41
|
"semaphore.ts": "^0.2.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@types/node": "^24.2.1"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|
package/dist/test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/test.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
const utils_1 = require("@freesignal/utils");
|
|
13
|
-
const _1 = require(".");
|
|
14
|
-
console.log("FreeSignal protocol test");
|
|
15
|
-
const bob = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap(), bundles: new _1.AsyncMap(), bootstraps: new _1.AsyncMap() });
|
|
16
|
-
const alice = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap(), bundles: new _1.AsyncMap(), bootstraps: new _1.AsyncMap() });
|
|
17
|
-
setImmediate(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
-
const bobBootstrap = yield bob.packBootstrap(alice.userId);
|
|
19
|
-
alice.onRequest = (request) => { request.accept(); };
|
|
20
|
-
const test = (yield alice.open(bobBootstrap)).datagram;
|
|
21
|
-
const aliceHandshake = yield alice.getRequest(bob.userId.toString());
|
|
22
|
-
if (!aliceHandshake)
|
|
23
|
-
throw new Error("Bootstrap Failed");
|
|
24
|
-
const bobHandshake = (yield bob.open(aliceHandshake)).datagram;
|
|
25
|
-
if (!bobHandshake)
|
|
26
|
-
throw new Error("Handshake Failed");
|
|
27
|
-
console.log(!!(yield alice.open(bobHandshake)).header);
|
|
28
|
-
const first = (yield bob.packData(alice.userId, "Hi Alice!")).toBytes();
|
|
29
|
-
console.log("Bob: ", (0, utils_1.decodeData)((yield alice.open(first)).payload));
|
|
30
|
-
const second = yield alice.packData(bob.userId, "Hi Bob!");
|
|
31
|
-
console.log("Test");
|
|
32
|
-
console.log("Alice: ", (0, utils_1.decodeData)((yield bob.open(second)).payload));
|
|
33
|
-
const third = yield Promise.all(["How are you?", "How are this days?", "For me it's a good time"].map(msg => bob.packData(alice.userId, msg)));
|
|
34
|
-
third.forEach((data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
-
console.log("Bob: ", (0, utils_1.decodeData)((yield alice.open(data)).payload));
|
|
36
|
-
}));
|
|
37
|
-
const fourth = yield alice.packData(bob.userId, "Not so bad my man");
|
|
38
|
-
console.log("Alice: ", (0, utils_1.decodeData)((yield bob.open(fourth)).payload));
|
|
39
|
-
const fifth = yield Promise.all(["I'm thinking...", "His this secure?"].map(msg => bob.packData(alice.userId, msg)));
|
|
40
|
-
fifth.forEach((data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
-
console.log("Bob: ", (0, utils_1.decodeData)((yield alice.open(data)).payload));
|
|
42
|
-
}));
|
|
43
|
-
//const testone = await Promise.all(Array(400).fill(0).map(() => alice.packData(bob.userId, decodeBase64(crypto.randomBytes(64)))));
|
|
44
|
-
//console.log(((await bob.open(testone[350])).payload));
|
|
45
|
-
}));
|