@freesignal/protocol 0.2.9 → 0.3.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 +61 -40
- package/double-ratchet.js +265 -113
- package/index.d.ts +15 -21
- package/index.js +39 -26
- package/node.d.ts +38 -0
- package/node.js +139 -0
- package/package.json +4 -8
- package/test.js +24 -26
- package/types.d.ts +66 -136
- package/types.js +238 -366
- package/x3dh.d.ts +18 -19
- package/x3dh.js +77 -116
- package/api.d.ts +0 -52
- package/api.js +0 -194
package/x3dh.d.ts
CHANGED
|
@@ -17,36 +17,35 @@
|
|
|
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 {
|
|
20
|
+
import { ExportedKeySession, KeySession } from "./double-ratchet";
|
|
21
|
+
import { IdentityKey, PrivateIdentityKey } from "./types";
|
|
22
22
|
export interface ExportedKeyExchange {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
bundleStore: Array<[string, Crypto.KeyPair]>;
|
|
23
|
+
privateIdentityKey: PrivateIdentityKey;
|
|
24
|
+
storage: Array<[string, Crypto.KeyPair]>;
|
|
26
25
|
}
|
|
27
26
|
export declare class KeyExchange {
|
|
28
27
|
static readonly version = 1;
|
|
29
28
|
private static readonly hkdfInfo;
|
|
30
29
|
private static readonly maxOPK;
|
|
31
|
-
private readonly
|
|
32
|
-
private readonly
|
|
33
|
-
private readonly
|
|
34
|
-
constructor(
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
private readonly privateIdentityKey;
|
|
31
|
+
private readonly storage;
|
|
32
|
+
private readonly sessions;
|
|
33
|
+
constructor(storage: {
|
|
34
|
+
keys: LocalStorage<string, Crypto.KeyPair>;
|
|
35
|
+
sessions: LocalStorage<string, ExportedKeySession>;
|
|
36
|
+
}, privateIdentityKey?: PrivateIdentityKey);
|
|
37
|
+
get identityKey(): IdentityKey;
|
|
37
38
|
private generateSPK;
|
|
38
39
|
private generateOPK;
|
|
39
|
-
generateBundle(length?: number): KeyExchangeDataBundle
|
|
40
|
-
generateData(): KeyExchangeData
|
|
41
|
-
digestData(message: KeyExchangeData): {
|
|
40
|
+
generateBundle(length?: number): Promise<KeyExchangeDataBundle>;
|
|
41
|
+
generateData(): Promise<KeyExchangeData>;
|
|
42
|
+
digestData(message: KeyExchangeData): Promise<{
|
|
42
43
|
session: KeySession;
|
|
43
44
|
message: KeyExchangeSynMessage;
|
|
44
|
-
|
|
45
|
-
}
|
|
45
|
+
identityKey: IdentityKey;
|
|
46
|
+
}>;
|
|
46
47
|
digestMessage(message: KeyExchangeSynMessage): Promise<{
|
|
47
48
|
session: KeySession;
|
|
48
|
-
|
|
49
|
+
identityKey: IdentityKey;
|
|
49
50
|
}>;
|
|
50
|
-
toJSON(): ExportedKeyExchange;
|
|
51
|
-
static from(data: ExportedKeyExchange): KeyExchange;
|
|
52
51
|
}
|
package/x3dh.js
CHANGED
|
@@ -34,159 +34,120 @@ exports.KeyExchange = void 0;
|
|
|
34
34
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
35
35
|
const double_ratchet_1 = require("./double-ratchet");
|
|
36
36
|
const utils_1 = require("@freesignal/utils");
|
|
37
|
+
const types_1 = require("./types");
|
|
38
|
+
const _1 = require(".");
|
|
37
39
|
class KeyExchange {
|
|
38
|
-
constructor(
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
40
|
+
constructor(storage, privateIdentityKey) {
|
|
41
|
+
this.storage = storage.keys;
|
|
42
|
+
this.sessions = storage.sessions;
|
|
43
|
+
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
44
|
+
}
|
|
45
|
+
get identityKey() {
|
|
46
|
+
return this.privateIdentityKey.identityKey;
|
|
42
47
|
}
|
|
43
|
-
get signatureKey() { return this._signatureKey.publicKey; }
|
|
44
|
-
get identityKey() { return this._identityKey.publicKey; }
|
|
45
48
|
generateSPK() {
|
|
46
49
|
const signedPreKey = crypto_1.default.ECDH.keyPair();
|
|
47
50
|
const signedPreKeyHash = crypto_1.default.hash(signedPreKey.publicKey);
|
|
48
|
-
this.
|
|
51
|
+
this.storage.set((0, utils_1.decodeBase64)(signedPreKeyHash), signedPreKey);
|
|
49
52
|
return { signedPreKey, signedPreKeyHash };
|
|
50
53
|
}
|
|
51
54
|
generateOPK(spkHash) {
|
|
52
55
|
const onetimePreKey = crypto_1.default.ECDH.keyPair();
|
|
53
56
|
const onetimePreKeyHash = crypto_1.default.hash(onetimePreKey.publicKey);
|
|
54
|
-
this.
|
|
57
|
+
this.storage.set((0, utils_1.decodeBase64)(spkHash).concat((0, utils_1.decodeBase64)(onetimePreKeyHash)), onetimePreKey);
|
|
55
58
|
return { onetimePreKey, onetimePreKeyHash };
|
|
56
59
|
}
|
|
57
60
|
generateBundle(length) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const { signedPreKey, signedPreKeyHash } = this.generateSPK();
|
|
63
|
+
const onetimePreKey = new Array(length !== null && length !== void 0 ? length : KeyExchange.maxOPK).fill(0).map(() => this.generateOPK(signedPreKeyHash).onetimePreKey);
|
|
64
|
+
return {
|
|
65
|
+
version: KeyExchange.version,
|
|
66
|
+
identityKey: this.identityKey.toString(),
|
|
67
|
+
signedPreKey: (0, utils_1.decodeBase64)(signedPreKey.publicKey),
|
|
68
|
+
signature: (0, utils_1.decodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
|
|
69
|
+
onetimePreKey: onetimePreKey.map(opk => (0, utils_1.decodeBase64)(opk.publicKey))
|
|
70
|
+
};
|
|
71
|
+
});
|
|
68
72
|
}
|
|
69
73
|
generateData() {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
const { signedPreKey, signedPreKeyHash } = this.generateSPK();
|
|
76
|
+
const { onetimePreKey } = this.generateOPK(signedPreKeyHash);
|
|
77
|
+
return {
|
|
78
|
+
version: KeyExchange.version,
|
|
79
|
+
identityKey: this.identityKey.toString(),
|
|
80
|
+
signedPreKey: (0, utils_1.decodeBase64)(signedPreKey.publicKey),
|
|
81
|
+
signature: (0, utils_1.decodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
|
|
82
|
+
onetimePreKey: (0, utils_1.decodeBase64)(onetimePreKey.publicKey)
|
|
83
|
+
};
|
|
84
|
+
});
|
|
80
85
|
}
|
|
81
86
|
digestData(message) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
},
|
|
115
|
-
};
|
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
const ephemeralKey = crypto_1.default.ECDH.keyPair();
|
|
89
|
+
const signedPreKey = (0, utils_1.encodeBase64)(message.signedPreKey);
|
|
90
|
+
const identityKey = types_1.IdentityKey.from(message.identityKey);
|
|
91
|
+
if (!crypto_1.default.EdDSA.verify(crypto_1.default.hash(signedPreKey), (0, utils_1.encodeBase64)(message.signature), identityKey.signatureKey))
|
|
92
|
+
throw new Error("Signature verification failed");
|
|
93
|
+
const onetimePreKey = message.onetimePreKey ? (0, utils_1.encodeBase64)(message.onetimePreKey) : undefined;
|
|
94
|
+
const signedPreKeyHash = crypto_1.default.hash(signedPreKey);
|
|
95
|
+
const onetimePreKeyHash = onetimePreKey ? crypto_1.default.hash(onetimePreKey) : new Uint8Array();
|
|
96
|
+
const rootKey = crypto_1.default.hkdf(new Uint8Array([
|
|
97
|
+
...crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, signedPreKey),
|
|
98
|
+
...crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, identityKey.exchangeKey),
|
|
99
|
+
...crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
|
|
100
|
+
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
|
|
101
|
+
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
|
|
102
|
+
const session = new double_ratchet_1.KeySession(this.sessions, { remoteKey: identityKey.exchangeKey, rootKey });
|
|
103
|
+
const cyphertext = yield session.encrypt((0, utils_1.concatArrays)(crypto_1.default.hash(this.identityKey.toBytes()), crypto_1.default.hash(identityKey.toBytes())));
|
|
104
|
+
if (!cyphertext)
|
|
105
|
+
throw new Error("Decryption error");
|
|
106
|
+
return {
|
|
107
|
+
session,
|
|
108
|
+
message: {
|
|
109
|
+
version: KeyExchange.version,
|
|
110
|
+
identityKey: this.identityKey.toString(),
|
|
111
|
+
ephemeralKey: (0, utils_1.decodeBase64)(ephemeralKey.publicKey),
|
|
112
|
+
signedPreKeyHash: (0, utils_1.decodeBase64)(signedPreKeyHash),
|
|
113
|
+
onetimePreKeyHash: (0, utils_1.decodeBase64)(onetimePreKeyHash),
|
|
114
|
+
associatedData: (0, utils_1.decodeBase64)(cyphertext.toBytes())
|
|
115
|
+
},
|
|
116
|
+
identityKey
|
|
117
|
+
};
|
|
118
|
+
});
|
|
116
119
|
}
|
|
117
120
|
digestMessage(message) {
|
|
118
121
|
return __awaiter(this, void 0, void 0, function* () {
|
|
119
|
-
const signedPreKey = yield this.
|
|
122
|
+
const signedPreKey = yield this.storage.get(message.signedPreKeyHash);
|
|
120
123
|
const hash = message.signedPreKeyHash.concat(message.onetimePreKeyHash);
|
|
121
|
-
const onetimePreKey = yield this.
|
|
124
|
+
const onetimePreKey = yield this.storage.get(hash);
|
|
125
|
+
const identityKey = types_1.IdentityKey.from(message.identityKey);
|
|
122
126
|
if (!signedPreKey || !onetimePreKey || !message.identityKey || !message.ephemeralKey)
|
|
123
127
|
throw new Error("ACK message malformed");
|
|
124
|
-
if (!this.
|
|
128
|
+
if (!this.storage.delete(hash))
|
|
125
129
|
throw new Error("Bundle store deleting error");
|
|
126
|
-
const identityKey = (0, utils_1.encodeBase64)(message.identityKey);
|
|
127
130
|
const ephemeralKey = (0, utils_1.encodeBase64)(message.ephemeralKey);
|
|
128
131
|
const rootKey = crypto_1.default.hkdf(new Uint8Array([
|
|
129
|
-
...crypto_1.default.ECDH.scalarMult(signedPreKey.secretKey, identityKey),
|
|
130
|
-
...crypto_1.default.ECDH.scalarMult(this.
|
|
132
|
+
...crypto_1.default.ECDH.scalarMult(signedPreKey.secretKey, identityKey.exchangeKey),
|
|
133
|
+
...crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, ephemeralKey),
|
|
131
134
|
...crypto_1.default.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
|
|
132
135
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
|
|
133
|
-
]), new Uint8Array(double_ratchet_1.KeySession.
|
|
134
|
-
const session = new double_ratchet_1.KeySession({ secretKey: this.
|
|
135
|
-
const cleartext = session.decrypt((0, utils_1.encodeBase64)(message.associatedData));
|
|
136
|
+
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
|
|
137
|
+
const session = new double_ratchet_1.KeySession(this.sessions, { secretKey: this.privateIdentityKey.exchangeKey, rootKey });
|
|
138
|
+
const cleartext = yield session.decrypt((0, utils_1.encodeBase64)(message.associatedData));
|
|
136
139
|
if (!cleartext)
|
|
137
140
|
throw new Error("Error decrypting ACK message");
|
|
138
|
-
if (!(0, utils_1.verifyArrays)(cleartext, (0, utils_1.concatArrays)(crypto_1.default.hash(identityKey), crypto_1.default.hash(this.
|
|
141
|
+
if (!(0, utils_1.verifyArrays)(cleartext, (0, utils_1.concatArrays)(crypto_1.default.hash(identityKey.toBytes()), crypto_1.default.hash(this.identityKey.toBytes()))))
|
|
139
142
|
throw new Error("Error verifing Associated Data");
|
|
140
143
|
return {
|
|
141
144
|
session,
|
|
142
|
-
|
|
143
|
-
publicKey: message.publicKey,
|
|
144
|
-
identityKey: message.identityKey
|
|
145
|
-
}
|
|
145
|
+
identityKey
|
|
146
146
|
};
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
|
-
toJSON() {
|
|
150
|
-
return {
|
|
151
|
-
identityKey: this._identityKey,
|
|
152
|
-
signatureKey: this._signatureKey,
|
|
153
|
-
bundleStore: Array.from(this.bundleStore.entries())
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
static from(data) {
|
|
157
|
-
return new KeyExchange(data.signatureKey.secretKey, data.identityKey.secretKey, new AsyncMap(data.bundleStore));
|
|
158
|
-
}
|
|
159
149
|
}
|
|
160
150
|
exports.KeyExchange = KeyExchange;
|
|
161
151
|
KeyExchange.version = 1;
|
|
162
|
-
KeyExchange.hkdfInfo =
|
|
152
|
+
KeyExchange.hkdfInfo = "freesignal/x3dh/" + KeyExchange.version;
|
|
163
153
|
KeyExchange.maxOPK = 10;
|
|
164
|
-
class AsyncMap {
|
|
165
|
-
constructor(iterable) {
|
|
166
|
-
this.map = new Map(iterable);
|
|
167
|
-
}
|
|
168
|
-
set(key, value) {
|
|
169
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
170
|
-
this.map.set(key, value);
|
|
171
|
-
return;
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
get(key) {
|
|
175
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
176
|
-
return this.map.get(key);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
has(key) {
|
|
180
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
181
|
-
return this.map.has(key);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
delete(key) {
|
|
185
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
-
return this.map.delete(key);
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
entries() {
|
|
190
|
-
return this.map.entries();
|
|
191
|
-
}
|
|
192
|
-
}
|
package/api.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
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, Database, KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, LocalStorage } from "@freesignal/interfaces";
|
|
20
|
-
import { ExportedKeySession } from "./double-ratchet";
|
|
21
|
-
import { KeyExchange } from "./x3dh";
|
|
22
|
-
import { Datagram, IdentityKeys, EncryptedData, UserId } from "./types";
|
|
23
|
-
type DatagramId = string;
|
|
24
|
-
export declare class FreeSignalAPI {
|
|
25
|
-
protected readonly signKey: Crypto.KeyPair;
|
|
26
|
-
protected readonly boxKey: Crypto.KeyPair;
|
|
27
|
-
protected readonly sessions: LocalStorage<UserId, ExportedKeySession>;
|
|
28
|
-
protected readonly keyExchange: KeyExchange;
|
|
29
|
-
protected readonly users: LocalStorage<UserId, IdentityKeys>;
|
|
30
|
-
readonly userId: UserId;
|
|
31
|
-
constructor(secretSignKey: Uint8Array, secretBoxKey: Uint8Array, storage: Database<{
|
|
32
|
-
sessions: LocalStorage<UserId, ExportedKeySession>;
|
|
33
|
-
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
34
|
-
users: LocalStorage<UserId, IdentityKeys>;
|
|
35
|
-
}>);
|
|
36
|
-
get identityKeys(): IdentityKeys;
|
|
37
|
-
encryptData(data: Uint8Array, userId: string): Promise<EncryptedData>;
|
|
38
|
-
decryptData(data: Uint8Array, userId: string): Promise<Uint8Array>;
|
|
39
|
-
getHandshake(url: string, userId?: UserId): Promise<KeyExchangeData>;
|
|
40
|
-
postHandshake(url: string, message: KeyExchangeSynMessage): Promise<boolean>;
|
|
41
|
-
putHandshake(url: string, publicKey: string | Uint8Array, bundle: KeyExchangeDataBundle): Promise<boolean>;
|
|
42
|
-
deleteHandshake(url: string, publicKey: string | Uint8Array): Promise<boolean>;
|
|
43
|
-
getDatagrams(publicKey: string | Uint8Array, url: string): Promise<Datagram[]>;
|
|
44
|
-
postDatagrams(datagrams: Datagram[], publicKey: string | Uint8Array, url: string): Promise<number>;
|
|
45
|
-
deleteDatagrams(datagramIds: DatagramId[], publicKey: string | Uint8Array, url: string): Promise<number>;
|
|
46
|
-
createToken(publicKey: Uint8Array): string;
|
|
47
|
-
protected digestToken(auth?: string): Promise<{
|
|
48
|
-
identityKeys: IdentityKeys;
|
|
49
|
-
userId: UserId;
|
|
50
|
-
}>;
|
|
51
|
-
}
|
|
52
|
-
export {};
|
package/api.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
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
|
-
*/
|
|
20
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
21
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
22
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
23
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
24
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
25
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
26
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
30
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
|
-
};
|
|
32
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.FreeSignalAPI = void 0;
|
|
34
|
-
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
35
|
-
const double_ratchet_1 = require("./double-ratchet");
|
|
36
|
-
const x3dh_1 = require("./x3dh");
|
|
37
|
-
const utils_1 = require("@freesignal/utils");
|
|
38
|
-
const types_1 = require("./types");
|
|
39
|
-
class FreeSignalAPI {
|
|
40
|
-
constructor(secretSignKey, secretBoxKey, storage) {
|
|
41
|
-
this.signKey = crypto_1.default.EdDSA.keyPair(secretSignKey);
|
|
42
|
-
this.boxKey = crypto_1.default.ECDH.keyPair(secretBoxKey);
|
|
43
|
-
this.sessions = storage.sessions;
|
|
44
|
-
this.keyExchange = new x3dh_1.KeyExchange(secretSignKey, secretBoxKey, storage.keyExchange);
|
|
45
|
-
this.users = storage.users;
|
|
46
|
-
this.userId = types_1.UserId.getUserId(this.signKey.publicKey).toString();
|
|
47
|
-
}
|
|
48
|
-
get identityKeys() {
|
|
49
|
-
return {
|
|
50
|
-
publicKey: (0, utils_1.decodeBase64)(this.signKey.publicKey),
|
|
51
|
-
identityKey: (0, utils_1.decodeBase64)(this.boxKey.publicKey)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
encryptData(data, userId) {
|
|
55
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
-
const sessionJson = yield this.sessions.get(userId);
|
|
57
|
-
if (!sessionJson)
|
|
58
|
-
throw new Error('Session not found for user: ' + userId);
|
|
59
|
-
const session = double_ratchet_1.KeySession.from(sessionJson);
|
|
60
|
-
const encrypted = session.encrypt(data);
|
|
61
|
-
this.sessions.set(userId, session.toJSON()); // Ensure session is updated
|
|
62
|
-
return encrypted;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
decryptData(data, userId) {
|
|
66
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
-
const sessionJson = yield this.sessions.get(userId);
|
|
68
|
-
if (!sessionJson)
|
|
69
|
-
throw new Error('Session not found for user: ' + userId);
|
|
70
|
-
const session = double_ratchet_1.KeySession.from(sessionJson);
|
|
71
|
-
const decrypted = session.decrypt(data);
|
|
72
|
-
if (!decrypted)
|
|
73
|
-
throw new Error('Decryption failed for user: ' + userId);
|
|
74
|
-
this.sessions.set(userId, session.toJSON()); // Ensure session is updated
|
|
75
|
-
return decrypted;
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
getHandshake(url, userId) {
|
|
79
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
-
const res = yield fetch(`${url}/${userId !== null && userId !== void 0 ? userId : ''}`, {
|
|
81
|
-
method: 'GET'
|
|
82
|
-
});
|
|
83
|
-
const body = types_1.XFreeSignal.decodeBody(new Uint8Array(yield res.arrayBuffer()));
|
|
84
|
-
if (body.type === 'error')
|
|
85
|
-
throw new Error(body.data);
|
|
86
|
-
return body.data;
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
postHandshake(url, message) {
|
|
90
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
-
const res = yield fetch(url, {
|
|
92
|
-
method: 'POST',
|
|
93
|
-
headers: {
|
|
94
|
-
'Content-Type': types_1.XFreeSignal.MIME
|
|
95
|
-
},
|
|
96
|
-
body: types_1.XFreeSignal.encodeBody('data', message)
|
|
97
|
-
});
|
|
98
|
-
return res.status === 200;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
putHandshake(url, publicKey, bundle) {
|
|
102
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
-
const res = yield fetch(url, {
|
|
104
|
-
method: 'PUT',
|
|
105
|
-
headers: {
|
|
106
|
-
'Content-Type': types_1.XFreeSignal.MIME,
|
|
107
|
-
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
108
|
-
},
|
|
109
|
-
body: types_1.XFreeSignal.encodeBody('data', bundle)
|
|
110
|
-
});
|
|
111
|
-
return res.status === 201;
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
deleteHandshake(url, publicKey) {
|
|
115
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
116
|
-
const res = yield fetch(url, {
|
|
117
|
-
method: 'DELETE',
|
|
118
|
-
headers: {
|
|
119
|
-
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
return res.status === 200;
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
getDatagrams(publicKey, url) {
|
|
126
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
127
|
-
const res = yield fetch(url, {
|
|
128
|
-
method: 'GET',
|
|
129
|
-
headers: {
|
|
130
|
-
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
const body = types_1.XFreeSignal.decodeBody(new Uint8Array(yield res.arrayBuffer()));
|
|
134
|
-
if (body.type === 'error')
|
|
135
|
-
throw new Error(body.data);
|
|
136
|
-
return types_1.DataEncoder.from(yield this.decryptData(body.data, types_1.UserId.getUserId(publicKey).toString())).data.map(array => types_1.Datagram.from(array));
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
postDatagrams(datagrams, publicKey, url) {
|
|
140
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
141
|
-
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());
|
|
142
|
-
const res = yield fetch(url, {
|
|
143
|
-
method: 'POST',
|
|
144
|
-
headers: {
|
|
145
|
-
'Content-Type': types_1.XFreeSignal.MIME,
|
|
146
|
-
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
147
|
-
},
|
|
148
|
-
body: types_1.XFreeSignal.encodeBody('data', data.encode())
|
|
149
|
-
});
|
|
150
|
-
const body = types_1.XFreeSignal.decodeBody(new Uint8Array(yield res.arrayBuffer()));
|
|
151
|
-
if (body.type === 'error')
|
|
152
|
-
throw new Error(body.data);
|
|
153
|
-
return types_1.DataEncoder.from(yield this.decryptData(body.data, types_1.UserId.getUserId(publicKey).toString())).data;
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
deleteDatagrams(datagramIds, publicKey, url) {
|
|
157
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
-
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());
|
|
159
|
-
const res = yield fetch(url, {
|
|
160
|
-
method: 'DELETE',
|
|
161
|
-
headers: {
|
|
162
|
-
'Content-Type': types_1.XFreeSignal.MIME,
|
|
163
|
-
authorization: this.createToken(publicKey instanceof Uint8Array ? publicKey : (0, utils_1.encodeBase64)(publicKey))
|
|
164
|
-
},
|
|
165
|
-
body: types_1.XFreeSignal.encodeBody('data', data.encode())
|
|
166
|
-
});
|
|
167
|
-
const body = types_1.XFreeSignal.decodeBody(new Uint8Array(yield res.arrayBuffer()));
|
|
168
|
-
if (body.type === 'error')
|
|
169
|
-
throw new Error(body.data);
|
|
170
|
-
return types_1.DataEncoder.from(yield this.decryptData(body.data, types_1.UserId.getUserId(publicKey).toString())).data;
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
createToken(publicKey) {
|
|
174
|
-
const signature = crypto_1.default.EdDSA.sign(crypto_1.default.hash(crypto_1.default.ECDH.scalarMult(publicKey, this.boxKey.secretKey)), this.signKey.secretKey);
|
|
175
|
-
return `Bearer ${this.userId}:${(0, utils_1.decodeBase64)(signature)}`;
|
|
176
|
-
}
|
|
177
|
-
;
|
|
178
|
-
digestToken(auth) {
|
|
179
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
180
|
-
if (auth && auth.startsWith("Bearer ")) {
|
|
181
|
-
const [userId, signature] = auth.substring(7).split(":");
|
|
182
|
-
const identityKeys = yield this.users.get(userId);
|
|
183
|
-
if (!identityKeys)
|
|
184
|
-
throw new Error('User not found or invalid auth token');
|
|
185
|
-
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)))
|
|
186
|
-
return { identityKeys, userId: auth };
|
|
187
|
-
else
|
|
188
|
-
throw new Error('Authorization token not valid');
|
|
189
|
-
}
|
|
190
|
-
throw new Error('Authorization header is required');
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
exports.FreeSignalAPI = FreeSignalAPI;
|