Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
converse.js
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
converse.js
Commits
2884549b
Commit
2884549b
authored
Aug 04, 2018
by
JC Brand
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Test decryption of incoming OMEMO message
updates #497
parent
713f4945
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
104 additions
and
86 deletions
+104
-86
dist/converse.js
dist/converse.js
+47
-35
spec/omemo.js
spec/omemo.js
+7
-12
src/converse-message-view.js
src/converse-message-view.js
+2
-1
src/converse-omemo.js
src/converse-omemo.js
+47
-37
tests/mock.js
tests/mock.js
+1
-1
No files found.
dist/converse.js
View file @
2884549b
...
...
@@ -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));
});
},
spec/omemo.js
View file @
2884549b
...
...
@@ -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
();
});
}));
...
...
src/converse-message-view.js
View file @
2884549b
...
...
@@ -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/
,
''
);
}
...
...
src/converse-omemo.js
View file @
2884549b
...
...
@@ -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
))
...
...
tests/mock.js
View file @
2884549b
...
...
@@ -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
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment