@freesignal/protocol 0.2.4 → 0.2.6
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/api.d.ts +24 -7
- package/api.js +77 -61
- package/double-ratchet.d.ts +2 -2
- package/double-ratchet.js +6 -4
- package/index.d.ts +21 -12
- package/index.js +22 -24
- package/package.json +2 -2
- package/test.js +0 -1
- package/types.d.ts +54 -14
- package/types.js +216 -68
- package/x3dh.js +2 -2
package/api.d.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* FreeSignal Protocol
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2025 Christian Braghette
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
|
+
*/
|
|
19
|
+
import { Crypto, KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, LocalStorage } from "@freesignal/interfaces";
|
|
2
20
|
import { KeySession } from "./double-ratchet";
|
|
3
21
|
import { KeyExchange } from "./x3dh";
|
|
4
22
|
import { Datagram, IdentityKeys, EncryptedData, UserId } from "./types";
|
|
5
|
-
export declare const FREESIGNAL_MIME = "application/x-freesignal";
|
|
6
23
|
type DatagramId = string;
|
|
7
24
|
export declare class FreeSignalAPI {
|
|
8
25
|
protected readonly signKey: Crypto.KeyPair;
|
|
@@ -10,6 +27,7 @@ export declare class FreeSignalAPI {
|
|
|
10
27
|
protected readonly sessions: LocalStorage<UserId, KeySession>;
|
|
11
28
|
protected readonly keyExchange: KeyExchange;
|
|
12
29
|
protected readonly users: LocalStorage<UserId, IdentityKeys>;
|
|
30
|
+
readonly userId: UserId;
|
|
13
31
|
constructor(opts: {
|
|
14
32
|
secretSignKey: Uint8Array;
|
|
15
33
|
secretBoxKey: Uint8Array;
|
|
@@ -17,10 +35,13 @@ export declare class FreeSignalAPI {
|
|
|
17
35
|
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
18
36
|
users: LocalStorage<UserId, IdentityKeys>;
|
|
19
37
|
});
|
|
20
|
-
get userId(): Uint8Array;
|
|
21
38
|
get identityKeys(): IdentityKeys;
|
|
22
39
|
encryptData(data: Uint8Array, userId: string): Promise<EncryptedData>;
|
|
23
40
|
decryptData(data: Uint8Array, userId: string): Promise<Uint8Array>;
|
|
41
|
+
getHandshake(url: string, userId?: UserId): Promise<KeyExchangeData>;
|
|
42
|
+
postHandshake(url: string, message: KeyExchangeSynMessage): Promise<boolean>;
|
|
43
|
+
putHandshake(url: string, publicKey: string | Uint8Array, bundle: KeyExchangeDataBundle): Promise<boolean>;
|
|
44
|
+
deleteHandshake(url: string, publicKey: string | Uint8Array): Promise<boolean>;
|
|
24
45
|
getDatagrams(publicKey: string | Uint8Array, url: string): Promise<Datagram[]>;
|
|
25
46
|
postDatagrams(datagrams: Datagram[], publicKey: string | Uint8Array, url: string): Promise<number>;
|
|
26
47
|
deleteDatagrams(datagramIds: DatagramId[], publicKey: string | Uint8Array, url: string): Promise<number>;
|
|
@@ -29,9 +50,5 @@ export declare class FreeSignalAPI {
|
|
|
29
50
|
identityKeys: IdentityKeys;
|
|
30
51
|
userId: UserId;
|
|
31
52
|
}>;
|
|
32
|
-
protected packIdList(datagramIds: DatagramId[]): Uint8Array;
|
|
33
|
-
protected unpackIdList(data: Uint8Array): DatagramId[];
|
|
34
|
-
protected packDatagrams(messages: Datagram[]): Uint8Array;
|
|
35
|
-
protected unpackDatagrams(data: Uint8Array): Datagram[];
|
|
36
53
|
}
|
|
37
54
|
export {};
|
package/api.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* FreeSignal Protocol
|
|
4
|
+
*
|
|
5
|
+
* Copyright (C) 2025 Christian Braghette
|
|
6
|
+
*
|
|
7
|
+
* This program is free software: you can redistribute it and/or modify
|
|
8
|
+
* it under the terms of the GNU General Public License as published by
|
|
9
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
* (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* This program is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
* GNU General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* You should have received a copy of the GNU General Public License
|
|
18
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
|
+
*/
|
|
2
20
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
21
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
22
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -12,13 +30,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
30
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
31
|
};
|
|
14
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.FreeSignalAPI =
|
|
33
|
+
exports.FreeSignalAPI = void 0;
|
|
16
34
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
17
35
|
const x3dh_1 = require("./x3dh");
|
|
18
36
|
const utils_1 = require("@freesignal/utils");
|
|
19
37
|
const types_1 = require("./types");
|
|
20
|
-
const fflate_1 = __importDefault(require("fflate"));
|
|
21
|
-
exports.FREESIGNAL_MIME = "application/x-freesignal";
|
|
22
38
|
class FreeSignalAPI {
|
|
23
39
|
constructor(opts) {
|
|
24
40
|
const { secretSignKey, secretBoxKey, sessions, keyExchange, users } = opts;
|
|
@@ -27,9 +43,7 @@ class FreeSignalAPI {
|
|
|
27
43
|
this.sessions = sessions;
|
|
28
44
|
this.keyExchange = new x3dh_1.KeyExchange(secretSignKey, secretBoxKey, keyExchange);
|
|
29
45
|
this.users = users;
|
|
30
|
-
|
|
31
|
-
get userId() {
|
|
32
|
-
return crypto_1.default.hash(this.signKey.publicKey);
|
|
46
|
+
this.userId = types_1.UserId.getUserId(this.signKey.publicKey).toString();
|
|
33
47
|
}
|
|
34
48
|
get identityKeys() {
|
|
35
49
|
return {
|
|
@@ -59,6 +73,50 @@ class FreeSignalAPI {
|
|
|
59
73
|
return decrypted;
|
|
60
74
|
});
|
|
61
75
|
}
|
|
76
|
+
getHandshake(url, userId) {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
const res = yield fetch(`${url}/${userId !== null && userId !== void 0 ? userId : ''}`, {
|
|
79
|
+
method: 'GET'
|
|
80
|
+
});
|
|
81
|
+
return (0, utils_1.decodeJSON)(new Uint8Array(yield res.arrayBuffer()));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
postHandshake(url, message) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
const res = yield fetch(url, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': types_1.XFreeSignal.MIME
|
|
90
|
+
},
|
|
91
|
+
body: types_1.XFreeSignal.encodeBody('data', message)
|
|
92
|
+
});
|
|
93
|
+
return res.status === 200;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
putHandshake(url, publicKey, bundle) {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
const res = yield fetch(url, {
|
|
99
|
+
method: 'PUT',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': types_1.XFreeSignal.MIME,
|
|
102
|
+
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
103
|
+
},
|
|
104
|
+
body: types_1.XFreeSignal.encodeBody('data', bundle)
|
|
105
|
+
});
|
|
106
|
+
return res.status === 201;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
deleteHandshake(url, publicKey) {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
const res = yield fetch(url, {
|
|
112
|
+
method: 'DELETE',
|
|
113
|
+
headers: {
|
|
114
|
+
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return res.status === 200;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
62
120
|
getDatagrams(publicKey, url) {
|
|
63
121
|
return __awaiter(this, void 0, void 0, function* () {
|
|
64
122
|
const res = yield fetch(url, {
|
|
@@ -67,50 +125,50 @@ class FreeSignalAPI {
|
|
|
67
125
|
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
68
126
|
}
|
|
69
127
|
});
|
|
70
|
-
return
|
|
128
|
+
return types_1.DataEncoder.from(yield this.decryptData(new Uint8Array(yield res.arrayBuffer()), types_1.UserId.getUserId(publicKey).toString())).data.map(array => types_1.Datagram.from(array));
|
|
71
129
|
});
|
|
72
130
|
}
|
|
73
131
|
postDatagrams(datagrams, publicKey, url) {
|
|
74
132
|
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
-
const data = yield this.encryptData(
|
|
133
|
+
const data = yield this.encryptData(new types_1.DataEncoder(datagrams.map(datagram => types_1.Datagram.from(datagram).encode())).encode(), types_1.UserId.getUserId(publicKey).toString());
|
|
76
134
|
const res = yield fetch(url, {
|
|
77
135
|
method: 'POST',
|
|
78
136
|
headers: {
|
|
79
|
-
'Content-Type':
|
|
137
|
+
'Content-Type': types_1.XFreeSignal.MIME,
|
|
80
138
|
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
81
139
|
},
|
|
82
|
-
body: data.encode()
|
|
140
|
+
body: types_1.XFreeSignal.encodeBody('data', data.encode())
|
|
83
141
|
});
|
|
84
|
-
return (0, utils_1.
|
|
142
|
+
return (0, utils_1.numberFromArray)(yield this.decryptData(new Uint8Array(yield res.arrayBuffer()), types_1.UserId.getUserId(publicKey).toString()));
|
|
85
143
|
});
|
|
86
144
|
}
|
|
87
145
|
deleteDatagrams(datagramIds, publicKey, url) {
|
|
88
146
|
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
-
const data = yield this.encryptData(
|
|
147
|
+
const data = yield this.encryptData(new types_1.DataEncoder(datagramIds.map(datagramId => crypto_1.default.UUID.parse(datagramId))).encode(), types_1.UserId.getUserId(publicKey).toString());
|
|
90
148
|
const res = yield fetch(url, {
|
|
91
149
|
method: 'DELETE',
|
|
92
150
|
headers: {
|
|
93
|
-
'Content-Type':
|
|
151
|
+
'Content-Type': types_1.XFreeSignal.MIME,
|
|
94
152
|
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
95
153
|
},
|
|
96
|
-
body: data.encode()
|
|
154
|
+
body: types_1.XFreeSignal.encodeBody('data', data.encode())
|
|
97
155
|
});
|
|
98
|
-
return (0, utils_1.
|
|
156
|
+
return (0, utils_1.numberFromArray)(yield this.decryptData(new Uint8Array(yield res.arrayBuffer()), types_1.UserId.getUserId(publicKey).toString()));
|
|
99
157
|
});
|
|
100
158
|
}
|
|
101
159
|
createToken(publicKey) {
|
|
102
|
-
const
|
|
103
|
-
return `Bearer ${
|
|
160
|
+
const signature = crypto_1.default.EdDSA.sign(crypto_1.default.hash(crypto_1.default.ECDH.scalarMult(publicKey, this.boxKey.secretKey)), this.signKey.secretKey);
|
|
161
|
+
return `Bearer ${this.userId}:${(0, utils_1.decodeBase64)(signature)}`;
|
|
104
162
|
}
|
|
105
163
|
;
|
|
106
164
|
digestToken(auth) {
|
|
107
165
|
return __awaiter(this, void 0, void 0, function* () {
|
|
108
166
|
if (auth && auth.startsWith("Bearer ")) {
|
|
109
|
-
const [userId,
|
|
167
|
+
const [userId, signature] = auth.substring(7).split(":");
|
|
110
168
|
const identityKeys = yield this.users.get(userId);
|
|
111
169
|
if (!identityKeys)
|
|
112
170
|
throw new Error('User not found or invalid auth token');
|
|
113
|
-
if (
|
|
171
|
+
if (crypto_1.default.EdDSA.verify(crypto_1.default.hash(crypto_1.default.ECDH.scalarMult((0, utils_1.encodeBase64)(identityKeys.identityKey), this.boxKey.secretKey)), (0, utils_1.encodeBase64)(signature), (0, utils_1.encodeBase64)(identityKeys.publicKey)))
|
|
114
172
|
return { identityKeys, userId: auth };
|
|
115
173
|
else
|
|
116
174
|
throw new Error('Authorization token not valid');
|
|
@@ -118,47 +176,5 @@ class FreeSignalAPI {
|
|
|
118
176
|
throw new Error('Authorization header is required');
|
|
119
177
|
});
|
|
120
178
|
}
|
|
121
|
-
packIdList(datagramIds) {
|
|
122
|
-
return datagramIds.map(datagramId => crypto_1.default.UUID.parse(datagramId)).reduce((prev, curr) => new Uint8Array([...prev, ...curr]), new Uint8Array());
|
|
123
|
-
}
|
|
124
|
-
unpackIdList(data) {
|
|
125
|
-
const ids = [];
|
|
126
|
-
for (let i = 0; i < data.length; i += 16) {
|
|
127
|
-
ids.push(crypto_1.default.UUID.stringify(data.subarray(i, i + 16)));
|
|
128
|
-
}
|
|
129
|
-
return ids;
|
|
130
|
-
}
|
|
131
|
-
packDatagrams(messages) {
|
|
132
|
-
return fflate_1.default.deflateSync((0, utils_1.concatUint8Array)(...messages.flatMap(datagram => {
|
|
133
|
-
const encoded = types_1.Datagram.from(datagram).encode();
|
|
134
|
-
return [(0, utils_1.numberToUint8Array)(encoded.length, 8), encoded];
|
|
135
|
-
})));
|
|
136
|
-
}
|
|
137
|
-
unpackDatagrams(data) {
|
|
138
|
-
const messages = [];
|
|
139
|
-
let offset = 0;
|
|
140
|
-
data = fflate_1.default.inflateSync(data);
|
|
141
|
-
while (offset < data.length) {
|
|
142
|
-
const length = data.subarray(offset, offset + 8);
|
|
143
|
-
if (length.length < 8) {
|
|
144
|
-
throw new Error('Invalid message length');
|
|
145
|
-
}
|
|
146
|
-
const messageLength = (0, utils_1.numberFromUint8Array)(length);
|
|
147
|
-
offset += 8;
|
|
148
|
-
if (offset + messageLength > data.length) {
|
|
149
|
-
throw new Error('Invalid message length');
|
|
150
|
-
}
|
|
151
|
-
const messageData = data.subarray(offset, offset + messageLength);
|
|
152
|
-
offset += messageLength;
|
|
153
|
-
try {
|
|
154
|
-
const datagram = types_1.Datagram.from(messageData);
|
|
155
|
-
messages.push(datagram);
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
throw new Error('Invalid datagram format');
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return messages;
|
|
162
|
-
}
|
|
163
179
|
}
|
|
164
180
|
exports.FreeSignalAPI = FreeSignalAPI;
|
package/double-ratchet.d.ts
CHANGED
|
@@ -83,14 +83,14 @@ export declare class KeySession {
|
|
|
83
83
|
/**
|
|
84
84
|
* Export the state of the session;
|
|
85
85
|
*/
|
|
86
|
-
|
|
86
|
+
toJSON(): ExportedKeySession;
|
|
87
87
|
/**
|
|
88
88
|
* Import a state.
|
|
89
89
|
*
|
|
90
90
|
* @param json string returned by `export()` method.
|
|
91
91
|
* @returns session with the state parsed.
|
|
92
92
|
*/
|
|
93
|
-
static
|
|
93
|
+
static parse(json: string): KeySession;
|
|
94
94
|
/**
|
|
95
95
|
* The fixed key length (in bytes) used throughout the Double Ratchet session.
|
|
96
96
|
* Typically 32 bytes (256 bits) for symmetric keys.
|
package/double-ratchet.js
CHANGED
|
@@ -117,7 +117,9 @@ class KeySession {
|
|
|
117
117
|
var _a;
|
|
118
118
|
const encrypted = types_1.EncryptedData.from(payload);
|
|
119
119
|
const publicKey = encrypted.publicKey;
|
|
120
|
-
if (!
|
|
120
|
+
if (!this._remoteKey)
|
|
121
|
+
throw new Error("Missing remoteKey");
|
|
122
|
+
if (!(0, utils_1.verifyArrays)(publicKey, this._remoteKey)) {
|
|
121
123
|
while (this.receivingCount < encrypted.previous)
|
|
122
124
|
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
123
125
|
this.setRemoteKey(publicKey);
|
|
@@ -141,9 +143,9 @@ class KeySession {
|
|
|
141
143
|
/**
|
|
142
144
|
* Export the state of the session;
|
|
143
145
|
*/
|
|
144
|
-
|
|
146
|
+
toJSON() {
|
|
145
147
|
return {
|
|
146
|
-
secretKey: (0, utils_1.decodeBase64)((0, utils_1.
|
|
148
|
+
secretKey: (0, utils_1.decodeBase64)((0, utils_1.concatArrays)(this.keyPair.secretKey)),
|
|
147
149
|
remoteKey: (0, utils_1.decodeBase64)(this._remoteKey),
|
|
148
150
|
rootKey: (0, utils_1.decodeBase64)(this.rootKey),
|
|
149
151
|
sendingChain: (0, utils_1.decodeBase64)(this.sendingChain),
|
|
@@ -160,7 +162,7 @@ class KeySession {
|
|
|
160
162
|
* @param json string returned by `export()` method.
|
|
161
163
|
* @returns session with the state parsed.
|
|
162
164
|
*/
|
|
163
|
-
static
|
|
165
|
+
static parse(json) {
|
|
164
166
|
const data = JSON.parse(json);
|
|
165
167
|
const session = new KeySession({ secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: (0, utils_1.encodeBase64)(data.rootKey) });
|
|
166
168
|
session._remoteKey = (0, utils_1.encodeBase64)(data.remoteKey);
|
package/index.d.ts
CHANGED
|
@@ -21,12 +21,13 @@ import { LocalStorage, Crypto } from "@freesignal/interfaces";
|
|
|
21
21
|
import { KeySession } from "./double-ratchet";
|
|
22
22
|
import { KeyExchange } from "./x3dh";
|
|
23
23
|
/**
|
|
24
|
-
* Creates a new Double Ratchet session.
|
|
24
|
+
* Creates a new Double Ratchet session for secure message exchange.
|
|
25
25
|
*
|
|
26
|
-
* @param opts
|
|
27
|
-
* @param opts.
|
|
28
|
-
*
|
|
29
|
-
* @
|
|
26
|
+
* @param opts - Optional parameters for session initialization.
|
|
27
|
+
* @param opts.secretKey - The local party's secret key as a Uint8Array.
|
|
28
|
+
* @param opts.remoteKey - The remote party's public key as a Uint8Array.
|
|
29
|
+
* @param opts.rootKey - An optional root key to initialize the session.
|
|
30
|
+
* @returns A new instance of {@link KeySession}.
|
|
30
31
|
*/
|
|
31
32
|
export declare function createKeySession(opts?: {
|
|
32
33
|
secretKey?: Uint8Array;
|
|
@@ -34,15 +35,23 @@ export declare function createKeySession(opts?: {
|
|
|
34
35
|
rootKey?: Uint8Array;
|
|
35
36
|
}): KeySession;
|
|
36
37
|
/**
|
|
37
|
-
* Creates a new X3DH session.
|
|
38
|
+
* Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
|
|
38
39
|
*
|
|
39
|
-
* @param
|
|
40
|
-
* @param
|
|
41
|
-
* @
|
|
40
|
+
* @param signSecretKey - The EdDSA signing secret key as a Uint8Array.
|
|
41
|
+
* @param boxSecretKey - The ECDH box secret key as a Uint8Array.
|
|
42
|
+
* @param bundleStore - Optional local storage for key bundles.
|
|
43
|
+
* @returns A new instance of {@link KeyExchange}.
|
|
42
44
|
*/
|
|
43
45
|
export declare function createKeyExchange(signSecretKey: Uint8Array, boxSecretKey: Uint8Array, bundleStore?: LocalStorage<string, crypto.KeyPair>): KeyExchange;
|
|
46
|
+
/**
|
|
47
|
+
* Generates identity key pairs for signing and encryption.
|
|
48
|
+
*
|
|
49
|
+
* @param signSecretKey - Optional secret key for EdDSA signing.
|
|
50
|
+
* @param boxSecretKey - Optional secret key for ECDH encryption.
|
|
51
|
+
* @returns An object containing readonly signing and box key pairs.
|
|
52
|
+
*/
|
|
44
53
|
export declare function createIdentityKeys(signSecretKey?: Uint8Array, boxSecretKey?: Uint8Array): {
|
|
45
|
-
sign: Crypto.KeyPair;
|
|
46
|
-
box: Crypto.KeyPair;
|
|
54
|
+
readonly sign: Crypto.KeyPair;
|
|
55
|
+
readonly box: Crypto.KeyPair;
|
|
47
56
|
};
|
|
48
|
-
export { IdentityKeys, Protocols,
|
|
57
|
+
export { UserId, IdentityKeys, Protocols, Datagram, EncryptedData } from "./types";
|
package/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.
|
|
24
|
+
exports.EncryptedData = exports.Datagram = exports.Protocols = exports.IdentityKeys = exports.UserId = void 0;
|
|
25
25
|
exports.createKeySession = createKeySession;
|
|
26
26
|
exports.createKeyExchange = createKeyExchange;
|
|
27
27
|
exports.createIdentityKeys = createIdentityKeys;
|
|
@@ -29,43 +29,41 @@ const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
|
29
29
|
const double_ratchet_1 = require("./double-ratchet");
|
|
30
30
|
const x3dh_1 = require("./x3dh");
|
|
31
31
|
/**
|
|
32
|
-
* Creates a new Double Ratchet session.
|
|
32
|
+
* Creates a new Double Ratchet session for secure message exchange.
|
|
33
33
|
*
|
|
34
|
-
* @param opts
|
|
35
|
-
* @param opts.
|
|
36
|
-
*
|
|
37
|
-
* @
|
|
34
|
+
* @param opts - Optional parameters for session initialization.
|
|
35
|
+
* @param opts.secretKey - The local party's secret key as a Uint8Array.
|
|
36
|
+
* @param opts.remoteKey - The remote party's public key as a Uint8Array.
|
|
37
|
+
* @param opts.rootKey - An optional root key to initialize the session.
|
|
38
|
+
* @returns A new instance of {@link KeySession}.
|
|
38
39
|
*/
|
|
39
40
|
function createKeySession(opts) {
|
|
40
41
|
return new double_ratchet_1.KeySession(opts);
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
43
|
-
* Creates a new X3DH session.
|
|
44
|
+
* Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
|
|
44
45
|
*
|
|
45
|
-
* @param
|
|
46
|
-
* @param
|
|
47
|
-
* @
|
|
46
|
+
* @param signSecretKey - The EdDSA signing secret key as a Uint8Array.
|
|
47
|
+
* @param boxSecretKey - The ECDH box secret key as a Uint8Array.
|
|
48
|
+
* @param bundleStore - Optional local storage for key bundles.
|
|
49
|
+
* @returns A new instance of {@link KeyExchange}.
|
|
48
50
|
*/
|
|
49
51
|
function createKeyExchange(signSecretKey, boxSecretKey, bundleStore) {
|
|
50
52
|
return new x3dh_1.KeyExchange(signSecretKey, boxSecretKey, bundleStore);
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Generates identity key pairs for signing and encryption.
|
|
56
|
+
*
|
|
57
|
+
* @param signSecretKey - Optional secret key for EdDSA signing.
|
|
58
|
+
* @param boxSecretKey - Optional secret key for ECDH encryption.
|
|
59
|
+
* @returns An object containing readonly signing and box key pairs.
|
|
60
|
+
*/
|
|
52
61
|
function createIdentityKeys(signSecretKey, boxSecretKey) {
|
|
53
|
-
return {
|
|
54
|
-
sign: crypto_1.default.EdDSA.keyPair(signSecretKey),
|
|
55
|
-
box: crypto_1.default.ECDH.keyPair(boxSecretKey)
|
|
56
|
-
};
|
|
62
|
+
return { sign: crypto_1.default.EdDSA.keyPair(signSecretKey), box: crypto_1.default.ECDH.keyPair(boxSecretKey) };
|
|
57
63
|
}
|
|
58
|
-
/*export function createAPI(opts: {
|
|
59
|
-
secretSignKey: Uint8Array;
|
|
60
|
-
secretBoxKey: Uint8Array;
|
|
61
|
-
sessions: LocalStorage<UserId, KeySession>;
|
|
62
|
-
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
63
|
-
users: LocalStorage<UserId, IdentityKeys>;
|
|
64
|
-
}): FreeSignalAPI {
|
|
65
|
-
return new FreeSignalAPI(opts);
|
|
66
|
-
}*/
|
|
67
64
|
var types_1 = require("./types");
|
|
65
|
+
Object.defineProperty(exports, "UserId", { enumerable: true, get: function () { return types_1.UserId; } });
|
|
68
66
|
Object.defineProperty(exports, "IdentityKeys", { enumerable: true, get: function () { return types_1.IdentityKeys; } });
|
|
69
67
|
Object.defineProperty(exports, "Protocols", { enumerable: true, get: function () { return types_1.Protocols; } });
|
|
70
|
-
Object.defineProperty(exports, "EncryptedData", { enumerable: true, get: function () { return types_1.EncryptedData; } });
|
|
71
68
|
Object.defineProperty(exports, "Datagram", { enumerable: true, get: function () { return types_1.Datagram; } });
|
|
69
|
+
Object.defineProperty(exports, "EncryptedData", { enumerable: true, get: function () { return types_1.EncryptedData; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@freesignal/crypto": "^0.3.0",
|
|
15
15
|
"@freesignal/interfaces": "^0.1.1",
|
|
16
|
-
"@freesignal/utils": "^1.0
|
|
16
|
+
"@freesignal/utils": "^1.2.0",
|
|
17
17
|
"base64-js": "^1.5.1",
|
|
18
18
|
"fflate": "^0.8.2",
|
|
19
19
|
"js-sha3": "^0.9.3",
|
package/test.js
CHANGED
|
@@ -24,7 +24,6 @@ bob.digestMessage(aliceack).then(({ session: bobsession, identityKeys }) => {
|
|
|
24
24
|
console.log("Error during handshake");
|
|
25
25
|
const longmsg = _1.Datagram.create(alice.signatureKey, bob.signatureKey, _1.Protocols.MESSAGE, alicesession.encrypt(new Uint8Array(1000000).fill(33).map(val => val + Math.floor(Math.random() * 93))));
|
|
26
26
|
console.log(longmsg.encode().length);
|
|
27
|
-
console.log(longmsg.encode(false).length);
|
|
28
27
|
}
|
|
29
28
|
else
|
|
30
29
|
console.log("Error");
|
package/types.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare namespace UserId {
|
|
|
23
23
|
private readonly array;
|
|
24
24
|
constructor(array: Uint8Array);
|
|
25
25
|
toString(): string;
|
|
26
|
+
toJSON(): string;
|
|
26
27
|
toUint8Array(): Uint8Array;
|
|
27
28
|
}
|
|
28
29
|
export function getUserId(publicKey: string | Uint8Array): UserIdConstructor;
|
|
@@ -49,9 +50,9 @@ export declare namespace IdentityKeys {
|
|
|
49
50
|
}
|
|
50
51
|
export declare enum Protocols {
|
|
51
52
|
NULL = "",
|
|
52
|
-
MESSAGE = "/freesignal/message
|
|
53
|
-
RELAY = "/freesignal/relay
|
|
54
|
-
HANDSHAKE = "/freesignal/handshake
|
|
53
|
+
MESSAGE = "/freesignal/message",
|
|
54
|
+
RELAY = "/freesignal/relay",
|
|
55
|
+
HANDSHAKE = "/freesignal/handshake"
|
|
55
56
|
}
|
|
56
57
|
export declare namespace Protocols {
|
|
57
58
|
function isProtocol(protocol: any): boolean;
|
|
@@ -68,6 +69,7 @@ export interface Datagram {
|
|
|
68
69
|
readonly protocol: Protocols;
|
|
69
70
|
readonly createdAt: number;
|
|
70
71
|
payload?: Uint8Array;
|
|
72
|
+
readonly signature?: string;
|
|
71
73
|
}
|
|
72
74
|
export declare namespace Datagram {
|
|
73
75
|
export const version = 1;
|
|
@@ -78,12 +80,18 @@ export declare namespace Datagram {
|
|
|
78
80
|
readonly receiver: UserId;
|
|
79
81
|
readonly protocol: Protocols;
|
|
80
82
|
readonly createdAt: number;
|
|
81
|
-
|
|
83
|
+
_payload?: Uint8Array;
|
|
84
|
+
_signature?: Uint8Array;
|
|
85
|
+
private secretKey?;
|
|
82
86
|
private static headerOffset;
|
|
83
87
|
constructor(sender: Uint8Array | string, receiver: Uint8Array | string, protocol: Protocols, payload?: Uint8Array | Encodable);
|
|
84
88
|
constructor(data: Uint8Array | Datagram);
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
get signed(): boolean;
|
|
90
|
+
get signature(): string | undefined;
|
|
91
|
+
set payload(data: Uint8Array);
|
|
92
|
+
get payload(): Uint8Array | undefined;
|
|
93
|
+
encode(): Uint8Array;
|
|
94
|
+
sign(secretKey: Uint8Array): this;
|
|
87
95
|
toString(): string;
|
|
88
96
|
toJSON(): string;
|
|
89
97
|
}
|
|
@@ -165,14 +173,46 @@ export declare class EncryptedDataConstructor implements EncryptedData {
|
|
|
165
173
|
get nonce(): Uint8Array;
|
|
166
174
|
get ciphertext(): Uint8Array;
|
|
167
175
|
encode(): Uint8Array;
|
|
168
|
-
decode(): {
|
|
169
|
-
version: number;
|
|
170
|
-
count: number;
|
|
171
|
-
previous: number;
|
|
172
|
-
publicKey: string;
|
|
173
|
-
nonce: string;
|
|
174
|
-
ciphertext: string;
|
|
175
|
-
};
|
|
176
176
|
toString(): string;
|
|
177
177
|
toJSON(): string;
|
|
178
178
|
}
|
|
179
|
+
declare enum DataType {
|
|
180
|
+
UKNOWN = -1,
|
|
181
|
+
RAW = 0,
|
|
182
|
+
NUMBER = 1,
|
|
183
|
+
STRING = 2,
|
|
184
|
+
ARRAY = 3,
|
|
185
|
+
OBJECT = 4
|
|
186
|
+
}
|
|
187
|
+
declare namespace DataType {
|
|
188
|
+
function getType(type: string): DataType;
|
|
189
|
+
function getName(type: DataType): string;
|
|
190
|
+
function from(data: any): DataType;
|
|
191
|
+
}
|
|
192
|
+
export declare class DataEncoder<T> implements Encodable {
|
|
193
|
+
readonly data: T;
|
|
194
|
+
readonly type: string;
|
|
195
|
+
constructor(data: T);
|
|
196
|
+
protected get _type(): DataType;
|
|
197
|
+
encode(): Uint8Array;
|
|
198
|
+
toString(): string;
|
|
199
|
+
toJSON(): string;
|
|
200
|
+
static from<T = any>(array: Uint8Array): DataEncoder<T>;
|
|
201
|
+
}
|
|
202
|
+
export declare namespace XFreeSignal {
|
|
203
|
+
export const MIME = "application/x-freesignal";
|
|
204
|
+
export const version = 1;
|
|
205
|
+
export function encodeBody(type: 'data' | 'error', data: any, compressed?: boolean): BodyInit;
|
|
206
|
+
export function decodeBody<T = any>(body: Uint8Array): Body<T>;
|
|
207
|
+
class Body<T> implements Encodable {
|
|
208
|
+
readonly type: 'data' | 'error';
|
|
209
|
+
readonly data: T;
|
|
210
|
+
constructor(type: 'data' | 'error', data: T);
|
|
211
|
+
encode(compressed?: boolean): Uint8Array;
|
|
212
|
+
toString(): string;
|
|
213
|
+
toJSON(): string;
|
|
214
|
+
static from<T = any>(array: Uint8Array): Body<T>;
|
|
215
|
+
}
|
|
216
|
+
export {};
|
|
217
|
+
}
|
|
218
|
+
export {};
|
package/types.js
CHANGED
|
@@ -21,11 +21,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.EncryptedDataConstructor = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.IdentityKeys = exports.UserId = void 0;
|
|
24
|
+
exports.XFreeSignal = exports.DataEncoder = exports.EncryptedDataConstructor = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.IdentityKeys = exports.UserId = void 0;
|
|
25
25
|
const utils_1 = require("@freesignal/utils");
|
|
26
26
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
27
|
-
const fflate_1 = __importDefault(require("fflate"));
|
|
28
27
|
const double_ratchet_1 = require("./double-ratchet");
|
|
28
|
+
const fflate_1 = __importDefault(require("fflate"));
|
|
29
29
|
var UserId;
|
|
30
30
|
(function (UserId) {
|
|
31
31
|
class UserIdConstructor {
|
|
@@ -36,6 +36,9 @@ var UserId;
|
|
|
36
36
|
toString() {
|
|
37
37
|
return (0, utils_1.decodeBase64)(this.array);
|
|
38
38
|
}
|
|
39
|
+
toJSON() {
|
|
40
|
+
return JSON.stringify(this.toString());
|
|
41
|
+
}
|
|
39
42
|
toUint8Array() {
|
|
40
43
|
return this.array;
|
|
41
44
|
}
|
|
@@ -66,13 +69,13 @@ var IdentityKeys;
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
encode() {
|
|
69
|
-
return (0, utils_1.
|
|
72
|
+
return (0, utils_1.concatArrays)((0, utils_1.encodeBase64)(this.publicKey), (0, utils_1.encodeBase64)(this.identityKey));
|
|
70
73
|
}
|
|
71
74
|
toString() {
|
|
72
75
|
throw (0, utils_1.decodeBase64)(this.encode());
|
|
73
76
|
}
|
|
74
77
|
toJSON() {
|
|
75
|
-
throw this.toString();
|
|
78
|
+
throw JSON.stringify(this.toString());
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
function isIdentityKeys(obj) {
|
|
@@ -87,9 +90,9 @@ var IdentityKeys;
|
|
|
87
90
|
var Protocols;
|
|
88
91
|
(function (Protocols) {
|
|
89
92
|
Protocols["NULL"] = "";
|
|
90
|
-
Protocols["MESSAGE"] = "/freesignal/message
|
|
91
|
-
Protocols["RELAY"] = "/freesignal/relay
|
|
92
|
-
Protocols["HANDSHAKE"] = "/freesignal/handshake
|
|
93
|
+
Protocols["MESSAGE"] = "/freesignal/message";
|
|
94
|
+
Protocols["RELAY"] = "/freesignal/relay";
|
|
95
|
+
Protocols["HANDSHAKE"] = "/freesignal/handshake";
|
|
93
96
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
94
97
|
(function (Protocols) {
|
|
95
98
|
function isProtocol(protocol) {
|
|
@@ -105,16 +108,11 @@ var Protocols;
|
|
|
105
108
|
}
|
|
106
109
|
Protocols.toCode = toCode;
|
|
107
110
|
function encode(protocol, length) {
|
|
108
|
-
|
|
109
|
-
raw[0] |= (raw.length - 1) << 6;
|
|
110
|
-
return raw;*/
|
|
111
|
-
return (0, utils_1.numberToUint8Array)(Protocols.toCode(protocol), length);
|
|
111
|
+
return (0, utils_1.numberToArray)(Protocols.toCode(protocol), length);
|
|
112
112
|
}
|
|
113
113
|
Protocols.encode = encode;
|
|
114
114
|
function decode(array) {
|
|
115
|
-
|
|
116
|
-
//array = array.reverse();
|
|
117
|
-
return Protocols.fromCode((0, utils_1.numberFromUint8Array)(array));
|
|
115
|
+
return Protocols.fromCode((0, utils_1.numberFromArray)(array));
|
|
118
116
|
}
|
|
119
117
|
Protocols.decode = decode;
|
|
120
118
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
@@ -125,21 +123,15 @@ var Datagram;
|
|
|
125
123
|
constructor(data, receiver, protocol, payload) {
|
|
126
124
|
if (!receiver && !protocol && !payload) {
|
|
127
125
|
if (data instanceof Uint8Array) {
|
|
128
|
-
this.version = data[0] &
|
|
126
|
+
this.version = data[0] & 127;
|
|
129
127
|
this.protocol = Protocols.decode(data.subarray(1, 2));
|
|
130
128
|
this.id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
|
|
131
|
-
this.createdAt = (0, utils_1.
|
|
129
|
+
this.createdAt = (0, utils_1.numberFromArray)(data.subarray(18, 26));
|
|
132
130
|
this.sender = (0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength));
|
|
133
131
|
this.receiver = (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, DatagramConstructor.headerOffset));
|
|
134
|
-
if (data[0] & 128)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
throw new Error('Invalid signature for Datagram');
|
|
138
|
-
}
|
|
139
|
-
if (data[0] & 64)
|
|
140
|
-
this.payload = fflate_1.default.inflateSync(data.subarray(DatagramConstructor.headerOffset, data.length));
|
|
141
|
-
else
|
|
142
|
-
this.payload = data.subarray(DatagramConstructor.headerOffset, data.length);
|
|
132
|
+
if (data[0] & 128)
|
|
133
|
+
this._signature = data.subarray(data.length - crypto_1.default.EdDSA.signatureLength);
|
|
134
|
+
this._payload = data.subarray(DatagramConstructor.headerOffset, data.length);
|
|
143
135
|
}
|
|
144
136
|
else if (Datagram.isDatagram(data)) {
|
|
145
137
|
const datagram = data;
|
|
@@ -149,7 +141,8 @@ var Datagram;
|
|
|
149
141
|
this.receiver = datagram.receiver;
|
|
150
142
|
this.protocol = datagram.protocol;
|
|
151
143
|
this.createdAt = datagram.createdAt;
|
|
152
|
-
this.
|
|
144
|
+
this._payload = datagram.payload;
|
|
145
|
+
this._signature = (0, utils_1.encodeBase64)(datagram.signature);
|
|
153
146
|
}
|
|
154
147
|
else
|
|
155
148
|
throw new Error('Invalid constructor arguments for Datagram');
|
|
@@ -161,45 +154,50 @@ var Datagram;
|
|
|
161
154
|
this.receiver = typeof receiver === 'string' ? receiver : (0, utils_1.decodeBase64)(receiver);
|
|
162
155
|
this.protocol = protocol;
|
|
163
156
|
this.createdAt = Date.now();
|
|
164
|
-
this.
|
|
157
|
+
this._payload = payload instanceof Uint8Array ? payload : payload === null || payload === void 0 ? void 0 : payload.encode();
|
|
165
158
|
}
|
|
166
159
|
else
|
|
167
160
|
throw new Error('Invalid constructor arguments for Datagram');
|
|
168
161
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
162
|
+
get signed() {
|
|
163
|
+
return !this._signature && !this.secretKey ? false : true;
|
|
164
|
+
}
|
|
165
|
+
get signature() {
|
|
166
|
+
if (this.signed) {
|
|
167
|
+
if (!this._signature)
|
|
168
|
+
this.encode();
|
|
169
|
+
return (0, utils_1.decodeBase64)(this._signature);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
set payload(data) {
|
|
173
|
+
this._signature = undefined;
|
|
174
|
+
this._payload = data;
|
|
175
|
+
}
|
|
176
|
+
get payload() {
|
|
177
|
+
return this._payload;
|
|
178
|
+
}
|
|
179
|
+
encode() {
|
|
180
|
+
var _a, _b, _c;
|
|
181
|
+
const data = (0, utils_1.concatArrays)(new Uint8Array(1).fill(this.version | (this.secretKey ? 128 : 0)), //1
|
|
173
182
|
Protocols.encode(this.protocol), //1
|
|
174
183
|
(_a = crypto_1.default.UUID.parse(this.id)) !== null && _a !== void 0 ? _a : [], //16
|
|
175
|
-
(0, utils_1.
|
|
184
|
+
(0, utils_1.numberToArray)(this.createdAt, 8), //8
|
|
176
185
|
(0, utils_1.encodeBase64)(this.sender), //32
|
|
177
186
|
(0, utils_1.encodeBase64)(this.receiver), //32
|
|
178
|
-
|
|
187
|
+
(_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array());
|
|
188
|
+
if (this.secretKey)
|
|
189
|
+
this._signature = crypto_1.default.EdDSA.sign(data, this.secretKey);
|
|
190
|
+
return (0, utils_1.concatArrays)(data, (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
|
|
179
191
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
header[0] |= 128; // Set the sign bit
|
|
184
|
-
const signature = crypto_1.default.EdDSA.sign(header, secretKey);
|
|
185
|
-
return (0, utils_1.concatUint8Array)(header, signature);
|
|
192
|
+
sign(secretKey) {
|
|
193
|
+
this.secretKey = secretKey;
|
|
194
|
+
return this;
|
|
186
195
|
}
|
|
187
196
|
toString() {
|
|
188
197
|
return (0, utils_1.decodeBase64)(this.encode());
|
|
189
198
|
}
|
|
190
199
|
toJSON() {
|
|
191
|
-
|
|
192
|
-
id: this.id,
|
|
193
|
-
version: this.version,
|
|
194
|
-
senderKey: this.senderKey,
|
|
195
|
-
senderRelay: this.senderRelay,
|
|
196
|
-
receiverKey: this.receiverKey,
|
|
197
|
-
receiverRelay: this.receiverRelay,
|
|
198
|
-
protocol: this.protocol,
|
|
199
|
-
createdAt: this.createdAt,
|
|
200
|
-
payload: this.payload ? encodeBase64(this.payload) : undefined
|
|
201
|
-
});*/
|
|
202
|
-
return this.toString();
|
|
200
|
+
return JSON.stringify(this.toString());
|
|
203
201
|
}
|
|
204
202
|
}
|
|
205
203
|
DatagramConstructor.headerOffset = 26 + crypto_1.default.EdDSA.publicKeyLength * 2;
|
|
@@ -241,43 +239,40 @@ class EncryptedDataConstructor {
|
|
|
241
239
|
return this;
|
|
242
240
|
}
|
|
243
241
|
if (typeof arrays[0] === 'number')
|
|
244
|
-
arrays[0] = (0, utils_1.
|
|
242
|
+
arrays[0] = (0, utils_1.numberToArray)(arrays[0], EncryptedDataConstructor.countLength);
|
|
245
243
|
if (typeof arrays[1] === 'number')
|
|
246
|
-
arrays[1] = (0, utils_1.
|
|
244
|
+
arrays[1] = (0, utils_1.numberToArray)(arrays[1], EncryptedDataConstructor.countLength);
|
|
247
245
|
if (arrays.length === 6) {
|
|
248
|
-
arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.
|
|
246
|
+
arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToArray)(arrays[5]) : arrays[5]);
|
|
249
247
|
arrays.pop();
|
|
250
248
|
}
|
|
251
249
|
else if (arrays.length > 1) {
|
|
252
|
-
arrays.unshift((0, utils_1.
|
|
250
|
+
arrays.unshift((0, utils_1.numberToArray)(double_ratchet_1.KeySession.version));
|
|
253
251
|
}
|
|
254
|
-
this.raw = (0, utils_1.
|
|
252
|
+
this.raw = (0, utils_1.concatArrays)(...arrays);
|
|
255
253
|
}
|
|
256
254
|
get length() { return this.raw.length; }
|
|
257
|
-
get version() { return (0, utils_1.
|
|
258
|
-
get count() { return (0, utils_1.
|
|
259
|
-
get previous() { return (0, utils_1.
|
|
255
|
+
get version() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
|
|
256
|
+
get count() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
|
|
257
|
+
get previous() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
|
|
260
258
|
get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
|
|
261
259
|
get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
|
|
262
260
|
get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
|
|
263
261
|
encode() {
|
|
264
262
|
return this.raw;
|
|
265
263
|
}
|
|
266
|
-
|
|
267
|
-
return
|
|
264
|
+
toString() {
|
|
265
|
+
return (0, utils_1.decodeBase64)(this.raw);
|
|
266
|
+
}
|
|
267
|
+
toJSON() {
|
|
268
|
+
return JSON.stringify({
|
|
268
269
|
version: this.version,
|
|
269
270
|
count: this.count,
|
|
270
271
|
previous: this.previous,
|
|
271
272
|
publicKey: (0, utils_1.decodeBase64)(this.publicKey),
|
|
272
273
|
nonce: (0, utils_1.decodeBase64)(this.nonce),
|
|
273
274
|
ciphertext: (0, utils_1.decodeBase64)(this.ciphertext)
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
toString() {
|
|
277
|
-
return (0, utils_1.decodeBase64)(this.raw);
|
|
278
|
-
}
|
|
279
|
-
toJSON() {
|
|
280
|
-
return JSON.stringify(this.decode());
|
|
275
|
+
});
|
|
281
276
|
}
|
|
282
277
|
}
|
|
283
278
|
exports.EncryptedDataConstructor = EncryptedDataConstructor;
|
|
@@ -310,3 +305,156 @@ Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.count
|
|
|
310
305
|
Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
|
|
311
306
|
Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
|
|
312
307
|
Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
|
|
308
|
+
var DataType;
|
|
309
|
+
(function (DataType) {
|
|
310
|
+
DataType[DataType["UKNOWN"] = -1] = "UKNOWN";
|
|
311
|
+
DataType[DataType["RAW"] = 0] = "RAW";
|
|
312
|
+
DataType[DataType["NUMBER"] = 1] = "NUMBER";
|
|
313
|
+
DataType[DataType["STRING"] = 2] = "STRING";
|
|
314
|
+
DataType[DataType["ARRAY"] = 3] = "ARRAY";
|
|
315
|
+
DataType[DataType["OBJECT"] = 4] = "OBJECT";
|
|
316
|
+
})(DataType || (DataType = {}));
|
|
317
|
+
(function (DataType) {
|
|
318
|
+
function getType(type) {
|
|
319
|
+
return Object.values(DataType).indexOf(type.toLocaleUpperCase());
|
|
320
|
+
}
|
|
321
|
+
DataType.getType = getType;
|
|
322
|
+
function getName(type) {
|
|
323
|
+
return DataType[type].toLowerCase();
|
|
324
|
+
}
|
|
325
|
+
DataType.getName = getName;
|
|
326
|
+
function from(data) {
|
|
327
|
+
if (data instanceof Uint8Array)
|
|
328
|
+
return DataType.RAW;
|
|
329
|
+
return getType(typeof data);
|
|
330
|
+
}
|
|
331
|
+
DataType.from = from;
|
|
332
|
+
})(DataType || (DataType = {}));
|
|
333
|
+
class DataEncoder {
|
|
334
|
+
constructor(data) {
|
|
335
|
+
this.data = data;
|
|
336
|
+
this.type = DataType.getName(DataType.from(this.data));
|
|
337
|
+
}
|
|
338
|
+
get _type() {
|
|
339
|
+
return DataType.getType(this.type);
|
|
340
|
+
}
|
|
341
|
+
encode() {
|
|
342
|
+
let data;
|
|
343
|
+
switch (this._type) {
|
|
344
|
+
case DataType.RAW:
|
|
345
|
+
data = this.data;
|
|
346
|
+
break;
|
|
347
|
+
case DataType.NUMBER:
|
|
348
|
+
data = (0, utils_1.numberToArray)(this._type);
|
|
349
|
+
break;
|
|
350
|
+
case DataType.STRING:
|
|
351
|
+
data = (0, utils_1.encodeUTF8)(this.data);
|
|
352
|
+
break;
|
|
353
|
+
case DataType.ARRAY:
|
|
354
|
+
data = (0, utils_1.concatArrays)(...Array.from(this.data).flatMap(value => {
|
|
355
|
+
const data = new DataEncoder(value).encode();
|
|
356
|
+
return [(0, utils_1.numberToArray)(data.length, 8), data];
|
|
357
|
+
}));
|
|
358
|
+
break;
|
|
359
|
+
case DataType.OBJECT:
|
|
360
|
+
data = (0, utils_1.encodeJSON)(this.data);
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
throw new Error("Uknown type");
|
|
364
|
+
}
|
|
365
|
+
return (0, utils_1.concatArrays)((0, utils_1.numberToArray)(this._type), data);
|
|
366
|
+
}
|
|
367
|
+
toString() {
|
|
368
|
+
return "[Object XFreeSignalData]";
|
|
369
|
+
}
|
|
370
|
+
toJSON() {
|
|
371
|
+
return JSON.stringify(this.data);
|
|
372
|
+
}
|
|
373
|
+
static from(array) {
|
|
374
|
+
const type = array[0];
|
|
375
|
+
let rawData = array.subarray(1), data;
|
|
376
|
+
switch (type) {
|
|
377
|
+
case DataType.RAW:
|
|
378
|
+
data = rawData;
|
|
379
|
+
break;
|
|
380
|
+
case DataType.NUMBER:
|
|
381
|
+
data = (0, utils_1.numberFromArray)(rawData);
|
|
382
|
+
break;
|
|
383
|
+
case DataType.STRING:
|
|
384
|
+
data = (0, utils_1.decodeUTF8)(rawData);
|
|
385
|
+
break;
|
|
386
|
+
case DataType.ARRAY:
|
|
387
|
+
const arrayData = [];
|
|
388
|
+
let offset = 0;
|
|
389
|
+
while (offset < rawData.length) {
|
|
390
|
+
const length = rawData.subarray(offset, offset + 8);
|
|
391
|
+
if (length.length < 8)
|
|
392
|
+
throw new Error('Invalid data length');
|
|
393
|
+
const messageLength = (0, utils_1.numberFromArray)(length);
|
|
394
|
+
offset += 8;
|
|
395
|
+
if (offset + messageLength > rawData.length) {
|
|
396
|
+
throw new Error('Invalid data length');
|
|
397
|
+
}
|
|
398
|
+
arrayData.push(rawData.subarray(offset, offset + messageLength));
|
|
399
|
+
offset += messageLength;
|
|
400
|
+
}
|
|
401
|
+
data = arrayData;
|
|
402
|
+
break;
|
|
403
|
+
case DataType.OBJECT:
|
|
404
|
+
data = (0, utils_1.decodeJSON)(rawData);
|
|
405
|
+
break;
|
|
406
|
+
default:
|
|
407
|
+
throw new Error('Invalid data format');
|
|
408
|
+
}
|
|
409
|
+
return new DataEncoder(data);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
exports.DataEncoder = DataEncoder;
|
|
413
|
+
var XFreeSignal;
|
|
414
|
+
(function (XFreeSignal) {
|
|
415
|
+
XFreeSignal.MIME = "application/x-freesignal";
|
|
416
|
+
XFreeSignal.version = 1;
|
|
417
|
+
function encodeBody(type, data, compressed = false) {
|
|
418
|
+
return new Body(type, data).encode(compressed);
|
|
419
|
+
}
|
|
420
|
+
XFreeSignal.encodeBody = encodeBody;
|
|
421
|
+
function decodeBody(body) {
|
|
422
|
+
return Body.from(body);
|
|
423
|
+
}
|
|
424
|
+
XFreeSignal.decodeBody = decodeBody;
|
|
425
|
+
let BodyType;
|
|
426
|
+
(function (BodyType) {
|
|
427
|
+
BodyType[BodyType["DATA"] = 0] = "DATA";
|
|
428
|
+
BodyType[BodyType["ERROR"] = 1] = "ERROR";
|
|
429
|
+
})(BodyType || (BodyType = {}));
|
|
430
|
+
(function (BodyType) {
|
|
431
|
+
function getName(type) {
|
|
432
|
+
return BodyType[type].toLowerCase();
|
|
433
|
+
}
|
|
434
|
+
BodyType.getName = getName;
|
|
435
|
+
})(BodyType || (BodyType = {}));
|
|
436
|
+
class Body {
|
|
437
|
+
constructor(type, data) {
|
|
438
|
+
this.type = type;
|
|
439
|
+
this.data = data;
|
|
440
|
+
}
|
|
441
|
+
encode(compressed = false) {
|
|
442
|
+
const data = new DataEncoder(this.data).encode();
|
|
443
|
+
return (0, utils_1.concatArrays)((0, utils_1.numberToArray)(((this.type === 'data' ? BodyType.DATA : BodyType.ERROR) << 6)
|
|
444
|
+
+ (compressed ? 32 : 0)
|
|
445
|
+
+ XFreeSignal.version), compressed ? fflate_1.default.deflateSync(data) : data);
|
|
446
|
+
}
|
|
447
|
+
toString() {
|
|
448
|
+
return "[Object XFreeSignalBody]";
|
|
449
|
+
}
|
|
450
|
+
toJSON() {
|
|
451
|
+
return JSON.stringify({
|
|
452
|
+
type: this.type,
|
|
453
|
+
data: this.data
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
static from(array) {
|
|
457
|
+
return new Body(BodyType.getName((array[0] & 64) >> 6), DataEncoder.from((array[0] & 32) >> 5 === 1 ? fflate_1.default.inflateSync(array.subarray(1)) : array.subarray(1)).data);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
})(XFreeSignal || (exports.XFreeSignal = XFreeSignal = {}));
|
package/x3dh.js
CHANGED
|
@@ -94,7 +94,7 @@ class KeyExchange {
|
|
|
94
94
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
|
|
95
95
|
]), new Uint8Array(double_ratchet_1.KeySession.rootKeyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.rootKeyLength);
|
|
96
96
|
const session = new double_ratchet_1.KeySession({ remoteKey: identityKey, rootKey });
|
|
97
|
-
const cyphertext = session.encrypt((0, utils_1.
|
|
97
|
+
const cyphertext = session.encrypt((0, utils_1.concatArrays)(crypto_1.default.hash(this._identityKey.publicKey), crypto_1.default.hash(identityKey)));
|
|
98
98
|
if (!cyphertext)
|
|
99
99
|
throw new Error("Decryption error");
|
|
100
100
|
return {
|
|
@@ -135,7 +135,7 @@ class KeyExchange {
|
|
|
135
135
|
const cleartext = session.decrypt((0, utils_1.encodeBase64)(message.associatedData));
|
|
136
136
|
if (!cleartext)
|
|
137
137
|
throw new Error("Error decrypting ACK message");
|
|
138
|
-
if (!(0, utils_1.
|
|
138
|
+
if (!(0, utils_1.verifyArrays)(cleartext, (0, utils_1.concatArrays)(crypto_1.default.hash(identityKey), crypto_1.default.hash(this._identityKey.publicKey))))
|
|
139
139
|
throw new Error("Error verifing Associated Data");
|
|
140
140
|
return {
|
|
141
141
|
session,
|