Merge remote-tracking branch 'origin/master' into olmkit

This commit is contained in:
manuroe 2016-11-07 17:21:39 +01:00
commit 5d1b66c350
27 changed files with 438 additions and 72 deletions

1
.gitignore vendored
View file

@ -2,6 +2,7 @@
/CHANGELOG.html /CHANGELOG.html
/docs/megolm.html /docs/megolm.html
/docs/olm.html /docs/olm.html
/docs/signing.html
/olm-*.tgz /olm-*.tgz
/README.html /README.html
/tracing/README.html /tracing/README.html

View file

@ -1,8 +1,26 @@
Changes in `2.0.0 <http://matrix.org/git/olm/commit/?h=2.0.0>`_
===============================================================
This release includes the following changes since 1.3.0:
* Fix a buffer bounds check when decoding group messages.
* Update ``olm_group_decrypt`` to return the ratchet index for decrypted
messages.
* Fix ``olm_pickle_account``, ``olm_pickle_session``,
``olm_pickle_inbound_group_session`` and
``olm_pickle_outbound_group_session`` to correctly return the length of the
pickled object.
* Add a `specification <./docs/megolm.rst>`_ of the Megolm ratchet, and add
some information on mitigating unknown key-share attacks to the `Olm
specification <./docs/olm.rst>`_.
* Add an ``install-headers`` target to the Makefile (and run it when installing
the library). (Credit to Emmanuel Gil Peyrot).
Changes in `1.3.0 <http://matrix.org/git/olm/commit/?h=1.3.0>`_ Changes in `1.3.0 <http://matrix.org/git/olm/commit/?h=1.3.0>`_
=============================================================== ===============================================================
The release updates the group session identifier to avoid collisions. This release updates the group session identifier to avoid collisions.
The group sessions are now identified by their ed25519 public key. Group sessions are now identified by their ed25519 public key.
These changes alter the pickle format of outbound group sessions, attempting These changes alter the pickle format of outbound group sessions, attempting
to unpickle an outbound group session created with a previous version of olm to unpickle an outbound group session created with a previous version of olm

View file

@ -1,7 +1,7 @@
#!/usr/bin/make -f #!/usr/bin/make -f
MAJOR := 1 MAJOR := 2
MINOR := 3 MINOR := 0
PATCH := 0 PATCH := 0
VERSION := $(MAJOR).$(MINOR).$(PATCH) VERSION := $(MAJOR).$(MINOR).$(PATCH)
PREFIX ?= /usr/local PREFIX ?= /usr/local
@ -46,6 +46,7 @@ JS_POST := javascript/olm_outbound_group_session.js \
DOCS := tracing/README.html \ DOCS := tracing/README.html \
docs/megolm.html \ docs/megolm.html \
docs/olm.html \ docs/olm.html \
docs/signing.html \
README.html \ README.html \
CHANGELOG.html CHANGELOG.html

View file

@ -5,8 +5,12 @@ An implementation of the Double Ratchet cryptographic ratchet described by
https://github.com/trevp/double_ratchet/wiki, written in C and C++11 and https://github.com/trevp/double_ratchet/wiki, written in C and C++11 and
exposed as a C API. exposed as a C API.
The specification of the Olm ratchet can be found in docs/olm.rst or The specification of the Olm ratchet can be found in ``docs/olm.rst`` or
https://matrix.org/docs/spec/olm.html https://matrix.org/docs/spec/olm.html.
This library also includes an implementation of the Megolm cryptographic
ratchet, as specified in ``docs/megolm.rst`` or
https://matrix.org/docs/spec/megolm.html.
Building Building
-------- --------

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -27,7 +27,7 @@ The Megolm ratchet is intended for encrypted messaging applications where there
may be a large number of recipients of each message, thus precluding the use of may be a large number of recipients of each message, thus precluding the use of
peer-to-peer encryption systems such as `Olm`_. peer-to-peer encryption systems such as `Olm`_.
It also allows a receipient to decrypt received messages multiple times. For It also allows a recipient to decrypt received messages multiple times. For
instance, in client/server applications, a copy of the ciphertext can be stored instance, in client/server applications, a copy of the ciphertext can be stored
on the (untrusted) server, while the client need only store the session keys. on the (untrusted) server, while the client need only store the session keys.
@ -143,7 +143,7 @@ copy of the counter, ratchet, and public key.
Message encryption Message encryption
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
This version of Megolm uses AES-256_ in CBC_ mode with `PCKS#7`_ padding and This version of Megolm uses AES-256_ in CBC_ mode with `PKCS#7`_ padding and
HMAC-SHA-256_ (truncated to 64 bits). The 256 bit AES key, 256 bit HMAC key, HMAC-SHA-256_ (truncated to 64 bits). The 256 bit AES key, 256 bit HMAC key,
and 128 bit AES IV are derived from the megolm ratchet :math:`R_i`: and 128 bit AES IV are derived from the megolm ratchet :math:`R_i`:
@ -199,9 +199,9 @@ session.
In order to maintain the ability to decrypt conversation history, inbound In order to maintain the ability to decrypt conversation history, inbound
sessions should store a copy of their earliest known ratchet value (unless they sessions should store a copy of their earliest known ratchet value (unless they
explicitly want to drop the ability to decrypt that history). They may also explicitly want to drop the ability to decrypt that history - see `Partial
choose to cache calculated ratchet values, but the decision of which ratchet Forward Secrecy`_\ ). They may also choose to cache calculated ratchet values,
states to cache is left to the application. but the decision of which ratchet states to cache is left to the application.
Data exchange formats Data exchange formats
--------------------- ---------------------
@ -269,7 +269,79 @@ protocol). The MAC protects all of the bytes preceding the MAC.
The length of the signature is determined by the signing algorithm being used The length of the signature is determined by the signing algorithm being used
(64 bytes in this version of the protocol). The signature covers all of the (64 bytes in this version of the protocol). The signature covers all of the
bytes preceding the signaure. bytes preceding the signature.
Limitations
-----------
Message Replays
---------------
A message can be decrypted successfully multiple times. This means that an
attacker can re-send a copy of an old message, and the recipient will treat it
as a new message.
To mitigate this it is recommended that applications track the ratchet indices
they have received and that they reject messages with a ratchet index that
they have already decrypted.
Lack of Transcript Consistency
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In a group conversation, there is no guarantee that all recipients have
received the same messages. For example, if Alice is in a conversation with Bob
and Charlie, she could send different messages to Bob and Charlie, or could
send some messages to Bob but not Charlie, or vice versa.
Solving this is, in general, a hard problem, particularly in a protocol which
does not guarantee in-order message delivery. For now it remains the subject of
future research.
Lack of Backward Secrecy
~~~~~~~~~~~~~~~~~~~~~~~~
Once the key to a Megolm session is compromised, the attacker can decrypt any
future messages sent via that session.
In order to mitigate this, the application should ensure that Megolm sessions
are not used indefinitely. Instead it should periodically start a new session,
with new keys shared over a secure channel.
.. TODO: Can we recommend sensible lifetimes for Megolm sessions? Probably
depends how paranoid we're feeling, but some guidelines might be useful.
Partial Forward Secrecy
~~~~~~~~~~~~~~~~~~~~~~~
Each recipient maintains a record of the ratchet value which allows them to
decrypt any messages sent in the session after the corresponding point in the
conversation. If this value is compromised, an attacker can similarly decrypt
those past messages.
To mitigate this issue, the application should offer the user the option to
discard historical conversations, by winding forward any stored ratchet values,
or discarding sessions altogether.
Dependency on secure channel for key exchange
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The design of the Megolm ratchet relies on the availability of a secure
peer-to-peer channel for the exchange of session keys. Any vulnerabilities in
the underlying channel are likely to be amplified when applied to Megolm
session setup.
For example, if the peer-to-peer channel is vulnerable to an unknown key-share
attack, the entire Megolm session become similarly vulnerable. For example:
Alice starts a group chat with Eve, and shares the session keys with Eve. Eve
uses the unknown key-share attack to forward the session keys to Bob, who
believes Alice is starting the session with him. Eve then forwards messages
from the Megolm session to Bob, who again believes they are coming from
Alice. Provided the peer-to-peer channel is not vulnerable to this attack, Bob
will realise that the key-sharing message was forwarded by Eve, and can treat
the Megolm session as a forgery.
A second example: if the peer-to-peer channel is vulnerable to a replay
attack, this can be extended to entire Megolm sessions.
License License
------- -------
@ -285,6 +357,6 @@ Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_.
.. _`SHA-256`: https://tools.ietf.org/html/rfc6234 .. _`SHA-256`: https://tools.ietf.org/html/rfc6234
.. _`AES-256`: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf .. _`AES-256`: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
.. _`CBC`: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf .. _`CBC`: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
.. _`PCKS#7`: https://tools.ietf.org/html/rfc2315 .. _`PKCS#7`: https://tools.ietf.org/html/rfc2315
.. _`Olm`: ./olm.html .. _`Olm`: ./olm.html
.. _`Protocol Buffers encoding`: https://developers.google.com/protocol-buffers/docs/encoding .. _`Protocol Buffers encoding`: https://developers.google.com/protocol-buffers/docs/encoding

View file

@ -30,7 +30,7 @@ Initial setup
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
The setup takes four Curve25519_ inputs: Identity keys for Alice and Bob, The setup takes four Curve25519_ inputs: Identity keys for Alice and Bob,
:math:`I_A` and :math:`I_B`, and ephemeral keys for Alice and Bob, :math:`I_A` and :math:`I_B`, and one-time keys for Alice and Bob,
:math:`E_A` and :math:`E_B`. A shared secret, :math:`S`, is generated using :math:`E_A` and :math:`E_B`. A shared secret, :math:`S`, is generated using
`Triple Diffie-Hellman`_. The initial 256 bit root key, :math:`R_0`, and 256 `Triple Diffie-Hellman`_. The initial 256 bit root key, :math:`R_0`, and 256
bit chain key, :math:`C_{0,0}`, are derived from the shared secret using an bit chain key, :math:`C_{0,0}`, are derived from the shared secret using an
@ -279,7 +279,7 @@ Olm Authenticated Encryption
Version 1 Version 1
~~~~~~~~~ ~~~~~~~~~
Version 1 of Olm uses AES-256_ in CBC_ mode with `PCKS#7`_ padding for Version 1 of Olm uses AES-256_ in CBC_ mode with `PKCS#7`_ padding for
encryption and HMAC-SHA-256_ (truncated to 64 bits) for authentication. The encryption and HMAC-SHA-256_ (truncated to 64 bits) for authentication. The
256 bit AES key, 256 bit HMAC key, and 128 bit AES IV are derived from the 256 bit AES key, 256 bit HMAC key, and 128 bit AES IV are derived from the
message key using HKDF-SHA-256_ using the default salt and an info of message key using HKDF-SHA-256_ using the default salt and an info of
@ -298,6 +298,37 @@ and the IV :math:`AES\_IV_{i,j}` to give the cipher-text, :math:`X_{i,j}`.
Then the entire message (including the Version Byte and all Payload Bytes) are Then the entire message (including the Version Byte and all Payload Bytes) are
passed through HMAC-SHA-256. The first 8 bytes of the MAC are appended to the message. passed through HMAC-SHA-256. The first 8 bytes of the MAC are appended to the message.
Message authentication concerns
-------------------------------
To avoid unknown key-share attacks, the application must include identifying
data for the sending and receiving user in the plain-text of (at least) the
pre-key messages. Such data could be a user ID, a telephone number;
alternatively it could be the public part of a keypair which the relevant user
has proven ownership of.
.. admonition:: Example attacks
1. Alice publishes her public Curve25519 identity key, :math:`I_A`. Eve
publishes the same identity key, claiming it as her own. Bob downloads
Eve's keys, and associates :math:`I_A` with Eve. Alice sends a message to
Bob; Eve intercepts it before forwarding it to Bob. Bob believes the
message came from Eve rather than Alice.
This is prevented if Alice includes her user ID in the plain-text of the
pre-key message, so that Bob can see that the message was sent by Alice
originally.
2. Bob publishes his public Curve25519 identity key, :math:`I_B`. Eve
publishes the same identity key, claiming it as her own. Alice downloads
Eve's keys, and associates :math:`I_B` with Eve. Alice sends a message to
Eve; Eve cannot decrypt it, but forwards it to Bob. Bob believes the
Alice sent the message to him, wheras Alice intended it to go to Eve.
This is prevented by Alice including the user ID of the intended recpient
(Eve) in the plain-text of the pre-key message. Bob can now tell that the
message was meant for Eve rather than him.
IPR IPR
--- ---
@ -323,4 +354,4 @@ an entirely new implementation written by the Matrix.org team.
.. _`SHA-256`: https://tools.ietf.org/html/rfc6234 .. _`SHA-256`: https://tools.ietf.org/html/rfc6234
.. _`AES-256`: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf .. _`AES-256`: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
.. _`CBC`: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf .. _`CBC`: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
.. _`PCKS#7`: https://tools.ietf.org/html/rfc2315 .. _`PKCS#7`: https://tools.ietf.org/html/rfc2315

118
docs/signing.rst Normal file
View file

@ -0,0 +1,118 @@
.. Copyright 2016 OpenMarket Ltd
..
.. Licensed under the Apache License, Version 2.0 (the "License");
.. you may not use this file except in compliance with the License.
.. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
Signature keys and user identity in libolm
==========================================
The use of any public-key based cryptography system such as Olm presents the
need for our users Alice and Bob to verify that they are in fact communicating
with each other, rather than a man-in-the-middle. Typically this requires an
out-of-band process in which Alice and Bob verify that they have the correct
public keys for each other. For example, this might be done via physical
presence or via a voice call.
In the basic `Olm <olm.html>`_ protocol, it is sufficient to compare the public
Curve25519 identity keys. As a naive example, Alice would meet Bob and ensure
that the identity key she downloaded from the key server matched that shown by
his device. This prevents the eavesdropper Eve from decrypting any messages
sent from Alice to Bob, or from masquerading as Bob to send messages to Alice:
she has neither Alice's nor Bob's private identity key, so cannot successfully
complete the triple-DH calculation to compute the shared secret, :math:`S`,
which in turn prevents her decrypting intercepted messages, or from creating
new messages with valid MACs. Obviously, for protection to be complete, Bob
must similarly verify Alice's key.
However, the use of the Curve25519 key as the "fingerprint" in this way makes
it difficult to carry out signing operations. For instance, it may be useful to
cross-sign identity keys for different devices, or, as discussed below, to sign
one-time keys. Curve25519 keys are intended for use in DH calculations, and
their use to calculate signatures is non-trivial.
The solution adopted in this library is to generate a signing key for each
user. This is an `Ed25519`_ keypair, which is used to calculate a signature on
an object including both the public Ed25519 signing key and the public
Curve25519 identity key. It is then the **public Ed25519 signing key** which is
used as the device fingerprint which Alice and Bob verify with each other.
By verifying the signatures on the key object, Alice and Bob then get the same
level of assurance about the ownership of the Curve25519 identity keys as if
they had compared those directly.
Signing one-time keys
---------------------
The Olm protocol requires users to publish a set of one-time keys to a key
server. To establish an Olm session, the originator downloads a key for the
recipient from this server. The decision of whether to sign these one-time keys
is left to the application. There are both advantages and disadvantages to
doing so.
Consider the scenario where one-time keys are unsigned. Alice wants to initiate
an Olm session with Bob. Bob uploads his one-time keys, :math:`E_B`, but Eve
replaces them with ones she controls, :math:`E_E`. Alice downloads one of the
compromised keys, and sends a pre-key message using a shared secret :math:`S`,
where:
.. math::
S = ECDH\left(I_A,\,E_E\right)\;\parallel\;ECDH\left(E_A,\,I_B\right)\;
\parallel\;ECDH\left(E_A,\,E_E\right)
Eve cannot decrypt the message because she does not have the private parts of
either :math:`E_A` nor :math:`I_B`, so cannot calculate
:math:`ECDH\left(E_A,\,I_B\right)`. However, suppose she later compromises
Bob's identity key :math:`I_B`. This would give her the ability to decrypt any
pre-key messages sent to Bob using the compromised one-time keys, and is thus a
problematic loss of forward secrecy. If Bob signs his keys with his Ed25519
signing key (and Alice verifies the signature before using them), this problem
is avoided.
On the other hand, signing the one-time keys leads to a reduction in
deniability. Recall that the shared secret is calculated as follows:
.. math::
S = ECDH\left(I_A,\,E_B\right)\;\parallel\;ECDH\left(E_A,\,I_B\right)\;
\parallel\;ECDH\left(E_A,\,E_B\right)
If keys are unsigned, a forger can make up values of :math:`E_A` and
:math:`E_B`, and construct a transcript of a conversation which looks like it
was between Alice and Bob. Alice and Bob can therefore plausibly deny their
partition in any conversation even if they are both forced to divulge their
private identity keys, since it is impossible to prove that the transcript was
a conversation between the two of them, rather than constructed by a forger.
If :math:`E_B` is signed, it is no longer possible to construct arbitrary
transcripts. Given a transcript and Alice and Bob's identity keys, we can now
show that at least one of Alice or Bob was involved in the conversation,
because the ability to calculate :math:`ECDH\left(I_A,\,E_B\right)` requires
knowledge of the private parts of either :math:`I_A` (proving Alice's
involvement) or :math:`E_B` (proving Bob's involvement, via the
signature). Note that it remains impossible to show that *both* Alice and Bob
were involved.
In conclusion, applications should consider whether to sign one-time keys based
on the trade-off between forward secrecy and deniability.
License
-------
This document is licensed under the `Apache License, Version 2.0
<http://www.apache.org/licenses/LICENSE-2.0>`_.
Feedback
--------
Questions and feedback can be sent to richard at matrix.org.
.. _`Ed25519`: http://ed25519.cr.yp.to/

51
fuzzers/README.rst Normal file
View file

@ -0,0 +1,51 @@
Fuzzers
=======
This directory contains a collection of fuzzing tools. Each tests a different
entry point to the code.
Usage notes:
1. Install AFL:
.. code::
apt-get install afl
2. Build the fuzzers:
.. code::
make fuzzers
3. Some of the tests (eg ``fuzz_decrypt`` and ``fuzz_group_decrypt``) require a
session file. You can use the ones generated by the python test script
(``python/test.sh``).
4. Make some work directories:
.. code::
mkdir -p fuzzing/in fuzzing/out
5. Generate starting input:
.. code::
echo "Test" > fuzzing/in/test
6. Run the test under ``afl-fuzz``:
.. code::
afl-fuzz -i fuzzing/in -o fuzzing/out -- \
./build/fuzzers/fuzz_<fuzzing_tool> [<test args>]
7. To resume with the data produced by an earlier run:
.. code::
afl-fuzz -i- -o existing_output_dir [...etc...]
8. If it shows failures, pipe the failure case into
``./build/fuzzers/debug_<fuzzing_tool>``, fix, and repeat.

View file

@ -54,6 +54,8 @@ int main(int argc, const char *argv[]) {
uint8_t plaintext[max_length]; uint8_t plaintext[max_length];
uint32_t ratchet_index;
size_t length = check_error( size_t length = check_error(
olm_inbound_group_session_last_error, olm_inbound_group_session_last_error,
session, session,
@ -61,7 +63,7 @@ int main(int argc, const char *argv[]) {
olm_group_decrypt( olm_group_decrypt(
session, session,
message_buffer, message_length, message_buffer, message_length,
plaintext, max_length plaintext, max_length, &ratchet_index
) )
); );

View file

@ -140,7 +140,8 @@ size_t olm_group_decrypt(
uint8_t * message, size_t message_length, uint8_t * message, size_t message_length,
/* output */ /* output */
uint8_t * plaintext, size_t max_plaintext_length uint8_t * plaintext, size_t max_plaintext_length,
uint32_t * message_index
); );

View file

@ -320,7 +320,8 @@ int olm_session_has_received_message(
/** Checks if the PRE_KEY message is for this in-bound session. This can happen /** Checks if the PRE_KEY message is for this in-bound session. This can happen
* if multiple messages are sent to this account before this account sends a * if multiple messages are sent to this account before this account sends a
* message in reply. Returns olm_error() on failure. If the base64 * message in reply. Returns 1 if the session matches. Returns 0 if the session
* does not match. Returns olm_error() on failure. If the base64
* couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64".
* If the message was for an unsupported protocol version then * If the message was for an unsupported protocol version then
* olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message
@ -333,7 +334,8 @@ size_t olm_matches_inbound_session(
/** Checks if the PRE_KEY message is for this in-bound session. This can happen /** Checks if the PRE_KEY message is for this in-bound session. This can happen
* if multiple messages are sent to this account before this account sends a * if multiple messages are sent to this account before this account sends a
* message in reply. Returns olm_error() on failure. If the base64 * message in reply. Returns 1 if the session matches. Returns 0 if the session
* does not match. Returns olm_error() on failure. If the base64
* couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64".
* If the message was for an unsupported protocol version then * If the message was for an unsupported protocol version then
* olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message

View file

@ -96,7 +96,7 @@ size_t olm_init_outbound_group_session_random_length(
*/ */
size_t olm_init_outbound_group_session( size_t olm_init_outbound_group_session(
OlmOutboundGroupSession *session, OlmOutboundGroupSession *session,
uint8_t const * random, size_t random_length uint8_t *random, size_t random_length
); );
/** /**

View file

@ -403,8 +403,8 @@ DemoUser.prototype.decryptGroup = function(jsonpacket, callback) {
throw new Error("Unknown session id " + session_id); throw new Error("Unknown session id " + session_id);
} }
var plaintext = session.decrypt(packet.body); var result = session.decrypt(packet.body);
done(plaintext); done(result.plaintext);
}, callback); }, callback);
}; };

View file

@ -73,10 +73,12 @@ InboundGroupSession.prototype['decrypt'] = restore_stack(function(
// So we copy the array to a new buffer // So we copy the array to a new buffer
var message_buffer = stack(message_array); var message_buffer = stack(message_array);
var plaintext_buffer = stack(max_plaintext_length + NULL_BYTE_PADDING_LENGTH); var plaintext_buffer = stack(max_plaintext_length + NULL_BYTE_PADDING_LENGTH);
var message_index = stack(4);
var plaintext_length = inbound_group_session_method(Module["_olm_group_decrypt"])( var plaintext_length = inbound_group_session_method(Module["_olm_group_decrypt"])(
this.ptr, this.ptr,
message_buffer, message_array.length, message_buffer, message_array.length,
plaintext_buffer, max_plaintext_length plaintext_buffer, max_plaintext_length,
message_index
); );
// Pointer_stringify requires a null-terminated argument (the optional // Pointer_stringify requires a null-terminated argument (the optional
@ -86,7 +88,10 @@ InboundGroupSession.prototype['decrypt'] = restore_stack(function(
0, "i8" 0, "i8"
); );
return Pointer_stringify(plaintext_buffer); return {
"plaintext": Pointer_stringify(plaintext_buffer),
"message_index": Module['getValue'](message_index, "i32")
}
}); });
InboundGroupSession.prototype['session_id'] = restore_stack(function() { InboundGroupSession.prototype['session_id'] = restore_stack(function() {

View file

@ -1,6 +1,6 @@
{ {
"name": "olm", "name": "olm",
"version": "1.3.0", "version": "2.0.0",
"description": "An implementation of the Double Ratchet cryptographic ratchet", "description": "An implementation of the Double Ratchet cryptographic ratchet",
"main": "olm.js", "main": "olm.js",
"files": [ "files": [

View file

@ -328,7 +328,7 @@ def do_group_decrypt(args):
session = InboundGroupSession() session = InboundGroupSession()
session.unpickle(args.key, read_base64_file(args.session_file)) session.unpickle(args.key, read_base64_file(args.session_file))
message = args.message_file.read() message = args.message_file.read()
plaintext = session.decrypt(message) plaintext, message_index = session.decrypt(message)
with open(args.session_file, "wb") as f: with open(args.session_file, "wb") as f:
f.write(session.pickle(args.key)) f.write(session.pickle(args.key))
args.plaintext_file.write(plaintext) args.plaintext_file.write(plaintext)

View file

@ -7,7 +7,7 @@ def read_random(n):
return f.read(n) return f.read(n)
lib = cdll.LoadLibrary(os.path.join( lib = cdll.LoadLibrary(os.path.join(
os.path.dirname(__file__), "..", "..", "build", "libolm.so.1") os.path.dirname(__file__), "..", "..", "build", "libolm.so.2")
) )
lib.olm_error.argtypes = [] lib.olm_error.argtypes = []

View file

@ -41,6 +41,7 @@ account_function(lib.olm_account_one_time_keys_length)
account_function(lib.olm_account_one_time_keys, c_void_p, c_size_t) account_function(lib.olm_account_one_time_keys, c_void_p, c_size_t)
account_function(lib.olm_account_mark_keys_as_published) account_function(lib.olm_account_mark_keys_as_published)
account_function(lib.olm_account_max_number_of_one_time_keys) account_function(lib.olm_account_max_number_of_one_time_keys)
account_function(lib.olm_pickle_account_length)
account_function( account_function(
lib.olm_account_generate_one_time_keys_random_length, lib.olm_account_generate_one_time_keys_random_length,
c_size_t c_size_t

View file

@ -43,6 +43,7 @@ inbound_group_session_function(
lib.olm_group_decrypt, lib.olm_group_decrypt,
c_void_p, c_size_t, # message c_void_p, c_size_t, # message
c_void_p, c_size_t, # plaintext c_void_p, c_size_t, # plaintext
POINTER(c_uint32), # message_index
) )
inbound_group_session_function(lib.olm_inbound_group_session_id_length) inbound_group_session_function(lib.olm_inbound_group_session_id_length)
@ -82,11 +83,14 @@ class InboundGroupSession(object):
) )
plaintext_buffer = create_string_buffer(max_plaintext_length) plaintext_buffer = create_string_buffer(max_plaintext_length)
message_buffer = create_string_buffer(message) message_buffer = create_string_buffer(message)
message_index = c_uint32()
plaintext_length = lib.olm_group_decrypt( plaintext_length = lib.olm_group_decrypt(
self.ptr, message_buffer, len(message), self.ptr, message_buffer, len(message),
plaintext_buffer, max_plaintext_length plaintext_buffer, max_plaintext_length,
byref(message_index)
) )
return plaintext_buffer.raw[:plaintext_length] return plaintext_buffer.raw[:plaintext_length], message_index
def session_id(self): def session_id(self):
id_length = lib.olm_inbound_group_session_id_length(self.ptr) id_length = lib.olm_inbound_group_session_id_length(self.ptr)

View file

@ -58,6 +58,7 @@ session_function(
c_void_p, c_size_t, # Identity Key c_void_p, c_size_t, # Identity Key
c_void_p, c_size_t, # Pre Key Message c_void_p, c_size_t, # Pre Key Message
) )
session_function(lib.olm_pickle_session_length)
session_function(lib.olm_encrypt_message_type) session_function(lib.olm_encrypt_message_type)
session_function(lib.olm_encrypt_random_length) session_function(lib.olm_encrypt_random_length)
session_function(lib.olm_encrypt_message_length, c_size_t) session_function(lib.olm_encrypt_message_length, c_size_t)

View file

@ -263,7 +263,8 @@ size_t olm_group_decrypt_max_plaintext_length(
static size_t _decrypt( static size_t _decrypt(
OlmInboundGroupSession *session, OlmInboundGroupSession *session,
uint8_t * message, size_t message_length, uint8_t * message, size_t message_length,
uint8_t * plaintext, size_t max_plaintext_length uint8_t * plaintext, size_t max_plaintext_length,
uint32_t * message_index
) { ) {
struct _OlmDecodeGroupMessageResults decoded_results; struct _OlmDecodeGroupMessageResults decoded_results;
size_t max_length, r; size_t max_length, r;
@ -286,6 +287,10 @@ static size_t _decrypt(
return (size_t)-1; return (size_t)-1;
} }
if (message_index != NULL) {
*message_index = decoded_results.message_index;
}
/* verify the signature. We could do this before decoding the message, but /* verify the signature. We could do this before decoding the message, but
* we allow for the possibility of future protocol versions which use a * we allow for the possibility of future protocol versions which use a
* different signing mechanism; we would rather throw "BAD_MESSAGE_VERSION" * different signing mechanism; we would rather throw "BAD_MESSAGE_VERSION"
@ -349,7 +354,8 @@ static size_t _decrypt(
size_t olm_group_decrypt( size_t olm_group_decrypt(
OlmInboundGroupSession *session, OlmInboundGroupSession *session,
uint8_t * message, size_t message_length, uint8_t * message, size_t message_length,
uint8_t * plaintext, size_t max_plaintext_length uint8_t * plaintext, size_t max_plaintext_length,
uint32_t * message_index
) { ) {
size_t raw_message_length; size_t raw_message_length;
@ -361,7 +367,8 @@ size_t olm_group_decrypt(
return _decrypt( return _decrypt(
session, message, raw_message_length, session, message, raw_message_length,
plaintext, max_plaintext_length plaintext, max_plaintext_length,
message_index
); );
} }

View file

@ -214,11 +214,13 @@ void olm::decode_message(
reader.ciphertext = nullptr; reader.ciphertext = nullptr;
reader.ciphertext_length = 0; reader.ciphertext_length = 0;
if (pos == end) return;
if (input_length < mac_length) return; if (input_length < mac_length) return;
if (pos == end) return;
reader.version = *(pos++); reader.version = *(pos++);
while (pos != end) { while (pos != end) {
unknown = pos;
pos = decode( pos = decode(
pos, end, RATCHET_KEY_TAG, pos, end, RATCHET_KEY_TAG,
reader.ratchet_key, reader.ratchet_key_length reader.ratchet_key, reader.ratchet_key_length
@ -234,7 +236,6 @@ void olm::decode_message(
if (unknown == pos) { if (unknown == pos) {
pos = skip_unknown(pos, end); pos = skip_unknown(pos, end);
} }
unknown = pos;
} }
} }
@ -303,6 +304,7 @@ void olm::decode_one_time_key_message(
reader.version = *(pos++); reader.version = *(pos++);
while (pos != end) { while (pos != end) {
unknown = pos;
pos = decode( pos = decode(
pos, end, ONE_TIME_KEY_ID_TAG, pos, end, ONE_TIME_KEY_ID_TAG,
reader.one_time_key, reader.one_time_key_length reader.one_time_key, reader.one_time_key_length
@ -322,7 +324,6 @@ void olm::decode_one_time_key_message(
if (unknown == pos) { if (unknown == pos) {
pos = skip_unknown(pos, end); pos = skip_unknown(pos, end);
} }
unknown = pos;
} }
} }
@ -377,9 +378,12 @@ void _olm_decode_group_message(
results->ciphertext_length = 0; results->ciphertext_length = 0;
if (input_length < trailer_length) return; if (input_length < trailer_length) return;
if (pos == end) return;
results->version = *(pos++); results->version = *(pos++);
while (pos != end) { while (pos != end) {
unknown = pos;
pos = decode( pos = decode(
pos, end, GROUP_MESSAGE_INDEX_TAG, pos, end, GROUP_MESSAGE_INDEX_TAG,
results->message_index, has_message_index results->message_index, has_message_index
@ -391,7 +395,6 @@ void _olm_decode_group_message(
if (unknown == pos) { if (unknown == pos) {
pos = skip_unknown(pos, end); pos = skip_unknown(pos, end);
} }
unknown = pos;
} }
results->has_message_index = (int)has_message_index; results->has_message_index = (int)has_message_index;

View file

@ -154,20 +154,23 @@ size_t olm_init_outbound_group_session_random_length(
size_t olm_init_outbound_group_session( size_t olm_init_outbound_group_session(
OlmOutboundGroupSession *session, OlmOutboundGroupSession *session,
uint8_t const * random, size_t random_length uint8_t *random, size_t random_length
) { ) {
const uint8_t *random_ptr = random;
if (random_length < olm_init_outbound_group_session_random_length(session)) { if (random_length < olm_init_outbound_group_session_random_length(session)) {
/* Insufficient random data for new session */ /* Insufficient random data for new session */
session->last_error = OLM_NOT_ENOUGH_RANDOM; session->last_error = OLM_NOT_ENOUGH_RANDOM;
return (size_t)-1; return (size_t)-1;
} }
megolm_init(&(session->ratchet), random, 0); megolm_init(&(session->ratchet), random_ptr, 0);
random += MEGOLM_RATCHET_LENGTH; random_ptr += MEGOLM_RATCHET_LENGTH;
_olm_crypto_ed25519_generate_key(random, &(session->signing_key)); _olm_crypto_ed25519_generate_key(random_ptr, &(session->signing_key));
random += ED25519_RANDOM_LENGTH; random_ptr += ED25519_RANDOM_LENGTH;
_olm_unset(random, random_length);
return 0; return 0;
} }

View file

@ -60,7 +60,7 @@ size_t _olm_enc_output(
raw_output, length raw_output, length
); );
_olm_encode_base64(raw_output, length, output); _olm_encode_base64(raw_output, length, output);
return raw_length; return base64_length;
} }

View file

@ -28,23 +28,26 @@ int main() {
size_t pickle_length = olm_pickle_outbound_group_session_length(session); size_t pickle_length = olm_pickle_outbound_group_session_length(session);
uint8_t pickle1[pickle_length]; uint8_t pickle1[pickle_length];
olm_pickle_outbound_group_session(session, size_t res = olm_pickle_outbound_group_session(
"secret_key", 10, session, "secret_key", 10, pickle1, pickle_length
pickle1, pickle_length); );
assert_equals(pickle_length, res);
uint8_t pickle2[pickle_length]; uint8_t pickle2[pickle_length];
memcpy(pickle2, pickle1, pickle_length); memcpy(pickle2, pickle1, pickle_length);
uint8_t buffer2[size]; uint8_t buffer2[size];
OlmOutboundGroupSession *session2 = olm_outbound_group_session(buffer2); OlmOutboundGroupSession *session2 = olm_outbound_group_session(buffer2);
size_t res = olm_unpickle_outbound_group_session(session2, res = olm_unpickle_outbound_group_session(
"secret_key", 10, session2, "secret_key", 10, pickle2, pickle_length
pickle2, pickle_length); );
assert_not_equals((size_t)-1, res); assert_not_equals((size_t)-1, res);
assert_equals(pickle_length, assert_equals(pickle_length,
olm_pickle_outbound_group_session_length(session2)); olm_pickle_outbound_group_session_length(session2));
olm_pickle_outbound_group_session(session2, res = olm_pickle_outbound_group_session(
"secret_key", 10, session2, "secret_key", 10, pickle2, pickle_length
pickle2, pickle_length); );
assert_equals(pickle_length, res);
assert_equals(pickle1, pickle2, pickle_length); assert_equals(pickle1, pickle2, pickle_length);
} }
@ -59,23 +62,25 @@ int main() {
size_t pickle_length = olm_pickle_inbound_group_session_length(session); size_t pickle_length = olm_pickle_inbound_group_session_length(session);
uint8_t pickle1[pickle_length]; uint8_t pickle1[pickle_length];
olm_pickle_inbound_group_session(session, size_t res = olm_pickle_inbound_group_session(
"secret_key", 10, session, "secret_key", 10, pickle1, pickle_length
pickle1, pickle_length); );
assert_equals(pickle_length, res);
uint8_t pickle2[pickle_length]; uint8_t pickle2[pickle_length];
memcpy(pickle2, pickle1, pickle_length); memcpy(pickle2, pickle1, pickle_length);
uint8_t buffer2[size]; uint8_t buffer2[size];
OlmInboundGroupSession *session2 = olm_inbound_group_session(buffer2); OlmInboundGroupSession *session2 = olm_inbound_group_session(buffer2);
size_t res = olm_unpickle_inbound_group_session(session2, res = olm_unpickle_inbound_group_session(
"secret_key", 10, session2, "secret_key", 10, pickle2, pickle_length
pickle2, pickle_length); );
assert_not_equals((size_t)-1, res); assert_not_equals((size_t)-1, res);
assert_equals(pickle_length, assert_equals(pickle_length,
olm_pickle_inbound_group_session_length(session2)); olm_pickle_inbound_group_session_length(session2));
olm_pickle_inbound_group_session(session2, res = olm_pickle_inbound_group_session(
"secret_key", 10, session2, "secret_key", 10, pickle2, pickle_length
pickle2, pickle_length); );
assert_equals(pickle1, pickle2, pickle_length); assert_equals(pickle1, pickle2, pickle_length);
} }
@ -161,10 +166,12 @@ int main() {
memcpy(msgcopy, msg, msglen); memcpy(msgcopy, msg, msglen);
size = olm_group_decrypt_max_plaintext_length(inbound_session, msgcopy, msglen); size = olm_group_decrypt_max_plaintext_length(inbound_session, msgcopy, msglen);
uint8_t plaintext_buf[size]; uint8_t plaintext_buf[size];
uint32_t message_index;
res = olm_group_decrypt(inbound_session, msg, msglen, res = olm_group_decrypt(inbound_session, msg, msglen,
plaintext_buf, size); plaintext_buf, size, &message_index);
assert_equals(plaintext_length, res); assert_equals(plaintext_length, res);
assert_equals(plaintext, plaintext_buf, res); assert_equals(plaintext, plaintext_buf, res);
assert_equals(message_index, uint32_t(0));
} }
{ {
@ -208,9 +215,11 @@ int main() {
memcpy(msgcopy, message, msglen); memcpy(msgcopy, message, msglen);
uint8_t plaintext_buf[size]; uint8_t plaintext_buf[size];
uint32_t message_index;
res = olm_group_decrypt( res = olm_group_decrypt(
inbound_session, msgcopy, msglen, plaintext_buf, size inbound_session, msgcopy, msglen, plaintext_buf, size, &message_index
); );
assert_equals(message_index, uint32_t(0));
assert_equals(plaintext_length, res); assert_equals(plaintext_length, res);
assert_equals(plaintext, plaintext_buf, res); assert_equals(plaintext, plaintext_buf, res);
@ -227,7 +236,7 @@ int main() {
memcpy(msgcopy, message, msglen); memcpy(msgcopy, message, msglen);
res = olm_group_decrypt( res = olm_group_decrypt(
inbound_session, msgcopy, msglen, inbound_session, msgcopy, msglen,
plaintext_buf, size plaintext_buf, size, &message_index
); );
assert_equals((size_t)-1, res); assert_equals((size_t)-1, res);
assert_equals( assert_equals(

View file

@ -49,7 +49,9 @@ mock_random(ot_random, sizeof(ot_random));
std::size_t pickle_length = ::olm_pickle_account_length(account); std::size_t pickle_length = ::olm_pickle_account_length(account);
std::uint8_t pickle1[pickle_length]; std::uint8_t pickle1[pickle_length];
::olm_pickle_account(account, "secret_key", 10, pickle1, pickle_length); std::size_t res = ::olm_pickle_account(account, "secret_key", 10, pickle1, pickle_length);
assert_equals(pickle_length, res);
std::uint8_t pickle2[pickle_length]; std::uint8_t pickle2[pickle_length];
std::memcpy(pickle2, pickle1, pickle_length); std::memcpy(pickle2, pickle1, pickle_length);
@ -59,10 +61,10 @@ assert_not_equals(std::size_t(-1), ::olm_unpickle_account(
account2, "secret_key", 10, pickle2, pickle_length account2, "secret_key", 10, pickle2, pickle_length
)); ));
assert_equals(pickle_length, ::olm_pickle_account_length(account2)); assert_equals(pickle_length, ::olm_pickle_account_length(account2));
::olm_pickle_account(account2, "secret_key", 10, pickle2, pickle_length); res = ::olm_pickle_account(account2, "secret_key", 10, pickle2, pickle_length);
assert_equals(pickle_length, res);
assert_equals(pickle1, pickle2, pickle_length); assert_equals(pickle1, pickle2, pickle_length);
} }
@ -122,7 +124,9 @@ mock_random(random2, sizeof(random2));
std::size_t pickle_length = ::olm_pickle_session_length(session); std::size_t pickle_length = ::olm_pickle_session_length(session);
std::uint8_t pickle1[pickle_length]; std::uint8_t pickle1[pickle_length];
::olm_pickle_session(session, "secret_key", 10, pickle1, pickle_length); std::size_t res = ::olm_pickle_session(session, "secret_key", 10, pickle1, pickle_length);
assert_equals(pickle_length, res);
std::uint8_t pickle2[pickle_length]; std::uint8_t pickle2[pickle_length];
std::memcpy(pickle2, pickle1, pickle_length); std::memcpy(pickle2, pickle1, pickle_length);
@ -132,10 +136,10 @@ assert_not_equals(std::size_t(-1), ::olm_unpickle_session(
session2, "secret_key", 10, pickle2, pickle_length session2, "secret_key", 10, pickle2, pickle_length
)); ));
assert_equals(pickle_length, ::olm_pickle_session_length(session2)); assert_equals(pickle_length, ::olm_pickle_session_length(session2));
::olm_pickle_session(session2, "secret_key", 10, pickle2, pickle_length); res = ::olm_pickle_session(session2, "secret_key", 10, pickle2, pickle_length);
assert_equals(pickle_length, res);
assert_equals(pickle1, pickle2, pickle_length); assert_equals(pickle1, pickle2, pickle_length);
} }
{ /** Loopback test */ { /** Loopback test */
@ -161,6 +165,9 @@ std::uint8_t o_random[::olm_account_generate_one_time_keys_random_length(
mock_random_b(o_random, sizeof(o_random)); mock_random_b(o_random, sizeof(o_random));
::olm_account_generate_one_time_keys(b_account, 42, o_random, sizeof(o_random)); ::olm_account_generate_one_time_keys(b_account, 42, o_random, sizeof(o_random));
std::uint8_t a_id_keys[::olm_account_identity_keys_length(a_account)];
::olm_account_identity_keys(a_account, a_id_keys, sizeof(a_id_keys));
std::uint8_t b_id_keys[::olm_account_identity_keys_length(b_account)]; std::uint8_t b_id_keys[::olm_account_identity_keys_length(b_account)];
std::uint8_t b_ot_keys[::olm_account_one_time_keys_length(b_account)]; std::uint8_t b_ot_keys[::olm_account_one_time_keys_length(b_account)];
::olm_account_identity_keys(b_account, b_id_keys, sizeof(b_id_keys)); ::olm_account_identity_keys(b_account, b_id_keys, sizeof(b_id_keys));
@ -172,8 +179,8 @@ std::uint8_t a_rand[::olm_create_outbound_session_random_length(a_session)];
mock_random_a(a_rand, sizeof(a_rand)); mock_random_a(a_rand, sizeof(a_rand));
assert_not_equals(std::size_t(-1), ::olm_create_outbound_session( assert_not_equals(std::size_t(-1), ::olm_create_outbound_session(
a_session, a_account, a_session, a_account,
b_id_keys + 15, 43, b_id_keys + 15, 43, // B's curve25519 identity key
b_ot_keys + 25, 43, b_ot_keys + 25, 43, // B's curve25519 one time key
a_rand, sizeof(a_rand) a_rand, sizeof(a_rand)
)); ));
@ -198,6 +205,31 @@ std::uint8_t b_session_buffer[::olm_account_size()];
b_session, b_account, tmp_message_1, sizeof(message_1) b_session, b_account, tmp_message_1, sizeof(message_1)
); );
// Check that the inbound session matches the message it was created from.
std::memcpy(tmp_message_1, message_1, sizeof(message_1));
assert_equals(std::size_t(1), ::olm_matches_inbound_session(
b_session,
tmp_message_1, sizeof(message_1)
));
// Check that the inbound session matches the key this message is supposed
// to be from.
std::memcpy(tmp_message_1, message_1, sizeof(message_1));
assert_equals(std::size_t(1), ::olm_matches_inbound_session_from(
b_session,
a_id_keys + 15, 43, // A's curve125519 identity key.
tmp_message_1, sizeof(message_1)
));
// Check that the inbound session isn't from a different user.
std::memcpy(tmp_message_1, message_1, sizeof(message_1));
assert_equals(std::size_t(0), ::olm_matches_inbound_session_from(
b_session,
b_id_keys + 15, 43, // B's curve25519 identity key.
tmp_message_1, sizeof(message_1)
));
// Check that we can decrypt the message.
std::memcpy(tmp_message_1, message_1, sizeof(message_1)); std::memcpy(tmp_message_1, message_1, sizeof(message_1));
std::uint8_t plaintext_1[::olm_decrypt_max_plaintext_length( std::uint8_t plaintext_1[::olm_decrypt_max_plaintext_length(
b_session, 0, tmp_message_1, sizeof(message_1) b_session, 0, tmp_message_1, sizeof(message_1)