Commit 2884549b authored by JC Brand's avatar JC Brand

Test decryption of incoming OMEMO message

updates #497
parent 713f4945
......@@ -68608,7 +68608,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
msg.querySelector('.chat-msg__media').innerHTML = _.flow(_.partial(u.renderFileURL, _converse), _.partial(u.renderMovieURL, _converse), _.partial(u.renderAudioURL, _converse), _.partial(u.renderImageURL, _converse))(url);
}
let text = this.model.get('message');
const encrypted = this.model.get('encrypted');
let text = encrypted ? this.model.get('plaintext') : this.model.get('message');
if (is_me_message) {
text = text.replace(/^\/me/, '');
......@@ -73420,21 +73421,47 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}).then(out => new TextDecoder().decode(out)).catch(e => _converse.log(e.toString(), Strophe.LogLevel.ERROR));
},
decryptFromKeyAndTag(key_and_tag, obj) {
const aes_data = this.getKeyAndTag(u.arrayBufferToString(key_and_tag));
return this.decryptMessage(_.extend(obj, {
'key': aes_data.key,
'tag': aes_data.tag
}));
},
handlePreKeyMessage(attrs) {
// TODO
const _converse = this.__super__._converse; // If this is the case, a new session is built from this received element. The client
// SHOULD then republish their bundle information, replacing the used PreKey, such
// that it won't be used again by a different client. If the client already has a session
// with the sender's device, it MUST replace this session with the newly built session.
// The client MUST delete the private key belonging to the PreKey after use.
const address = new libsignal.SignalProtocolAddress(attrs.from, attrs.encrypted.device_id),
session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address),
libsignal_payload = JSON.parse(atob(attrs.encrypted.key));
return session_cipher.decryptPreKeyWhisperMessage(libsignal_payload.body, 'binary').then(key_and_tag => this.decryptFromKeyAndTag(key_and_tag, attrs.encrypted)).then(f => {// TODO handle new key...
// _converse.omemo.publishBundle()
});
},
decrypt(attrs) {
if (attrs.prekey === 'true') {
return this.handlePreKeyMessage(attrs);
}
const _converse = this.__super__._converse,
address = new libsignal.SignalProtocolAddress(attrs.from, attrs.encrypted.device_id),
session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address),
libsignal_payload = JSON.parse(atob(attrs.encrypted.key));
return new Promise((resolve, reject) => {
session_cipher.decryptWhisperMessage(libsignal_payload.body, 'binary').then(key_and_tag => this.decryptMessage(key_and_tag, attrs.encrypted)).then(f => {
// TODO handle decrypted messagej
//
resolve(f);
}).catch(reject);
session_cipher.decryptWhisperMessage(libsignal_payload.body, 'binary').then(key_and_tag => this.decryptFromKeyAndTag(key_and_tag, attrs.encrypted)).then(resolve).catch(reject);
});
},
getEncryptionAttributesfromStanza(encrypted) {
getEncryptionAttributesfromStanza(stanza, original_stanza) {
const _converse = this.__super__._converse;
const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop();
return new Promise((resolve, reject) => {
this.__super__.getMessageAttributesFromStanza.apply(this, arguments).then(attrs => {
const _converse = this.__super__._converse,
......@@ -73446,33 +73473,14 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'device_id': header.getAttribute('sid'),
'iv': header.querySelector('iv').textContent,
'key': key.textContent,
'payload': _.get(encrypted.querySelector('payload'), 'textContent', null)
'payload': _.get(encrypted.querySelector('payload'), 'textContent', null),
'prekey': key.getAttribute('prekey')
};
if (key.getAttribute('prekey') === 'true') {
// If this is the case, a new session is built from this received element. The client
// SHOULD then republish their bundle information, replacing the used PreKey, such
// that it won't be used again by a different client. If the client already has a session
// with the sender's device, it MUST replace this session with the newly built session.
// The client MUST delete the private key belonging to the PreKey after use.
const address = new libsignal.SignalProtocolAddress(attrs.from, attrs.encrypted.device_id),
session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address),
libsignal_payload = JSON.parse(atob(attrs.encrypted.key));
session_cipher.decryptPreKeyWhisperMessage(libsignal_payload.body, 'binary').then(key_and_tag => this.decryptMessage(attrs.encrypted)).then(f => {
// TODO handle new key...
// _converse.omemo.publishBundle()
resolve(f);
}).catch(reject);
}
if (attrs.encrypted.payload) {
this.decrypt(attrs).then(text => {
attrs.plaintext = text;
resolve(attrs);
}).catch(reject);
}
this.decrypt(attrs).then(plaintext => resolve(_.extend(attrs, {
'plaintext': plaintext
}))).catch(reject);
}
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
});
},
......@@ -73482,7 +73490,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
if (!encrypted) {
return this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
} else {
return this.getEncryptionAttributesfromStanza(encrypted);
return this.getEncryptionAttributesfromStanza(stanza, original_stanza);
}
},
......@@ -73507,9 +73515,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
return window.crypto.subtle.encrypt(algo, key, new TextEncoder().encode(plaintext));
}).then(ciphertext => {
return window.crypto.subtle.exportKey("jwk", key).then(key_obj => {
const tag = u.arrayBufferToBase64(ciphertext.slice(ciphertext.byteLength - (TAG_LENGTH + 7 >> 3)));
console.log('XXXX: Base64 TAG is ' + tag);
console.log('YYY: KEY is ' + key_obj.k);
return Promise.resolve({
'key': key_obj.k,
'tag': u.arrayBufferToBase64(ciphertext.slice(ciphertext.byteLength - (TAG_LENGTH + 7 >> 3))),
'tag': tag,
'key_and_tag': btoa(key_obj.k + tag),
'payload': u.arrayBufferToBase64(ciphertext),
'iv': u.arrayBufferToBase64(iv)
});
......@@ -73585,7 +73597,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
// devices associated with the contact, the result of this
// concatenation is encrypted using the corresponding
// long-standing SignalProtocol session.
const promises = devices.filter(device => device.get('trusted') != UNTRUSTED).map(device => this.encryptKey(obj.key + obj.tag, device));
const promises = devices.filter(device => device.get('trusted') != UNTRUSTED).map(device => this.encryptKey(obj.key_and_tag, device));
return Promise.all(promises).then(dicts => this.addKeysToMessageStanza(stanza, dicts, obj.iv)).then(stanza => stanza.c('payload').t(obj.payload)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
});
},
......@@ -182,13 +182,13 @@
// Test reception of an encrypted message
return view.model.encryptMessage('This is an encrypted message from the contact')
}).then((payload) => {
}).then((obj) => {
// XXX: Normally the key will be encrypted via libsignal.
// However, we're mocking libsignal in the tests, so we include
// it as plaintext in the message.
const key = btoa(JSON.stringify({
'type': 1,
'body': payload.key_str+payload.tag,
'body': obj.key_and_tag,
'registrationId': '1337'
}));
const stanza = $msg({
......@@ -200,21 +200,16 @@
.c('encrypted', {'xmlns': Strophe.NS.OMEMO})
.c('header', {'sid': '555'})
.c('key', {'rid': _converse.omemo_store.get('device_id')}).t(key).up()
.c('iv').t(payload.iv)
.c('iv').t(obj.iv)
.up().up()
.c('payload').t(payload.ciphertext);
.c('payload').t(obj.payload);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => {
expect(view.model.messages.length).toBe(2);
const last_msg = view.model.messages.at(1),
encrypted = last_msg.get('encrypted');
expect(encrypted instanceof Object).toBe(true);
expect(encrypted.device_id).toBe('555');
expect(encrypted.iv).toBe(btoa('1234'));
expect(encrypted.key).toBe(btoa('c1ph3R73X7'));
expect(encrypted.payload).toBe(btoa('M04R-c1ph3R73X7'));
const last_msg = view.model.messages.at(1);
expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
.toBe('This is an encrypted message from the contact');
done();
});
}));
......
......@@ -158,7 +158,8 @@
_.partial(u.renderImageURL, _converse))(url);
}
let text = this.model.get('message');
const encrypted = this.model.get('encrypted');
let text = encrypted ? this.model.get('plaintext') : this.model.get('message');
if (is_me_message) {
text = text.replace(/^\/me/, '');
}
......
......@@ -161,7 +161,35 @@
.catch(e => _converse.log(e.toString(), Strophe.LogLevel.ERROR));
},
decryptFromKeyAndTag (key_and_tag, obj) {
const aes_data = this.getKeyAndTag(u.arrayBufferToString(key_and_tag));
return this.decryptMessage(_.extend(obj, {'key': aes_data.key, 'tag': aes_data.tag}));
},
handlePreKeyMessage (attrs) {
// TODO
const { _converse } = this.__super__;
// If this is the case, a new session is built from this received element. The client
// SHOULD then republish their bundle information, replacing the used PreKey, such
// that it won't be used again by a different client. If the client already has a session
// with the sender's device, it MUST replace this session with the newly built session.
// The client MUST delete the private key belonging to the PreKey after use.
const address = new libsignal.SignalProtocolAddress(attrs.from, attrs.encrypted.device_id),
session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address),
libsignal_payload = JSON.parse(atob(attrs.encrypted.key));
return session_cipher.decryptPreKeyWhisperMessage(libsignal_payload.body, 'binary')
.then(key_and_tag => this.decryptFromKeyAndTag(key_and_tag, attrs.encrypted))
.then((f) => {
// TODO handle new key...
// _converse.omemo.publishBundle()
});
},
decrypt (attrs) {
if (attrs.prekey === 'true') {
return this.handlePreKeyMessage(attrs)
}
const { _converse } = this.__super__,
address = new libsignal.SignalProtocolAddress(attrs.from, attrs.encrypted.device_id),
session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address),
......@@ -169,16 +197,16 @@
return new Promise((resolve, reject) => {
session_cipher.decryptWhisperMessage(libsignal_payload.body, 'binary')
.then((key_and_tag) => this.decryptMessage(key_and_tag, attrs.encrypted))
.then((f) => {
// TODO handle decrypted messagej
//
resolve(f);
}).catch(reject);
.then((key_and_tag) => this.decryptFromKeyAndTag(key_and_tag, attrs.encrypted))
.then(resolve)
.catch(reject);
});
},
getEncryptionAttributesfromStanza (encrypted) {
getEncryptionAttributesfromStanza (stanza, original_stanza) {
const { _converse } = this.__super__;
const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop();
return new Promise((resolve, reject) => {
this.__super__.getMessageAttributesFromStanza.apply(this, arguments)
.then((attrs) => {
......@@ -191,36 +219,14 @@
'device_id': header.getAttribute('sid'),
'iv': header.querySelector('iv').textContent,
'key': key.textContent,
'payload': _.get(encrypted.querySelector('payload'), 'textContent', null)
'payload': _.get(encrypted.querySelector('payload'), 'textContent', null),
'prekey': key.getAttribute('prekey')
}
if (key.getAttribute('prekey') === 'true') {
// If this is the case, a new session is built from this received element. The client
// SHOULD then republish their bundle information, replacing the used PreKey, such
// that it won't be used again by a different client. If the client already has a session
// with the sender's device, it MUST replace this session with the newly built session.
// The client MUST delete the private key belonging to the PreKey after use.
const address = new libsignal.SignalProtocolAddress(attrs.from, attrs.encrypted.device_id),
session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address),
libsignal_payload = JSON.parse(atob(attrs.encrypted.key));
session_cipher.decryptPreKeyWhisperMessage(libsignal_payload.body, 'binary')
.then(key_and_tag => this.decryptMessage(attrs.encrypted))
.then((f) => {
// TODO handle new key...
// _converse.omemo.publishBundle()
resolve(f);
}).catch(reject);
}
if (attrs.encrypted.payload) {
this.decrypt(attrs)
.then((text) => {
attrs.plaintext = text
resolve(attrs);
})
this.decrypt(attrs)
.then((plaintext) => resolve(_.extend(attrs, {'plaintext': plaintext})))
.catch(reject);
}
}
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
});
},
......@@ -229,7 +235,7 @@
if (!encrypted) {
return this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
} else {
return this.getEncryptionAttributesfromStanza(encrypted);
return this.getEncryptionAttributesfromStanza(stanza, original_stanza);
}
},
......@@ -257,9 +263,13 @@
}).then((ciphertext) => {
return window.crypto.subtle.exportKey("jwk", key)
.then((key_obj) => {
const tag = u.arrayBufferToBase64(ciphertext.slice(ciphertext.byteLength - ((TAG_LENGTH + 7) >> 3)));
console.log('XXXX: Base64 TAG is '+tag);
console.log('YYY: KEY is '+key_obj.k);
return Promise.resolve({
'key': key_obj.k,
'tag': u.arrayBufferToBase64(ciphertext.slice(ciphertext.byteLength - ((TAG_LENGTH + 7) >> 3))),
'tag': tag,
'key_and_tag': btoa(key_obj.k + tag),
'payload': u.arrayBufferToBase64(ciphertext),
'iv': u.arrayBufferToBase64(iv)
});
......@@ -328,7 +338,7 @@
// long-standing SignalProtocol session.
const promises = devices
.filter(device => device.get('trusted') != UNTRUSTED)
.map(device => this.encryptKey(obj.key+obj.tag, device));
.map(device => this.encryptKey(obj.key_and_tag, device));
return Promise.all(promises)
.then((dicts) => this.addKeysToMessageStanza(stanza, dicts, obj.iv))
......
......@@ -22,7 +22,7 @@
'registrationId': '1337'
});
this.decryptWhisperMessage = (key_and_tag) => {
return Promise.resolve(u.stringToArrayBuffer(key_and_tag));
return Promise.resolve(u.stringToArrayBuffer(atob(key_and_tag)));
}
},
'SessionBuilder': function (storage, remote_address) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment