@freesignal/protocol 0.6.2 → 0.7.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/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 +36 -25
- package/dist/node.js +106 -82
- 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 -48
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,28 @@ 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
|
+
request?: BootstrapRequest;
|
|
40
|
+
};
|
|
41
|
+
type MessageEventData = {
|
|
42
|
+
header: DatagramHeader;
|
|
43
|
+
payload: Uint8Array;
|
|
44
|
+
};
|
|
33
45
|
export declare class FreeSignalNode {
|
|
34
46
|
protected readonly privateIdentityKey: PrivateIdentityKey;
|
|
35
47
|
protected readonly sessions: SessionMap;
|
|
@@ -37,36 +49,35 @@ export declare class FreeSignalNode {
|
|
|
37
49
|
protected readonly keyExchange: KeyExchange;
|
|
38
50
|
protected readonly discovers: Set<string>;
|
|
39
51
|
protected readonly bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
52
|
+
protected readonly emitter: EventEmitter<"message" | "send" | "handshake" | "ping" | "bootstrap", NodeEventData>;
|
|
40
53
|
constructor(storage: Database<{
|
|
41
54
|
sessions: LocalStorage<string, ExportedKeySession>;
|
|
42
55
|
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
43
56
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
44
57
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
45
58
|
}>, privateIdentityKey?: PrivateIdentityKey);
|
|
59
|
+
protected messageHandler: EventCall<"message", NodeEventData>;
|
|
60
|
+
protected sendHandler: EventCall<"send", NodeEventData>;
|
|
61
|
+
protected handshakeHandler: EventCall<"handshake", NodeEventData>;
|
|
62
|
+
protected bootstrapHandler: EventCall<"bootstrap", NodeEventData>;
|
|
63
|
+
onMessage: (data: MessageEventData) => void;
|
|
64
|
+
onSend: (data: Uint8Array) => void;
|
|
65
|
+
onHandshake: (userId: UserId) => void;
|
|
66
|
+
onRequest: (request: BootstrapRequest) => void;
|
|
67
|
+
getRequest(userId: string): Promise<BootstrapRequest | undefined>;
|
|
68
|
+
waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
|
|
46
69
|
get identityKey(): IdentityKey;
|
|
47
70
|
get userId(): UserId;
|
|
48
|
-
onRequest: (request: BootstrapRequest) => void;
|
|
49
|
-
getRequest(userId: string): Promise<Datagram | undefined>;
|
|
50
71
|
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
sendHandshake(data: KeyExchangeData): Promise<void>;
|
|
73
|
+
sendHandshake(receiverId: string | UserId): Promise<void>;
|
|
74
|
+
sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
|
|
75
|
+
sendRelay(receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
76
|
+
sendPing(receiverId: string | UserId): Promise<void>;
|
|
77
|
+
sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void>;
|
|
78
|
+
sendBootstrap(receiverId: string | UserId): Promise<void>;
|
|
58
79
|
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
|
-
}>;
|
|
80
|
+
protected open(datagram: Datagram | Uint8Array): Promise<void>;
|
|
70
81
|
}
|
|
71
82
|
declare class SessionMap implements LocalStorage<string, KeySession> {
|
|
72
83
|
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,25 +81,43 @@ _BootstrapRequest_status = new WeakMap();
|
|
|
81
81
|
class FreeSignalNode {
|
|
82
82
|
constructor(storage, privateIdentityKey) {
|
|
83
83
|
this.discovers = new Set();
|
|
84
|
+
this.emitter = new easyemitter_ts_1.default();
|
|
85
|
+
this.messageHandler = (data) => { var _a, _b; return this.onMessage({ header: (_a = data.data) === null || _a === void 0 ? void 0 : _a.header, payload: (_b = data.data) === null || _b === void 0 ? void 0 : _b.payload }); };
|
|
86
|
+
this.sendHandler = (data) => this.onSend(data.data.datagram.toBytes());
|
|
87
|
+
this.handshakeHandler = (data) => this.onHandshake(types_1.UserId.from(data.data.header.sender));
|
|
88
|
+
this.bootstrapHandler = (data) => { var _a; return this.onRequest((_a = data.data) === null || _a === void 0 ? void 0 : _a.request); };
|
|
89
|
+
this.onMessage = () => { };
|
|
90
|
+
this.onSend = () => { };
|
|
91
|
+
this.onHandshake = () => { };
|
|
84
92
|
this.onRequest = () => { };
|
|
85
93
|
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
86
94
|
this.sessions = new SessionMap(storage.sessions);
|
|
87
95
|
this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
|
|
88
96
|
this.bundles = storage.bundles;
|
|
89
97
|
this.bootstraps = storage.bootstraps;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
get userId() {
|
|
95
|
-
return types_1.UserId.fromKey(this.identityKey);
|
|
98
|
+
this.emitter.on('message', this.messageHandler);
|
|
99
|
+
this.emitter.on('send', this.sendHandler);
|
|
100
|
+
this.emitter.on('handshake', this.handshakeHandler);
|
|
101
|
+
this.emitter.on('bootstrap', this.bootstrapHandler);
|
|
96
102
|
}
|
|
97
103
|
getRequest(userId) {
|
|
104
|
+
return this.bootstraps.get(userId);
|
|
105
|
+
}
|
|
106
|
+
waitHandshaked(userId, timeout) {
|
|
98
107
|
return __awaiter(this, void 0, void 0, function* () {
|
|
99
108
|
var _a;
|
|
100
|
-
|
|
109
|
+
if (timeout)
|
|
110
|
+
setTimeout(() => { throw new Error(); }, timeout);
|
|
111
|
+
while (((_a = (yield this.emitter.wait('handshake', timeout))) === null || _a === void 0 ? void 0 : _a.header.sender) !== userId.toString())
|
|
112
|
+
;
|
|
101
113
|
});
|
|
102
114
|
}
|
|
115
|
+
get identityKey() {
|
|
116
|
+
return this.privateIdentityKey.identityKey;
|
|
117
|
+
}
|
|
118
|
+
get userId() {
|
|
119
|
+
return this.identityKey.userId;
|
|
120
|
+
}
|
|
103
121
|
encrypt(receiverId, protocol, data) {
|
|
104
122
|
return __awaiter(this, void 0, void 0, function* () {
|
|
105
123
|
if (receiverId instanceof types_1.UserId)
|
|
@@ -112,35 +130,50 @@ class FreeSignalNode {
|
|
|
112
130
|
return new types_1.Datagram(this.userId.toString(), receiverId, protocol, encrypted).sign(this.privateIdentityKey.signatureKey);
|
|
113
131
|
});
|
|
114
132
|
}
|
|
115
|
-
|
|
133
|
+
sendHandshake(data) {
|
|
116
134
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
135
|
var _a;
|
|
118
136
|
if (typeof data === 'string' || data instanceof types_1.UserId) {
|
|
119
|
-
//console.debug("
|
|
137
|
+
//console.debug("Sending Handshake Ack");
|
|
120
138
|
const userId = data.toString();
|
|
121
139
|
const identityKey = (_a = (yield this.sessions.get(userId))) === null || _a === void 0 ? void 0 : _a.identityKey;
|
|
122
140
|
if (!identityKey)
|
|
123
141
|
throw new Error("Missing user");
|
|
124
|
-
const
|
|
125
|
-
|
|
142
|
+
const datagram = yield this.encrypt(userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey));
|
|
143
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
144
|
+
return;
|
|
126
145
|
}
|
|
127
|
-
//console.debug("
|
|
146
|
+
//console.debug("Sending Handshake Syn");
|
|
128
147
|
const { session, message } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
|
|
129
148
|
yield this.sessions.set(session.userId.toString(), session);
|
|
130
|
-
|
|
149
|
+
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);
|
|
150
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
sendData(receiverId, data) {
|
|
154
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
//console.debug("Sending Data");
|
|
156
|
+
const datagram = yield this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
|
|
157
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
131
158
|
});
|
|
132
159
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
160
|
+
sendRelay(receiverId, data) {
|
|
161
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
162
|
+
//console.debug("Sending Relay");
|
|
163
|
+
const datagram = yield this.encrypt(receiverId, types_1.Protocols.RELAY, data.toBytes());
|
|
164
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
165
|
+
});
|
|
136
166
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
167
|
+
sendPing(receiverId) {
|
|
168
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
169
|
+
//console.debug("Sending Ping");
|
|
170
|
+
const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.PING);
|
|
171
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
172
|
+
});
|
|
140
173
|
}
|
|
141
|
-
|
|
174
|
+
sendDiscover(receiverId, discoverId) {
|
|
142
175
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
-
//console.debug("
|
|
176
|
+
//console.debug("Sending Discover");
|
|
144
177
|
if (receiverId instanceof types_1.UserId)
|
|
145
178
|
receiverId = receiverId.toString();
|
|
146
179
|
if (discoverId instanceof types_1.UserId)
|
|
@@ -150,19 +183,17 @@ class FreeSignalNode {
|
|
|
150
183
|
discoverId
|
|
151
184
|
};
|
|
152
185
|
this.discovers.add(receiverId);
|
|
153
|
-
|
|
186
|
+
const datagram = yield this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message));
|
|
187
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
154
188
|
});
|
|
155
189
|
}
|
|
156
|
-
|
|
190
|
+
sendBootstrap(receiverId) {
|
|
157
191
|
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
-
//console.debug("
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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);
|
|
192
|
+
//console.debug("Sending Bootstrap");
|
|
193
|
+
if (yield this.sessions.has(receiverId.toString()))
|
|
194
|
+
throw new Error("Session exists");
|
|
195
|
+
const datagram = new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
|
|
196
|
+
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
166
197
|
});
|
|
167
198
|
}
|
|
168
199
|
decrypt(datagram) {
|
|
@@ -183,20 +214,11 @@ class FreeSignalNode {
|
|
|
183
214
|
return decrypted;
|
|
184
215
|
});
|
|
185
216
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Open the datagram and execute operation of Discover and Handshake.
|
|
188
|
-
*
|
|
189
|
-
* @param datagram
|
|
190
|
-
* @returns Header and decrypted payload
|
|
191
|
-
*/
|
|
192
217
|
open(datagram) {
|
|
193
218
|
return __awaiter(this, void 0, void 0, function* () {
|
|
194
|
-
var _a
|
|
219
|
+
var _a;
|
|
195
220
|
if (datagram instanceof Uint8Array)
|
|
196
221
|
datagram = types_1.Datagram.from(datagram);
|
|
197
|
-
let out = {
|
|
198
|
-
header: types_1.DatagramHeader.from(datagram.header)
|
|
199
|
-
};
|
|
200
222
|
switch (datagram.protocol) {
|
|
201
223
|
case types_1.Protocols.HANDSHAKE:
|
|
202
224
|
if (!datagram.payload)
|
|
@@ -209,7 +231,8 @@ class FreeSignalNode {
|
|
|
209
231
|
throw new Error("Missing user");
|
|
210
232
|
if (!(0, utils_1.compareBytes)(payload, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey)))
|
|
211
233
|
throw new Error("Error validating handshake data");
|
|
212
|
-
|
|
234
|
+
this.emitter.emit('handshake', { header: datagram.header });
|
|
235
|
+
return;
|
|
213
236
|
}
|
|
214
237
|
//console.debug("Opening Handshake Syn");
|
|
215
238
|
const data = (0, utils_1.decodeData)(datagram.payload);
|
|
@@ -218,18 +241,17 @@ class FreeSignalNode {
|
|
|
218
241
|
const { session, associatedData } = yield this.keyExchange.digestMessage(data);
|
|
219
242
|
yield this.sessions.set(session.userId.toString(), session);
|
|
220
243
|
yield this.bundles.set(session.userId.toString(), (0, utils_1.decodeData)(associatedData));
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return out;
|
|
244
|
+
yield this.sendHandshake(session.userId);
|
|
245
|
+
this.emitter.emit('handshake', { header: datagram.header });
|
|
246
|
+
return;
|
|
225
247
|
case types_1.Protocols.MESSAGE:
|
|
226
248
|
//console.debug("Opening Message");
|
|
227
|
-
|
|
228
|
-
return
|
|
249
|
+
this.emitter.emit('message', { header: datagram.header, payload: yield this.decrypt(datagram) });
|
|
250
|
+
return;
|
|
229
251
|
case types_1.Protocols.RELAY:
|
|
230
252
|
//console.debug("Opening Relay");
|
|
231
|
-
|
|
232
|
-
return
|
|
253
|
+
this.emitter.emit('send', { header: types_1.Datagram.from(yield this.decrypt(datagram)) });
|
|
254
|
+
return;
|
|
233
255
|
case types_1.Protocols.DISCOVER:
|
|
234
256
|
//console.debug("Opening Discover");
|
|
235
257
|
const message = (0, utils_1.decodeData)(yield this.decrypt(datagram));
|
|
@@ -241,12 +263,12 @@ class FreeSignalNode {
|
|
|
241
263
|
else {
|
|
242
264
|
const bundle = yield this.bundles.get(message.discoverId);
|
|
243
265
|
if (!bundle)
|
|
244
|
-
return
|
|
266
|
+
return;
|
|
245
267
|
const { version, identityKey, signedPreKey, signature } = bundle;
|
|
246
268
|
const onetimePreKey = bundle.onetimePreKeys.shift();
|
|
247
269
|
if (!onetimePreKey) {
|
|
248
270
|
yield this.bundles.delete(message.discoverId);
|
|
249
|
-
return
|
|
271
|
+
return;
|
|
250
272
|
}
|
|
251
273
|
data = {
|
|
252
274
|
version,
|
|
@@ -257,31 +279,33 @@ class FreeSignalNode {
|
|
|
257
279
|
};
|
|
258
280
|
}
|
|
259
281
|
const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
260
|
-
|
|
282
|
+
this.emitter.emit('send', yield this.encrypt(datagram.sender, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response)));
|
|
261
283
|
}
|
|
262
284
|
else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
263
285
|
this.discovers.delete(message.discoverId);
|
|
264
286
|
if (message.data)
|
|
265
|
-
|
|
287
|
+
yield this.sendHandshake(message.data);
|
|
266
288
|
}
|
|
267
|
-
return
|
|
289
|
+
return;
|
|
268
290
|
case types_1.Protocols.BOOTSTRAP:
|
|
269
291
|
//console.debug("Opening Bootstrap");
|
|
270
|
-
if (datagram.payload)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
292
|
+
if (!datagram.payload)
|
|
293
|
+
throw new Error("Invalid Bootstrap");
|
|
294
|
+
const keyExchangeData = (0, utils_1.decodeData)(datagram.payload);
|
|
295
|
+
if (!(0, utils_1.compareBytes)(types_1.UserId.fromKey(keyExchangeData.identityKey).toBytes(), (0, utils_1.encodeBase64)(datagram.sender)))
|
|
296
|
+
new Error("Malicious bootstrap request");
|
|
297
|
+
const request = new BootstrapRequest(datagram.sender, keyExchangeData);
|
|
298
|
+
request.onChange = () => {
|
|
299
|
+
if (!request.data)
|
|
300
|
+
throw new Error("Error sending handshake");
|
|
301
|
+
this.sendHandshake(request.data);
|
|
302
|
+
};
|
|
303
|
+
yield this.bootstraps.set(datagram.sender, request);
|
|
304
|
+
this.emitter.emit('bootstrap', { header: datagram.header, request });
|
|
305
|
+
return;
|
|
283
306
|
case types_1.Protocols.PING:
|
|
284
|
-
|
|
307
|
+
this.emitter.emit('ping', { header: datagram.header });
|
|
308
|
+
return;
|
|
285
309
|
default:
|
|
286
310
|
throw new Error("Invalid protocol");
|
|
287
311
|
}
|
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.1",
|
|
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,48 +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 msg = yield alice.packData(bob.userId, (0, utils_1.encodeData)("test"));
|
|
44
|
-
const relay = yield alice.packRelay(bob.userId, msg);
|
|
45
|
-
console.log((0, utils_1.compareBytes)(msg.toBytes(), (yield bob.open(relay)).payload));
|
|
46
|
-
//const testone = await Promise.all(Array(400).fill(0).map(() => alice.packData(bob.userId, decodeBase64(crypto.randomBytes(64)))));
|
|
47
|
-
//console.log(((await bob.open(testone[350])).payload));
|
|
48
|
-
}));
|