Prekeys sending: first pass (#12)

* add 'recipient' attribute in prekey database to allow binding a prekey to a recipient pub key

* Attach a PreKeyBundleMessage to the ContentMessage in fallback encryption mode

* Skip generating 100 prekeys upon registration

* Store the signed key signature in database
pull/14/head
sachaaaaa 7 years ago committed by GitHub
parent 54201d6665
commit 886f47b8e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -44,7 +44,9 @@ const migrations = [
transaction.db.createObjectStore('sessions');
transaction.db.createObjectStore('identityKeys');
transaction.db.createObjectStore('preKeys');
const preKeys = transaction.db.createObjectStore('preKeys', { keyPath: 'id'});
preKeys.createIndex('recipient', 'recipient', { unique: true });
transaction.db.createObjectStore('signedPreKeys');
transaction.db.createObjectStore('items');

@ -223,6 +223,24 @@
);
});
},
loadPreKeyForContactIdentityKeyString(contactIdentityKeyString) {
const prekey = new PreKey({ recipient: contactIdentityKeyString });
return new Promise(resolve => {
prekey.fetch().then(
() => {
window.log.info('Successfully fetched prekey for recipient :', contactIdentityKeyString);
resolve({
pubKey: prekey.get('publicKey'),
privKey: prekey.get('privateKey'),
keyId: prekey.get('id'),
});
},
() => {
resolve();
}
);
});
},
loadContactPreKey(pubKey) {
const prekey = new ContactPreKey({ identityKeyString: pubKey });
return new Promise(resolve => {
@ -256,11 +274,12 @@
});
});
},
storePreKey(keyId, keyPair) {
storePreKey(keyId, keyPair, contactIdentityKeyString) {
const prekey = new PreKey({
id: keyId,
publicKey: keyPair.pubKey,
privateKey: keyPair.privKey,
recipient: contactIdentityKeyString,
});
return new Promise(resolve => {
prekey.save().always(() => {
@ -312,6 +331,7 @@
created_at: prekey.get('created_at'),
keyId: prekey.get('id'),
confirmed: prekey.get('confirmed'),
signature: prekey.get('signature'),
});
})
.fail(() => {
@ -362,18 +382,20 @@
created_at: prekey.get('created_at'),
keyId: prekey.get('id'),
confirmed: prekey.get('confirmed'),
signature: prekey.get('signature'),
}))
);
});
});
},
storeSignedPreKey(keyId, keyPair, confirmed) {
storeSignedPreKey(keyId, keyPair, confirmed, signature) {
const prekey = new SignedPreKey({
id: keyId,
publicKey: keyPair.pubKey,
privateKey: keyPair.privKey,
created_at: Date.now(),
confirmed: Boolean(confirmed),
signature,
});
return new Promise(resolve => {
prekey.save().always(() => {

@ -47,8 +47,47 @@
}
}
}
getPreKeyBundleForNumber = async function(pubKey) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const identityKey = myKeyPair.pubKey;
// Retrieve ids. The ids stored are always the latest generated + 1
const signedKeyId = textsecure.storage.get('signedKeyId', 1) - 1;
const [signedKey, preKey] = await Promise.all([
textsecure.storage.protocol.loadSignedPreKey(signedKeyId),
new Promise(async (resolve, reject) => {
// retrieve existing prekey if we already generated one for that recipient
const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContactIdentityKeyString(pubKey);
if (storedPreKey) {
resolve({ pubKey: storedPreKey.pubKey, keyId: storedPreKey.keyId });
} else {
// generate and store new prekey
const preKeyId = textsecure.storage.get('maxPreKeyId', 1);
textsecure.storage.put('maxPreKeyId', preKeyId + 1);
const preKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
await textsecure.storage.protocol.storePreKey(preKey.keyId, preKey.keyPair, pubKey);
resolve({ pubKey: preKey.keyPair.pubKey, keyId: preKeyId });
}
})
]);
const preKeyMessage = new textsecure.protobuf.PreKeyBundleMessage({
identityKey,
deviceId: 1, // TODO: fetch from somewhere
preKeyId: preKey.keyId,
signedKeyId,
preKey: preKey.pubKey,
signedKey: signedKey.pubKey,
signature: signedKey.signature,
});
return preKeyMessage;
}
window.libloki.FallBackSessionCipher = FallBackSessionCipher;
window.libloki.getPreKeyBundleForNumber = getPreKeyBundleForNumber;
window.libloki.FallBackDecryptionError = FallBackDecryptionError;
})();

@ -47,7 +47,7 @@
registerSingleDevice() {
const createAccount = this.createAccount.bind(this);
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
const generateKeys = this.generateKeys.bind(this, 100);
const generateKeys = this.generateKeys.bind(this, 0);
const confirmKeys = this.confirmKeys.bind(this);
const registrationDone = this.registrationDone.bind(this);
return this.queueTask(() =>
@ -101,7 +101,7 @@
registerSecondDevice(setProvisioningUrl, confirmNumber, progressCallback) {
const createAccount = this.createAccount.bind(this);
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
const generateKeys = this.generateKeys.bind(this, 100, progressCallback);
const generateKeys = this.generateKeys.bind(this, 0, progressCallback);
const confirmKeys = this.confirmKeys.bind(this);
const registrationDone = this.registrationDone.bind(this);
const registerKeys = this.server.registerKeys.bind(this.server);
@ -193,7 +193,7 @@
);
},
refreshPreKeys() {
const generateKeys = this.generateKeys.bind(this, 100);
const generateKeys = this.generateKeys.bind(this, 0);
const registerKeys = this.server.registerKeys.bind(this.server);
return this.queueTask(() =>
@ -239,14 +239,14 @@
window.log.info('Saving new signed prekey', res.keyId);
return Promise.all([
textsecure.storage.put('signedKeyId', signedKeyId + 1),
store.storeSignedPreKey(res.keyId, res.keyPair),
store.storeSignedPreKey(res.keyId, res.keyPair, undefined, res.signature),
])
.then(() => {
const confirmed = true;
window.log.info('Confirming new signed prekey', res.keyId);
return Promise.all([
textsecure.storage.remove('signedKeyRotationRejected'),
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed, res.signature),
]);
})
.then(() => cleanSignedPreKeys());
@ -407,7 +407,7 @@
const confirmed = true;
window.log.info('confirmKeys: confirming key', key.keyId);
return store.storeSignedPreKey(key.keyId, key.keyPair, confirmed);
return store.storeSignedPreKey(key.keyId, key.keyPair, confirmed, key.signature);
},
generateKeys(count, providedProgressCallback) {
const progressCallback =
@ -449,7 +449,7 @@
identityKey,
signedKeyId
).then(res => {
store.storeSignedPreKey(res.keyId, res.keyPair);
store.storeSignedPreKey(res.keyId, res.keyPair, undefined, res.signature);
result.signedPreKey = {
keyId: res.keyId,
publicKey: res.keyPair.pubKey,

@ -109,7 +109,7 @@ OutgoingMessage.prototype = {
});
}
return null;
return true;
})
);
// TODO: check if still applicable
@ -125,9 +125,7 @@ OutgoingMessage.prototype = {
]).then((keys) => {
const [preKey, signedPreKey] = keys;
if (preKey == undefined || signedPreKey == undefined) {
log.error("Will need to request keys!")
this.fallBackEncryption = true;
return Promise.resolve();
return false;
}
else {
const identityKey = StringView.hexToArrayBuffer(number);
@ -369,7 +367,27 @@ OutgoingMessage.prototype = {
sendToNumber(number) {
return this.getStaleDeviceIdsForNumber(number).then(updateDevices =>
this.getKeysForNumber(number, updateDevices)
.then(this.reloadDevicesAndSend(number, true))
.then(async (keysFound) => {
let attachPrekeys = false;
if (!keysFound)
{
log.info("Fallback encryption enabled");
this.fallBackEncryption = true;
attachPrekeys = true;
} else {
try {
const conversation = ConversationController.get(number);
attachPrekeys = !conversation.isKeyExchangeCompleted();
} catch(e) {
// do nothing
}
}
if (attachPrekeys) {
log.info('attaching prekeys to outgoing message');
this.message.preKeyBundleMessage = await libloki.getPreKeyBundleForNumber(number);
}
}).then(this.reloadDevicesAndSend(number, true))
.catch(error => {
if (error.message === 'Identity key changed') {
// eslint-disable-next-line no-param-reassign

@ -11,7 +11,7 @@ message Envelope {
KEY_EXCHANGE = 2;
PREKEY_BUNDLE = 3;
RECEIPT = 5;
FRIEND_REQUEST = 6;
FRIEND_REQUEST = 6; // contains prekeys + message and is using simple encryption
}
optional Type type = 1;
@ -29,6 +29,17 @@ message Content {
optional CallMessage callMessage = 3;
optional NullMessage nullMessage = 4;
optional ReceiptMessage receiptMessage = 5;
optional PreKeyBundleMessage preKeyBundleMessage = 6;
}
message PreKeyBundleMessage {
optional bytes identityKey = 1;
optional uint32 deviceId = 2;
optional uint32 preKeyId = 3;
optional uint32 signedKeyId = 4;
optional bytes preKey = 5;
optional bytes signedKey = 6;
optional bytes signature = 7;
}
message CallMessage {

Loading…
Cancel
Save