|
|
|
function SignalProtocolStore() {
|
|
|
|
this.store = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
SignalProtocolStore.prototype = {
|
|
|
|
Direction: { SENDING: 1, RECEIVING: 2 },
|
|
|
|
getIdentityKeyPair() {
|
|
|
|
return Promise.resolve(this.get('identityKey'));
|
|
|
|
},
|
|
|
|
getLocalRegistrationId() {
|
|
|
|
return Promise.resolve(this.get('registrationId'));
|
|
|
|
},
|
|
|
|
put(key, value) {
|
|
|
|
if (
|
|
|
|
key === undefined ||
|
|
|
|
value === undefined ||
|
|
|
|
key === null ||
|
|
|
|
value === null
|
|
|
|
) {
|
|
|
|
throw new Error('Tried to store undefined/null');
|
|
|
|
}
|
|
|
|
this.store[key] = value;
|
|
|
|
},
|
|
|
|
get(key, defaultValue) {
|
|
|
|
if (key === null || key === undefined) {
|
|
|
|
throw new Error('Tried to get value for undefined/null key');
|
|
|
|
}
|
|
|
|
if (key in this.store) {
|
|
|
|
return this.store[key];
|
|
|
|
}
|
|
|
|
return defaultValue;
|
|
|
|
},
|
|
|
|
remove(key) {
|
|
|
|
if (key === null || key === undefined) {
|
|
|
|
throw new Error('Tried to remove value for undefined/null key');
|
|
|
|
}
|
|
|
|
delete this.store[key];
|
|
|
|
},
|
|
|
|
|
|
|
|
isTrustedIdentity(identifier, identityKey) {
|
|
|
|
if (identifier === null || identifier === undefined) {
|
|
|
|
throw new Error('tried to check identity key for undefined/null key');
|
|
|
|
}
|
|
|
|
if (!(identityKey instanceof ArrayBuffer)) {
|
|
|
|
throw new Error('Expected identityKey to be an ArrayBuffer');
|
|
|
|
}
|
|
|
|
const trusted = this.get(`identityKey${identifier}`);
|
|
|
|
if (trusted === undefined) {
|
|
|
|
return Promise.resolve(true);
|
|
|
|
}
|
|
|
|
return Promise.resolve(identityKey === trusted);
|
|
|
|
},
|
|
|
|
loadIdentityKey(identifier) {
|
|
|
|
if (identifier === null || identifier === undefined) {
|
|
|
|
throw new Error('Tried to get identity key for undefined/null key');
|
|
|
|
}
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.get(`identityKey${identifier}`));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
saveIdentity(identifier, identityKey) {
|
|
|
|
if (identifier === null || identifier === undefined) {
|
|
|
|
throw new Error('Tried to put identity key for undefined/null key');
|
|
|
|
}
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const existing = this.get(`identityKey${identifier}`);
|
|
|
|
this.put(`identityKey${identifier}`, identityKey);
|
|
|
|
if (existing && existing !== identityKey) {
|
|
|
|
resolve(true);
|
|
|
|
} else {
|
|
|
|
resolve(false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/* Returns a prekeypair object or undefined */
|
|
|
|
loadPreKey(keyId) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const res = this.get(`25519KeypreKey${keyId}`);
|
|
|
|
resolve(res);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
storePreKey(keyId, keyPair, contactPubKey = null) {
|
|
|
|
if (contactPubKey) {
|
|
|
|
const data = {
|
|
|
|
id: keyId,
|
|
|
|
publicKey: keyPair.pubKey,
|
|
|
|
privateKey: keyPair.privKey,
|
|
|
|
recipient: contactPubKey,
|
|
|
|
};
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.put(`25519KeypreKey${contactPubKey}`, data));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.put(`25519KeypreKey${keyId}`, keyPair));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
removePreKey(keyId) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.remove(`25519KeypreKey${keyId}`));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/* Returns a signed keypair object or undefined */
|
|
|
|
loadSignedPreKey(keyId) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const res = this.get(`25519KeysignedKey${keyId}`);
|
|
|
|
resolve(res);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
loadSignedPreKeys() {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const res = [];
|
|
|
|
const keys = Object.keys(this.store);
|
|
|
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
|
|
|
const key = keys[i];
|
|
|
|
if (key.startsWith('25519KeysignedKey')) {
|
|
|
|
res.push(this.store[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resolve(res);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
storeSignedPreKey(keyId, keyPair) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.put(`25519KeysignedKey${keyId}`, keyPair));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
removeSignedPreKey(keyId) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.remove(`25519KeysignedKey${keyId}`));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
loadSession(identifier) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.get(`session${identifier}`));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
storeSession(identifier, record) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
resolve(this.put(`session${identifier}`, record));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
removeAllSessions(identifier) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const keys = Object.keys(this.store);
|
|
|
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
|
|
|
const key = keys[i];
|
|
|
|
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
|
|
|
delete this.store[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
getDeviceIds(identifier) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const deviceIds = [];
|
|
|
|
const keys = Object.keys(this.store);
|
|
|
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
|
|
|
const key = keys[i];
|
|
|
|
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
|
|
|
deviceIds.push(parseInt(key.split('.')[1], 10));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resolve(deviceIds);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
async loadPreKeyForContact(contactPubKey) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const key = this.get(`25519KeypreKey${contactPubKey}`);
|
|
|
|
if (!key) {
|
|
|
|
resolve(undefined);
|
|
|
|
}
|
|
|
|
resolve({
|
|
|
|
pubKey: key.publicKey,
|
|
|
|
privKey: key.privateKey,
|
|
|
|
keyId: key.id,
|
|
|
|
recipient: key.recipient,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
async storeContactSignedPreKey(pubKey, signedPreKey) {
|
|
|
|
const key = {
|
|
|
|
identityKeyString: pubKey,
|
|
|
|
keyId: signedPreKey.keyId,
|
|
|
|
publicKey: signedPreKey.publicKey,
|
|
|
|
signature: signedPreKey.signature,
|
|
|
|
created_at: Date.now(),
|
|
|
|
confirmed: false,
|
|
|
|
};
|
|
|
|
this.put(`contactSignedPreKey${pubKey}`, key);
|
|
|
|
},
|
|
|
|
async loadContactSignedPreKey(pubKey) {
|
|
|
|
const preKey = this.get(`contactSignedPreKey${pubKey}`);
|
|
|
|
if (preKey) {
|
|
|
|
return {
|
|
|
|
id: preKey.id,
|
|
|
|
identityKeyString: preKey.identityKeyString,
|
|
|
|
publicKey: preKey.publicKey,
|
|
|
|
signature: preKey.signature,
|
|
|
|
created_at: preKey.created_at,
|
|
|
|
keyId: preKey.keyId,
|
|
|
|
confirmed: preKey.confirmed,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
window.log.warn('Failed to fetch contact signed prekey:', pubKey);
|
|
|
|
return undefined;
|
|
|
|
},
|
|
|
|
};
|