@freesignal/protocol 0.1.0 → 0.1.5

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/x3dh.d.ts CHANGED
@@ -17,58 +17,28 @@
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
19
  import crypto from "./crypto";
20
- type BoxKeyPair = crypto.KeyPair;
21
- type SignKeyPair = crypto.KeyPair;
22
- type ExportedX3DH = [
23
- SignKeyPair,
24
- [
25
- Array<[string, BoxKeyPair]>,
26
- Array<[string, BoxKeyPair]>
27
- ]
28
- ];
29
- interface SynMessage {
30
- readonly version: number;
31
- readonly PK: string;
32
- readonly IK: string;
33
- readonly SPK: string;
34
- readonly SPKsign: string;
35
- readonly OPK: string;
36
- }
37
- interface AckMessage {
38
- readonly version: number;
39
- readonly PK: string;
40
- readonly IK: string;
41
- readonly EK: string;
42
- readonly SPKhash: string;
43
- readonly OPKhash: string;
44
- readonly AD: string;
45
- }
46
- export interface Bundle {
47
- readonly version: number;
48
- readonly PK: string;
49
- readonly IK: string;
50
- readonly SPK: string;
51
- readonly SPKsign: string;
52
- readonly OPK: string[];
53
- }
54
- export declare class X3DH {
20
+ import { KeyExchangeData, KeyExchangeDataBundle, KeyExchangeSynMessage, LocalStorage } from "./types";
21
+ import { KeySession } from "./double-ratchet";
22
+ export declare class KeyExchange {
55
23
  static readonly version = 1;
56
24
  private static readonly hkdfInfo;
57
25
  private static readonly maxOPK;
58
- private readonly PK;
59
- private readonly IK;
26
+ private readonly _signatureKey;
27
+ private readonly _identityKey;
60
28
  private readonly bundleStore;
61
- constructor(signKeyPair: SignKeyPair, instance?: [Iterable<[string, BoxKeyPair]>, Iterable<[string, BoxKeyPair]>]);
29
+ constructor(signSecretKey: Uint8Array, boxSecretKey: Uint8Array, bundleStore?: LocalStorage<string, crypto.KeyPair>);
30
+ get signatureKey(): Uint8Array;
31
+ get identityKey(): Uint8Array;
62
32
  private generateSPK;
63
33
  private generateOPK;
64
- generateBundle(length?: number): Bundle;
65
- generateSyn(): SynMessage;
66
- digestSyn(message: SynMessage, encrypter?: (msg: Uint8Array, key: Uint8Array) => Uint8Array): {
67
- rootKey: Uint8Array;
68
- ackMessage: AckMessage;
34
+ generateBundle(length?: number): KeyExchangeDataBundle;
35
+ generateData(): KeyExchangeData;
36
+ digestData(message: KeyExchangeData): {
37
+ session: KeySession;
38
+ message: KeyExchangeSynMessage;
39
+ };
40
+ digestMessage(message: KeyExchangeSynMessage): {
41
+ session: KeySession;
42
+ cleartext: Uint8Array;
69
43
  };
70
- digestAck(message: AckMessage, verifier?: (ciphertext: Uint8Array, key: Uint8Array) => boolean): Uint8Array | undefined;
71
- export(): ExportedX3DH;
72
- static import(input: ExportedX3DH): X3DH;
73
44
  }
74
- export {};
package/x3dh.js CHANGED
@@ -21,116 +21,112 @@ 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.X3DH = void 0;
24
+ exports.KeyExchange = void 0;
25
25
  const crypto_1 = __importDefault(require("./crypto"));
26
26
  const double_ratchet_1 = require("./double-ratchet");
27
27
  const utils_1 = require("./utils");
28
- class X3DH {
29
- constructor(signKeyPair, instance) {
30
- this.PK = signKeyPair;
31
- this.IK = crypto_1.default.ECDH.keyPair(crypto_1.default.hash(signKeyPair.secretKey));
32
- this.bundleStore = {
33
- SPK: new Map(instance ? instance[0] : []),
34
- OPK: new Map(instance ? instance[1] : [])
35
- };
28
+ class KeyExchange {
29
+ constructor(signSecretKey, boxSecretKey, bundleStore) {
30
+ this._signatureKey = crypto_1.default.EdDSA.keyPair(signSecretKey);
31
+ this._identityKey = crypto_1.default.ECDH.keyPair(boxSecretKey);
32
+ this.bundleStore = bundleStore !== null && bundleStore !== void 0 ? bundleStore : new Map();
36
33
  }
34
+ get signatureKey() { return this._signatureKey.publicKey; }
35
+ get identityKey() { return this._identityKey.publicKey; }
37
36
  generateSPK() {
38
- const SPK = crypto_1.default.ECDH.keyPair();
39
- const SPKhash = crypto_1.default.hash(SPK.publicKey);
40
- this.bundleStore.SPK.set((0, utils_1.encodeBase64)(SPKhash), SPK);
41
- return { SPK, SPKhash };
37
+ const signedPreKey = crypto_1.default.ECDH.keyPair();
38
+ const signedPreKeyHash = crypto_1.default.hash(signedPreKey.publicKey);
39
+ this.bundleStore.set((0, utils_1.encodeBase64)(signedPreKeyHash), signedPreKey);
40
+ return { signedPreKey, signedPreKeyHash };
42
41
  }
43
42
  generateOPK(spkHash) {
44
- const OPK = crypto_1.default.ECDH.keyPair();
45
- const OPKhash = crypto_1.default.hash(OPK.publicKey);
46
- this.bundleStore.OPK.set((0, utils_1.encodeBase64)(spkHash).concat((0, utils_1.encodeBase64)(OPKhash)), OPK);
47
- return { OPK, OPKhash };
43
+ const onetimePreKey = crypto_1.default.ECDH.keyPair();
44
+ const onetimePreKeyHash = crypto_1.default.hash(onetimePreKey.publicKey);
45
+ this.bundleStore.set((0, utils_1.encodeBase64)(spkHash).concat((0, utils_1.encodeBase64)(onetimePreKeyHash)), onetimePreKey);
46
+ return { onetimePreKey, onetimePreKeyHash };
48
47
  }
49
48
  generateBundle(length) {
50
- const { SPK, SPKhash } = this.generateSPK();
51
- const OPK = new Array(length !== null && length !== void 0 ? length : X3DH.maxOPK).fill(0).map(() => this.generateOPK(SPKhash).OPK);
49
+ const { signedPreKey, signedPreKeyHash } = this.generateSPK();
50
+ const onetimePreKey = new Array(length !== null && length !== void 0 ? length : KeyExchange.maxOPK).fill(0).map(() => this.generateOPK(signedPreKeyHash).onetimePreKey);
52
51
  return {
53
- version: X3DH.version,
54
- PK: (0, utils_1.encodeBase64)(this.PK.publicKey),
55
- IK: (0, utils_1.encodeBase64)(this.IK.publicKey),
56
- SPK: (0, utils_1.encodeBase64)(SPK.publicKey),
57
- SPKsign: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign((0, utils_1.concatUint8Array)(crypto_1.default.hash(this.IK.publicKey), SPKhash), this.PK.secretKey)),
58
- OPK: OPK.map(opk => (0, utils_1.encodeBase64)(opk.publicKey))
52
+ version: KeyExchange.version,
53
+ publicKey: (0, utils_1.encodeBase64)(this._signatureKey.publicKey),
54
+ identityKey: (0, utils_1.encodeBase64)(this._identityKey.publicKey),
55
+ signedPreKey: (0, utils_1.encodeBase64)(signedPreKey.publicKey),
56
+ signature: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this._signatureKey.secretKey)),
57
+ onetimePreKey: onetimePreKey.map(opk => (0, utils_1.encodeBase64)(opk.publicKey))
59
58
  };
60
59
  }
61
- generateSyn() {
62
- const { SPK, SPKhash } = this.generateSPK();
63
- const { OPK } = this.generateOPK(SPKhash);
60
+ generateData() {
61
+ const { signedPreKey, signedPreKeyHash } = this.generateSPK();
62
+ const { onetimePreKey } = this.generateOPK(signedPreKeyHash);
64
63
  return {
65
- version: X3DH.version,
66
- PK: (0, utils_1.encodeBase64)(this.PK.publicKey),
67
- IK: (0, utils_1.encodeBase64)(this.IK.publicKey),
68
- SPK: (0, utils_1.encodeBase64)(SPK.publicKey),
69
- SPKsign: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign((0, utils_1.concatUint8Array)(crypto_1.default.hash(this.IK.publicKey), SPKhash), this.PK.secretKey)),
70
- OPK: (0, utils_1.encodeBase64)(OPK.publicKey)
64
+ version: KeyExchange.version,
65
+ publicKey: (0, utils_1.encodeBase64)(this._signatureKey.publicKey),
66
+ identityKey: (0, utils_1.encodeBase64)(this._identityKey.publicKey),
67
+ signedPreKey: (0, utils_1.encodeBase64)(signedPreKey.publicKey),
68
+ signature: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this._signatureKey.secretKey)),
69
+ onetimePreKey: (0, utils_1.encodeBase64)(onetimePreKey.publicKey)
71
70
  };
72
71
  }
73
- digestSyn(message, encrypter) {
74
- const EK = crypto_1.default.ECDH.keyPair();
75
- const SPK = (0, utils_1.decodeBase64)(message.SPK);
76
- const IK = (0, utils_1.decodeBase64)(message.IK);
77
- const OPK = message.OPK ? (0, utils_1.decodeBase64)(message.OPK) : undefined;
78
- const spkHash = crypto_1.default.hash(SPK);
79
- const opkHash = OPK ? crypto_1.default.hash(OPK) : new Uint8Array();
72
+ digestData(message) {
73
+ const ephemeralKey = crypto_1.default.ECDH.keyPair();
74
+ const signedPreKey = (0, utils_1.decodeBase64)(message.signedPreKey);
75
+ if (!crypto_1.default.EdDSA.verify(signedPreKey, (0, utils_1.decodeBase64)(message.signature), (0, utils_1.decodeBase64)(message.publicKey)))
76
+ throw new Error("Signature verification failed");
77
+ const identityKey = (0, utils_1.decodeBase64)(message.identityKey);
78
+ const onetimePreKey = message.onetimePreKey ? (0, utils_1.decodeBase64)(message.onetimePreKey) : undefined;
79
+ const signedPreKeyHash = crypto_1.default.hash(signedPreKey);
80
+ const onetimePreKeyHash = onetimePreKey ? crypto_1.default.hash(onetimePreKey) : new Uint8Array();
80
81
  const rootKey = crypto_1.default.hkdf(new Uint8Array([
81
- ...crypto_1.default.scalarMult(this.IK.secretKey, SPK),
82
- ...crypto_1.default.scalarMult(EK.secretKey, IK),
83
- ...crypto_1.default.scalarMult(EK.secretKey, SPK),
84
- ...OPK ? crypto_1.default.scalarMult(EK.secretKey, OPK) : new Uint8Array()
85
- ]), new Uint8Array(double_ratchet_1.Session.rootKeyLength).fill(0), X3DH.hkdfInfo, double_ratchet_1.Session.rootKeyLength);
86
- if (!encrypter)
87
- encrypter = (msg, key) => crypto_1.default.box.encrypt(msg, new Uint8Array(crypto_1.default.box.nonceLength).fill(0), key);
82
+ ...crypto_1.default.scalarMult(this._identityKey.secretKey, signedPreKey),
83
+ ...crypto_1.default.scalarMult(ephemeralKey.secretKey, identityKey),
84
+ ...crypto_1.default.scalarMult(ephemeralKey.secretKey, signedPreKey),
85
+ ...onetimePreKey ? crypto_1.default.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
86
+ ]), new Uint8Array(double_ratchet_1.KeySession.rootKeyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.rootKeyLength);
87
+ const session = new double_ratchet_1.KeySession({ remoteKey: identityKey, rootKey });
88
+ const cyphertext = session.encrypt((0, utils_1.concatUint8Array)(crypto_1.default.hash(this._identityKey.publicKey), crypto_1.default.hash(identityKey)));
89
+ if (!cyphertext)
90
+ throw new Error();
88
91
  return {
89
- rootKey,
90
- ackMessage: {
91
- version: X3DH.version,
92
- PK: (0, utils_1.encodeBase64)(this.PK.publicKey),
93
- IK: (0, utils_1.encodeBase64)(this.IK.publicKey),
94
- EK: (0, utils_1.encodeBase64)(EK.publicKey),
95
- SPKhash: (0, utils_1.encodeBase64)(spkHash),
96
- OPKhash: (0, utils_1.encodeBase64)(opkHash),
97
- AD: (0, utils_1.encodeBase64)(encrypter((0, utils_1.concatUint8Array)(crypto_1.default.hash(this.IK.publicKey), crypto_1.default.hash(IK)), rootKey))
92
+ session,
93
+ message: {
94
+ version: KeyExchange.version,
95
+ publicKey: (0, utils_1.encodeBase64)(this._signatureKey.publicKey),
96
+ identityKey: (0, utils_1.encodeBase64)(this._identityKey.publicKey),
97
+ ephemeralKey: (0, utils_1.encodeBase64)(ephemeralKey.publicKey),
98
+ signedPreKeyHash: (0, utils_1.encodeBase64)(signedPreKeyHash),
99
+ onetimePreKeyHash: (0, utils_1.encodeBase64)(onetimePreKeyHash),
100
+ associatedData: (0, utils_1.encodeBase64)(cyphertext.encode())
98
101
  }
99
102
  };
100
103
  }
101
- digestAck(message, verifier) {
102
- const SPK = this.bundleStore.SPK.get(message.SPKhash);
103
- const OPK = this.bundleStore.OPK.get(message.SPKhash.concat(message.OPKhash));
104
- if (!SPK || !OPK || !message.IK || !message.EK)
105
- return;
106
- const IK = (0, utils_1.decodeBase64)(message.IK);
107
- const EK = (0, utils_1.decodeBase64)(message.EK);
104
+ digestMessage(message) {
105
+ const signedPreKey = this.bundleStore.get(message.signedPreKeyHash);
106
+ const hash = message.signedPreKeyHash.concat(message.onetimePreKeyHash);
107
+ const onetimePreKey = this.bundleStore.get(hash);
108
+ if (!signedPreKey || !onetimePreKey || !message.identityKey || !message.ephemeralKey)
109
+ throw new Error("ACK message malformed");
110
+ if (!this.bundleStore.delete(hash))
111
+ throw new Error("Bundle store deleting error");
112
+ const identityKey = (0, utils_1.decodeBase64)(message.identityKey);
113
+ const ephemeralKey = (0, utils_1.decodeBase64)(message.ephemeralKey);
108
114
  const rootKey = crypto_1.default.hkdf(new Uint8Array([
109
- ...crypto_1.default.scalarMult(SPK.secretKey, IK),
110
- ...crypto_1.default.scalarMult(this.IK.secretKey, EK),
111
- ...crypto_1.default.scalarMult(SPK.secretKey, EK),
112
- ...OPK ? crypto_1.default.scalarMult(OPK.secretKey, EK) : new Uint8Array()
113
- ]), new Uint8Array(double_ratchet_1.Session.rootKeyLength).fill(0), X3DH.hkdfInfo, double_ratchet_1.Session.rootKeyLength);
114
- if (!verifier)
115
- verifier = (ciphertext, key) => (0, utils_1.verifyUint8Array)(crypto_1.default.box.decrypt(ciphertext, new Uint8Array(crypto_1.default.box.nonceLength).fill(0), key), (0, utils_1.concatUint8Array)(crypto_1.default.hash(IK), crypto_1.default.hash(this.IK.publicKey)));
116
- if (!verifier((0, utils_1.decodeBase64)(message.AD), rootKey))
117
- return;
118
- return rootKey;
119
- }
120
- export() {
121
- return [
122
- this.IK,
123
- [
124
- Array.from(this.bundleStore.SPK.entries()),
125
- Array.from(this.bundleStore.OPK.entries())
126
- ]
127
- ];
128
- }
129
- static import(input) {
130
- return new X3DH(...input);
115
+ ...crypto_1.default.scalarMult(signedPreKey.secretKey, identityKey),
116
+ ...crypto_1.default.scalarMult(this._identityKey.secretKey, ephemeralKey),
117
+ ...crypto_1.default.scalarMult(signedPreKey.secretKey, ephemeralKey),
118
+ ...onetimePreKey ? crypto_1.default.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
119
+ ]), new Uint8Array(double_ratchet_1.KeySession.rootKeyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.rootKeyLength);
120
+ const session = new double_ratchet_1.KeySession({ secretKey: this._identityKey.secretKey, rootKey });
121
+ const cleartext = session.decrypt((0, utils_1.decodeBase64)(message.associatedData));
122
+ if (!cleartext)
123
+ throw new Error("Error decrypting ACK message");
124
+ if (!(0, utils_1.verifyUint8Array)(cleartext, (0, utils_1.concatUint8Array)(crypto_1.default.hash(identityKey), crypto_1.default.hash(this._identityKey.publicKey))))
125
+ throw new Error("Error verifing Associated Data");
126
+ return { session, cleartext };
131
127
  }
132
128
  }
133
- exports.X3DH = X3DH;
134
- X3DH.version = 1;
135
- X3DH.hkdfInfo = (0, utils_1.decodeUTF8)("freesignal/x3dh/" + X3DH.version);
136
- X3DH.maxOPK = 10;
129
+ exports.KeyExchange = KeyExchange;
130
+ KeyExchange.version = 1;
131
+ KeyExchange.hkdfInfo = (0, utils_1.decodeUTF8)("freesignal/x3dh/" + KeyExchange.version);
132
+ KeyExchange.maxOPK = 10;