Move local identitykey and registrationid to indexeddb

This commit is contained in:
lilia 2015-05-11 15:40:21 -07:00
parent fe1d78b5fa
commit d230df5622
6 changed files with 410 additions and 391 deletions

View file

@ -82,23 +82,27 @@
}); });
var IdentityKey = Model.extend({ storeName: 'identityKeys' }); var IdentityKey = Model.extend({ storeName: 'identityKeys' });
var Group = Model.extend({ storeName: 'groups' }); var Group = Model.extend({ storeName: 'groups' });
var Item = Model.extend({ storeName: 'items' });
function AxolotlStore() {} function AxolotlStore() {}
AxolotlStore.prototype = { AxolotlStore.prototype = {
constructor: AxolotlStore, constructor: AxolotlStore,
getMyIdentityKey: function() { getMyIdentityKey: function() {
var res = textsecure.storage.get('identityKey'); var item = new Item({id: 'identityKey'});
if (res === undefined) return new Promise(function(resolve) {
return undefined; item.fetch().then(function() {
resolve(item.get('value'));
return { });
pubKey: convertToArrayBuffer(res.pubKey), });
privKey: convertToArrayBuffer(res.privKey)
};
}, },
getMyRegistrationId: function() { getMyRegistrationId: function() {
return textsecure.storage.get('registrationId'); var item = new Item({id: 'registrationId'});
return new Promise(function(resolve) {
item.fetch().then(function() {
resolve(item.get('value'));
});
});
}, },
/* Returns a prekeypair object or undefined */ /* Returns a prekeypair object or undefined */

View file

@ -42,6 +42,7 @@
var preKeys = transaction.db.createObjectStore("preKeys"); var preKeys = transaction.db.createObjectStore("preKeys");
var signedPreKeys = transaction.db.createObjectStore("signedPreKeys"); var signedPreKeys = transaction.db.createObjectStore("signedPreKeys");
var items = transaction.db.createObjectStore("items");
next(); next();
} }
} }

View file

@ -37122,77 +37122,77 @@ window.axolotl.protocol = function(storage_interface) {
} }
var initSession = function(isInitiator, ourEphemeralKey, ourSignedKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, theirSignedPubKey) { var initSession = function(isInitiator, ourEphemeralKey, ourSignedKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, theirSignedPubKey) {
var ourIdentityKey = storage_interface.getMyIdentityKey(); return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
if (isInitiator) {
if (isInitiator) { if (ourSignedKey !== undefined)
if (ourSignedKey !== undefined) throw new Error("Invalid call to initSession");
throw new Error("Invalid call to initSession"); ourSignedKey = ourEphemeralKey;
ourSignedKey = ourEphemeralKey; } else {
} else { if (theirSignedPubKey !== undefined)
if (theirSignedPubKey !== undefined) throw new Error("Invalid call to initSession");
throw new Error("Invalid call to initSession"); theirSignedPubKey = theirEphemeralPubKey;
theirSignedPubKey = theirEphemeralPubKey;
}
var sharedSecret;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
sharedSecret = new Uint8Array(32 * 4);
else
sharedSecret = new Uint8Array(32 * 5);
for (var i = 0; i < 32; i++)
sharedSecret[i] = 0xff;
return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourIdentityKey.privKey).then(function(ecRes1) {
function finishInit() {
return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourSignedKey.privKey).then(function(ecRes) {
sharedSecret.set(new Uint8Array(ecRes), 32 * 3);
return HKDF(sharedSecret.buffer, '', "WhisperText").then(function(masterKey) {
var session = {currentRatchet: { rootKey: masterKey[0], lastRemoteEphemeralKey: theirSignedPubKey, previousCounter: 0 },
indexInfo: { remoteIdentityKey: theirIdentityPubKey, closed: -1 },
oldRatchetList: []
};
if (!isInitiator)
session.indexInfo.baseKey = theirEphemeralPubKey;
else
session.indexInfo.baseKey = ourEphemeralKey.pubKey;
// If we're initiating we go ahead and set our first sending ephemeral key now,
// otherwise we figure it out when we first maybeStepRatchet with the remote's ephemeral key
if (isInitiator) {
return axolotlInternal.crypto.createKeyPair().then(function(ourSendingEphemeralKey) {
session.currentRatchet.ephemeralKeyPair = ourSendingEphemeralKey;
return calculateRatchet(session, theirSignedPubKey, true).then(function() {
return session;
});
});
} else {
session.currentRatchet.ephemeralKeyPair = ourSignedKey;
return session;
}
});
});
} }
var promise; var sharedSecret;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined) if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
promise = Promise.resolve(new ArrayBuffer(0)); sharedSecret = new Uint8Array(32 * 4);
else else
promise = axolotlInternal.crypto.ECDHE(theirEphemeralPubKey, ourEphemeralKey.privKey); sharedSecret = new Uint8Array(32 * 5);
return promise.then(function(ecRes4) {
sharedSecret.set(new Uint8Array(ecRes4), 32 * 4);
if (isInitiator) for (var i = 0; i < 32; i++)
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) { sharedSecret[i] = 0xff;
sharedSecret.set(new Uint8Array(ecRes1), 32);
sharedSecret.set(new Uint8Array(ecRes2), 32 * 2); return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourIdentityKey.privKey).then(function(ecRes1) {
}).then(finishInit); function finishInit() {
return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourSignedKey.privKey).then(function(ecRes) {
sharedSecret.set(new Uint8Array(ecRes), 32 * 3);
return HKDF(sharedSecret.buffer, '', "WhisperText").then(function(masterKey) {
var session = {currentRatchet: { rootKey: masterKey[0], lastRemoteEphemeralKey: theirSignedPubKey, previousCounter: 0 },
indexInfo: { remoteIdentityKey: theirIdentityPubKey, closed: -1 },
oldRatchetList: []
};
if (!isInitiator)
session.indexInfo.baseKey = theirEphemeralPubKey;
else
session.indexInfo.baseKey = ourEphemeralKey.pubKey;
// If we're initiating we go ahead and set our first sending ephemeral key now,
// otherwise we figure it out when we first maybeStepRatchet with the remote's ephemeral key
if (isInitiator) {
return axolotlInternal.crypto.createKeyPair().then(function(ourSendingEphemeralKey) {
session.currentRatchet.ephemeralKeyPair = ourSendingEphemeralKey;
return calculateRatchet(session, theirSignedPubKey, true).then(function() {
return session;
});
});
} else {
session.currentRatchet.ephemeralKeyPair = ourSignedKey;
return session;
}
});
});
}
var promise;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
promise = Promise.resolve(new ArrayBuffer(0));
else else
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) { promise = axolotlInternal.crypto.ECDHE(theirEphemeralPubKey, ourEphemeralKey.privKey);
sharedSecret.set(new Uint8Array(ecRes1), 32 * 2); return promise.then(function(ecRes4) {
sharedSecret.set(new Uint8Array(ecRes2), 32) sharedSecret.set(new Uint8Array(ecRes4), 32 * 4);
}).then(finishInit);
if (isInitiator)
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) {
sharedSecret.set(new Uint8Array(ecRes1), 32);
sharedSecret.set(new Uint8Array(ecRes2), 32 * 2);
}).then(finishInit);
else
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) {
sharedSecret.set(new Uint8Array(ecRes1), 32 * 2);
sharedSecret.set(new Uint8Array(ecRes2), 32)
}).then(finishInit);
});
}); });
}); });
} }
@ -37386,39 +37386,41 @@ window.axolotl.protocol = function(storage_interface) {
return fillMessageKeys(chain, message.counter).then(function() { return fillMessageKeys(chain, message.counter).then(function() {
return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[message.counter]), '', "WhisperMessageKeys").then(function(keys) { return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[message.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[message.counter]; return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
delete chain.messageKeys[message.counter];
var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto); var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto);
var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1); var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey))); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey)), 33); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1); macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1);
return verifyMAC(macInput.buffer, keys[1], mac).then(function() { return verifyMAC(macInput.buffer, keys[1], mac).then(function() {
return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16)) return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16))
.then(function(paddedPlaintext) { .then(function(paddedPlaintext) {
paddedPlaintext = new Uint8Array(paddedPlaintext); paddedPlaintext = new Uint8Array(paddedPlaintext);
var plaintext; var plaintext;
for (var i = paddedPlaintext.length - 1; i >= 0; i--) { for (var i = paddedPlaintext.length - 1; i >= 0; i--) {
if (paddedPlaintext[i] == 0x80) { if (paddedPlaintext[i] == 0x80) {
plaintext = new Uint8Array(i); plaintext = new Uint8Array(i);
plaintext.set(paddedPlaintext.subarray(0, i)); plaintext.set(paddedPlaintext.subarray(0, i));
plaintext = plaintext.buffer; plaintext = plaintext.buffer;
break; break;
} else if (paddedPlaintext[i] != 0x00) } else if (paddedPlaintext[i] != 0x00)
throw new Error('Invalid padding'); throw new Error('Invalid padding');
} }
delete session['pendingPreKey']; delete session['pendingPreKey'];
removeOldChains(session); removeOldChains(session);
return crypto_storage.saveSession(encodedNumber, session, registrationId).then(function() { return crypto_storage.saveSession(encodedNumber, session, registrationId).then(function() {
return [plaintext, function() { return [plaintext, function() {
closeSession(session, true); closeSession(session, true);
removeOldChains(session); removeOldChains(session);
return crypto_storage.saveSession(encodedNumber, session); return crypto_storage.saveSession(encodedNumber, session);
}]; }];
});
}); });
}); });
}); });
@ -37452,92 +37454,96 @@ window.axolotl.protocol = function(storage_interface) {
// return Promise(encoded [PreKey]WhisperMessage) // return Promise(encoded [PreKey]WhisperMessage)
self.encryptMessageFor = function(deviceObject, pushMessageContent) { self.encryptMessageFor = function(deviceObject, pushMessageContent) {
return crypto_storage.getOpenSession(deviceObject.encodedNumber).then(function(session) { return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
var hadSession = session !== undefined; return storage_interface.getMyRegistrationId().then(function(myRegistrationId) {
return crypto_storage.getOpenSession(deviceObject.encodedNumber).then(function(session) {
var hadSession = session !== undefined;
var doEncryptPushMessageContent = function() { var doEncryptPushMessageContent = function() {
var msg = new axolotlInternal.protobuf.WhisperMessage(); var msg = new axolotlInternal.protobuf.WhisperMessage();
var plaintext = axolotlInternal.utils.convertToArrayBuffer(pushMessageContent.encode()); var plaintext = axolotlInternal.utils.convertToArrayBuffer(pushMessageContent.encode());
var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1); var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1);
paddedPlaintext.set(new Uint8Array(plaintext)); paddedPlaintext.set(new Uint8Array(plaintext));
paddedPlaintext[plaintext.byteLength] = 0x80; paddedPlaintext[plaintext.byteLength] = 0x80;
msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey); msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)]; var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)];
return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() { return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) { return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[chain.chainKey.counter]; delete chain.messageKeys[chain.chainKey.counter];
msg.counter = chain.chainKey.counter; msg.counter = chain.chainKey.counter;
msg.previousCounter = session.currentRatchet.previousCounter; msg.previousCounter = session.currentRatchet.previousCounter;
return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) { return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) {
msg.ciphertext = ciphertext; msg.ciphertext = ciphertext;
var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode()); var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode());
var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1); var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey))); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(encodedMsg), 33*2 + 1); macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
return axolotlInternal.crypto.sign(keys[1], macInput.buffer).then(function(mac) { return axolotlInternal.crypto.sign(keys[1], macInput.buffer).then(function(mac) {
var result = new Uint8Array(encodedMsg.byteLength + 9); var result = new Uint8Array(encodedMsg.byteLength + 9);
result[0] = (3 << 4) | 3; result[0] = (3 << 4) | 3;
result.set(new Uint8Array(encodedMsg), 1); result.set(new Uint8Array(encodedMsg), 1);
result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1); result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1);
removeOldChains(session); removeOldChains(session);
return crypto_storage.saveSession(deviceObject.encodedNumber, session, !hadSession ? deviceObject.registrationId : undefined).then(function() { return crypto_storage.saveSession(deviceObject.encodedNumber, session, !hadSession ? deviceObject.registrationId : undefined).then(function() {
return result; return result;
});
});
}); });
}); });
}); });
}); }
});
}
var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage(); var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage();
preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey); preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey);
preKeyMsg.registrationId = storage_interface.getMyRegistrationId(); preKeyMsg.registrationId = myRegistrationId;
if (session === undefined) { if (session === undefined) {
var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey); var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey);
var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey); var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey);
return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() { return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() {
return axolotlInternal.crypto.createKeyPair().then(function(baseKey) { return axolotlInternal.crypto.createKeyPair().then(function(baseKey) {
preKeyMsg.preKeyId = deviceObject.preKeyId; preKeyMsg.preKeyId = deviceObject.preKeyId;
preKeyMsg.signedPreKeyId = deviceObject.signedKeyId; preKeyMsg.signedPreKeyId = deviceObject.signedKeyId;
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey); preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey);
return initSession(true, baseKey, undefined, return initSession(true, baseKey, undefined,
deviceObject.encodedNumber, deviceIdentityKey, deviceObject.encodedNumber, deviceIdentityKey,
axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey), axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey),
deviceSignedKey).then(function(new_session) { deviceSignedKey).then(function(new_session) {
session = new_session; session = new_session;
session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey }; session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey };
return doEncryptPushMessageContent().then(function(message) { return doEncryptPushMessageContent().then(function(message) {
preKeyMsg.message = message; preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode()); var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
return {type: 3, body: result}; return {type: 3, body: result};
});
});
}); });
}); });
});
});
} else
return doEncryptPushMessageContent().then(function(message) {
if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey);
preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
return {type: 3, body: result};
} else } else
return {type: 1, body: axolotlInternal.utils.convertToString(message)}; return doEncryptPushMessageContent().then(function(message) {
if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey);
preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
return {type: 3, body: result};
} else
return {type: 1, body: axolotlInternal.utils.convertToString(message)};
});
}); });
});
}); });
} }
@ -37987,7 +37993,7 @@ axolotlInternal.RecipientRecord = function() {
window.textsecure.storage = window.textsecure.storage || {}; window.textsecure.storage = window.textsecure.storage || {};
// Overrideable storage implementation // Overrideable storage implementation
window.textsecure.storage.impl = { window.textsecure.storage.impl = window.textsecure.storage.impl || {
/***************************** /*****************************
*** Base Storage Routines *** *** Base Storage Routines ***
*****************************/ *****************************/
@ -39408,40 +39414,41 @@ function generateKeys(count, progressCallback) {
textsecure.protocol_wrapper.startWorker(); textsecure.protocol_wrapper.startWorker();
var store = textsecure.storage.axolotl; var store = textsecure.storage.axolotl;
var identityKey = store.getMyIdentityKey(); return store.getMyIdentityKey().then(function(identityKey) {
var result = { preKeys: [], identityKey: identityKey.pubKey }; var result = { preKeys: [], identityKey: identityKey.pubKey };
var promises = []; var promises = [];
for (var keyId = startId; keyId < startId+count; ++keyId) {
promises.push(
axolotl.util.generatePreKey(keyId).then(function(res) {
store.putPreKey(res.keyId, res.keyPair);
result.preKeys.push({
keyId : res.keyId,
publicKey : res.keyPair.pubKey
});
if (progressCallback) { progressCallback(); }
})
);
}
for (var keyId = startId; keyId < startId+count; ++keyId) {
promises.push( promises.push(
axolotl.util.generatePreKey(keyId).then(function(res) { axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) {
store.putPreKey(res.keyId, res.keyPair); store.putSignedPreKey(res.keyId, res.keyPair);
result.preKeys.push({ result.signedPreKey = {
keyId : res.keyId, keyId : res.keyId,
publicKey : res.keyPair.pubKey publicKey : res.keyPair.pubKey,
}); signature : res.signature
if (progressCallback) { progressCallback(); } };
}) })
); );
}
promises.push( store.removeSignedPreKey(signedKeyId - 2);
axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) { textsecure.storage.put('maxPreKeyId', startId + count);
store.putSignedPreKey(res.keyId, res.keyPair); textsecure.storage.put('signedKeyId', signedKeyId + 1);
result.signedPreKey = { return Promise.all(promises).then(function() {
keyId : res.keyId, textsecure.protocol_wrapper.stopWorker();
publicKey : res.keyPair.pubKey, return result;
signature : res.signature });
};
})
);
store.removeSignedPreKey(signedKeyId - 2);
textsecure.storage.put('maxPreKeyId', startId + count);
textsecure.storage.put('signedKeyId', signedKeyId + 1);
return Promise.all(promises).then(function() {
textsecure.protocol_wrapper.stopWorker();
return result;
}); });
} }

View file

@ -123,39 +123,40 @@ function generateKeys(count, progressCallback) {
textsecure.protocol_wrapper.startWorker(); textsecure.protocol_wrapper.startWorker();
var store = textsecure.storage.axolotl; var store = textsecure.storage.axolotl;
var identityKey = store.getMyIdentityKey(); return store.getMyIdentityKey().then(function(identityKey) {
var result = { preKeys: [], identityKey: identityKey.pubKey }; var result = { preKeys: [], identityKey: identityKey.pubKey };
var promises = []; var promises = [];
for (var keyId = startId; keyId < startId+count; ++keyId) {
promises.push(
axolotl.util.generatePreKey(keyId).then(function(res) {
store.putPreKey(res.keyId, res.keyPair);
result.preKeys.push({
keyId : res.keyId,
publicKey : res.keyPair.pubKey
});
if (progressCallback) { progressCallback(); }
})
);
}
for (var keyId = startId; keyId < startId+count; ++keyId) {
promises.push( promises.push(
axolotl.util.generatePreKey(keyId).then(function(res) { axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) {
store.putPreKey(res.keyId, res.keyPair); store.putSignedPreKey(res.keyId, res.keyPair);
result.preKeys.push({ result.signedPreKey = {
keyId : res.keyId, keyId : res.keyId,
publicKey : res.keyPair.pubKey publicKey : res.keyPair.pubKey,
}); signature : res.signature
if (progressCallback) { progressCallback(); } };
}) })
); );
}
promises.push( store.removeSignedPreKey(signedKeyId - 2);
axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) { textsecure.storage.put('maxPreKeyId', startId + count);
store.putSignedPreKey(res.keyId, res.keyPair); textsecure.storage.put('signedKeyId', signedKeyId + 1);
result.signedPreKey = { return Promise.all(promises).then(function() {
keyId : res.keyId, textsecure.protocol_wrapper.stopWorker();
publicKey : res.keyPair.pubKey, return result;
signature : res.signature });
};
})
);
store.removeSignedPreKey(signedKeyId - 2);
textsecure.storage.put('maxPreKeyId', startId + count);
textsecure.storage.put('signedKeyId', signedKeyId + 1);
return Promise.all(promises).then(function() {
textsecure.protocol_wrapper.stopWorker();
return result;
}); });
} }

View file

@ -37045,77 +37045,77 @@ window.axolotl.protocol = function(storage_interface) {
} }
var initSession = function(isInitiator, ourEphemeralKey, ourSignedKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, theirSignedPubKey) { var initSession = function(isInitiator, ourEphemeralKey, ourSignedKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, theirSignedPubKey) {
var ourIdentityKey = storage_interface.getMyIdentityKey(); return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
if (isInitiator) {
if (isInitiator) { if (ourSignedKey !== undefined)
if (ourSignedKey !== undefined) throw new Error("Invalid call to initSession");
throw new Error("Invalid call to initSession"); ourSignedKey = ourEphemeralKey;
ourSignedKey = ourEphemeralKey; } else {
} else { if (theirSignedPubKey !== undefined)
if (theirSignedPubKey !== undefined) throw new Error("Invalid call to initSession");
throw new Error("Invalid call to initSession"); theirSignedPubKey = theirEphemeralPubKey;
theirSignedPubKey = theirEphemeralPubKey;
}
var sharedSecret;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
sharedSecret = new Uint8Array(32 * 4);
else
sharedSecret = new Uint8Array(32 * 5);
for (var i = 0; i < 32; i++)
sharedSecret[i] = 0xff;
return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourIdentityKey.privKey).then(function(ecRes1) {
function finishInit() {
return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourSignedKey.privKey).then(function(ecRes) {
sharedSecret.set(new Uint8Array(ecRes), 32 * 3);
return HKDF(sharedSecret.buffer, '', "WhisperText").then(function(masterKey) {
var session = {currentRatchet: { rootKey: masterKey[0], lastRemoteEphemeralKey: theirSignedPubKey, previousCounter: 0 },
indexInfo: { remoteIdentityKey: theirIdentityPubKey, closed: -1 },
oldRatchetList: []
};
if (!isInitiator)
session.indexInfo.baseKey = theirEphemeralPubKey;
else
session.indexInfo.baseKey = ourEphemeralKey.pubKey;
// If we're initiating we go ahead and set our first sending ephemeral key now,
// otherwise we figure it out when we first maybeStepRatchet with the remote's ephemeral key
if (isInitiator) {
return axolotlInternal.crypto.createKeyPair().then(function(ourSendingEphemeralKey) {
session.currentRatchet.ephemeralKeyPair = ourSendingEphemeralKey;
return calculateRatchet(session, theirSignedPubKey, true).then(function() {
return session;
});
});
} else {
session.currentRatchet.ephemeralKeyPair = ourSignedKey;
return session;
}
});
});
} }
var promise; var sharedSecret;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined) if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
promise = Promise.resolve(new ArrayBuffer(0)); sharedSecret = new Uint8Array(32 * 4);
else else
promise = axolotlInternal.crypto.ECDHE(theirEphemeralPubKey, ourEphemeralKey.privKey); sharedSecret = new Uint8Array(32 * 5);
return promise.then(function(ecRes4) {
sharedSecret.set(new Uint8Array(ecRes4), 32 * 4);
if (isInitiator) for (var i = 0; i < 32; i++)
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) { sharedSecret[i] = 0xff;
sharedSecret.set(new Uint8Array(ecRes1), 32);
sharedSecret.set(new Uint8Array(ecRes2), 32 * 2); return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourIdentityKey.privKey).then(function(ecRes1) {
}).then(finishInit); function finishInit() {
return axolotlInternal.crypto.ECDHE(theirSignedPubKey, ourSignedKey.privKey).then(function(ecRes) {
sharedSecret.set(new Uint8Array(ecRes), 32 * 3);
return HKDF(sharedSecret.buffer, '', "WhisperText").then(function(masterKey) {
var session = {currentRatchet: { rootKey: masterKey[0], lastRemoteEphemeralKey: theirSignedPubKey, previousCounter: 0 },
indexInfo: { remoteIdentityKey: theirIdentityPubKey, closed: -1 },
oldRatchetList: []
};
if (!isInitiator)
session.indexInfo.baseKey = theirEphemeralPubKey;
else
session.indexInfo.baseKey = ourEphemeralKey.pubKey;
// If we're initiating we go ahead and set our first sending ephemeral key now,
// otherwise we figure it out when we first maybeStepRatchet with the remote's ephemeral key
if (isInitiator) {
return axolotlInternal.crypto.createKeyPair().then(function(ourSendingEphemeralKey) {
session.currentRatchet.ephemeralKeyPair = ourSendingEphemeralKey;
return calculateRatchet(session, theirSignedPubKey, true).then(function() {
return session;
});
});
} else {
session.currentRatchet.ephemeralKeyPair = ourSignedKey;
return session;
}
});
});
}
var promise;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
promise = Promise.resolve(new ArrayBuffer(0));
else else
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) { promise = axolotlInternal.crypto.ECDHE(theirEphemeralPubKey, ourEphemeralKey.privKey);
sharedSecret.set(new Uint8Array(ecRes1), 32 * 2); return promise.then(function(ecRes4) {
sharedSecret.set(new Uint8Array(ecRes2), 32) sharedSecret.set(new Uint8Array(ecRes4), 32 * 4);
}).then(finishInit);
if (isInitiator)
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) {
sharedSecret.set(new Uint8Array(ecRes1), 32);
sharedSecret.set(new Uint8Array(ecRes2), 32 * 2);
}).then(finishInit);
else
return axolotlInternal.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) {
sharedSecret.set(new Uint8Array(ecRes1), 32 * 2);
sharedSecret.set(new Uint8Array(ecRes2), 32)
}).then(finishInit);
});
}); });
}); });
} }
@ -37309,39 +37309,41 @@ window.axolotl.protocol = function(storage_interface) {
return fillMessageKeys(chain, message.counter).then(function() { return fillMessageKeys(chain, message.counter).then(function() {
return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[message.counter]), '', "WhisperMessageKeys").then(function(keys) { return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[message.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[message.counter]; return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
delete chain.messageKeys[message.counter];
var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto); var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto);
var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1); var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey))); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey)), 33); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1); macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1);
return verifyMAC(macInput.buffer, keys[1], mac).then(function() { return verifyMAC(macInput.buffer, keys[1], mac).then(function() {
return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16)) return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16))
.then(function(paddedPlaintext) { .then(function(paddedPlaintext) {
paddedPlaintext = new Uint8Array(paddedPlaintext); paddedPlaintext = new Uint8Array(paddedPlaintext);
var plaintext; var plaintext;
for (var i = paddedPlaintext.length - 1; i >= 0; i--) { for (var i = paddedPlaintext.length - 1; i >= 0; i--) {
if (paddedPlaintext[i] == 0x80) { if (paddedPlaintext[i] == 0x80) {
plaintext = new Uint8Array(i); plaintext = new Uint8Array(i);
plaintext.set(paddedPlaintext.subarray(0, i)); plaintext.set(paddedPlaintext.subarray(0, i));
plaintext = plaintext.buffer; plaintext = plaintext.buffer;
break; break;
} else if (paddedPlaintext[i] != 0x00) } else if (paddedPlaintext[i] != 0x00)
throw new Error('Invalid padding'); throw new Error('Invalid padding');
} }
delete session['pendingPreKey']; delete session['pendingPreKey'];
removeOldChains(session); removeOldChains(session);
return crypto_storage.saveSession(encodedNumber, session, registrationId).then(function() { return crypto_storage.saveSession(encodedNumber, session, registrationId).then(function() {
return [plaintext, function() { return [plaintext, function() {
closeSession(session, true); closeSession(session, true);
removeOldChains(session); removeOldChains(session);
return crypto_storage.saveSession(encodedNumber, session); return crypto_storage.saveSession(encodedNumber, session);
}]; }];
});
}); });
}); });
}); });
@ -37375,92 +37377,96 @@ window.axolotl.protocol = function(storage_interface) {
// return Promise(encoded [PreKey]WhisperMessage) // return Promise(encoded [PreKey]WhisperMessage)
self.encryptMessageFor = function(deviceObject, pushMessageContent) { self.encryptMessageFor = function(deviceObject, pushMessageContent) {
return crypto_storage.getOpenSession(deviceObject.encodedNumber).then(function(session) { return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
var hadSession = session !== undefined; return storage_interface.getMyRegistrationId().then(function(myRegistrationId) {
return crypto_storage.getOpenSession(deviceObject.encodedNumber).then(function(session) {
var hadSession = session !== undefined;
var doEncryptPushMessageContent = function() { var doEncryptPushMessageContent = function() {
var msg = new axolotlInternal.protobuf.WhisperMessage(); var msg = new axolotlInternal.protobuf.WhisperMessage();
var plaintext = axolotlInternal.utils.convertToArrayBuffer(pushMessageContent.encode()); var plaintext = axolotlInternal.utils.convertToArrayBuffer(pushMessageContent.encode());
var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1); var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1);
paddedPlaintext.set(new Uint8Array(plaintext)); paddedPlaintext.set(new Uint8Array(plaintext));
paddedPlaintext[plaintext.byteLength] = 0x80; paddedPlaintext[plaintext.byteLength] = 0x80;
msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey); msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)]; var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)];
return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() { return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) { return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[chain.chainKey.counter]; delete chain.messageKeys[chain.chainKey.counter];
msg.counter = chain.chainKey.counter; msg.counter = chain.chainKey.counter;
msg.previousCounter = session.currentRatchet.previousCounter; msg.previousCounter = session.currentRatchet.previousCounter;
return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) { return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) {
msg.ciphertext = ciphertext; msg.ciphertext = ciphertext;
var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode()); var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode());
var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1); var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey))); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33); macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(encodedMsg), 33*2 + 1); macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
return axolotlInternal.crypto.sign(keys[1], macInput.buffer).then(function(mac) { return axolotlInternal.crypto.sign(keys[1], macInput.buffer).then(function(mac) {
var result = new Uint8Array(encodedMsg.byteLength + 9); var result = new Uint8Array(encodedMsg.byteLength + 9);
result[0] = (3 << 4) | 3; result[0] = (3 << 4) | 3;
result.set(new Uint8Array(encodedMsg), 1); result.set(new Uint8Array(encodedMsg), 1);
result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1); result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1);
removeOldChains(session); removeOldChains(session);
return crypto_storage.saveSession(deviceObject.encodedNumber, session, !hadSession ? deviceObject.registrationId : undefined).then(function() { return crypto_storage.saveSession(deviceObject.encodedNumber, session, !hadSession ? deviceObject.registrationId : undefined).then(function() {
return result; return result;
});
});
}); });
}); });
}); });
}); }
});
}
var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage(); var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage();
preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey); preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey);
preKeyMsg.registrationId = storage_interface.getMyRegistrationId(); preKeyMsg.registrationId = myRegistrationId;
if (session === undefined) { if (session === undefined) {
var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey); var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey);
var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey); var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey);
return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() { return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() {
return axolotlInternal.crypto.createKeyPair().then(function(baseKey) { return axolotlInternal.crypto.createKeyPair().then(function(baseKey) {
preKeyMsg.preKeyId = deviceObject.preKeyId; preKeyMsg.preKeyId = deviceObject.preKeyId;
preKeyMsg.signedPreKeyId = deviceObject.signedKeyId; preKeyMsg.signedPreKeyId = deviceObject.signedKeyId;
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey); preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey);
return initSession(true, baseKey, undefined, return initSession(true, baseKey, undefined,
deviceObject.encodedNumber, deviceIdentityKey, deviceObject.encodedNumber, deviceIdentityKey,
axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey), axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey),
deviceSignedKey).then(function(new_session) { deviceSignedKey).then(function(new_session) {
session = new_session; session = new_session;
session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey }; session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey };
return doEncryptPushMessageContent().then(function(message) { return doEncryptPushMessageContent().then(function(message) {
preKeyMsg.message = message; preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode()); var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
return {type: 3, body: result}; return {type: 3, body: result};
});
});
}); });
}); });
});
});
} else
return doEncryptPushMessageContent().then(function(message) {
if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey);
preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
return {type: 3, body: result};
} else } else
return {type: 1, body: axolotlInternal.utils.convertToString(message)}; return doEncryptPushMessageContent().then(function(message) {
if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey);
preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
return {type: 3, body: result};
} else
return {type: 1, body: axolotlInternal.utils.convertToString(message)};
});
}); });
});
}); });
} }

View file

@ -25,7 +25,7 @@
window.textsecure.storage = window.textsecure.storage || {}; window.textsecure.storage = window.textsecure.storage || {};
// Overrideable storage implementation // Overrideable storage implementation
window.textsecure.storage.impl = { window.textsecure.storage.impl = window.textsecure.storage.impl || {
/***************************** /*****************************
*** Base Storage Routines *** *** Base Storage Routines ***
*****************************/ *****************************/