@freesignal/protocol 0.3.0 → 0.4.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/{double-ratchet.d.ts → dist/double-ratchet.d.ts} +13 -49
- package/dist/double-ratchet.js +167 -0
- package/{index.d.ts → dist/index.d.ts} +2 -1
- package/dist/node.d.ts +71 -0
- package/dist/node.js +261 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +41 -0
- package/{types.d.ts → dist/types.d.ts} +38 -11
- package/{types.js → dist/types.js} +146 -36
- package/{x3dh.d.ts → dist/x3dh.d.ts} +4 -7
- package/{x3dh.js → dist/x3dh.js} +13 -13
- package/package.json +29 -6
- package/double-ratchet.js +0 -352
- package/node.d.ts +0 -38
- package/node.js +0 -139
- package/test.js +0 -28
- /package/{index.js → dist/index.js} +0 -0
|
@@ -16,13 +16,16 @@
|
|
|
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 { LocalStorage, Encodable } from "@freesignal/interfaces";
|
|
20
|
-
|
|
19
|
+
import { LocalStorage, Encodable, KeyExchangeData } from "@freesignal/interfaces";
|
|
20
|
+
import { KeySession } from "./double-ratchet";
|
|
21
|
+
export declare function encryptData(session: KeySession, data: Uint8Array): EncryptedData;
|
|
22
|
+
export declare function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array;
|
|
23
|
+
export declare class UserId implements Encodable {
|
|
21
24
|
private readonly array;
|
|
22
25
|
private constructor();
|
|
23
26
|
toString(): string;
|
|
24
27
|
toJSON(): string;
|
|
25
|
-
|
|
28
|
+
toBytes(): Uint8Array;
|
|
26
29
|
static fromKey(identityKey: string | Uint8Array | IdentityKey): UserId;
|
|
27
30
|
static from(userId: string | Uint8Array | UserId): UserId;
|
|
28
31
|
}
|
|
@@ -51,12 +54,22 @@ export declare namespace PrivateIdentityKey {
|
|
|
51
54
|
function from(identityKey: PrivateIdentityKey | Uint8Array | string): PrivateIdentityKey;
|
|
52
55
|
function from(signatureKey: Uint8Array | string, exchangeKey: Uint8Array | string): PrivateIdentityKey;
|
|
53
56
|
}
|
|
57
|
+
export declare enum DiscoverType {
|
|
58
|
+
REQUEST = 0,
|
|
59
|
+
RESPONSE = 1
|
|
60
|
+
}
|
|
61
|
+
export interface DiscoverMessage {
|
|
62
|
+
type: DiscoverType;
|
|
63
|
+
discoverId: string;
|
|
64
|
+
data?: KeyExchangeData;
|
|
65
|
+
}
|
|
54
66
|
export declare enum Protocols {
|
|
55
67
|
NULL = "",
|
|
56
68
|
MESSAGE = "/freesignal/message",
|
|
57
69
|
RELAY = "/freesignal/relay",
|
|
58
70
|
HANDSHAKE = "/freesignal/handshake",
|
|
59
|
-
DISCOVER = "/freesignal/discover"
|
|
71
|
+
DISCOVER = "/freesignal/discover",
|
|
72
|
+
BOOTSTRAP = "/freesignal/bootstrap"
|
|
60
73
|
}
|
|
61
74
|
export declare namespace Protocols {
|
|
62
75
|
function isProtocol(protocol: any): boolean;
|
|
@@ -65,6 +78,19 @@ export declare namespace Protocols {
|
|
|
65
78
|
function encode(protocol: Protocols, length?: number): Uint8Array;
|
|
66
79
|
function decode(array: Uint8Array): Protocols;
|
|
67
80
|
}
|
|
81
|
+
interface DatagramJSON {
|
|
82
|
+
id: string;
|
|
83
|
+
version: number;
|
|
84
|
+
sender: string;
|
|
85
|
+
receiver: string;
|
|
86
|
+
protocol: Protocols;
|
|
87
|
+
createdAt: number;
|
|
88
|
+
payload: string | undefined;
|
|
89
|
+
signature: string | undefined;
|
|
90
|
+
}
|
|
91
|
+
export interface SignedDatagram extends Datagram {
|
|
92
|
+
signature: string;
|
|
93
|
+
}
|
|
68
94
|
export declare class Datagram implements Encodable {
|
|
69
95
|
static version: number;
|
|
70
96
|
private _id;
|
|
@@ -75,20 +101,20 @@ export declare class Datagram implements Encodable {
|
|
|
75
101
|
private _createdAt;
|
|
76
102
|
private _payload?;
|
|
77
103
|
private _signature?;
|
|
78
|
-
private secretKey?;
|
|
79
104
|
private static headerOffset;
|
|
80
105
|
constructor(sender: Uint8Array | string, receiver: Uint8Array | string, protocol: Protocols, payload?: Uint8Array | Encodable);
|
|
81
106
|
get id(): string;
|
|
82
107
|
get version(): number;
|
|
83
108
|
get createdAt(): number;
|
|
84
|
-
get signed(): boolean;
|
|
85
|
-
get signature(): string | undefined;
|
|
86
109
|
set payload(data: Uint8Array);
|
|
87
110
|
get payload(): Uint8Array | undefined;
|
|
111
|
+
get signature(): string | undefined;
|
|
112
|
+
private get unsigned();
|
|
88
113
|
toBytes(): Uint8Array;
|
|
89
|
-
sign(secretKey: Uint8Array):
|
|
114
|
+
sign(secretKey: Uint8Array): SignedDatagram;
|
|
90
115
|
toString(): string;
|
|
91
|
-
toJSON():
|
|
116
|
+
toJSON(): DatagramJSON;
|
|
117
|
+
static verify(datagram: Datagram, publicKey: Uint8Array): boolean;
|
|
92
118
|
static from(data: Uint8Array | Datagram | string): Datagram;
|
|
93
119
|
}
|
|
94
120
|
/**
|
|
@@ -144,14 +170,14 @@ export interface EncryptedData extends Encodable {
|
|
|
144
170
|
ciphertext: string;
|
|
145
171
|
};
|
|
146
172
|
}
|
|
147
|
-
export declare
|
|
173
|
+
export declare namespace EncryptedData {
|
|
148
174
|
/**
|
|
149
175
|
* Static factory method that constructs an `EncryptedPayload` from a raw Uint8Array.
|
|
150
176
|
*
|
|
151
177
|
* @param array - A previously serialized encrypted payload.
|
|
152
178
|
* @returns An instance of `EncryptedPayload`.
|
|
153
179
|
*/
|
|
154
|
-
|
|
180
|
+
function from(array: Uint8Array | EncryptedData): EncryptedData;
|
|
155
181
|
}
|
|
156
182
|
export declare class AsyncMap<K, V> implements LocalStorage<K, V> {
|
|
157
183
|
private readonly map;
|
|
@@ -163,3 +189,4 @@ export declare class AsyncMap<K, V> implements LocalStorage<K, V> {
|
|
|
163
189
|
clear(): Promise<void>;
|
|
164
190
|
entries(): ArrayIterator<[K, V]>;
|
|
165
191
|
}
|
|
192
|
+
export {};
|
|
@@ -30,10 +30,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
30
30
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
31
|
};
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.AsyncMap = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
|
|
33
|
+
exports.AsyncMap = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
|
|
34
|
+
exports.encryptData = encryptData;
|
|
35
|
+
exports.decryptData = decryptData;
|
|
34
36
|
const utils_1 = require("@freesignal/utils");
|
|
35
37
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
36
38
|
const double_ratchet_1 = require("./double-ratchet");
|
|
39
|
+
function encryptData(session, data) {
|
|
40
|
+
const key = session.getSendingKey();
|
|
41
|
+
if (!key)
|
|
42
|
+
throw new Error("Error generating key");
|
|
43
|
+
const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
|
|
44
|
+
const ciphertext = crypto_1.default.box.encrypt(data, nonce, key.secretKey);
|
|
45
|
+
return new EncryptedDataConstructor(key.count, key.previous, key.publicKey, nonce, ciphertext);
|
|
46
|
+
}
|
|
47
|
+
function decryptData(session, encryptedData) {
|
|
48
|
+
const encrypted = EncryptedData.from(encryptedData);
|
|
49
|
+
const key = session.getReceivingKey(encrypted);
|
|
50
|
+
if (!key)
|
|
51
|
+
throw new Error("Error calculating key");
|
|
52
|
+
const decrypted = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
|
|
53
|
+
if (!decrypted)
|
|
54
|
+
throw new Error("Error decrypting data");
|
|
55
|
+
return decrypted;
|
|
56
|
+
}
|
|
37
57
|
class UserId {
|
|
38
58
|
constructor(array) {
|
|
39
59
|
this.array = array;
|
|
@@ -45,7 +65,7 @@ class UserId {
|
|
|
45
65
|
toJSON() {
|
|
46
66
|
return this.toString();
|
|
47
67
|
}
|
|
48
|
-
|
|
68
|
+
toBytes() {
|
|
49
69
|
return this.array;
|
|
50
70
|
}
|
|
51
71
|
static fromKey(identityKey) {
|
|
@@ -88,7 +108,7 @@ var IdentityKey;
|
|
|
88
108
|
return UserId.fromKey(this.toBytes()).toString();
|
|
89
109
|
}
|
|
90
110
|
toBytes() {
|
|
91
|
-
return (0, utils_1.
|
|
111
|
+
return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.info, 1), this.signatureKey, this.exchangeKey);
|
|
92
112
|
}
|
|
93
113
|
toString() {
|
|
94
114
|
return (0, utils_1.decodeBase64)(this.toBytes());
|
|
@@ -110,7 +130,7 @@ var IdentityKey;
|
|
|
110
130
|
else
|
|
111
131
|
return key;
|
|
112
132
|
});
|
|
113
|
-
return new IdentityKeyConstructor(keys.length === 2 ? (0, utils_1.
|
|
133
|
+
return new IdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(info + IdentityKey.version, 1), ...keys) : keys[0]);
|
|
114
134
|
}
|
|
115
135
|
IdentityKey.from = from;
|
|
116
136
|
})(IdentityKey || (exports.IdentityKey = IdentityKey = {}));
|
|
@@ -121,8 +141,8 @@ var PrivateIdentityKey;
|
|
|
121
141
|
PrivateIdentityKey.version = 1;
|
|
122
142
|
class PrivateIdentityKeyConstructor {
|
|
123
143
|
constructor(privateIdentityKey) {
|
|
124
|
-
this.info = info + PrivateIdentityKey.version;
|
|
125
144
|
if (privateIdentityKey instanceof PrivateIdentityKeyConstructor) {
|
|
145
|
+
this.info = privateIdentityKey.info;
|
|
126
146
|
this.signatureKey = privateIdentityKey.signatureKey;
|
|
127
147
|
this.exchangeKey = privateIdentityKey.exchangeKey;
|
|
128
148
|
this.identityKey = privateIdentityKey.identityKey;
|
|
@@ -132,6 +152,7 @@ var PrivateIdentityKey;
|
|
|
132
152
|
privateIdentityKey = (0, utils_1.encodeBase64)(privateIdentityKey);
|
|
133
153
|
if (!isIdentityKeys(privateIdentityKey))
|
|
134
154
|
throw new Error("Invalid key length");
|
|
155
|
+
this.info = privateIdentityKey[0];
|
|
135
156
|
this.signatureKey = privateIdentityKey.subarray(1, crypto_1.default.EdDSA.secretKeyLength + 1);
|
|
136
157
|
this.exchangeKey = privateIdentityKey.subarray(crypto_1.default.EdDSA.secretKeyLength + 1, PrivateIdentityKey.keyLength);
|
|
137
158
|
this.identityKey = IdentityKey.from(crypto_1.default.EdDSA.keyPair(this.signatureKey).publicKey, crypto_1.default.ECDH.keyPair(this.exchangeKey).publicKey);
|
|
@@ -141,7 +162,7 @@ var PrivateIdentityKey;
|
|
|
141
162
|
return UserId.fromKey(this.identityKey.toBytes()).toString();
|
|
142
163
|
}
|
|
143
164
|
toBytes() {
|
|
144
|
-
return (0, utils_1.
|
|
165
|
+
return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.info, 1), this.signatureKey, this.exchangeKey);
|
|
145
166
|
}
|
|
146
167
|
toString() {
|
|
147
168
|
return (0, utils_1.decodeBase64)(this.toBytes());
|
|
@@ -163,10 +184,15 @@ var PrivateIdentityKey;
|
|
|
163
184
|
else
|
|
164
185
|
return key;
|
|
165
186
|
});
|
|
166
|
-
return new PrivateIdentityKeyConstructor(keys.length === 2 ? (0, utils_1.
|
|
187
|
+
return new PrivateIdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(info + PrivateIdentityKey.version, 1), ...keys) : keys[0]);
|
|
167
188
|
}
|
|
168
189
|
PrivateIdentityKey.from = from;
|
|
169
190
|
})(PrivateIdentityKey || (exports.PrivateIdentityKey = PrivateIdentityKey = {}));
|
|
191
|
+
var DiscoverType;
|
|
192
|
+
(function (DiscoverType) {
|
|
193
|
+
DiscoverType[DiscoverType["REQUEST"] = 0] = "REQUEST";
|
|
194
|
+
DiscoverType[DiscoverType["RESPONSE"] = 1] = "RESPONSE";
|
|
195
|
+
})(DiscoverType || (exports.DiscoverType = DiscoverType = {}));
|
|
170
196
|
var Protocols;
|
|
171
197
|
(function (Protocols) {
|
|
172
198
|
Protocols["NULL"] = "";
|
|
@@ -174,6 +200,7 @@ var Protocols;
|
|
|
174
200
|
Protocols["RELAY"] = "/freesignal/relay";
|
|
175
201
|
Protocols["HANDSHAKE"] = "/freesignal/handshake";
|
|
176
202
|
Protocols["DISCOVER"] = "/freesignal/discover";
|
|
203
|
+
Protocols["BOOTSTRAP"] = "/freesignal/bootstrap";
|
|
177
204
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
178
205
|
(function (Protocols) {
|
|
179
206
|
function isProtocol(protocol) {
|
|
@@ -188,15 +215,16 @@ var Protocols;
|
|
|
188
215
|
return Object.values(Protocols).indexOf(protocol);
|
|
189
216
|
}
|
|
190
217
|
Protocols.toCode = toCode;
|
|
191
|
-
function encode(protocol, length) {
|
|
192
|
-
return (0, utils_1.
|
|
218
|
+
function encode(protocol, length = 1) {
|
|
219
|
+
return (0, utils_1.numberToBytes)(Protocols.toCode(protocol), length);
|
|
193
220
|
}
|
|
194
221
|
Protocols.encode = encode;
|
|
195
222
|
function decode(array) {
|
|
196
|
-
return Protocols.fromCode((0, utils_1.
|
|
223
|
+
return Protocols.fromCode((0, utils_1.bytesToNumber)(array));
|
|
197
224
|
}
|
|
198
225
|
Protocols.decode = decode;
|
|
199
226
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
227
|
+
;
|
|
200
228
|
class Datagram {
|
|
201
229
|
constructor(sender, receiver, protocol, payload) {
|
|
202
230
|
this._id = crypto_1.default.UUID.generate().toString();
|
|
@@ -216,16 +244,6 @@ class Datagram {
|
|
|
216
244
|
get createdAt() {
|
|
217
245
|
return this._createdAt;
|
|
218
246
|
}
|
|
219
|
-
get signed() {
|
|
220
|
-
return !!this._signature || !!this.secretKey;
|
|
221
|
-
}
|
|
222
|
-
get signature() {
|
|
223
|
-
if (this.signed) {
|
|
224
|
-
if (!this._signature)
|
|
225
|
-
this.toBytes();
|
|
226
|
-
return (0, utils_1.decodeBase64)(this._signature);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
247
|
set payload(data) {
|
|
230
248
|
this._signature = undefined;
|
|
231
249
|
this._payload = data;
|
|
@@ -233,37 +251,56 @@ class Datagram {
|
|
|
233
251
|
get payload() {
|
|
234
252
|
return this._payload;
|
|
235
253
|
}
|
|
254
|
+
get signature() {
|
|
255
|
+
return this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined;
|
|
256
|
+
}
|
|
257
|
+
get unsigned() {
|
|
258
|
+
const data = this.toBytes();
|
|
259
|
+
data[0] &= 127;
|
|
260
|
+
return data.subarray(0, data.length - (this._signature ? crypto_1.default.EdDSA.signatureLength : 0));
|
|
261
|
+
}
|
|
236
262
|
toBytes() {
|
|
237
263
|
var _a, _b, _c;
|
|
238
|
-
|
|
264
|
+
return (0, utils_1.concatBytes)(new Uint8Array(1).fill(this.version | (this.signature ? 128 : 0)), //1
|
|
239
265
|
Protocols.encode(this.protocol), //1
|
|
240
266
|
(_a = crypto_1.default.UUID.parse(this.id)) !== null && _a !== void 0 ? _a : [], //16
|
|
241
|
-
(0, utils_1.
|
|
267
|
+
new Uint8Array((0, utils_1.numberToBytes)(this._createdAt, 8)), //8
|
|
242
268
|
(0, utils_1.encodeBase64)(this.sender), //32
|
|
243
269
|
(0, utils_1.encodeBase64)(this.receiver), //32
|
|
244
|
-
(_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array());
|
|
245
|
-
if (this.secretKey)
|
|
246
|
-
this._signature = crypto_1.default.EdDSA.sign(data, this.secretKey);
|
|
247
|
-
return (0, utils_1.concatArrays)(data, (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
|
|
270
|
+
(_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array(), (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
|
|
248
271
|
}
|
|
249
272
|
sign(secretKey) {
|
|
250
|
-
this.
|
|
273
|
+
this._signature = crypto_1.default.EdDSA.sign(this.unsigned, secretKey);
|
|
251
274
|
return this;
|
|
252
275
|
}
|
|
253
276
|
toString() {
|
|
254
277
|
return (0, utils_1.decodeBase64)(this.toBytes());
|
|
255
278
|
}
|
|
256
279
|
toJSON() {
|
|
257
|
-
return
|
|
280
|
+
return {
|
|
281
|
+
id: this.id,
|
|
282
|
+
version: this.version,
|
|
283
|
+
sender: this.sender,
|
|
284
|
+
receiver: this.receiver,
|
|
285
|
+
protocol: this.protocol,
|
|
286
|
+
createdAt: this.createdAt,
|
|
287
|
+
payload: this.payload ? (0, utils_1.decodeBase64)(this.payload) : undefined,
|
|
288
|
+
signature: this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
static verify(datagram, publicKey) {
|
|
292
|
+
if (!datagram._signature)
|
|
293
|
+
throw new Error("Datagram not signed");
|
|
294
|
+
return crypto_1.default.EdDSA.verify(datagram.unsigned, datagram._signature, publicKey);
|
|
258
295
|
}
|
|
259
296
|
static from(data) {
|
|
260
297
|
if (typeof data === 'string')
|
|
261
298
|
data = (0, utils_1.encodeBase64)(data);
|
|
262
299
|
if (data instanceof Uint8Array) {
|
|
263
|
-
const datagram = new Datagram((0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength)), (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, Datagram.headerOffset)), Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerOffset, data.length));
|
|
300
|
+
const datagram = new Datagram((0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength)), (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, Datagram.headerOffset)), Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerOffset, data.length - (data[0] & 128 ? crypto_1.default.EdDSA.signatureLength : 0)));
|
|
264
301
|
datagram._version = data[0] & 127;
|
|
265
302
|
datagram._id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
|
|
266
|
-
datagram._createdAt = (0, utils_1.
|
|
303
|
+
datagram._createdAt = (0, utils_1.bytesToNumber)(data.subarray(18, 26));
|
|
267
304
|
if (data[0] & 128)
|
|
268
305
|
datagram._signature = data.subarray(data.length - crypto_1.default.EdDSA.signatureLength);
|
|
269
306
|
return datagram;
|
|
@@ -272,8 +309,8 @@ class Datagram {
|
|
|
272
309
|
const datagram = new Datagram(data.sender, data.receiver, data.protocol, data.payload);
|
|
273
310
|
datagram._id = datagram.id;
|
|
274
311
|
datagram._version = datagram.version;
|
|
275
|
-
datagram._createdAt = datagram.
|
|
276
|
-
datagram._signature = datagram.
|
|
312
|
+
datagram._createdAt = datagram._createdAt;
|
|
313
|
+
datagram._signature = datagram._signature;
|
|
277
314
|
return datagram;
|
|
278
315
|
}
|
|
279
316
|
else
|
|
@@ -283,18 +320,91 @@ class Datagram {
|
|
|
283
320
|
exports.Datagram = Datagram;
|
|
284
321
|
Datagram.version = 1;
|
|
285
322
|
Datagram.headerOffset = 26 + crypto_1.default.EdDSA.publicKeyLength * 2;
|
|
286
|
-
|
|
323
|
+
var EncryptedData;
|
|
324
|
+
(function (EncryptedData) {
|
|
287
325
|
/**
|
|
288
326
|
* Static factory method that constructs an `EncryptedPayload` from a raw Uint8Array.
|
|
289
327
|
*
|
|
290
328
|
* @param array - A previously serialized encrypted payload.
|
|
291
329
|
* @returns An instance of `EncryptedPayload`.
|
|
292
330
|
*/
|
|
293
|
-
|
|
294
|
-
return new
|
|
331
|
+
function from(array) {
|
|
332
|
+
return new EncryptedDataConstructor(array);
|
|
333
|
+
}
|
|
334
|
+
EncryptedData.from = from;
|
|
335
|
+
})(EncryptedData || (exports.EncryptedData = EncryptedData = {}));
|
|
336
|
+
class EncryptedDataConstructor {
|
|
337
|
+
constructor(...arrays) {
|
|
338
|
+
arrays = arrays.filter(value => value !== undefined);
|
|
339
|
+
if (arrays[0] instanceof EncryptedDataConstructor) {
|
|
340
|
+
this.raw = arrays[0].raw;
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
if (typeof arrays[0] === 'number')
|
|
344
|
+
arrays[0] = (0, utils_1.numberToBytes)(arrays[0], EncryptedDataConstructor.countLength);
|
|
345
|
+
if (typeof arrays[1] === 'number')
|
|
346
|
+
arrays[1] = (0, utils_1.numberToBytes)(arrays[1], EncryptedDataConstructor.countLength);
|
|
347
|
+
if (arrays.length === 6) {
|
|
348
|
+
arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToBytes)(arrays[5], 1) : arrays[5]);
|
|
349
|
+
arrays.pop();
|
|
350
|
+
}
|
|
351
|
+
else if (arrays.length > 1) {
|
|
352
|
+
arrays.unshift((0, utils_1.numberToBytes)(double_ratchet_1.KeySession.version, 1));
|
|
353
|
+
}
|
|
354
|
+
this.raw = (0, utils_1.concatBytes)(...arrays);
|
|
355
|
+
}
|
|
356
|
+
get length() { return this.raw.length; }
|
|
357
|
+
get version() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
|
|
358
|
+
get count() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
|
|
359
|
+
get previous() { return (0, utils_1.bytesToNumber)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
|
|
360
|
+
get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
|
|
361
|
+
get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
|
|
362
|
+
get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
|
|
363
|
+
toBytes() {
|
|
364
|
+
return this.raw;
|
|
365
|
+
}
|
|
366
|
+
toString() {
|
|
367
|
+
return (0, utils_1.decodeBase64)(this.raw);
|
|
368
|
+
}
|
|
369
|
+
toJSON() {
|
|
370
|
+
return {
|
|
371
|
+
version: this.version,
|
|
372
|
+
count: this.count,
|
|
373
|
+
previous: this.previous,
|
|
374
|
+
publicKey: (0, utils_1.decodeBase64)(this.publicKey),
|
|
375
|
+
nonce: (0, utils_1.decodeBase64)(this.nonce),
|
|
376
|
+
ciphertext: (0, utils_1.decodeBase64)(this.ciphertext)
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
|
|
381
|
+
EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
|
|
382
|
+
EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
|
|
383
|
+
EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
|
|
384
|
+
EncryptedDataConstructor.countLength = 2;
|
|
385
|
+
class Offsets {
|
|
386
|
+
static set(start, length) {
|
|
387
|
+
class Offset {
|
|
388
|
+
constructor(start, length) {
|
|
389
|
+
this.start = start;
|
|
390
|
+
this.length = length;
|
|
391
|
+
if (typeof length === 'number')
|
|
392
|
+
this.end = start + length;
|
|
393
|
+
}
|
|
394
|
+
get get() {
|
|
395
|
+
return [this.start, this.length];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return new Offset(start, length);
|
|
295
399
|
}
|
|
296
400
|
}
|
|
297
|
-
|
|
401
|
+
Offsets.checksum = Offsets.set(0, 0);
|
|
402
|
+
Offsets.version = Offsets.set(Offsets.checksum.end, 1);
|
|
403
|
+
Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
|
|
404
|
+
Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
|
|
405
|
+
Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
|
|
406
|
+
Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
|
|
407
|
+
Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
|
|
298
408
|
class AsyncMap {
|
|
299
409
|
constructor(iterable) {
|
|
300
410
|
this.map = new Map(iterable);
|
|
@@ -17,7 +17,7 @@
|
|
|
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 {
|
|
20
|
+
import { KeySession } from "./double-ratchet";
|
|
21
21
|
import { IdentityKey, PrivateIdentityKey } from "./types";
|
|
22
22
|
export interface ExportedKeyExchange {
|
|
23
23
|
privateIdentityKey: PrivateIdentityKey;
|
|
@@ -29,17 +29,13 @@ export declare class KeyExchange {
|
|
|
29
29
|
private static readonly maxOPK;
|
|
30
30
|
private readonly privateIdentityKey;
|
|
31
31
|
private readonly storage;
|
|
32
|
-
|
|
33
|
-
constructor(storage: {
|
|
34
|
-
keys: LocalStorage<string, Crypto.KeyPair>;
|
|
35
|
-
sessions: LocalStorage<string, ExportedKeySession>;
|
|
36
|
-
}, privateIdentityKey?: PrivateIdentityKey);
|
|
32
|
+
constructor(storage: LocalStorage<string, Crypto.KeyPair>, privateIdentityKey?: PrivateIdentityKey);
|
|
37
33
|
get identityKey(): IdentityKey;
|
|
38
34
|
private generateSPK;
|
|
39
35
|
private generateOPK;
|
|
40
36
|
generateBundle(length?: number): Promise<KeyExchangeDataBundle>;
|
|
41
37
|
generateData(): Promise<KeyExchangeData>;
|
|
42
|
-
digestData(message: KeyExchangeData): Promise<{
|
|
38
|
+
digestData(message: KeyExchangeData, associatedData?: Uint8Array): Promise<{
|
|
43
39
|
session: KeySession;
|
|
44
40
|
message: KeyExchangeSynMessage;
|
|
45
41
|
identityKey: IdentityKey;
|
|
@@ -47,5 +43,6 @@ export declare class KeyExchange {
|
|
|
47
43
|
digestMessage(message: KeyExchangeSynMessage): Promise<{
|
|
48
44
|
session: KeySession;
|
|
49
45
|
identityKey: IdentityKey;
|
|
46
|
+
associatedData: Uint8Array;
|
|
50
47
|
}>;
|
|
51
48
|
}
|
package/{x3dh.js → dist/x3dh.js}
RENAMED
|
@@ -38,8 +38,7 @@ const types_1 = require("./types");
|
|
|
38
38
|
const _1 = require(".");
|
|
39
39
|
class KeyExchange {
|
|
40
40
|
constructor(storage, privateIdentityKey) {
|
|
41
|
-
this.storage = storage
|
|
42
|
-
this.sessions = storage.sessions;
|
|
41
|
+
this.storage = storage;
|
|
43
42
|
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
44
43
|
}
|
|
45
44
|
get identityKey() {
|
|
@@ -66,7 +65,7 @@ class KeyExchange {
|
|
|
66
65
|
identityKey: this.identityKey.toString(),
|
|
67
66
|
signedPreKey: (0, utils_1.decodeBase64)(signedPreKey.publicKey),
|
|
68
67
|
signature: (0, utils_1.decodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
|
|
69
|
-
|
|
68
|
+
onetimePreKeys: onetimePreKey.map(opk => (0, utils_1.decodeBase64)(opk.publicKey))
|
|
70
69
|
};
|
|
71
70
|
});
|
|
72
71
|
}
|
|
@@ -83,7 +82,7 @@ class KeyExchange {
|
|
|
83
82
|
};
|
|
84
83
|
});
|
|
85
84
|
}
|
|
86
|
-
digestData(message) {
|
|
85
|
+
digestData(message, associatedData) {
|
|
87
86
|
return __awaiter(this, void 0, void 0, function* () {
|
|
88
87
|
const ephemeralKey = crypto_1.default.ECDH.keyPair();
|
|
89
88
|
const signedPreKey = (0, utils_1.encodeBase64)(message.signedPreKey);
|
|
@@ -99,9 +98,9 @@ class KeyExchange {
|
|
|
99
98
|
...crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
|
|
100
99
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
|
|
101
100
|
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
|
|
102
|
-
const session = new double_ratchet_1.KeySession(
|
|
103
|
-
const
|
|
104
|
-
if (!
|
|
101
|
+
const session = new double_ratchet_1.KeySession({ remoteKey: identityKey.exchangeKey, rootKey });
|
|
102
|
+
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()));
|
|
103
|
+
if (!encrypted)
|
|
105
104
|
throw new Error("Decryption error");
|
|
106
105
|
return {
|
|
107
106
|
session,
|
|
@@ -111,7 +110,7 @@ class KeyExchange {
|
|
|
111
110
|
ephemeralKey: (0, utils_1.decodeBase64)(ephemeralKey.publicKey),
|
|
112
111
|
signedPreKeyHash: (0, utils_1.decodeBase64)(signedPreKeyHash),
|
|
113
112
|
onetimePreKeyHash: (0, utils_1.decodeBase64)(onetimePreKeyHash),
|
|
114
|
-
associatedData: (0, utils_1.decodeBase64)(
|
|
113
|
+
associatedData: (0, utils_1.decodeBase64)(encrypted.toBytes())
|
|
115
114
|
},
|
|
116
115
|
identityKey
|
|
117
116
|
};
|
|
@@ -134,15 +133,16 @@ class KeyExchange {
|
|
|
134
133
|
...crypto_1.default.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
|
|
135
134
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
|
|
136
135
|
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
|
|
137
|
-
const session = new double_ratchet_1.KeySession(
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
136
|
+
const session = new double_ratchet_1.KeySession({ secretKey: this.privateIdentityKey.exchangeKey, rootKey });
|
|
137
|
+
const data = (0, types_1.decryptData)(session, (0, utils_1.encodeBase64)(message.associatedData));
|
|
138
|
+
if (!data)
|
|
140
139
|
throw new Error("Error decrypting ACK message");
|
|
141
|
-
if (!(0, utils_1.
|
|
140
|
+
if (!(0, utils_1.compareBytes)(data.subarray(0, 64), (0, utils_1.concatBytes)(crypto_1.default.hash(identityKey.toBytes()), crypto_1.default.hash(this.identityKey.toBytes()))))
|
|
142
141
|
throw new Error("Error verifing Associated Data");
|
|
143
142
|
return {
|
|
144
143
|
session,
|
|
145
|
-
identityKey
|
|
144
|
+
identityKey,
|
|
145
|
+
associatedData: data.subarray(64)
|
|
146
146
|
};
|
|
147
147
|
});
|
|
148
148
|
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
7
7
|
"type": "commonjs",
|
|
8
|
-
"
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./double-ratchet": {
|
|
15
|
+
"import": "./dist/double-ratchet.js",
|
|
16
|
+
"require": "./dist/double-ratchet.js",
|
|
17
|
+
"types": "./dist/double-ratchet.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./node": {
|
|
20
|
+
"import": "./dist/node.js",
|
|
21
|
+
"require": "./dist/node.js",
|
|
22
|
+
"types": "./dist/node.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./x3dh": {
|
|
25
|
+
"import": "./dist/x3dh.js",
|
|
26
|
+
"require": "./dist/x3dh.js",
|
|
27
|
+
"types": "./dist/x3dh.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.js",
|
|
9
31
|
"scripts": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
32
|
+
"pretest": "tsc",
|
|
33
|
+
"test": "node ./dist/test.js",
|
|
34
|
+
"prepare": "tsc"
|
|
12
35
|
},
|
|
13
36
|
"dependencies": {
|
|
14
37
|
"@freesignal/crypto": "^0.3.0",
|
|
15
38
|
"@freesignal/interfaces": "^0.2.0",
|
|
16
|
-
"@freesignal/utils": "^1.
|
|
39
|
+
"@freesignal/utils": "^1.4.1",
|
|
17
40
|
"semaphore.ts": "^0.2.0"
|
|
18
41
|
},
|
|
19
42
|
"devDependencies": {
|
|
20
43
|
"@types/node": "^24.2.1"
|
|
21
44
|
}
|
|
22
|
-
}
|
|
45
|
+
}
|