@freesignal/protocol 0.7.10 → 0.8.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 +1 -1
- package/dist/double-ratchet.js +28 -35
- package/dist/index.d.ts +4 -4
- package/dist/index.js +14 -34
- package/dist/node.d.ts +6 -7
- package/dist/node.js +61 -70
- package/dist/types.d.ts +1 -1
- package/dist/types.js +75 -91
- package/dist/x3dh.d.ts +2 -2
- package/dist/x3dh.js +49 -56
- package/package.json +6 -6
package/dist/double-ratchet.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* You should have received a copy of the GNU General Public License
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
|
-
import { IdentityKey, UserId } from "./types";
|
|
19
|
+
import { IdentityKey, UserId } from "./types.js";
|
|
20
20
|
export interface ExportedKeySession {
|
|
21
21
|
identityKey: string;
|
|
22
22
|
secretKey: string;
|
package/dist/double-ratchet.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -17,31 +16,26 @@
|
|
|
17
16
|
* You should have received a copy of the GNU General Public License
|
|
18
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.KeySession = void 0;
|
|
25
|
-
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
26
|
-
const utils_1 = require("@freesignal/utils");
|
|
27
|
-
const types_1 = require("./types");
|
|
19
|
+
import crypto from "@freesignal/crypto";
|
|
20
|
+
import { decodeBase64, encodeBase64, compareBytes } from "@freesignal/utils";
|
|
21
|
+
import { IdentityKey } from "./types.js";
|
|
28
22
|
/**
|
|
29
23
|
* Represents a secure Double Ratchet session.
|
|
30
24
|
* Used for forward-secure encryption and decryption of messages.
|
|
31
25
|
*/
|
|
32
|
-
class KeySession {
|
|
26
|
+
export class KeySession {
|
|
33
27
|
constructor({ identityKey, secretKey, remoteKey, rootKey, headerKey, nextHeaderKey }) {
|
|
34
28
|
this.headerKeys = new Map();
|
|
35
29
|
this.previousKeys = new KeyMap();
|
|
36
30
|
this.identityKey = identityKey;
|
|
37
31
|
this.rootKey = rootKey;
|
|
38
|
-
this.sessionTag =
|
|
39
|
-
this.keyPair =
|
|
32
|
+
this.sessionTag = decodeBase64(crypto.hkdf(rootKey, new Uint8Array(32).fill(0), "/freesignal/session-authtag", 32));
|
|
33
|
+
this.keyPair = crypto.ECDH.keyPair(secretKey);
|
|
40
34
|
if (headerKey)
|
|
41
35
|
this.headerKey = headerKey;
|
|
42
36
|
if (nextHeaderKey) {
|
|
43
37
|
this.nextHeaderKey = nextHeaderKey;
|
|
44
|
-
this.headerKeys.set(
|
|
38
|
+
this.headerKeys.set(decodeBase64(crypto.hash(nextHeaderKey)), nextHeaderKey);
|
|
45
39
|
}
|
|
46
40
|
if (remoteKey) {
|
|
47
41
|
this.sendingChain = this.getChain(remoteKey, this.headerKey);
|
|
@@ -52,10 +46,10 @@ class KeySession {
|
|
|
52
46
|
return this.identityKey.userId;
|
|
53
47
|
}
|
|
54
48
|
getChain(remoteKey, headerKey, previousCount) {
|
|
55
|
-
const sharedKey =
|
|
49
|
+
const sharedKey = crypto.ECDH.scalarMult(this.keyPair.secretKey, remoteKey);
|
|
56
50
|
if (!this.rootKey)
|
|
57
|
-
this.rootKey =
|
|
58
|
-
const hashkey =
|
|
51
|
+
this.rootKey = crypto.hash(sharedKey);
|
|
52
|
+
const hashkey = crypto.hkdf(sharedKey, this.rootKey, KeySession.info, KeySession.keyLength * 3);
|
|
59
53
|
this.rootKey = hashkey.subarray(0, KeySession.keyLength);
|
|
60
54
|
return new KeyChain(this.publicKey, remoteKey, hashkey.subarray(KeySession.keyLength, KeySession.keyLength * 2), hashkey.subarray(KeySession.keyLength * 2), headerKey, previousCount);
|
|
61
55
|
}
|
|
@@ -79,17 +73,17 @@ class KeySession {
|
|
|
79
73
|
}
|
|
80
74
|
getReceivingKey(encryptionKeys) {
|
|
81
75
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
82
|
-
if (!this.previousKeys.has(
|
|
83
|
-
if (!
|
|
76
|
+
if (!this.previousKeys.has(decodeBase64(encryptionKeys.publicKey) + encryptionKeys.count.toString())) {
|
|
77
|
+
if (!compareBytes(encryptionKeys.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
|
|
84
78
|
while (this.receivingChain && this.receivingChain.count < encryptionKeys.previous) {
|
|
85
79
|
const key = this.receivingChain.getKey();
|
|
86
|
-
this.previousKeys.set(
|
|
80
|
+
this.previousKeys.set(decodeBase64(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
|
|
87
81
|
}
|
|
88
82
|
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);
|
|
89
|
-
this.headerKeys.set(
|
|
83
|
+
this.headerKeys.set(decodeBase64(crypto.hash(this.receivingChain.nextHeaderKey)), this.receivingChain.nextHeaderKey);
|
|
90
84
|
if (this.nextHeaderKey)
|
|
91
85
|
this.nextHeaderKey = undefined;
|
|
92
|
-
this.keyPair =
|
|
86
|
+
this.keyPair = crypto.ECDH.keyPair();
|
|
93
87
|
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);
|
|
94
88
|
if (this.headerKey)
|
|
95
89
|
this.headerKey = undefined;
|
|
@@ -98,10 +92,10 @@ class KeySession {
|
|
|
98
92
|
throw new Error("Error initializing receivingChain");
|
|
99
93
|
while (this.receivingChain.count < encryptionKeys.count) {
|
|
100
94
|
const key = this.receivingChain.getKey();
|
|
101
|
-
this.previousKeys.set(
|
|
95
|
+
this.previousKeys.set(decodeBase64(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
|
|
102
96
|
}
|
|
103
97
|
}
|
|
104
|
-
return this.previousKeys.get(
|
|
98
|
+
return this.previousKeys.get(decodeBase64(encryptionKeys.publicKey) + encryptionKeys.count.toString());
|
|
105
99
|
}
|
|
106
100
|
/**
|
|
107
101
|
* Whether both the sending and receiving chains are initialized.
|
|
@@ -118,11 +112,11 @@ class KeySession {
|
|
|
118
112
|
var _a, _b;
|
|
119
113
|
return {
|
|
120
114
|
identityKey: this.identityKey.toString(),
|
|
121
|
-
secretKey:
|
|
122
|
-
rootKey:
|
|
115
|
+
secretKey: decodeBase64(this.keyPair.secretKey),
|
|
116
|
+
rootKey: decodeBase64(this.rootKey),
|
|
123
117
|
sendingChain: (_a = this.sendingChain) === null || _a === void 0 ? void 0 : _a.toJSON(),
|
|
124
118
|
receivingChain: (_b = this.receivingChain) === null || _b === void 0 ? void 0 : _b.toJSON(),
|
|
125
|
-
headerKey: this.headerKey ?
|
|
119
|
+
headerKey: this.headerKey ? decodeBase64(this.headerKey) : undefined,
|
|
126
120
|
headerKeys: Array.from(this.headerKeys.entries()),
|
|
127
121
|
previousKeys: Array.from(this.previousKeys.entries())
|
|
128
122
|
};
|
|
@@ -134,14 +128,13 @@ class KeySession {
|
|
|
134
128
|
* @returns session with the state parsed.
|
|
135
129
|
*/
|
|
136
130
|
static from(data) {
|
|
137
|
-
const session = new KeySession({ identityKey:
|
|
131
|
+
const session = new KeySession({ identityKey: IdentityKey.from(data.identityKey), secretKey: encodeBase64(data.secretKey), rootKey: encodeBase64(data.rootKey) });
|
|
138
132
|
session.sendingChain = data.sendingChain ? KeyChain.from(data.sendingChain) : undefined;
|
|
139
133
|
session.receivingChain = data.receivingChain ? KeyChain.from(data.receivingChain) : undefined;
|
|
140
134
|
session.previousKeys = new KeyMap(data.previousKeys);
|
|
141
135
|
return session;
|
|
142
136
|
}
|
|
143
137
|
}
|
|
144
|
-
exports.KeySession = KeySession;
|
|
145
138
|
KeySession.keyLength = 32;
|
|
146
139
|
KeySession.version = 1;
|
|
147
140
|
KeySession.info = "/freesignal/double-ratchet/v0." + KeySession.version;
|
|
@@ -159,7 +152,7 @@ class KeyChain {
|
|
|
159
152
|
getKey() {
|
|
160
153
|
if (++this._count >= KeySession.maxCount)
|
|
161
154
|
throw new Error("SendingChain count too big");
|
|
162
|
-
const hash =
|
|
155
|
+
const hash = crypto.hkdf(this.chainKey, new Uint8Array(KeySession.keyLength).fill(0), KeySession.info, KeySession.keyLength * 2);
|
|
163
156
|
this.chainKey = hash.subarray(0, KeySession.keyLength);
|
|
164
157
|
return hash.subarray(KeySession.keyLength);
|
|
165
158
|
}
|
|
@@ -171,18 +164,18 @@ class KeyChain {
|
|
|
171
164
|
}
|
|
172
165
|
toJSON() {
|
|
173
166
|
return {
|
|
174
|
-
publicKey:
|
|
175
|
-
remoteKey:
|
|
176
|
-
headerKey: this.headerKey ?
|
|
177
|
-
nextHeaderKey:
|
|
178
|
-
chainKey:
|
|
167
|
+
publicKey: decodeBase64(this.publicKey),
|
|
168
|
+
remoteKey: decodeBase64(this.remoteKey),
|
|
169
|
+
headerKey: this.headerKey ? decodeBase64(this.headerKey) : undefined,
|
|
170
|
+
nextHeaderKey: decodeBase64(this.nextHeaderKey),
|
|
171
|
+
chainKey: decodeBase64(this.chainKey),
|
|
179
172
|
count: this.count,
|
|
180
173
|
previousCount: this.previousCount
|
|
181
174
|
};
|
|
182
175
|
}
|
|
183
176
|
static from(obj) {
|
|
184
177
|
//
|
|
185
|
-
const chain = new KeyChain(
|
|
178
|
+
const chain = new KeyChain(encodeBase64(obj.publicKey), encodeBase64(obj.remoteKey), encodeBase64(obj.chainKey), encodeBase64(obj.nextHeaderKey), obj.headerKey ? encodeBase64(obj.headerKey) : undefined, obj.previousCount);
|
|
186
179
|
chain._count = obj.count;
|
|
187
180
|
return chain;
|
|
188
181
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
19
|
import { LocalStorage, Crypto, Database, KeyExchangeDataBundle } from "@freesignal/interfaces";
|
|
20
|
-
import { ExportedKeySession } from "./double-ratchet";
|
|
21
|
-
import { PrivateIdentityKey } from "./types";
|
|
22
|
-
import { BootstrapRequest, FreeSignalNode } from "./node";
|
|
20
|
+
import { ExportedKeySession } from "./double-ratchet.js";
|
|
21
|
+
import { PrivateIdentityKey } from "./types.js";
|
|
22
|
+
import { BootstrapRequest, FreeSignalNode } from "./node.js";
|
|
23
23
|
/**
|
|
24
24
|
* Generates identity key
|
|
25
25
|
*
|
|
@@ -35,4 +35,4 @@ export declare function createNode(storage: Database<{
|
|
|
35
35
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
36
36
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
37
37
|
}>, privateIdentityKey?: PrivateIdentityKey): FreeSignalNode;
|
|
38
|
-
export * from "./types";
|
|
38
|
+
export * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -17,45 +16,26 @@
|
|
|
17
16
|
* You should have received a copy of the GNU General Public License
|
|
18
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
-
}
|
|
26
|
-
Object.defineProperty(o, k2, desc);
|
|
27
|
-
}) : (function(o, m, k, k2) {
|
|
28
|
-
if (k2 === undefined) k2 = k;
|
|
29
|
-
o[k2] = m[k];
|
|
30
|
-
}));
|
|
31
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
32
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
33
|
-
};
|
|
34
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
-
};
|
|
37
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.createIdentity = createIdentity;
|
|
39
|
-
exports.createNode = createNode;
|
|
40
|
-
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
41
|
-
const types_1 = require("./types");
|
|
42
|
-
const node_1 = require("./node");
|
|
19
|
+
import crypto from "@freesignal/crypto";
|
|
20
|
+
import { PrivateIdentityKey } from "./types.js";
|
|
21
|
+
import { FreeSignalNode } from "./node.js";
|
|
43
22
|
/**
|
|
44
23
|
* Generates identity key
|
|
45
24
|
*
|
|
46
25
|
* @param seed - Seed to generate the key.
|
|
47
26
|
* @returns An object containing readonly signing and box key pairs.
|
|
48
27
|
*/
|
|
49
|
-
function createIdentity(seed) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
28
|
+
export function createIdentity(seed) {
|
|
29
|
+
const seedLength = 32;
|
|
30
|
+
seed !== null && seed !== void 0 ? seed : (seed = crypto.randomBytes(seedLength));
|
|
31
|
+
const signatureSeed = crypto.hkdf(seed, new Uint8Array(seedLength).fill(0), "identity-ed25519", seedLength);
|
|
32
|
+
const exchangeSeed = crypto.hkdf(seed, new Uint8Array(crypto.ECDH.secretKeyLength).fill(0), "identity-x25519", crypto.ECDH.secretKeyLength);
|
|
33
|
+
const signatureKeyPair = crypto.EdDSA.keyPairFromSeed(signatureSeed);
|
|
34
|
+
const exchangeKeyPair = crypto.ECDH.keyPair(exchangeSeed);
|
|
35
|
+
return PrivateIdentityKey.from(signatureKeyPair.secretKey, exchangeKeyPair.secretKey);
|
|
56
36
|
}
|
|
57
37
|
/** */
|
|
58
|
-
function createNode(storage, privateIdentityKey) {
|
|
59
|
-
return new
|
|
38
|
+
export function createNode(storage, privateIdentityKey) {
|
|
39
|
+
return new FreeSignalNode(storage, privateIdentityKey);
|
|
60
40
|
}
|
|
61
|
-
|
|
41
|
+
export * from "./types.js";
|
package/dist/node.d.ts
CHANGED
|
@@ -17,18 +17,17 @@
|
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
19
|
import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
|
|
20
|
-
import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
|
|
21
|
-
import { KeyExchange } from "./x3dh";
|
|
22
|
-
import { ExportedKeySession, KeySession } from "./double-ratchet";
|
|
23
|
-
import EventEmitter,
|
|
20
|
+
import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types.js";
|
|
21
|
+
import { KeyExchange } from "./x3dh.js";
|
|
22
|
+
import { ExportedKeySession, KeySession } from "./double-ratchet.js";
|
|
23
|
+
import { EventEmitter, EventCallback } from "easyemitter.ts";
|
|
24
24
|
export declare class BootstrapRequest extends EventEmitter<'change', BootstrapRequest> {
|
|
25
25
|
#private;
|
|
26
26
|
readonly senderId: UserId | string;
|
|
27
|
-
|
|
28
|
-
constructor(senderId: UserId | string,
|
|
27
|
+
readonly data: KeyExchangeData;
|
|
28
|
+
constructor(senderId: UserId | string, data: KeyExchangeData);
|
|
29
29
|
onChange: EventCallback<BootstrapRequest, this>;
|
|
30
30
|
get status(): "pending" | "accepted" | "denied";
|
|
31
|
-
get data(): KeyExchangeData | undefined;
|
|
32
31
|
accept(): void;
|
|
33
32
|
deny(): void;
|
|
34
33
|
}
|
package/dist/node.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -37,24 +36,19 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
37
36
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
38
37
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
39
38
|
};
|
|
40
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
-
};
|
|
43
39
|
var _BootstrapRequest_status;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class BootstrapRequest extends easyemitter_ts_1.default {
|
|
54
|
-
constructor(senderId, keyExchangeData) {
|
|
40
|
+
import { Datagram, decryptData, DiscoverType, encryptData, EncryptedDatagram, IdentityKey, Protocols, UserId } from "./types.js";
|
|
41
|
+
import { KeyExchange } from "./x3dh.js";
|
|
42
|
+
import { KeySession } from "./double-ratchet.js";
|
|
43
|
+
import { createIdentity } from "./index.js";
|
|
44
|
+
import { decodeData, encodeData, compareBytes, concatBytes, decodeBase64 } from "@freesignal/utils";
|
|
45
|
+
import crypto from "@freesignal/crypto";
|
|
46
|
+
import { EventEmitter } from "easyemitter.ts";
|
|
47
|
+
export class BootstrapRequest extends EventEmitter {
|
|
48
|
+
constructor(senderId, data) {
|
|
55
49
|
super();
|
|
56
50
|
this.senderId = senderId;
|
|
57
|
-
this.
|
|
51
|
+
this.data = data;
|
|
58
52
|
_BootstrapRequest_status.set(this, 'pending');
|
|
59
53
|
this.onChange = () => { };
|
|
60
54
|
this.on('change', (data, emitter) => this.onChange(data, emitter));
|
|
@@ -62,9 +56,6 @@ class BootstrapRequest extends easyemitter_ts_1.default {
|
|
|
62
56
|
get status() {
|
|
63
57
|
return __classPrivateFieldGet(this, _BootstrapRequest_status, "f");
|
|
64
58
|
}
|
|
65
|
-
get data() {
|
|
66
|
-
return __classPrivateFieldGet(this, _BootstrapRequest_status, "f") === 'accepted' ? this.keyExchangeData : undefined;
|
|
67
|
-
}
|
|
68
59
|
accept() {
|
|
69
60
|
if (this.status === 'pending') {
|
|
70
61
|
__classPrivateFieldSet(this, _BootstrapRequest_status, 'accepted', "f");
|
|
@@ -78,24 +69,23 @@ class BootstrapRequest extends easyemitter_ts_1.default {
|
|
|
78
69
|
}
|
|
79
70
|
}
|
|
80
71
|
}
|
|
81
|
-
exports.BootstrapRequest = BootstrapRequest;
|
|
82
72
|
_BootstrapRequest_status = new WeakMap();
|
|
83
|
-
class FreeSignalNode {
|
|
73
|
+
export class FreeSignalNode {
|
|
84
74
|
constructor(storage, privateIdentityKey) {
|
|
85
75
|
this.discovers = new Set();
|
|
86
|
-
this.emitter = new
|
|
76
|
+
this.emitter = new EventEmitter();
|
|
87
77
|
this.messageHandler = (data) => this.onMessage({ session: data.session, payload: data.payload });
|
|
88
78
|
this.sendHandler = (data) => this.onSend(data.datagram.toBytes());
|
|
89
|
-
this.handshakeHandler = (data) => { var _a; return this.onHandshake(
|
|
79
|
+
this.handshakeHandler = (data) => { var _a; return this.onHandshake(UserId.from((_a = data.session) === null || _a === void 0 ? void 0 : _a.userId)); };
|
|
90
80
|
this.bootstrapHandler = (data) => this.onRequest(data.request);
|
|
91
81
|
this.onMessage = () => { };
|
|
92
82
|
this.onSend = () => { };
|
|
93
83
|
this.onHandshake = () => { };
|
|
94
84
|
this.onRequest = () => { };
|
|
95
|
-
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey :
|
|
85
|
+
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : createIdentity();
|
|
96
86
|
this.sessions = new SessionMap(storage.sessions);
|
|
97
87
|
this.users = storage.users;
|
|
98
|
-
this.keyExchange = new
|
|
88
|
+
this.keyExchange = new KeyExchange(storage.keyExchange, this.privateIdentityKey);
|
|
99
89
|
this.bundles = storage.bundles;
|
|
100
90
|
this.bootstraps = storage.bootstraps;
|
|
101
91
|
this.emitter.on('message', this.messageHandler);
|
|
@@ -129,39 +119,39 @@ class FreeSignalNode {
|
|
|
129
119
|
const session = yield this.sessions.get(sessionTag);
|
|
130
120
|
if (!session)
|
|
131
121
|
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
132
|
-
const encrypted =
|
|
122
|
+
const encrypted = encryptData(session, data);
|
|
133
123
|
this.sessions.set(receiverId.toString(), session);
|
|
134
|
-
return { session, userId:
|
|
124
|
+
return { session, userId: UserId.from(receiverId), datagram: new EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
|
|
135
125
|
});
|
|
136
126
|
}
|
|
137
127
|
sendHandshake(data) {
|
|
138
128
|
return __awaiter(this, void 0, void 0, function* () {
|
|
139
|
-
if (data instanceof
|
|
129
|
+
if (data instanceof KeySession) {
|
|
140
130
|
//console.debug("Sending Handshake Ack");
|
|
141
131
|
const session = yield this.sessions.get(data.sessionTag);
|
|
142
132
|
if (!session)
|
|
143
133
|
throw new Error("Session not found for sessionTag: " + data.sessionTag);
|
|
144
|
-
this.emitter.emit('send', yield this.encrypt(session.userId,
|
|
134
|
+
this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.HANDSHAKE, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
|
|
145
135
|
return;
|
|
146
136
|
}
|
|
147
137
|
//console.debug("Sending Handshake Syn");
|
|
148
|
-
const { session, message } = yield this.keyExchange.digestData(data,
|
|
138
|
+
const { session, message } = yield this.keyExchange.digestData(data, encodeData(yield this.keyExchange.generateBundle()));
|
|
149
139
|
yield this.sessions.set(session.sessionTag, session);
|
|
150
140
|
yield this.users.set(session.userId.toString(), session.sessionTag);
|
|
151
|
-
const datagram = new
|
|
141
|
+
const datagram = new Datagram(Protocols.HANDSHAKE, encodeData(message), session.sessionTag).sign(this.privateIdentityKey.signatureKey);
|
|
152
142
|
this.emitter.emit('send', { session, datagram, userId: session.userId });
|
|
153
143
|
});
|
|
154
144
|
}
|
|
155
145
|
sendData(receiverId, data) {
|
|
156
146
|
return __awaiter(this, void 0, void 0, function* () {
|
|
157
147
|
//console.debug("Sending Data");
|
|
158
|
-
this.emitter.emit('send', yield this.encrypt(receiverId,
|
|
148
|
+
this.emitter.emit('send', yield this.encrypt(receiverId, Protocols.MESSAGE, encodeData(data)));
|
|
159
149
|
});
|
|
160
150
|
}
|
|
161
151
|
sendRelay(relayId, receiverId, data) {
|
|
162
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
163
153
|
//console.debug("Sending Relay");
|
|
164
|
-
this.emitter.emit('send', yield this.encrypt(relayId,
|
|
154
|
+
this.emitter.emit('send', yield this.encrypt(relayId, Protocols.RELAY, concatBytes(UserId.from(receiverId).toBytes(), data.toBytes())));
|
|
165
155
|
});
|
|
166
156
|
}
|
|
167
157
|
sendPing(receiverId) {
|
|
@@ -173,28 +163,28 @@ class FreeSignalNode {
|
|
|
173
163
|
const session = yield this.sessions.get(sessionTag);
|
|
174
164
|
if (!session)
|
|
175
165
|
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
176
|
-
const datagram = new
|
|
166
|
+
const datagram = new Datagram(Protocols.PING, undefined, session.sessionTag);
|
|
177
167
|
this.emitter.emit('send', { session, datagram, userId: session.userId });
|
|
178
168
|
});
|
|
179
169
|
}
|
|
180
170
|
sendDiscover(receiverId, discoverId) {
|
|
181
171
|
return __awaiter(this, void 0, void 0, function* () {
|
|
182
172
|
//console.debug("Sending Discover");
|
|
183
|
-
if (receiverId instanceof
|
|
173
|
+
if (receiverId instanceof UserId)
|
|
184
174
|
receiverId = receiverId.toString();
|
|
185
|
-
if (discoverId instanceof
|
|
175
|
+
if (discoverId instanceof UserId)
|
|
186
176
|
discoverId = discoverId.toString();
|
|
187
177
|
const message = {
|
|
188
|
-
type:
|
|
178
|
+
type: DiscoverType.REQUEST,
|
|
189
179
|
discoverId
|
|
190
180
|
};
|
|
191
181
|
this.discovers.add(receiverId);
|
|
192
|
-
this.emitter.emit('send', yield this.encrypt(receiverId,
|
|
182
|
+
this.emitter.emit('send', yield this.encrypt(receiverId, Protocols.DISCOVER, encodeData(message)));
|
|
193
183
|
});
|
|
194
184
|
}
|
|
195
185
|
packBootstrap() {
|
|
196
186
|
return __awaiter(this, void 0, void 0, function* () {
|
|
197
|
-
return new
|
|
187
|
+
return new Datagram(Protocols.BOOTSTRAP, encodeData(yield this.keyExchange.generateData()));
|
|
198
188
|
});
|
|
199
189
|
}
|
|
200
190
|
sendBootstrap(receiverId) {
|
|
@@ -203,12 +193,12 @@ class FreeSignalNode {
|
|
|
203
193
|
if (yield this.sessions.has(receiverId.toString()))
|
|
204
194
|
throw new Error("Session exists");
|
|
205
195
|
const datagram = yield this.packBootstrap();
|
|
206
|
-
this.emitter.emit('send', { datagram, userId:
|
|
196
|
+
this.emitter.emit('send', { datagram, userId: UserId.from(receiverId) });
|
|
207
197
|
});
|
|
208
198
|
}
|
|
209
199
|
decrypt(datagram) {
|
|
210
200
|
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
-
datagram =
|
|
201
|
+
datagram = EncryptedDatagram.from(datagram);
|
|
212
202
|
if (!datagram.sessionTag)
|
|
213
203
|
throw new Error("Datagram not encrypted");
|
|
214
204
|
const session = yield this.sessions.get(datagram.sessionTag);
|
|
@@ -218,14 +208,14 @@ class FreeSignalNode {
|
|
|
218
208
|
throw new Error("Signature not verified");
|
|
219
209
|
if (!datagram.payload)
|
|
220
210
|
throw new Error("Missing payload");
|
|
221
|
-
const decrypted =
|
|
211
|
+
const decrypted = decryptData(session, datagram.payload);
|
|
222
212
|
this.sessions.set(datagram.sessionTag, session);
|
|
223
213
|
return { session, payload: decrypted };
|
|
224
214
|
});
|
|
225
215
|
}
|
|
226
216
|
openHandshake(datagram) {
|
|
227
217
|
return __awaiter(this, void 0, void 0, function* () {
|
|
228
|
-
const encrypted =
|
|
218
|
+
const encrypted = EncryptedDatagram.from(datagram);
|
|
229
219
|
if (!encrypted.payload)
|
|
230
220
|
throw new Error("Missing payload");
|
|
231
221
|
if (yield this.sessions.has(encrypted.sessionTag)) {
|
|
@@ -234,28 +224,28 @@ class FreeSignalNode {
|
|
|
234
224
|
const { payload } = yield this.decrypt(encrypted);
|
|
235
225
|
if (!session)
|
|
236
226
|
throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
|
|
237
|
-
if (!
|
|
227
|
+
if (!compareBytes(payload, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)))
|
|
238
228
|
throw new Error("Error validating handshake data");
|
|
239
229
|
return 'ack';
|
|
240
230
|
}
|
|
241
231
|
//console.debug("Opening Handshake Syn");
|
|
242
|
-
const data =
|
|
243
|
-
if (!encrypted.verify(
|
|
232
|
+
const data = decodeData(encrypted.payload);
|
|
233
|
+
if (!encrypted.verify(IdentityKey.from(data.identityKey).signatureKey))
|
|
244
234
|
throw new Error("Signature not verified");
|
|
245
235
|
const { session, associatedData } = yield this.keyExchange.digestMessage(data);
|
|
246
236
|
yield this.sessions.set(session.sessionTag, session);
|
|
247
237
|
yield this.users.set(session.userId.toString(), session.sessionTag);
|
|
248
|
-
yield this.bundles.set(session.userId.toString(),
|
|
238
|
+
yield this.bundles.set(session.userId.toString(), decodeData(associatedData));
|
|
249
239
|
return 'syn';
|
|
250
240
|
});
|
|
251
241
|
}
|
|
252
242
|
open(datagram) {
|
|
253
243
|
return __awaiter(this, void 0, void 0, function* () {
|
|
254
244
|
if (datagram instanceof Uint8Array)
|
|
255
|
-
datagram =
|
|
245
|
+
datagram = Datagram.from(datagram);
|
|
256
246
|
switch (datagram.protocol) {
|
|
257
|
-
case
|
|
258
|
-
const encrypted =
|
|
247
|
+
case Protocols.HANDSHAKE: {
|
|
248
|
+
const encrypted = EncryptedDatagram.from(datagram);
|
|
259
249
|
if (!encrypted.payload)
|
|
260
250
|
throw new Error("Missing payload");
|
|
261
251
|
if ((yield this.openHandshake(datagram)) === 'ack')
|
|
@@ -267,28 +257,28 @@ class FreeSignalNode {
|
|
|
267
257
|
this.emitter.emit('handshake', { session });
|
|
268
258
|
return;
|
|
269
259
|
}
|
|
270
|
-
case
|
|
260
|
+
case Protocols.MESSAGE:
|
|
271
261
|
//console.debug("Opening Message");
|
|
272
262
|
this.emitter.emit('message', yield this.decrypt(datagram));
|
|
273
263
|
return;
|
|
274
|
-
case
|
|
264
|
+
case Protocols.RELAY: {
|
|
275
265
|
//console.debug("Opening Relay");
|
|
276
266
|
const opened = yield this.decrypt(datagram);
|
|
277
|
-
const userId =
|
|
267
|
+
const userId = decodeBase64(opened.payload.subarray(0, UserId.keyLength));
|
|
278
268
|
const sessionTag = yield this.users.get(userId);
|
|
279
269
|
if (!sessionTag)
|
|
280
270
|
throw new Error("Session not found for user: " + userId);
|
|
281
271
|
const session = yield this.sessions.get(sessionTag);
|
|
282
272
|
if (!session)
|
|
283
273
|
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
284
|
-
this.emitter.emit('send', { session, datagram:
|
|
274
|
+
this.emitter.emit('send', { session, datagram: Datagram.from(opened.payload.slice(UserId.keyLength)), userId: session.userId });
|
|
285
275
|
return;
|
|
286
276
|
}
|
|
287
|
-
case
|
|
277
|
+
case Protocols.DISCOVER: {
|
|
288
278
|
//console.debug("Opening Discover");
|
|
289
279
|
const { session, payload } = yield this.decrypt(datagram);
|
|
290
|
-
const message =
|
|
291
|
-
if (message.type ===
|
|
280
|
+
const message = decodeData(payload);
|
|
281
|
+
if (message.type === DiscoverType.REQUEST && message.discoverId && !(yield this.sessions.has(message.discoverId))) {
|
|
292
282
|
let data;
|
|
293
283
|
if (message.discoverId === this.userId.toString()) {
|
|
294
284
|
data = yield this.keyExchange.generateData();
|
|
@@ -311,34 +301,36 @@ class FreeSignalNode {
|
|
|
311
301
|
onetimePreKey
|
|
312
302
|
};
|
|
313
303
|
}
|
|
314
|
-
const response = { type:
|
|
315
|
-
this.emitter.emit('send', yield this.encrypt(session.userId,
|
|
304
|
+
const response = { type: DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
305
|
+
this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.DISCOVER, encodeData(response)));
|
|
316
306
|
}
|
|
317
|
-
else if (message.type ===
|
|
307
|
+
else if (message.type === DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
318
308
|
this.discovers.delete(message.discoverId);
|
|
319
309
|
if (message.data)
|
|
320
310
|
yield this.sendHandshake(message.data);
|
|
321
311
|
}
|
|
322
312
|
return;
|
|
323
313
|
}
|
|
324
|
-
case
|
|
314
|
+
case Protocols.BOOTSTRAP: {
|
|
325
315
|
//console.debug("Opening Bootstrap");
|
|
326
316
|
if (!datagram.payload)
|
|
327
317
|
throw new Error("Invalid Bootstrap");
|
|
328
|
-
const keyExchangeData =
|
|
329
|
-
const userId =
|
|
318
|
+
const keyExchangeData = decodeData(datagram.payload);
|
|
319
|
+
const userId = UserId.fromKey(keyExchangeData.identityKey);
|
|
330
320
|
const request = new BootstrapRequest(userId, keyExchangeData);
|
|
321
|
+
let sended = false;
|
|
331
322
|
request.onChange = (request) => {
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
323
|
+
if (request.status === 'accepted' && !sended) {
|
|
324
|
+
sended = true;
|
|
325
|
+
this.sendHandshake(request.data);
|
|
326
|
+
}
|
|
335
327
|
};
|
|
336
328
|
yield this.bootstraps.set(userId.toString(), request);
|
|
337
329
|
this.emitter.emit('bootstrap', { request });
|
|
338
330
|
return;
|
|
339
331
|
}
|
|
340
|
-
case
|
|
341
|
-
datagram =
|
|
332
|
+
case Protocols.PING:
|
|
333
|
+
datagram = EncryptedDatagram.from(datagram);
|
|
342
334
|
const session = yield this.sessions.get(datagram.sessionTag);
|
|
343
335
|
if (!session)
|
|
344
336
|
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
@@ -350,7 +342,6 @@ class FreeSignalNode {
|
|
|
350
342
|
});
|
|
351
343
|
}
|
|
352
344
|
}
|
|
353
|
-
exports.FreeSignalNode = FreeSignalNode;
|
|
354
345
|
class SessionMap {
|
|
355
346
|
constructor(storage, maxSize = 50) {
|
|
356
347
|
this.storage = storage;
|
|
@@ -368,7 +359,7 @@ class SessionMap {
|
|
|
368
359
|
const sessionData = yield this.storage.get(key);
|
|
369
360
|
if (!sessionData)
|
|
370
361
|
return undefined;
|
|
371
|
-
return
|
|
362
|
+
return KeySession.from(sessionData);
|
|
372
363
|
}
|
|
373
364
|
return session;
|
|
374
365
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
19
|
import type { LocalStorage, Encodable, KeyExchangeData } from "@freesignal/interfaces";
|
|
20
|
-
import { EncryptionKeys, KeySession } from "./double-ratchet";
|
|
20
|
+
import { EncryptionKeys, KeySession } from "./double-ratchet.js";
|
|
21
21
|
export declare function encryptData(session: KeySession, data: Uint8Array): EncryptedData;
|
|
22
22
|
export declare function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array;
|
|
23
23
|
export declare class UserId implements Encodable {
|
package/dist/types.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -26,38 +25,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
26
25
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
27
26
|
});
|
|
28
27
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.AsyncMap = exports.EncryptedData = exports.EncryptionHeader = exports.EncryptedDatagram = exports.Datagram = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
|
|
34
|
-
exports.encryptData = encryptData;
|
|
35
|
-
exports.decryptData = decryptData;
|
|
36
|
-
const utils_1 = require("@freesignal/utils");
|
|
37
|
-
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
38
|
-
function encryptData(session, data) {
|
|
28
|
+
import { concatBytes, decodeBase64, encodeBase64, bytesToNumber, numberToBytes, compareBytes } from "@freesignal/utils";
|
|
29
|
+
import crypto from "@freesignal/crypto";
|
|
30
|
+
export function encryptData(session, data) {
|
|
39
31
|
const key = session.getSendingKey();
|
|
40
32
|
if (!key)
|
|
41
33
|
throw new Error("Error generating key");
|
|
42
|
-
const nonce =
|
|
43
|
-
const payload =
|
|
34
|
+
const nonce = crypto.randomBytes(EncryptionHeader.nonceLength);
|
|
35
|
+
const payload = crypto.box.encrypt(data, nonce, key.secretKey);
|
|
44
36
|
let header = new EncryptionHeader(key, nonce).toBytes();
|
|
45
37
|
const headerKey = session.getHeaderKey();
|
|
46
38
|
if (!headerKey)
|
|
47
39
|
return new EncryptedData({ header, payload });
|
|
48
|
-
const headerNonce =
|
|
40
|
+
const headerNonce = crypto.randomBytes(EncryptionHeader.nonceLength);
|
|
49
41
|
if (headerKey)
|
|
50
|
-
header =
|
|
51
|
-
return new EncryptedData({ hashkey:
|
|
42
|
+
header = crypto.box.encrypt(header, headerNonce, headerKey);
|
|
43
|
+
return new EncryptedData({ hashkey: crypto.hash(headerKey !== null && headerKey !== void 0 ? headerKey : new Uint8Array(32).fill(0)), header, nonce: headerNonce, payload });
|
|
52
44
|
}
|
|
53
|
-
function decryptData(session, encryptedData) {
|
|
45
|
+
export function decryptData(session, encryptedData) {
|
|
54
46
|
const encrypted = EncryptedData.from(encryptedData);
|
|
55
47
|
let headerData = encrypted.header;
|
|
56
48
|
if (encrypted.hashkey && encrypted.nonce) {
|
|
57
|
-
const headerKey = session.getHeaderKey(
|
|
49
|
+
const headerKey = session.getHeaderKey(decodeBase64(encrypted.hashkey));
|
|
58
50
|
if (!headerKey)
|
|
59
51
|
throw new Error("Error getting key");
|
|
60
|
-
const data =
|
|
52
|
+
const data = crypto.box.decrypt(encrypted.header, encrypted.nonce, headerKey);
|
|
61
53
|
if (!data)
|
|
62
54
|
throw new Error("Error calculating header");
|
|
63
55
|
headerData = data;
|
|
@@ -66,18 +58,18 @@ function decryptData(session, encryptedData) {
|
|
|
66
58
|
const key = session.getReceivingKey(header);
|
|
67
59
|
if (!key)
|
|
68
60
|
throw new Error("Error calculating key");
|
|
69
|
-
const decrypted =
|
|
61
|
+
const decrypted = crypto.box.decrypt(encrypted.payload, header.nonce, key);
|
|
70
62
|
if (!decrypted)
|
|
71
63
|
throw new Error("Error decrypting data");
|
|
72
64
|
return decrypted;
|
|
73
65
|
}
|
|
74
|
-
class UserId {
|
|
66
|
+
export class UserId {
|
|
75
67
|
constructor(array) {
|
|
76
68
|
this.array = array;
|
|
77
69
|
}
|
|
78
70
|
;
|
|
79
71
|
toString() {
|
|
80
|
-
return
|
|
72
|
+
return decodeBase64(this.array);
|
|
81
73
|
}
|
|
82
74
|
toJSON() {
|
|
83
75
|
return this.toString();
|
|
@@ -87,20 +79,19 @@ class UserId {
|
|
|
87
79
|
}
|
|
88
80
|
static fromKey(identityKey) {
|
|
89
81
|
if (typeof identityKey === 'string')
|
|
90
|
-
identityKey =
|
|
82
|
+
identityKey = encodeBase64(identityKey);
|
|
91
83
|
else if (identityKey instanceof IdentityKey)
|
|
92
84
|
identityKey = (identityKey).toBytes();
|
|
93
|
-
return new UserId(
|
|
85
|
+
return new UserId(crypto.hkdf(identityKey, new Uint8Array(32).fill(0), "/freesignal/userid", UserId.keyLength));
|
|
94
86
|
}
|
|
95
87
|
static from(userId) {
|
|
96
88
|
if (typeof userId === 'string')
|
|
97
|
-
userId =
|
|
89
|
+
userId = encodeBase64(userId);
|
|
98
90
|
return new UserId(userId instanceof Uint8Array ? userId : userId.array);
|
|
99
91
|
}
|
|
100
92
|
}
|
|
101
|
-
exports.UserId = UserId;
|
|
102
93
|
UserId.keyLength = 32;
|
|
103
|
-
class IdentityKey {
|
|
94
|
+
export class IdentityKey {
|
|
104
95
|
constructor(identityKey) {
|
|
105
96
|
if (identityKey instanceof IdentityKey) {
|
|
106
97
|
this.info = identityKey.info;
|
|
@@ -109,22 +100,22 @@ class IdentityKey {
|
|
|
109
100
|
}
|
|
110
101
|
else {
|
|
111
102
|
if (typeof identityKey === 'string')
|
|
112
|
-
identityKey =
|
|
103
|
+
identityKey = encodeBase64(identityKey);
|
|
113
104
|
if (identityKey.length !== IdentityKey.keyLength)
|
|
114
105
|
throw new Error("Invalid key length");
|
|
115
106
|
this.info = identityKey[0];
|
|
116
|
-
this.signatureKey = identityKey.subarray(1,
|
|
117
|
-
this.exchangeKey = identityKey.subarray(
|
|
107
|
+
this.signatureKey = identityKey.subarray(1, crypto.EdDSA.publicKeyLength + 1);
|
|
108
|
+
this.exchangeKey = identityKey.subarray(crypto.EdDSA.publicKeyLength + 1, IdentityKey.keyLength);
|
|
118
109
|
}
|
|
119
110
|
}
|
|
120
111
|
get userId() {
|
|
121
112
|
return UserId.fromKey(this.toBytes());
|
|
122
113
|
}
|
|
123
114
|
toBytes() {
|
|
124
|
-
return
|
|
115
|
+
return concatBytes(numberToBytes(this.info, 1), this.signatureKey, this.exchangeKey);
|
|
125
116
|
}
|
|
126
117
|
toString() {
|
|
127
|
-
return
|
|
118
|
+
return decodeBase64(this.toBytes());
|
|
128
119
|
}
|
|
129
120
|
toJSON() {
|
|
130
121
|
return this.toString();
|
|
@@ -134,18 +125,17 @@ class IdentityKey {
|
|
|
134
125
|
if (key instanceof IdentityKey)
|
|
135
126
|
return key.toBytes();
|
|
136
127
|
else if (typeof key === 'string')
|
|
137
|
-
return
|
|
128
|
+
return encodeBase64(key);
|
|
138
129
|
else
|
|
139
130
|
return key;
|
|
140
131
|
});
|
|
141
|
-
return new IdentityKey(keys.length === 2 ?
|
|
132
|
+
return new IdentityKey(keys.length === 2 ? concatBytes(numberToBytes(IdentityKey.info + IdentityKey.version, 1), ...keys) : keys[0]);
|
|
142
133
|
}
|
|
143
134
|
}
|
|
144
|
-
|
|
145
|
-
IdentityKey.keyLength = crypto_1.default.EdDSA.publicKeyLength + crypto_1.default.ECDH.publicKeyLength + 1;
|
|
135
|
+
IdentityKey.keyLength = crypto.EdDSA.publicKeyLength + crypto.ECDH.publicKeyLength + 1;
|
|
146
136
|
IdentityKey.version = 1;
|
|
147
137
|
IdentityKey.info = 0x70;
|
|
148
|
-
class PrivateIdentityKey {
|
|
138
|
+
export class PrivateIdentityKey {
|
|
149
139
|
constructor(privateIdentityKey) {
|
|
150
140
|
if (privateIdentityKey instanceof PrivateIdentityKey) {
|
|
151
141
|
this.info = privateIdentityKey.info;
|
|
@@ -155,23 +145,23 @@ class PrivateIdentityKey {
|
|
|
155
145
|
}
|
|
156
146
|
else {
|
|
157
147
|
if (typeof privateIdentityKey === 'string')
|
|
158
|
-
privateIdentityKey =
|
|
148
|
+
privateIdentityKey = encodeBase64(privateIdentityKey);
|
|
159
149
|
if (!PrivateIdentityKey.isIdentityKeys(privateIdentityKey))
|
|
160
150
|
throw new Error("Invalid key length");
|
|
161
151
|
this.info = privateIdentityKey[0];
|
|
162
|
-
this.signatureKey = privateIdentityKey.subarray(1,
|
|
163
|
-
this.exchangeKey = privateIdentityKey.subarray(
|
|
164
|
-
this.identityKey = IdentityKey.from(
|
|
152
|
+
this.signatureKey = privateIdentityKey.subarray(1, crypto.EdDSA.secretKeyLength + 1);
|
|
153
|
+
this.exchangeKey = privateIdentityKey.subarray(crypto.EdDSA.secretKeyLength + 1, PrivateIdentityKey.keyLength);
|
|
154
|
+
this.identityKey = IdentityKey.from(crypto.EdDSA.keyPair(this.signatureKey).publicKey, crypto.ECDH.keyPair(this.exchangeKey).publicKey);
|
|
165
155
|
}
|
|
166
156
|
}
|
|
167
157
|
get userId() {
|
|
168
158
|
return UserId.fromKey(this.identityKey.toBytes()).toString();
|
|
169
159
|
}
|
|
170
160
|
toBytes() {
|
|
171
|
-
return
|
|
161
|
+
return concatBytes(numberToBytes(this.info, 1), this.signatureKey, this.exchangeKey);
|
|
172
162
|
}
|
|
173
163
|
toString() {
|
|
174
|
-
return
|
|
164
|
+
return decodeBase64(this.toBytes());
|
|
175
165
|
}
|
|
176
166
|
toJSON() {
|
|
177
167
|
return this.toString();
|
|
@@ -184,23 +174,22 @@ class PrivateIdentityKey {
|
|
|
184
174
|
if (key instanceof PrivateIdentityKey)
|
|
185
175
|
return key.toBytes();
|
|
186
176
|
else if (typeof key === 'string')
|
|
187
|
-
return
|
|
177
|
+
return encodeBase64(key);
|
|
188
178
|
else
|
|
189
179
|
return key;
|
|
190
180
|
});
|
|
191
|
-
return new PrivateIdentityKey(keys.length === 2 ?
|
|
181
|
+
return new PrivateIdentityKey(keys.length === 2 ? concatBytes(numberToBytes(PrivateIdentityKey.info + PrivateIdentityKey.version, 1), ...keys) : keys[0]);
|
|
192
182
|
}
|
|
193
183
|
}
|
|
194
|
-
|
|
195
|
-
PrivateIdentityKey.keyLength = crypto_1.default.EdDSA.secretKeyLength + crypto_1.default.ECDH.secretKeyLength + 1;
|
|
184
|
+
PrivateIdentityKey.keyLength = crypto.EdDSA.secretKeyLength + crypto.ECDH.secretKeyLength + 1;
|
|
196
185
|
PrivateIdentityKey.version = 1;
|
|
197
186
|
PrivateIdentityKey.info = 0x4E;
|
|
198
|
-
var DiscoverType;
|
|
187
|
+
export var DiscoverType;
|
|
199
188
|
(function (DiscoverType) {
|
|
200
189
|
DiscoverType[DiscoverType["REQUEST"] = 0] = "REQUEST";
|
|
201
190
|
DiscoverType[DiscoverType["RESPONSE"] = 1] = "RESPONSE";
|
|
202
|
-
})(DiscoverType || (
|
|
203
|
-
var Protocols;
|
|
191
|
+
})(DiscoverType || (DiscoverType = {}));
|
|
192
|
+
export var Protocols;
|
|
204
193
|
(function (Protocols) {
|
|
205
194
|
Protocols["PING"] = "/freesignal/ping";
|
|
206
195
|
Protocols["MESSAGE"] = "/freesignal/message";
|
|
@@ -208,7 +197,7 @@ var Protocols;
|
|
|
208
197
|
Protocols["HANDSHAKE"] = "/freesignal/handshake";
|
|
209
198
|
Protocols["DISCOVER"] = "/freesignal/discover";
|
|
210
199
|
Protocols["BOOTSTRAP"] = "/freesignal/bootstrap";
|
|
211
|
-
})(Protocols || (
|
|
200
|
+
})(Protocols || (Protocols = {}));
|
|
212
201
|
(function (Protocols) {
|
|
213
202
|
function isProtocol(protocol) {
|
|
214
203
|
return Object.values(Protocols).includes(protocol);
|
|
@@ -223,19 +212,19 @@ var Protocols;
|
|
|
223
212
|
}
|
|
224
213
|
Protocols.toCode = toCode;
|
|
225
214
|
function encode(protocol, length = 1) {
|
|
226
|
-
return
|
|
215
|
+
return numberToBytes(Protocols.toCode(protocol), length);
|
|
227
216
|
}
|
|
228
217
|
Protocols.encode = encode;
|
|
229
218
|
function decode(array) {
|
|
230
|
-
return Protocols.fromCode(
|
|
219
|
+
return Protocols.fromCode(bytesToNumber(array));
|
|
231
220
|
}
|
|
232
221
|
Protocols.decode = decode;
|
|
233
|
-
})(Protocols || (
|
|
222
|
+
})(Protocols || (Protocols = {}));
|
|
234
223
|
;
|
|
235
|
-
class Datagram {
|
|
224
|
+
export class Datagram {
|
|
236
225
|
constructor(protocol, payload, sessionTag) {
|
|
237
226
|
this._version = Datagram.version;
|
|
238
|
-
this.sessionTag = sessionTag instanceof Uint8Array ?
|
|
227
|
+
this.sessionTag = sessionTag instanceof Uint8Array ? decodeBase64(sessionTag) : sessionTag;
|
|
239
228
|
this.protocol = protocol;
|
|
240
229
|
this.payload = payload instanceof Uint8Array ? payload : payload === null || payload === void 0 ? void 0 : payload.toBytes();
|
|
241
230
|
}
|
|
@@ -243,50 +232,50 @@ class Datagram {
|
|
|
243
232
|
return this._version;
|
|
244
233
|
}
|
|
245
234
|
get signature() {
|
|
246
|
-
return this._signature ?
|
|
235
|
+
return this._signature ? decodeBase64(this._signature) : undefined;
|
|
247
236
|
}
|
|
248
237
|
get unsigned() {
|
|
249
238
|
const data = this.toBytes();
|
|
250
239
|
data[0] &= 127;
|
|
251
|
-
return data.subarray(0, data.length - (this._signature ?
|
|
240
|
+
return data.subarray(0, data.length - (this._signature ? crypto.EdDSA.signatureLength : 0));
|
|
252
241
|
}
|
|
253
242
|
toBytes() {
|
|
254
243
|
var _a, _b;
|
|
255
|
-
return
|
|
244
|
+
return concatBytes(new Uint8Array(1).fill(this.version | (this.signature ? 128 : 0)), //1
|
|
256
245
|
Protocols.encode(this.protocol), //1
|
|
257
|
-
this.sessionTag ?
|
|
246
|
+
this.sessionTag ? encodeBase64(this.sessionTag) : new Uint8Array(32).fill(0), //32
|
|
258
247
|
(_a = this.payload) !== null && _a !== void 0 ? _a : new Uint8Array(), (_b = this._signature) !== null && _b !== void 0 ? _b : new Uint8Array());
|
|
259
248
|
}
|
|
260
249
|
sign(secretKey) {
|
|
261
|
-
this._signature =
|
|
250
|
+
this._signature = crypto.EdDSA.sign(this.unsigned, secretKey);
|
|
262
251
|
return this;
|
|
263
252
|
}
|
|
264
253
|
verify(publicKey) {
|
|
265
254
|
if (!this._signature)
|
|
266
255
|
throw new Error("Datagram not signed");
|
|
267
|
-
return
|
|
256
|
+
return crypto.EdDSA.verify(this._signature, this.unsigned, publicKey);
|
|
268
257
|
}
|
|
269
258
|
toString() {
|
|
270
|
-
return
|
|
259
|
+
return decodeBase64(this.toBytes());
|
|
271
260
|
}
|
|
272
261
|
toJSON() {
|
|
273
262
|
return {
|
|
274
263
|
version: this.version,
|
|
275
264
|
sessionTag: this.sessionTag,
|
|
276
265
|
protocol: this.protocol,
|
|
277
|
-
payload: this.payload ?
|
|
278
|
-
signature: this._signature ?
|
|
266
|
+
payload: this.payload ? decodeBase64(this.payload) : undefined,
|
|
267
|
+
signature: this._signature ? decodeBase64(this._signature) : undefined
|
|
279
268
|
};
|
|
280
269
|
}
|
|
281
270
|
static from(data) {
|
|
282
271
|
if (typeof data === 'string')
|
|
283
|
-
data =
|
|
272
|
+
data = encodeBase64(data);
|
|
284
273
|
if (data instanceof Uint8Array) {
|
|
285
274
|
const authTag = data.subarray(2, Datagram.headerLength);
|
|
286
|
-
const datagram = new Datagram(Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerLength, data.length - (data[0] & 128 ?
|
|
275
|
+
const datagram = new Datagram(Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerLength, data.length - (data[0] & 128 ? crypto.EdDSA.signatureLength : 0)), compareBytes(authTag, new Uint8Array(32).fill(0)) ? undefined : decodeBase64(authTag));
|
|
287
276
|
datagram._version = data[0] & 127;
|
|
288
277
|
if (data[0] & 128)
|
|
289
|
-
datagram._signature = data.subarray(data.length -
|
|
278
|
+
datagram._signature = data.subarray(data.length - crypto.EdDSA.signatureLength);
|
|
290
279
|
return datagram;
|
|
291
280
|
}
|
|
292
281
|
else if (data instanceof Datagram) {
|
|
@@ -299,10 +288,9 @@ class Datagram {
|
|
|
299
288
|
throw new Error('Invalid constructor arguments for Datagram');
|
|
300
289
|
}
|
|
301
290
|
}
|
|
302
|
-
exports.Datagram = Datagram;
|
|
303
291
|
Datagram.version = 1;
|
|
304
292
|
Datagram.headerLength = 34;
|
|
305
|
-
class EncryptedDatagram extends Datagram {
|
|
293
|
+
export class EncryptedDatagram extends Datagram {
|
|
306
294
|
constructor(protocol, sessionTag, payload) {
|
|
307
295
|
super(protocol, payload, sessionTag);
|
|
308
296
|
}
|
|
@@ -313,8 +301,7 @@ class EncryptedDatagram extends Datagram {
|
|
|
313
301
|
return datagram;
|
|
314
302
|
}
|
|
315
303
|
}
|
|
316
|
-
|
|
317
|
-
class EncryptionHeader {
|
|
304
|
+
export class EncryptionHeader {
|
|
318
305
|
constructor(keys, nonce) {
|
|
319
306
|
this.nonce = nonce;
|
|
320
307
|
this.count = keys.count;
|
|
@@ -322,13 +309,13 @@ class EncryptionHeader {
|
|
|
322
309
|
this.publicKey = keys.publicKey;
|
|
323
310
|
}
|
|
324
311
|
toBytes() {
|
|
325
|
-
return
|
|
312
|
+
return concatBytes(numberToBytes(this.count, EncryptionHeader.countLength), numberToBytes(this.previous, EncryptionHeader.countLength), this.publicKey, this.nonce);
|
|
326
313
|
}
|
|
327
314
|
toJSON() {
|
|
328
315
|
return {
|
|
329
316
|
count: this.count,
|
|
330
317
|
previous: this.previous,
|
|
331
|
-
publicKey:
|
|
318
|
+
publicKey: decodeBase64(this.publicKey)
|
|
332
319
|
};
|
|
333
320
|
}
|
|
334
321
|
static from(data) {
|
|
@@ -336,17 +323,16 @@ class EncryptionHeader {
|
|
|
336
323
|
data = data.toBytes();
|
|
337
324
|
let offset = 0;
|
|
338
325
|
return new EncryptionHeader({
|
|
339
|
-
count:
|
|
340
|
-
previous:
|
|
326
|
+
count: bytesToNumber(data.subarray(offset, offset += EncryptionHeader.countLength)),
|
|
327
|
+
previous: bytesToNumber(data.subarray(offset, offset += EncryptionHeader.countLength)),
|
|
341
328
|
publicKey: data.subarray(offset, offset += EncryptionHeader.keyLength)
|
|
342
329
|
}, data.subarray(offset, offset += EncryptedData.nonceLength));
|
|
343
330
|
}
|
|
344
331
|
}
|
|
345
|
-
|
|
346
|
-
EncryptionHeader.
|
|
347
|
-
EncryptionHeader.nonceLength = crypto_1.default.box.nonceLength;
|
|
332
|
+
EncryptionHeader.keyLength = crypto.box.keyLength;
|
|
333
|
+
EncryptionHeader.nonceLength = crypto.box.nonceLength;
|
|
348
334
|
EncryptionHeader.countLength = 2;
|
|
349
|
-
class EncryptedData {
|
|
335
|
+
export class EncryptedData {
|
|
350
336
|
constructor({ hashkey, header, nonce, payload }) {
|
|
351
337
|
this._version = EncryptedData.version;
|
|
352
338
|
this.header = header;
|
|
@@ -362,22 +348,22 @@ class EncryptedData {
|
|
|
362
348
|
}
|
|
363
349
|
toBytes() {
|
|
364
350
|
var _a, _b;
|
|
365
|
-
return
|
|
351
|
+
return concatBytes(numberToBytes(this._version | (this.hashkey && this.nonce ? 128 : 0), 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);
|
|
366
352
|
}
|
|
367
353
|
toJSON() {
|
|
368
354
|
return {
|
|
369
355
|
version: this._version,
|
|
370
|
-
header:
|
|
371
|
-
hashkey: this.hashkey ?
|
|
372
|
-
nonce: this.nonce ?
|
|
373
|
-
payload:
|
|
356
|
+
header: decodeBase64(this.header),
|
|
357
|
+
hashkey: this.hashkey ? decodeBase64(this.hashkey) : undefined,
|
|
358
|
+
nonce: this.nonce ? decodeBase64(this.nonce) : undefined,
|
|
359
|
+
payload: decodeBase64(this.payload)
|
|
374
360
|
};
|
|
375
361
|
}
|
|
376
362
|
static from(data) {
|
|
377
363
|
if (data instanceof EncryptedData)
|
|
378
364
|
data = data.toBytes();
|
|
379
|
-
const versionByte =
|
|
380
|
-
const headerLength =
|
|
365
|
+
const versionByte = bytesToNumber(data.subarray(0, 1));
|
|
366
|
+
const headerLength = bytesToNumber(data.subarray(1, 4));
|
|
381
367
|
let offset = 4;
|
|
382
368
|
const header = data.subarray(offset, offset += headerLength);
|
|
383
369
|
let hashkey, nonce;
|
|
@@ -394,10 +380,9 @@ class EncryptedData {
|
|
|
394
380
|
return obj;
|
|
395
381
|
}
|
|
396
382
|
}
|
|
397
|
-
exports.EncryptedData = EncryptedData;
|
|
398
383
|
EncryptedData.version = 1;
|
|
399
|
-
EncryptedData.nonceLength =
|
|
400
|
-
class AsyncMap {
|
|
384
|
+
EncryptedData.nonceLength = crypto.box.nonceLength;
|
|
385
|
+
export class AsyncMap {
|
|
401
386
|
constructor(iterable) {
|
|
402
387
|
this.map = new Map(iterable);
|
|
403
388
|
}
|
|
@@ -433,4 +418,3 @@ class AsyncMap {
|
|
|
433
418
|
});
|
|
434
419
|
}
|
|
435
420
|
}
|
|
436
|
-
exports.AsyncMap = AsyncMap;
|
package/dist/x3dh.d.ts
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
19
|
import { KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, LocalStorage, Crypto } from "@freesignal/interfaces";
|
|
20
|
-
import { KeySession } from "./double-ratchet";
|
|
21
|
-
import { IdentityKey, PrivateIdentityKey } from "./types";
|
|
20
|
+
import { KeySession } from "./double-ratchet.js";
|
|
21
|
+
import { IdentityKey, PrivateIdentityKey } from "./types.js";
|
|
22
22
|
export interface ExportedKeyExchange {
|
|
23
23
|
privateIdentityKey: PrivateIdentityKey;
|
|
24
24
|
storage: Array<[string, Crypto.KeyPair]>;
|
package/dist/x3dh.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -26,34 +25,29 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
26
25
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
27
26
|
});
|
|
28
27
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const double_ratchet_1 = require("./double-ratchet");
|
|
36
|
-
const utils_1 = require("@freesignal/utils");
|
|
37
|
-
const types_1 = require("./types");
|
|
38
|
-
const _1 = require(".");
|
|
39
|
-
class KeyExchange {
|
|
28
|
+
import crypto from "@freesignal/crypto";
|
|
29
|
+
import { KeySession } from "./double-ratchet.js";
|
|
30
|
+
import { concatBytes, decodeBase64, encodeBase64, compareBytes } from "@freesignal/utils";
|
|
31
|
+
import { decryptData, encryptData, IdentityKey } from "./types.js";
|
|
32
|
+
import { createIdentity } from "./index.js";
|
|
33
|
+
export class KeyExchange {
|
|
40
34
|
constructor(storage, privateIdentityKey) {
|
|
41
35
|
this.storage = storage;
|
|
42
|
-
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey :
|
|
36
|
+
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : createIdentity();
|
|
43
37
|
}
|
|
44
38
|
get identityKey() {
|
|
45
39
|
return this.privateIdentityKey.identityKey;
|
|
46
40
|
}
|
|
47
41
|
generateSPK() {
|
|
48
|
-
const signedPreKey =
|
|
49
|
-
const signedPreKeyHash =
|
|
50
|
-
this.storage.set(
|
|
42
|
+
const signedPreKey = crypto.ECDH.keyPair();
|
|
43
|
+
const signedPreKeyHash = crypto.hash(signedPreKey.publicKey);
|
|
44
|
+
this.storage.set(decodeBase64(signedPreKeyHash), signedPreKey);
|
|
51
45
|
return { signedPreKey, signedPreKeyHash };
|
|
52
46
|
}
|
|
53
47
|
generateOPK(spkHash) {
|
|
54
|
-
const onetimePreKey =
|
|
55
|
-
const onetimePreKeyHash =
|
|
56
|
-
this.storage.set(
|
|
48
|
+
const onetimePreKey = crypto.ECDH.keyPair();
|
|
49
|
+
const onetimePreKeyHash = crypto.hash(onetimePreKey.publicKey);
|
|
50
|
+
this.storage.set(decodeBase64(spkHash).concat(decodeBase64(onetimePreKeyHash)), onetimePreKey);
|
|
57
51
|
return { onetimePreKey, onetimePreKeyHash };
|
|
58
52
|
}
|
|
59
53
|
generateBundle(length) {
|
|
@@ -63,9 +57,9 @@ class KeyExchange {
|
|
|
63
57
|
return {
|
|
64
58
|
version: KeyExchange.version,
|
|
65
59
|
identityKey: this.identityKey.toString(),
|
|
66
|
-
signedPreKey:
|
|
67
|
-
signature:
|
|
68
|
-
onetimePreKeys: onetimePreKey.map(opk =>
|
|
60
|
+
signedPreKey: decodeBase64(signedPreKey.publicKey),
|
|
61
|
+
signature: decodeBase64(crypto.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
|
|
62
|
+
onetimePreKeys: onetimePreKey.map(opk => decodeBase64(opk.publicKey))
|
|
69
63
|
};
|
|
70
64
|
});
|
|
71
65
|
}
|
|
@@ -76,31 +70,31 @@ class KeyExchange {
|
|
|
76
70
|
return {
|
|
77
71
|
version: KeyExchange.version,
|
|
78
72
|
identityKey: this.identityKey.toString(),
|
|
79
|
-
signedPreKey:
|
|
80
|
-
signature:
|
|
81
|
-
onetimePreKey:
|
|
73
|
+
signedPreKey: decodeBase64(signedPreKey.publicKey),
|
|
74
|
+
signature: decodeBase64(crypto.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
|
|
75
|
+
onetimePreKey: decodeBase64(onetimePreKey.publicKey)
|
|
82
76
|
};
|
|
83
77
|
});
|
|
84
78
|
}
|
|
85
79
|
digestData(message, associatedData) {
|
|
86
80
|
return __awaiter(this, void 0, void 0, function* () {
|
|
87
81
|
//console.debug("Digest Data")
|
|
88
|
-
const ephemeralKey =
|
|
89
|
-
const signedPreKey =
|
|
90
|
-
const identityKey =
|
|
91
|
-
if (!
|
|
82
|
+
const ephemeralKey = crypto.ECDH.keyPair();
|
|
83
|
+
const signedPreKey = encodeBase64(message.signedPreKey);
|
|
84
|
+
const identityKey = IdentityKey.from(message.identityKey);
|
|
85
|
+
if (!crypto.EdDSA.verify(encodeBase64(message.signature), crypto.hash(signedPreKey), identityKey.signatureKey))
|
|
92
86
|
throw new Error("Signature verification failed");
|
|
93
|
-
const onetimePreKey = message.onetimePreKey ?
|
|
94
|
-
const signedPreKeyHash =
|
|
95
|
-
const onetimePreKeyHash = onetimePreKey ?
|
|
96
|
-
const derivedKey =
|
|
97
|
-
...
|
|
98
|
-
...
|
|
99
|
-
...
|
|
100
|
-
...onetimePreKey ?
|
|
101
|
-
]), new Uint8Array(
|
|
102
|
-
const session = new
|
|
103
|
-
const encrypted =
|
|
87
|
+
const onetimePreKey = message.onetimePreKey ? encodeBase64(message.onetimePreKey) : undefined;
|
|
88
|
+
const signedPreKeyHash = crypto.hash(signedPreKey);
|
|
89
|
+
const onetimePreKeyHash = onetimePreKey ? crypto.hash(onetimePreKey) : new Uint8Array();
|
|
90
|
+
const derivedKey = crypto.hkdf(new Uint8Array([
|
|
91
|
+
...crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, signedPreKey),
|
|
92
|
+
...crypto.ECDH.scalarMult(ephemeralKey.secretKey, identityKey.exchangeKey),
|
|
93
|
+
...crypto.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
|
|
94
|
+
...onetimePreKey ? crypto.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
|
|
95
|
+
]), new Uint8Array(KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, KeySession.keyLength * 3);
|
|
96
|
+
const session = new KeySession({ identityKey, remoteKey: identityKey.exchangeKey, rootKey: derivedKey.subarray(0, KeySession.keyLength), headerKey: derivedKey.subarray(KeySession.keyLength, KeySession.keyLength * 2), nextHeaderKey: derivedKey.subarray(KeySession.keyLength * 2) });
|
|
97
|
+
const encrypted = encryptData(session, concatBytes(crypto.hash(this.identityKey.toBytes()), crypto.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
|
|
104
98
|
if (!encrypted)
|
|
105
99
|
throw new Error("Decryption error");
|
|
106
100
|
return {
|
|
@@ -108,10 +102,10 @@ class KeyExchange {
|
|
|
108
102
|
message: {
|
|
109
103
|
version: KeyExchange.version,
|
|
110
104
|
identityKey: this.identityKey.toString(),
|
|
111
|
-
ephemeralKey:
|
|
112
|
-
signedPreKeyHash:
|
|
113
|
-
onetimePreKeyHash:
|
|
114
|
-
associatedData:
|
|
105
|
+
ephemeralKey: decodeBase64(ephemeralKey.publicKey),
|
|
106
|
+
signedPreKeyHash: decodeBase64(signedPreKeyHash),
|
|
107
|
+
onetimePreKeyHash: decodeBase64(onetimePreKeyHash),
|
|
108
|
+
associatedData: decodeBase64(encrypted.toBytes())
|
|
115
109
|
}
|
|
116
110
|
};
|
|
117
111
|
});
|
|
@@ -122,23 +116,23 @@ class KeyExchange {
|
|
|
122
116
|
const signedPreKey = yield this.storage.get(message.signedPreKeyHash);
|
|
123
117
|
const hash = message.signedPreKeyHash.concat(message.onetimePreKeyHash);
|
|
124
118
|
const onetimePreKey = yield this.storage.get(hash);
|
|
125
|
-
const identityKey =
|
|
119
|
+
const identityKey = IdentityKey.from(message.identityKey);
|
|
126
120
|
if (!signedPreKey || !onetimePreKey || !message.identityKey || !message.ephemeralKey)
|
|
127
121
|
throw new Error("ACK message malformed");
|
|
128
122
|
if (!this.storage.delete(hash))
|
|
129
123
|
throw new Error("Bundle store deleting error");
|
|
130
|
-
const ephemeralKey =
|
|
131
|
-
const derivedKey =
|
|
132
|
-
...
|
|
133
|
-
...
|
|
134
|
-
...
|
|
135
|
-
...onetimePreKey ?
|
|
136
|
-
]), new Uint8Array(
|
|
137
|
-
const session = new
|
|
138
|
-
const data =
|
|
124
|
+
const ephemeralKey = encodeBase64(message.ephemeralKey);
|
|
125
|
+
const derivedKey = crypto.hkdf(new Uint8Array([
|
|
126
|
+
...crypto.ECDH.scalarMult(signedPreKey.secretKey, identityKey.exchangeKey),
|
|
127
|
+
...crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, ephemeralKey),
|
|
128
|
+
...crypto.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
|
|
129
|
+
...onetimePreKey ? crypto.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
|
|
130
|
+
]), new Uint8Array(KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, KeySession.keyLength * 3);
|
|
131
|
+
const session = new KeySession({ identityKey, secretKey: this.privateIdentityKey.exchangeKey, rootKey: derivedKey.subarray(0, KeySession.keyLength), nextHeaderKey: derivedKey.subarray(KeySession.keyLength, KeySession.keyLength * 2), headerKey: derivedKey.subarray(KeySession.keyLength * 2) });
|
|
132
|
+
const data = decryptData(session, encodeBase64(message.associatedData));
|
|
139
133
|
if (!data)
|
|
140
134
|
throw new Error("Error decrypting ACK message");
|
|
141
|
-
if (!
|
|
135
|
+
if (!compareBytes(data.subarray(0, 64), concatBytes(crypto.hash(identityKey.toBytes()), crypto.hash(this.identityKey.toBytes()))))
|
|
142
136
|
throw new Error("Error verifing Associated Data");
|
|
143
137
|
return {
|
|
144
138
|
session,
|
|
@@ -147,7 +141,6 @@ class KeyExchange {
|
|
|
147
141
|
});
|
|
148
142
|
}
|
|
149
143
|
}
|
|
150
|
-
exports.KeyExchange = KeyExchange;
|
|
151
144
|
KeyExchange.version = 1;
|
|
152
145
|
KeyExchange.hkdfInfo = "freesignal/x3dh/v." + KeyExchange.version;
|
|
153
146
|
KeyExchange.maxOPK = 10;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
7
|
-
"type": "
|
|
7
|
+
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"prepare": "tsc"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@freesignal/crypto": "^0.
|
|
38
|
-
"@freesignal/interfaces": "^0.
|
|
39
|
-
"@freesignal/utils": "^1.
|
|
40
|
-
"easyemitter.ts": "^1.0
|
|
37
|
+
"@freesignal/crypto": "^0.4.2",
|
|
38
|
+
"@freesignal/interfaces": "^0.3.0",
|
|
39
|
+
"@freesignal/utils": "^1.5.1",
|
|
40
|
+
"easyemitter.ts": "^1.1.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^24.2.1"
|