diff options
97 files changed, 6965 insertions, 2351 deletions
@@ -1,13 +1,7 @@ -build -.DS_Store -build -*.mode1v3 -*.pbxuser -project.xcworkspace -xcuserdata -.svn -DerivedData -*.orig -*.xccheckout - -xcode/Pods/ +/build +/CHANGELOG.html +/docs/megolm.html +/docs/olm.html +/olm-*.tgz +/README.html +/tracing/README.html
\ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..0ab2eeb --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,69 @@ +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. +The group sessions are now identified by their ed25519 public key. + +These changes alter the pickle format of outbound group sessions, attempting +to unpickle an outbound group session created with a previous version of olm +will give ``OLM_CORRUPTED_PICKLE``. Inbound sessions are unaffected. + +This release alters the format of group session_key messages to include the +ratchet counter. The session_key messages are now self signed with their +ed25519 key. No attempt was made to preserve backwards-compatibility. +Attempting to send session_keys between old and new versions will give +``OLM_BAD_SESSION_KEY``. + +Changes in `1.2.0 <http://matrix.org/git/olm/commit/?h=1.2.0>`_ +=============================================================== + +This release updates the implementation of group session communications, to +include Ed25519 signatures on group messages, to ensure that participants in +group sessions cannot masquerade as each other. + +These changes necessitate changes to the pickle format of inbound and outbound +group sessions, as well as the session_keys exchanged between them. No attempt +has been made to preserve backwards-compatibility: + +* Attempting to restore old pickles will give ``OLM_CORRUPTED_PICKLE``. +* Attempting to send session_keys between old and new versions will give + ``OLM_BAD_SESSION_KEY``. +* Attempting to send messages between old and new versions will give one of a + number of errors. + +There were also a number of implementation changes made as part of this +release, aimed at making the codebase more consistent, and to help with the +implementation of the group message signatures. + + +Changes in `1.1.0 <http://matrix.org/git/olm/commit/?h=1.1.0>`_ +=============================================================== + +This release includes a fix to a bug which caused Ed25519 keypairs to be +generated and used insecurely. Any Ed25519 keys generated by libolm 1.0.0 +or earlier should be considered compromised. + +The fix necessitates a change to the format of the OlmAccount pickle; since +existing OlmAccounts should in any case be considered compromised (as above), +the library refuses to load them, returning OLM_BAD_LEGACY_ACCOUNT_PICKLE. + + +Changes in `1.0.0 <http://matrix.org/git/olm/commit/?h=1.0.0>`_ +=============================================================== + +This release includes a fix to a bug which had the potential to leak sensitive +data to the application: see +https://github.com/vector-im/vector-web/issues/1719. Users of pre-1.x.x +versions of the Olm library should upgrade. Our thanks to `Dmitry Luyciv +<https://github.com/dluciv>`_ for bringing our attention to the bug. + +Other changes since 0.1.0: + + * *Experimental* implementation of the primitives for group sessions. This + implementation has not yet been used in an application and developers are + advised not to rely on its stability. + + * Replace custom build scripts with a Makefile. + + * Include the major version number in the soname of libolm.so (credit to + Emmanuel Gil Peyrot). diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4877dfb --- /dev/null +++ b/Makefile @@ -0,0 +1,242 @@ +#!/usr/bin/make -f + +MAJOR := 1 +MINOR := 3 +PATCH := 0 +VERSION := $(MAJOR).$(MINOR).$(PATCH) +PREFIX ?= /usr/local +BUILD_DIR := build +RELEASE_OPTIMIZE_FLAGS ?= -g -O3 +DEBUG_OPTIMIZE_FLAGS ?= -g -O0 +JS_OPTIMIZE_FLAGS ?= -O3 +FUZZING_OPTIMIZE_FLAGS ?= -O3 +CC = gcc +EMCC = emcc +AFL_CC = afl-gcc +AFL_CXX = afl-g++ + +RELEASE_TARGET := $(BUILD_DIR)/libolm.so.$(VERSION) +DEBUG_TARGET := $(BUILD_DIR)/libolm_debug.so.$(VERSION) +JS_TARGET := javascript/olm.js + +JS_EXPORTED_FUNCTIONS := javascript/exported_functions.json + +PUBLIC_HEADERS := include/olm/olm.h include/olm/outbound_group_session.h include/olm/inbound_group_session.h + +SOURCES := $(wildcard src/*.cpp) $(wildcard src/*.c) \ + lib/crypto-algorithms/sha256.c \ + lib/crypto-algorithms/aes.c \ + lib/curve25519-donna/curve25519-donna.c + +FUZZER_SOURCES := $(wildcard fuzzers/fuzz_*.cpp) $(wildcard fuzzers/fuzz_*.c) +TEST_SOURCES := $(wildcard tests/test_*.cpp) $(wildcard tests/test_*.c) + +OBJECTS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES))) +RELEASE_OBJECTS := $(addprefix $(BUILD_DIR)/release/,$(OBJECTS)) +DEBUG_OBJECTS := $(addprefix $(BUILD_DIR)/debug/,$(OBJECTS)) +FUZZER_OBJECTS := $(addprefix $(BUILD_DIR)/fuzzers/objects/,$(OBJECTS)) +FUZZER_BINARIES := $(addprefix $(BUILD_DIR)/,$(basename $(FUZZER_SOURCES))) +FUZZER_DEBUG_BINARIES := $(patsubst $(BUILD_DIR)/fuzzers/fuzz_%,$(BUILD_DIR)/fuzzers/debug_%,$(FUZZER_BINARIES)) +TEST_BINARIES := $(patsubst tests/%,$(BUILD_DIR)/tests/%,$(basename $(TEST_SOURCES))) +JS_OBJECTS := $(addprefix $(BUILD_DIR)/javascript/,$(OBJECTS)) +JS_PRE := $(wildcard javascript/*pre.js) +JS_POST := javascript/olm_outbound_group_session.js \ + javascript/olm_inbound_group_session.js \ + javascript/olm_post.js +DOCS := tracing/README.html \ + docs/megolm.html \ + docs/olm.html \ + README.html \ + CHANGELOG.html + +CPPFLAGS += -Iinclude -Ilib \ + -DOLMLIB_VERSION_MAJOR=$(MAJOR) -DOLMLIB_VERSION_MINOR=$(MINOR) \ + -DOLMLIB_VERSION_PATCH=$(PATCH) + +# we rely on <stdint.h>, which was introduced in C99 +CFLAGS += -Wall -Werror -std=c99 -fPIC +CXXFLAGS += -Wall -Werror -std=c++11 -fPIC +LDFLAGS += -Wall -Werror + +EMCCFLAGS = --closure 1 --memory-init-file 0 -s NO_FILESYSTEM=1 -s INVOKE_RUN=0 +# NO_BROWSER is kept for compatibility with emscripten 1.35.24, but is no +# longer needed. +EMCCFLAGS += -s NO_BROWSER=1 + +EMCC.c = $(EMCC) $(CFLAGS) $(CPPFLAGS) -c +EMCC.cc = $(EMCC) $(CXXFLAGS) $(CPPFLAGS) -c +EMCC_LINK = $(EMCC) $(LDFLAGS) $(EMCCFLAGS) + +AFL.c = $(AFL_CC) $(CFLAGS) $(CPPFLAGS) -c +AFL.cc = $(AFL_CXX) $(CXXFLAGS) $(CPPFLAGS) -c +AFL_LINK.c = $(AFL_CC) $(LDFLAGS) $(CFLAGS) $(CPPFLAGS) +AFL_LINK.cc = $(AFL_CXX) $(LDFLAGS) $(CXXFLAGS) $(CPPFLAGS) + +# generate .d files when compiling +CPPFLAGS += -MMD + +### per-target variables + +$(RELEASE_OBJECTS): CFLAGS += $(RELEASE_OPTIMIZE_FLAGS) +$(RELEASE_OBJECTS): CXXFLAGS += $(RELEASE_OPTIMIZE_FLAGS) +$(RELEASE_TARGET): LDFLAGS += $(RELEASE_OPTIMIZE_FLAGS) + +$(DEBUG_OBJECTS): CFLAGS += $(DEBUG_OPTIMIZE_FLAGS) +$(DEBUG_OBJECTS): CXXFLAGS += $(DEBUG_OPTIMIZE_FLAGS) +$(DEBUG_TARGET): LDFLAGS += $(DEBUG_OPTIMIZE_FLAGS) + +$(TEST_BINARIES): CPPFLAGS += -Itests/include +$(TEST_BINARIES): LDFLAGS += $(DEBUG_OPTIMIZE_FLAGS) -L$(BUILD_DIR) + +$(FUZZER_OBJECTS): CFLAGS += $(FUZZER_OPTIMIZE_FLAGS) +$(FUZZER_OBJECTS): CXXFLAGS += $(FUZZER_OPTIMIZE_FLAGS) +$(FUZZER_BINARIES): CPPFLAGS += -Ifuzzers/include +$(FUZZER_BINARIES): LDFLAGS += $(FUZZER_OPTIMIZE_FLAGS) -L$(BUILD_DIR) +$(FUZZER_DEBUG_BINARIES): CPPFLAGS += -Ifuzzers/include +$(FUZZER_DEBUG_BINARIES): LDFLAGS += $(DEBUG_OPTIMIZE_FLAGS) + +$(JS_OBJECTS): CFLAGS += $(JS_OPTIMIZE_FLAGS) +$(JS_OBJECTS): CXXFLAGS += $(JS_OPTIMIZE_FLAGS) +$(JS_TARGET): LDFLAGS += $(JS_OPTIMIZE_FLAGS) + +### top-level targets + +lib: $(RELEASE_TARGET) +.PHONY: lib + +$(RELEASE_TARGET): $(RELEASE_OBJECTS) + $(CXX) $(LDFLAGS) --shared -fPIC \ + -Wl,-soname,libolm.so.$(MAJOR) \ + -Wl,--version-script,version_script.ver \ + $(OUTPUT_OPTION) $(RELEASE_OBJECTS) + ln -sf libolm.so.$(VERSION) $(BUILD_DIR)/libolm.so.$(MAJOR) + +debug: $(DEBUG_TARGET) +.PHONY: debug + +$(DEBUG_TARGET): $(DEBUG_OBJECTS) + $(CXX) $(LDFLAGS) --shared -fPIC \ + -Wl,-soname,libolm_debug.so.$(MAJOR) \ + -Wl,--version-script,version_script.ver \ + $(OUTPUT_OPTION) $(DEBUG_OBJECTS) + ln -sf libolm_debug.so.$(VERSION) $(BUILD_DIR)/libolm_debug.so.$(MAJOR) + +js: $(JS_TARGET) +.PHONY: js + +$(JS_TARGET): $(JS_OBJECTS) $(JS_PRE) $(JS_POST) $(JS_EXPORTED_FUNCTIONS) + $(EMCC_LINK) \ + $(foreach f,$(JS_PRE),--pre-js $(f)) \ + $(foreach f,$(JS_POST),--post-js $(f)) \ + -s "EXPORTED_FUNCTIONS=@$(JS_EXPORTED_FUNCTIONS)" \ + $(JS_OBJECTS) -o $@ + +build_tests: $(TEST_BINARIES) + +test: build_tests + for i in $(TEST_BINARIES); do \ + echo $$i; \ + $$i || exit $$?; \ + done + +fuzzers: $(FUZZER_BINARIES) $(FUZZER_DEBUG_BINARIES) +.PHONY: fuzzers + +$(JS_EXPORTED_FUNCTIONS): $(PUBLIC_HEADERS) + perl -MJSON -ne '$$f{"_$$1"}=1 if /(olm_[^( ]*)\(/; END { @f=sort keys %f; print encode_json \@f }' $^ > $@.tmp + mv $@.tmp $@ + +all: test js lib debug doc +.PHONY: all + +install-headers: $(PUBLIC_HEADERS) + test -d $(DESTDIR)$(PREFIX)/include/olm || mkdir -p $(DESTDIR)$(PREFIX)/include/olm + install -Dm644 $(PUBLIC_HEADERS) $(DESTDIR)$(PREFIX)/include/olm/ +.PHONY: install-headers + +install-debug: debug install-headers + test -d $(DESTDIR)$(PREFIX)/lib || mkdir -p $(DESTDIR)$(PREFIX)/lib + install -Dm755 $(DEBUG_TARGET) $(DESTDIR)$(PREFIX)/lib/libolm_debug.so.$(VERSION) + ln -s libolm_debug.so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/libolm_debug.so.$(MAJOR) + ln -s libolm_debug.so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/libolm_debug.so +.PHONY: install-debug + +install: lib install-headers + test -d $(DESTDIR)$(PREFIX)/lib || mkdir -p $(DESTDIR)$(PREFIX)/lib + install -Dm755 $(RELEASE_TARGET) $(DESTDIR)$(PREFIX)/lib/libolm.so.$(VERSION) + ln -s libolm.so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/libolm.so.$(MAJOR) + ln -s libolm.so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/libolm.so +.PHONY: install + +clean:; + rm -rf $(BUILD_DIR) $(DOCS) +.PHONY: clean + +doc: $(DOCS) +.PHONY: doc + +### rules for building objects +$(BUILD_DIR)/release/%.o: %.c + mkdir -p $(dir $@) + $(COMPILE.c) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/release/%.o: %.cpp + mkdir -p $(dir $@) + $(COMPILE.cc) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/debug/%.o: %.c + mkdir -p $(dir $@) + $(COMPILE.c) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/debug/%.o: %.cpp + mkdir -p $(dir $@) + $(COMPILE.cc) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/javascript/%.o: %.c + mkdir -p $(dir $@) + $(EMCC.c) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/javascript/%.o: %.cpp + mkdir -p $(dir $@) + $(EMCC.cc) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/tests/%: tests/%.c $(DEBUG_OBJECTS) + mkdir -p $(dir $@) + $(LINK.c) $< $(DEBUG_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ + +$(BUILD_DIR)/tests/%: tests/%.cpp $(DEBUG_OBJECTS) + mkdir -p $(dir $@) + $(LINK.cc) $< $(DEBUG_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ + +$(BUILD_DIR)/fuzzers/objects/%.o: %.c + mkdir -p $(dir $@) + $(AFL.c) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/fuzzers/objects/%.o: %.cpp + mkdir -p $(dir $@) + $(AFL.cc) $(OUTPUT_OPTION) $< + +$(BUILD_DIR)/fuzzers/fuzz_%: fuzzers/fuzz_%.c $(FUZZER_OBJECTS) + $(AFL_LINK.c) $< $(FUZZER_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ + +$(BUILD_DIR)/fuzzers/fuzz_%: fuzzers/fuzz_%.cpp $(FUZZER_OBJECTS) + $(AFL_LINK.cc) $< $(FUZZER_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ + +$(BUILD_DIR)/fuzzers/debug_%: fuzzers/fuzz_%.c $(DEBUG_OBJECTS) + $(LINK.c) $< $(DEBUG_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ + +$(BUILD_DIR)/fuzzers/debug_%: fuzzers/fuzz_%.cpp $(DEBUG_OBJECTS) + $(LINK.cc) $< $(DEBUG_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ + +%.html: %.rst + rst2html $< $@ + +### dependencies + +-include $(RELEASE_OBJECTS:.o=.d) +-include $(DEBUG_OBJECTS:.o=.d) +-include $(JS_OBJECTS:.o=.d) +-include $(TEST_BINARIES:=.d) +-include $(FUZZER_OBJECTS:.o=.d) +-include $(FUZZER_BINARIES:=.d) +-include $(FUZZER_DEBUG_BINARIES:=.d) @@ -1,8 +1,12 @@ Olm === -An implementation of the cryptographic ratchet described by -https://github.com/trevp/axolotl/wiki, written in C++11 and exposed as a C API +An implementation of the Double Ratchet cryptographic ratchet described by +https://github.com/trevp/double_ratchet/wiki, written in C and C++11 and +exposed as a C API. + +The specification of the Olm ratchet can be found in docs/olm.rst or +https://matrix.org/docs/spec/olm.html Building -------- @@ -11,20 +15,40 @@ To build olm as a shared library run: .. code:: bash - ./build_shared_library.py + make To run the tests run: .. code:: bash - ./test.py - + make test To build the javascript bindings, install emscripten from http://kripken.github.io/emscripten-site/ and then run: .. code:: bash - javascript/build.py + make js + +Release process +--------------- + +.. code:: bash + + # Bump version numbers in ``Makefile`` and ``javascript/package.json`` + # Prepare changelog + git commit + make clean + make test + make js + npm pack javascript + VERSION=x.y.z + scp olm-$VERSION.tgz packages@ldc-prd-matrix-001:/sites/matrix/packages/npm/olm/ + git tag $VERSION -s + git push --tags + +It's probably sensible to do the above on a release branch (``release-vx.y.z`` +by convention), and merge back to master once complete. + Design ------ @@ -32,6 +56,11 @@ Design Olm is designed to be easy port to different platforms and to be easy to write bindings for. +It was originally implemented in C++, with a plain-C layer providing the public +API. As development has progressed, it has become clear that C++ gives little +advantage, and new functionality is being added in C, with C++ parts being +rewritten as the need ariases. + Error Handling ~~~~~~~~~~~~~~ @@ -67,8 +96,15 @@ the ratchet. While this decreases the performance it makes it much easier to compile the library for different architectures. What's an olm? -~~~~~~~~~~~~~~ +-------------- It's a really cool species of European troglodytic salamander. -Matthew once tried to climb into a pool full of them in Postojnska Jama. -http://www.postojnska-jama.eu/en/about-the-cave/meet-the-dragon-s-offspring/ +http://www.postojnska-jama.eu/en/come-and-visit-us/vivarium-proteus/ + +Legal Notice +------------ + +The software may be subject to the U.S. export control laws and regulations +and by downloading the software the user certifies that he/she/it is +authorized to do so in accordance with those export control laws and +regulations. diff --git a/build_shared_library.py b/build_shared_library.py deleted file mode 100755 index 1eb8675..0000000 --- a/build_shared_library.py +++ /dev/null @@ -1,36 +0,0 @@ -#! /usr/bin/python -# Copyright 2015 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. - -import subprocess -import glob -import os -import sys - -if not os.path.exists("build"): - os.mkdir("build") - -source_files = glob.glob("src/*.cpp") - -compile_args = "g++ -O3 -Iinclude -Ilib --std=c++11 --shared -fPIC".split() -compile_args += source_files -compile_args += sys.argv[1:] - -library = "build/libolm.so" - -def run(args): - print " ".join(args) - subprocess.check_call(args) - -run(compile_args + ["-o", library]) diff --git a/docs/megolm.rst b/docs/megolm.rst new file mode 100644 index 0000000..7853963 --- /dev/null +++ b/docs/megolm.rst @@ -0,0 +1,290 @@ +.. 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. + + +Megolm group ratchet +==================== + +An AES-based cryptographic ratchet intended for group communications. + +.. contents:: + +Background +---------- + +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 +peer-to-peer encryption systems such as `Olm`_. + +It also allows a receipient to decrypt received messages multiple times. For +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. + +Overview +-------- + +Each participant in a conversation uses their own outbound session for +encrypting messages. A session consists of a ratchet and an `Ed25519`_ keypair. + +Secrecy is provided by the ratchet, which can be wound forwards but not +backwards, and is used to derive a distinct message key for each message. + +Authenticity is provided via Ed25519 signatures. + +The value of the ratchet, and the public part of the Ed25519 key, are shared +with other participants in the conversation via secure peer-to-peer +channels. Provided that peer-to-peer channel provides authenticity of the +messages to the participants and deniability of the messages to third parties, +the Megolm session will inherit those properties. + +The Megolm ratchet algorithm +---------------------------- + +The Megolm ratchet :math:`R_i` consists of four parts, :math:`R_{i,j}` for +:math:`j \in {0,1,2,3}`. The length of each part depends on the hash function +in use (256 bits for this version of Megolm). + +The ratchet is initialised with cryptographically-secure random data, and +advanced as follows: + +.. math:: + \begin{align} + R_{i,0} &= + \begin{cases} + H_0\left(R_{2^24(n-1),0}\right) &\text{if }\exists n | i = 2^24n\\ + R_{i-1,0} &\text{otherwise} + \end{cases}\\ + R_{i,1} &= + \begin{cases} + H_1\left(R_{2^24(n-1),0}\right) &\text{if }\exists n | i = 2^24n\\ + H_1\left(R_{2^16(m-1),1}\right) &\text{if }\exists m | i = 2^16m\\ + R_{i-1,1} &\text{otherwise} + \end{cases}\\ + R_{i,2} &= + \begin{cases} + H_2\left(R_{2^24(n-1),0}\right) &\text{if }\exists n | i = 2^24n\\ + H_2\left(R_{2^16(m-1),1}\right) &\text{if }\exists m | i = 2^16m\\ + H_2\left(R_{2^8(p-1),2}\right) &\text{if }\exists p | i = 2^8p\\ + R_{i-1,2} &\text{otherwise} + \end{cases}\\ + R_{i,3} &= + \begin{cases} + H_3\left(R_{2^24(n-1),0}\right) &\text{if }\exists n | i = 2^24n\\ + H_3\left(R_{2^16(m-1),1}\right) &\text{if }\exists m | i = 2^16m\\ + H_3\left(R_{2^8(p-1),2}\right) &\text{if }\exists p | i = 2^8p\\ + H_3\left(R_{i-1,3}\right) &\text{otherwise} + \end{cases} + \end{align} + +where :math:`H_0`, :math:`H_1`, :math:`H_2`, and :math:`H_3` are different hash +functions. In summary: every :math:`2^8` iterations, :math:`R_{i,3}` is +reseeded from :math:`R_{i,2}`. Every :math:`2^16` iterations, :math:`R_{i,2}` +and :math:`R_{i,3}` are reseeded from :math:`R_{i,1}`. Every :math:`2^24` +iterations, :math:`R_{i,1}`, :math:`R_{i,2}` and :math:`R_{i,3}` are reseeded +from :math:`R_{i,0}`. + +The complete ratchet value, :math:`R_{i}`, is hashed to generate the keys used +to encrypt each message. This scheme allows the ratchet to be advanced an +arbitrary amount forwards while needing at most 1023 hash computations. A +client can decrypt chat history onwards from the earliest value of the ratchet +it is aware of, but cannot decrypt history from before that point without +reversing the hash function. + +This allows a participant to share its ability to decrypt chat history with +another from a point in the conversation onwards by giving a copy of the +ratchet at that point in the conversation. + + +The Megolm protocol +------------------- + +Session setup +~~~~~~~~~~~~~ + +Each participant in a conversation generates their own Megolm session. A +session consists of three parts: + +* a 32 bit counter, :math:`i`. +* an `Ed25519`_ keypair, :math:`K`. +* a ratchet, :math:`R_i`, which consists of four 256-bit values, + :math:`R_{i,j}` for :math:`j \in {0,1,2,3}`. + +The counter :math:`i` is initialised to :math:`0`. A new Ed25519 keypair is +generated for :math:`K`. The ratchet is simply initialised with 1024 bits of +cryptographically-secure random data. + +A single participant may use multiple sessions over the lifetime of a +conversation. The public part of :math:`K` is used as an identifier to +discriminate between sessions. + +Sharing session data +~~~~~~~~~~~~~~~~~~~~ + +To allow other participants in the conversation to decrypt messages, the +session data is formatted as described in `Session-sharing format`_. It is then +shared with other participants in the conversation via a secure peer-to-peer +channel (such as that provided by `Olm`_). + +When the session data is received from other participants, the recipient first +checks that the signature matches the public key. They then store their own +copy of the counter, ratchet, and public key. + +Message encryption +~~~~~~~~~~~~~~~~~~ + +This version of Megolm uses AES-256_ in CBC_ mode with `PCKS#7`_ padding and +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`: + +.. math:: + + \begin{align} + AES\_KEY_{i}\;\parallel\;HMAC\_KEY_{i}\;\parallel\;AES\_IV_{i} + &= HKDF\left(0,\,R_{i},\text{"MEGOLM\_KEYS"},\,80\right) \\ + \end{align} + +where :math:`\parallel` represents string splitting, and +:math:`HKDF\left(salt,\,IKM,\,info,\,L\right)` refers to the `HMAC-based key +derivation function`_ using using `SHA-256`_ as the hash function +(`HKDF-SHA-256`_) with a salt value of :math:`salt`, input key material of +:math:`IKM`, context string :math:`info`, and output keying material length of +:math:`L` bytes. + +The plain-text is encrypted with AES-256, using the key :math:`AES\_KEY_{i}` +and the IV :math:`AES\_IV_{i}` to give the cipher-text, :math:`X_{i}`. + +The ratchet index :math:`i`, and the cipher-text :math:`X_{i}`, are then packed +into a message as described in `Message format`_. Then the entire message +(including the version bytes and all payload bytes) are passed through +HMAC-SHA-256. The first 8 bytes of the MAC are appended to the message. + +Finally, the authenticated message is signed using the Ed25519 keypair; the 64 +byte signature is appended to the message. + +The complete signed message, together with the public part of :math:`K` (acting +as a session identifier), can then be sent over an insecure channel. The +message can then be authenticated and decrypted only by recipients who have +received the session data. + +Advancing the ratchet +~~~~~~~~~~~~~~~~~~~~~ + +After each message is encrypted, the ratchet is advanced. This is done as +described in `The Megolm ratchet algorithm`_, using the following definitions: + +.. math:: + \begin{align} + H_0(A) &\equiv HMAC(A,\text{"\textbackslash x00"}) \\ + H_1(A) &\equiv HMAC(A,\text{"\textbackslash x01"}) \\ + H_2(A) &\equiv HMAC(A,\text{"\textbackslash x02"}) \\ + H_3(A) &\equiv HMAC(A,\text{"\textbackslash x03"}) \\ + \end{align} + +where :math:`HMAC(A, T)` is the HMAC-SHA-256_ of ``T``, using ``A`` as the +key. + +For outbound sessions, the updated ratchet and counter are stored in the +session. + +In order to maintain the ability to decrypt conversation history, inbound +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 +choose to cache calculated ratchet values, but the decision of which ratchet +states to cache is left to the application. + +Data exchange formats +--------------------- + +Session-sharing format +~~~~~~~~~~~~~~~~~~~~~~ + +The Megolm key-sharing format is as follows: + +.. code:: + + +---+----+--------+--------+--------+--------+------+-----------+ + | V | i | R(i,0) | R(i,1) | R(i,2) | R(i,3) | Kpub | Signature | + +---+----+--------+--------+--------+--------+------+-----------+ + 0 1 5 37 69 101 133 165 229 bytes + +The version byte, ``V``, is ``"\x02"``. + +This is followed by the ratchet index, :math:`i`, which is encoded as a +big-endian 32-bit integer; the ratchet values :math:`R_{i,j}`; and the public +part of the Ed25519 keypair :math:`K`. + +The data is then signed using the Ed25519 keypair, and the 64-byte signature is +appended. + +Message format +~~~~~~~~~~~~~~ + +Megolm messages consist of a one byte version, followed by a variable length +payload, a fixed length message authentication code, and a fixed length +signature. + +.. code:: + + +---+------------------------------------+-----------+------------------+ + | V | Payload Bytes | MAC Bytes | Signature Bytes | + +---+------------------------------------+-----------+------------------+ + 0 1 N N+8 N+72 bytes + +The version byte, ``V``, is ``"\x03"``. + +The payload uses a format based on the `Protocol Buffers encoding`_. It +consists of the following key-value pairs: + +============= ===== ======== ================================================ + Name Tag Type Meaning +============= ===== ======== ================================================ +Message-Index 0x08 Integer The index of the ratchet, :math:`i` +Cipher-Text 0x12 String The cipher-text, :math:`X_{i}`, of the message +============= ===== ======== ================================================ + +Within the payload, integers are encoded using a variable length encoding. Each +integer is encoded as a sequence of bytes with the high bit set followed by a +byte with the high bit clear. The seven low bits of each byte store the bits of +the integer. The least significant bits are stored in the first byte. + +Strings are encoded as a variable-length integer followed by the string itself. + +Each key-value pair is encoded as a variable-length integer giving the tag, +followed by a string or variable-length integer giving the value. + +The payload is followed by the MAC. The length of the MAC is determined by the +authenticated encryption algorithm being used (8 bytes in this version of the +protocol). The MAC protects all of the bytes preceding the MAC. + +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 +bytes preceding the signaure. + +License +------- + +The Megolm specification (this document) is licensed under the `Apache License, +Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. + + +.. _`Ed25519`: http://ed25519.cr.yp.to/ +.. _`HMAC-based key derivation function`: https://tools.ietf.org/html/rfc5869 +.. _`HKDF-SHA-256`: https://tools.ietf.org/html/rfc5869 +.. _`HMAC-SHA-256`: https://tools.ietf.org/html/rfc2104 +.. _`SHA-256`: https://tools.ietf.org/html/rfc6234 +.. _`AES-256`: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf +.. _`CBC`: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf +.. _`PCKS#7`: https://tools.ietf.org/html/rfc2315 +.. _`Olm`: ./olm.html +.. _`Protocol Buffers encoding`: https://developers.google.com/protocol-buffers/docs/encoding diff --git a/docs/olm.rst b/docs/olm.rst index 0fb0602..99417e0 100644 --- a/docs/olm.rst +++ b/docs/olm.rst @@ -1,8 +1,8 @@ Olm: A Cryptographic Ratchet ============================ -An implementation of the cryptographic ratchet described by -https://github.com/trevp/axolotl/wiki. +An implementation of the double cryptographic ratchet described by +https://github.com/trevp/double_ratchet/wiki. Notation -------- @@ -16,7 +16,12 @@ When this document uses :math:`ECDH\left(K_A,\,K_B\right)` it means that each party computes a Diffie-Hellman agreement using their private key and the remote party's public key. So party :math:`A` computes :math:`ECDH\left(K_B_public,\,K_A_private\right)` -and party :math:`B` computes :math:`ECDH\left(K_A_public,\,K_B_private\right)` +and party :math:`B` computes :math:`ECDH\left(K_A_public,\,K_B_private\right)`. + +Where this document uses :math:`HKDF\left(salt,\,IKM,\,info,\,L\right)` it +refers to the `HMAC-based key derivation function`_ with a salt value of +:math:`salt`, input key material of :math:`IKM`, context string :math:`info`, +and output keying material length of :math:`L` bytes. The Olm Algorithm ----------------- @@ -36,7 +41,8 @@ HMAC-based Key Derivation Function using SHA-256_ as the hash function \begin{align} S&=ECDH\left(I_A,\,E_B\right)\;\parallel\;ECDH\left(E_A,\,I_B\right)\; \parallel\;ECDH\left(E_A,\,E_B\right)\\ - R_0\;\parallel\;C_{0,0}&=HKDF\left(S,\,\text{"OLM\_ROOT"}\right) + R_0\;\parallel\;C_{0,0}&= + HKDF\left(0,\,S,\,\text{"OLM\_ROOT"},\,64\right) \end{align} Advancing the root key @@ -54,9 +60,10 @@ info. .. math:: \begin{align} R_i\;\parallel\;C_{i,0}&=HKDF\left( - ECDH\left(T_{i-1},\,T_i\right),\, R_{i-1},\, - \text{"OLM\_RATCHET"} + ECDH\left(T_{i-1},\,T_i\right),\, + \text{"OLM\_RATCHET"},\, + 64 \right) \end{align} @@ -64,7 +71,7 @@ info. Advancing the chain key ~~~~~~~~~~~~~~~~~~~~~~~ -Advancing a root key takes the previous chain key, :math:`C_{i,j-i}`. The next +Advancing a chain key takes the previous chain key, :math:`C_{i,j-i}`. The next chain key, :math:`C_{i,j}`, is the HMAC-SHA-256_ of ``"\x02"`` using the previous chain key as the key. @@ -94,25 +101,32 @@ The Olm Protocol Creating an outbound session ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Bob publishes his identity key, :math:`I_B`, and some single-use one-time -keys :math:`E_B`. +Bob publishes the public parts of his identity key, :math:`I_B`, and some +single-use one-time keys :math:`E_B`. Alice downloads Bob's identity key, :math:`I_B`, and a one-time key, -:math:`E_B`. Alice takes her identity key, :math:`I_A`, and generates a new -single-use key, :math:`E_A`. Alice computes a root key, :math:`R_0`, and a -chain key :math:`C_{0,0}`. Alice generates a new ratchet key :math:`T_0`. +:math:`E_B`. She generates a new single-use key, :math:`E_A`, and computes a +root key, :math:`R_0`, and a chain key :math:`C_{0,0}`. She also generates a +new ratchet key :math:`T_0`. Sending the first pre-key messages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Alice computes a message key, :math:`M_{0,j}`, using the current chain key, -:math:`C_{0,j}`. Alice replaces the current chain key with :math:`C_{0,j+1}`. +Alice computes a message key, :math:`M_{0,j}`, and a new chain key, +:math:`C_{0,j+1}`, using the current chain key. She replaces the current chain +key with the new one. + Alice encrypts her plain-text with the message key, :math:`M_{0,j}`, using an authenticated encryption scheme (see below) to get a cipher-text, -:math:`X_{0,j}`. Alice sends her identity key, :math:`I_A`, her single-use key, -:math:`E_A`, Bob's single-use key, :math:`E_B`, the current chain index, -:math:`j`, her ratchet key, :math:`T_0`, and the cipher-text, :math:`X_{0,j}`, -to Bob. +:math:`X_{0,j}`. + +She then sends the following to Bob: + * The public part of her identity key, :math:`I_A` + * The public part of her single-use key, :math:`E_A` + * The public part of Bob's single-use key, :math:`E_B` + * The current chain index, :math:`j` + * The public part of her ratchet key, :math:`T_0` + * The cipher-text, :math:`X_{0,j}` Alice will continue to send pre-key messages until she receives a message from Bob. @@ -120,41 +134,58 @@ Bob. Creating an inbound session from a pre-key message ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Bob receives a pre-key message with Alice's identity key, :math:`I_A`, -Alice's single-use key, :math:`E_A`, the public part of his single-use key, -:math:`E_B`, the current chain index, :math:`j`, Alice's ratchet key, -:math:`T_0`, and the cipher-text, :math:`X_{0,j}`. Bob looks up the private -part of the single-use key, :math:`E_B`. Bob computes the root key :math:`R_0`, -and the chain key :math:`C_{0,0}`. Bob then advances the chain key to compute -the chain key used by the message, :math:`C_{0,j}`. Bob then creates the +Bob receives a pre-key message as above. + +Bob looks up the private part of his single-use key, :math:`E_B`. He can now +compute the root key, :math:`R_0`, and the chain key, :math:`C_{0,0}`, from +:math:`I_A`, :math:`E_A`, :math:`I_B`, and :math:`E_B`. + +Bob then advances the chain key :math:`j` times, to compute the chain key used +by the message, :math:`C_{0,j}`. He now creates the message key, :math:`M_{0,j}`, and attempts to decrypt the cipher-text, :math:`X_{0,j}`. If the cipher-text's authentication is correct then Bob can discard the private part of his single-use one-time key, :math:`E_B`. -Sending messages -~~~~~~~~~~~~~~~~ +Bob stores Alice's initial ratchet key, :math:`T_0`, until he wants to +send a message. + +Sending normal messages +~~~~~~~~~~~~~~~~~~~~~~~ -To send a message the user checks if they have a sender chain key, -:math:`C_{i,j}`. Alice use chain keys where :math:`i` is even. Bob uses chain +Once a message has been received from the other side, a session is considered +established, and a more compact form is used. + +To send a message, the user checks if they have a sender chain key, +:math:`C_{i,j}`. Alice uses chain keys where :math:`i` is even. Bob uses chain keys where :math:`i` is odd. If the chain key doesn't exist then a new ratchet -key :math:`T_i` is generated and a the chain key, :math:`C_{i,0}`, is computed -using :math:`R_{i-1}`, :math:`T_{i-1}` and :math:`T_i`. A message key, +key :math:`T_i` is generated and a new root key :math:`R_i` and chain key +:math:`C_{i,0}` are computed using :math:`R_{i-1}`, :math:`T_{i-1}` and +:math:`T_i`. + +A message key, :math:`M_{i,j}` is computed from the current chain key, :math:`C_{i,j}`, and the chain key is replaced with the next chain key, :math:`C_{i,j+1}`. The plain-text is encrypted with :math:`M_{i,j}`, using an authenticated encryption -scheme (see below) to get a cipher-text, :math:`X_{i,j}`. Then user sends the -current chain index, :math:`j`, the ratchet key, :math:`T_i`, and the -cipher-text, :math:`X_{i,j}`, to the other user. +scheme (see below) to get a cipher-text, :math:`X_{i,j}`. + +The user then sends the following to the recipient: + * The current chain index, :math:`j` + * The public part of the current ratchet key, :math:`T_i` + * The cipher-text, :math:`X_{i,j}` Receiving messages ~~~~~~~~~~~~~~~~~~ -The user receives a message with the current chain index, :math:`j`, the -ratchet key, :math:`T_i`, and the cipher-text, :math:`X_{i,j}`, from the -other user. The user checks if they have a receiver chain with the correct +The user receives a message as above with the sender's current chain index, :math:`j`, +the sender's ratchet key, :math:`T_i`, and the cipher-text, :math:`X_{i,j}`. + +The user checks if they have a receiver chain with the correct :math:`i` by comparing the ratchet key, :math:`T_i`. If the chain doesn't exist -then they compute a new receiver chain, :math:`C_{i,0}`, using :math:`R_{i-1}`, -:math:`T_{i-1}` and :math:`T_i`. If the :math:`j` of the message is less than +then they compute a new root key, :math:`R_i`, and a new receiver chain, with +chain key :math:`C_{i,0}`, using :math:`R_{i-1}`, :math:`T_{i-1}` and +:math:`T_i`. + +If the :math:`j` of the message is less than the current chain index on the receiver then the message may only be decrypted if the receiver has stored a copy of the message key :math:`M_{i,j}`. Otherwise the receiver computes the chain key, :math:`C_{i,j}`. The receiver computes the @@ -170,6 +201,9 @@ they will create a new chain when they next send a message. The Olm Message Format ---------------------- +Olm uses two types of messages. The underlying transport protocol must provide +a means for recipients to distinguish between them. + Normal Messages ~~~~~~~~~~~~~~~ @@ -182,7 +216,7 @@ payload followed by a fixed length message authentication code. | Version Byte | Payload Bytes | MAC Bytes | +--------------+------------------------------------+-----------+ -The version byte is ``"\x01"``. +The version byte is ``"\x03"``. The payload consists of key-value pairs where the keys are integers and the values are integers and strings. The keys are encoded as a variable length @@ -207,7 +241,8 @@ Cipher-Text 0x22 String The cipher-text, :math:`X_{i,j}`, of the message =========== ===== ======== ================================================ The length of the MAC is determined by the authenticated encryption algorithm -being used. The MAC protects all of the bytes preceding the MAC. +being used. (Olm version 1 uses HMAC-SHA-256, truncated to 8 bytes). The +MAC protects all of the bytes preceding the MAC. Pre-Key Messages ~~~~~~~~~~~~~~~~ @@ -221,7 +256,7 @@ length payload. | Version Byte | Payload Bytes | +--------------+------------------------------------+ -The version byte is ``"\x01"``. +The version byte is ``"\x03"``. The payload uses the same key-value format as for normal messages. @@ -245,21 +280,24 @@ Version 1 ~~~~~~~~~ Version 1 of Olm uses AES-256_ in CBC_ mode with `PCKS#7`_ padding for -encryption and HMAC-SHA-256_ for authentication. 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 ``"OLM_KEYS"``. - -First the plain-text is encrypted to get the cipher-text, :math:`X_{i,j}`. -Then the entire message, both the headers and cipher-text, are HMAC'd and the -MAC is appended to the message. +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 +message key using HKDF-SHA-256_ using the default salt and an info of +``"OLM_KEYS"``. .. math:: \begin{align} AES\_KEY_{i,j}\;\parallel\;HMAC\_KEY_{i,j}\;\parallel\;AES\_IV_{i,j} - &= HKDF\left(M_{i,j},\,\text{"OLM\_KEYS"}\right) \\ + &= HKDF\left(0,\,M_{i,j},\text{"OLM\_KEYS"},\,80\right) \\ \end{align} +The plain-text is encrypted with AES-256, using the key :math:`AES\_KEY_{i,j}` +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 +passed through HMAC-SHA-256. The first 8 bytes of the MAC are appended to the message. + IPR --- @@ -274,11 +312,12 @@ Acknowledgements ---------------- The ratchet that Olm implements was designed by Trevor Perrin and Moxie -Marlinspike - details at https://github.com/trevp/axolotl/wiki. Olm is an -entirely new implementation written by the Matrix.org team. +Marlinspike - details at https://github.com/trevp/double_ratchet/wiki. Olm is +an entirely new implementation written by the Matrix.org team. .. _`Curve25519`: http://cr.yp.to/ecdh.html .. _`Triple Diffie-Hellman`: https://whispersystems.org/blog/simplifying-otr-deniability/ +.. _`HMAC-based key derivation function`: https://tools.ietf.org/html/rfc5869 .. _`HKDF-SHA-256`: https://tools.ietf.org/html/rfc5869 .. _`HMAC-SHA-256`: https://tools.ietf.org/html/rfc2104 .. _`SHA-256`: https://tools.ietf.org/html/rfc6234 diff --git a/fuzzers/fuzz_decode_message.cpp b/fuzzers/fuzz_decode_message.cpp new file mode 100644 index 0000000..2ef734c --- /dev/null +++ b/fuzzers/fuzz_decode_message.cpp @@ -0,0 +1,14 @@ +#include "olm/message.hh" +#include "fuzzing.hh" + +int main(int argc, const char *argv[]) { + int message_fd = STDIN_FILENO; + uint8_t * message_buffer; + ssize_t message_length = check_errno( + "Error reading message file", read_file(message_fd, &message_buffer) + ); + olm::MessageReader * reader = new olm::MessageReader; + decode_message(*reader, message_buffer, message_length, 8); + free(message_buffer); + delete reader; +} diff --git a/fuzzers/fuzz_decrypt.cpp b/fuzzers/fuzz_decrypt.cpp new file mode 100644 index 0000000..0b48060 --- /dev/null +++ b/fuzzers/fuzz_decrypt.cpp @@ -0,0 +1,65 @@ +#include "olm/olm.hh" + +#include "fuzzing.hh" + +int main(int argc, const char *argv[]) { + size_t ignored; + if (argc <= 3) { + const char * message = "Usage: decrypt: <session_key> <session_file>" + " <message_type>\n"; + ignored = write(STDERR_FILENO, message, strlen(message)); + exit(3); + } + + const char * key = argv[1]; + size_t key_length = strlen(key); + + + int session_fd = check_errno( + "Error opening session file", open(argv[2], O_RDONLY) + ); + + int message_type = atoi(argv[3]); + + uint8_t *session_buffer; + ssize_t session_length = check_errno( + "Error reading session file", read_file(session_fd, &session_buffer) + ); + + int message_fd = STDIN_FILENO; + uint8_t * message_buffer; + ssize_t message_length = check_errno( + "Error reading message file", read_file(message_fd, &message_buffer) + ); + + uint8_t * tmp_buffer = (uint8_t *) malloc(message_length); + memcpy(tmp_buffer, message_buffer, message_length); + + uint8_t session_memory[olm_session_size()]; + OlmSession * session = olm_session(session_memory); + check_session(session, "Error unpickling session", olm_unpickle_session( + session, key, key_length, session_buffer, session_length + )); + + size_t max_length = check_session( + session, + "Error getting plaintext length", + olm_decrypt_max_plaintext_length( + session, message_type, tmp_buffer, message_length + ) + ); + + uint8_t plaintext[max_length]; + + size_t length = check_session( + session, "Error decrypting message", olm_decrypt( + session, message_type, + message_buffer, message_length, + plaintext, max_length + ) + ); + + ignored = write(STDOUT_FILENO, plaintext, length); + ignored = write(STDOUT_FILENO, "\n", 1); + return ignored; +} diff --git a/fuzzers/fuzz_group_decrypt.cpp b/fuzzers/fuzz_group_decrypt.cpp new file mode 100644 index 0000000..1fc99d7 --- /dev/null +++ b/fuzzers/fuzz_group_decrypt.cpp @@ -0,0 +1,71 @@ +#include "olm/olm.hh" + +#include "fuzzing.hh" + +int main(int argc, const char *argv[]) { + size_t ignored; + if (argc <= 2) { + const char * message = "Usage: decrypt <pickle_key> <group_session>\n"; + ignored = write(STDERR_FILENO, message, strlen(message)); + exit(3); + } + + const char * key = argv[1]; + size_t key_length = strlen(key); + + + int session_fd = check_errno( + "Error opening session file", open(argv[2], O_RDONLY) + ); + + uint8_t *session_buffer; + ssize_t session_length = check_errno( + "Error reading session file", read_file(session_fd, &session_buffer) + ); + + int message_fd = STDIN_FILENO; + uint8_t * message_buffer; + ssize_t message_length = check_errno( + "Error reading message file", read_file(message_fd, &message_buffer) + ); + + uint8_t * tmp_buffer = (uint8_t *) malloc(message_length); + memcpy(tmp_buffer, message_buffer, message_length); + + uint8_t session_memory[olm_inbound_group_session_size()]; + OlmInboundGroupSession * session = olm_inbound_group_session(session_memory); + check_error( + olm_inbound_group_session_last_error, + session, + "Error unpickling session", + olm_unpickle_inbound_group_session( + session, key, key_length, session_buffer, session_length + ) + ); + + size_t max_length = check_error( + olm_inbound_group_session_last_error, + session, + "Error getting plaintext length", + olm_group_decrypt_max_plaintext_length( + session, tmp_buffer, message_length + ) + ); + + uint8_t plaintext[max_length]; + + size_t length = check_error( + olm_inbound_group_session_last_error, + session, + "Error decrypting message", + olm_group_decrypt( + session, + message_buffer, message_length, + plaintext, max_length + ) + ); + + ignored = write(STDOUT_FILENO, plaintext, length); + ignored = write(STDOUT_FILENO, "\n", 1); + return ignored; +} diff --git a/fuzzers/fuzz_unpickle_account.cpp b/fuzzers/fuzz_unpickle_account.cpp new file mode 100644 index 0000000..12c6d9b --- /dev/null +++ b/fuzzers/fuzz_unpickle_account.cpp @@ -0,0 +1,14 @@ +#include "olm/account.hh" +#include "fuzzing.hh" + +int main(int argc, const char *argv[]) { + int pickle_fd = STDIN_FILENO; + uint8_t * pickle_buffer; + ssize_t pickle_length = check_errno( + "Error reading pickle file", read_file(pickle_fd, &pickle_buffer) + ); + olm::Account * account = new olm::Account; + unpickle(pickle_buffer, pickle_buffer + pickle_length, *account); + free(pickle_buffer); + delete account; +} diff --git a/fuzzers/fuzz_unpickle_session.cpp b/fuzzers/fuzz_unpickle_session.cpp new file mode 100644 index 0000000..6edbc96 --- /dev/null +++ b/fuzzers/fuzz_unpickle_session.cpp @@ -0,0 +1,14 @@ +#include "olm/session.hh" +#include "fuzzing.hh" + +int main(int argc, const char *argv[]) { + int pickle_fd = STDIN_FILENO; + uint8_t * pickle_buffer; + ssize_t pickle_length = check_errno( + "Error reading pickle file", read_file(pickle_fd, &pickle_buffer) + ); + olm::Session * session = new olm::Session; + unpickle(pickle_buffer, pickle_buffer + pickle_length, *session); + free(pickle_buffer); + delete session; +} diff --git a/fuzzers/include/fuzzing.hh b/fuzzers/include/fuzzing.hh new file mode 100644 index 0000000..b27c396 --- /dev/null +++ b/fuzzers/include/fuzzing.hh @@ -0,0 +1,82 @@ +#include "olm/olm.hh" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stddef.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + + +ssize_t read_file( + int fd, + uint8_t **buffer +) { + size_t buffer_size = 4096; + uint8_t * current_buffer = (uint8_t *) malloc(buffer_size); + if (current_buffer == NULL) return -1; + size_t buffer_pos = 0; + while (1) { + ssize_t count = read( + fd, current_buffer + buffer_pos, buffer_size - buffer_pos + ); + if (count < 0) break; + if (count == 0) { + uint8_t * return_buffer = (uint8_t *) realloc(current_buffer, buffer_pos); + if (return_buffer == NULL) break; + *buffer = return_buffer; + return buffer_pos; + } + buffer_pos += count; + if (buffer_pos == buffer_size) { + buffer_size *= 2; + uint8_t * new_buffer = (uint8_t *) realloc(current_buffer, buffer_size); + if (new_buffer == NULL) break; + current_buffer = new_buffer; + } + } + free(current_buffer); + return -1; +} + +template<typename T> +T check_errno( + const char * message, + T value +) { + if (value == T(-1)) { + perror(message); + exit(1); + } + return value; +} + +template<typename T, typename F> +size_t check_error( + F f, + T * object, + const char * message, + size_t value +) { + if (value == olm_error()) { + const char * olm_message = f(object); + ssize_t ignored; + ignored = write(STDERR_FILENO, message, strlen(message)); + ignored = write(STDERR_FILENO, ": ", 2); + ignored = write(STDERR_FILENO, olm_message, strlen(olm_message)); + ignored = write(STDERR_FILENO, "\n", 1); + exit(2); + return ignored; + } + return value; +} + +size_t check_session( + OlmSession * session, + const char * message, + size_t value +) { + return check_error(olm_session_last_error, session, message, value); +} diff --git a/include/olm/account.hh b/include/olm/account.hh index 209139a..7e58ca3 100644 --- a/include/olm/account.hh +++ b/include/olm/account.hh @@ -16,8 +16,8 @@ #define OLM_ACCOUNT_HH_ #include "olm/list.hh" -#include "olm/crypto.hh" -#include "olm/error.hh" +#include "olm/crypto.h" +#include "olm/error.h" #include <cstdint> @@ -25,14 +25,14 @@ namespace olm { struct IdentityKeys { - Ed25519KeyPair ed25519_key; - Curve25519KeyPair curve25519_key; + _olm_ed25519_key_pair ed25519_key; + _olm_curve25519_key_pair curve25519_key; }; struct OneTimeKey { std::uint32_t id; bool published; - Curve25519KeyPair key; + _olm_curve25519_key_pair key; }; @@ -44,7 +44,7 @@ struct Account { IdentityKeys identity_keys; List<OneTimeKey, MAX_ONE_TIME_KEYS> one_time_keys; std::uint32_t next_one_time_key_id; - ErrorCode last_error; + OlmErrorCode last_error; /** Number of random bytes needed to create a new account */ std::size_t new_account_random_length(); @@ -128,12 +128,12 @@ struct Account { /** Lookup a one time key with the given public key */ OneTimeKey const * lookup_key( - Curve25519PublicKey const & public_key + _olm_curve25519_public_key const & public_key ); /** Remove a one time key with the given public key */ std::size_t remove_key( - Curve25519PublicKey const & public_key + _olm_curve25519_public_key const & public_key ); }; diff --git a/include/olm/base64.h b/include/olm/base64.h new file mode 100644 index 0000000..80384a8 --- /dev/null +++ b/include/olm/base64.h @@ -0,0 +1,77 @@ +/* Copyright 2015, 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. + */ + +/* C bindings for base64 functions */ + + +#ifndef OLM_BASE64_H_ +#define OLM_BASE64_H_ + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The number of bytes of unpadded base64 needed to encode a length of input. + */ +size_t _olm_encode_base64_length( + size_t input_length +); + +/** + * Encode the raw input as unpadded base64. + * Writes encode_base64_length(input_length) bytes to the output buffer. + * The input can overlap with the last three quarters of the output buffer. + * That is, the input pointer may be output + output_length - input_length. + * + * Returns number of bytes encoded + */ +size_t _olm_encode_base64( + uint8_t const * input, size_t input_length, + uint8_t * output +); + +/** + * The number of bytes of raw data a length of unpadded base64 will encode to. + * Returns size_t(-1) if the length is not a valid length for base64. + */ +size_t _olm_decode_base64_length( + size_t input_length +); + +/** + * Decodes the unpadded base64 input to raw bytes. + * Writes decode_base64_length(input_length) bytes to the output buffer. + * The output can overlap with the first three quarters of the input buffer. + * That is, the input pointers and output pointer may be the same. + * + * Returns number of bytes decoded + */ +size_t _olm_decode_base64( + uint8_t const * input, size_t input_length, + uint8_t * output +); + + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif /* OLM_BASE64_H_ */ diff --git a/include/olm/base64.hh b/include/olm/base64.hh index da4641d..dfdccd0 100644 --- a/include/olm/base64.hh +++ b/include/olm/base64.hh @@ -23,11 +23,9 @@ namespace olm { /** * The number of bytes of unpadded base64 needed to encode a length of input. */ -static std::size_t encode_base64_length( +std::size_t encode_base64_length( std::size_t input_length -) { - return 4 * ((input_length + 2) / 3) + (input_length + 2) % 3 - 2; -} +); /** * Encode the raw input as unpadded base64. diff --git a/include/olm/cipher.h b/include/olm/cipher.h new file mode 100644 index 0000000..b26f8ba --- /dev/null +++ b/include/olm/cipher.h @@ -0,0 +1,138 @@ +/* Copyright 2015 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. + */ + +#ifndef OLM_CIPHER_H_ +#define OLM_CIPHER_H_ + +#include <stdint.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct _olm_cipher; + +struct _olm_cipher_ops { + /** + * Returns the length of the message authentication code that will be + * appended to the output. + */ + size_t (*mac_length)(const struct _olm_cipher *cipher); + + /** + * Returns the length of cipher-text for a given length of plain-text. + */ + size_t (*encrypt_ciphertext_length)( + const struct _olm_cipher *cipher, + size_t plaintext_length + ); + + /* + * Encrypts the plain-text into the output buffer and authenticates the + * contents of the output buffer covering both cipher-text and any other + * associated data in the output buffer. + * + * |---------------------------------------output_length-->| + * output |--ciphertext_length-->| |---mac_length-->| + * ciphertext + * + * The plain-text pointers and cipher-text pointers may be the same. + * + * Returns size_t(-1) if the length of the cipher-text or the output + * buffer is too small. Otherwise returns the length of the output buffer. + */ + size_t (*encrypt)( + const struct _olm_cipher *cipher, + uint8_t const * key, size_t key_length, + uint8_t const * plaintext, size_t plaintext_length, + uint8_t * ciphertext, size_t ciphertext_length, + uint8_t * output, size_t output_length + ); + + /** + * Returns the maximum length of plain-text that a given length of + * cipher-text can contain. + */ + size_t (*decrypt_max_plaintext_length)( + const struct _olm_cipher *cipher, + size_t ciphertext_length + ); + + /** + * Authenticates the input and decrypts the cipher-text into the plain-text + * buffer. + * + * |----------------------------------------input_length-->| + * input |--ciphertext_length-->| |---mac_length-->| + * ciphertext + * + * The plain-text pointers and cipher-text pointers may be the same. + * + * Returns size_t(-1) if the length of the plain-text buffer is too + * small or if the authentication check fails. Otherwise returns the length + * of the plain text. + */ + size_t (*decrypt)( + const struct _olm_cipher *cipher, + uint8_t const * key, size_t key_length, + uint8_t const * input, size_t input_length, + uint8_t const * ciphertext, size_t ciphertext_length, + uint8_t * plaintext, size_t max_plaintext_length + ); +}; + +struct _olm_cipher { + const struct _olm_cipher_ops *ops; + /* cipher-specific fields follow */ +}; + +struct _olm_cipher_aes_sha_256 { + struct _olm_cipher base_cipher; + + /** context string for the HKDF used for deriving the AES256 key, HMAC key, + * and AES IV, from the key material passed to encrypt/decrypt. + */ + uint8_t const * kdf_info; + + /** length of context string kdf_info */ + size_t kdf_info_length; +}; + +extern const struct _olm_cipher_ops _olm_cipher_aes_sha_256_ops; + +/** + * get an initializer for an instance of struct _olm_cipher_aes_sha_256. + * + * To use it, declare: + * + * struct _olm_cipher_aes_sha_256 MY_CIPHER = + * OLM_CIPHER_INIT_AES_SHA_256("MY_KDF"); + * struct _olm_cipher *cipher = OLM_CIPHER_BASE(&MY_CIPHER); + */ +#define OLM_CIPHER_INIT_AES_SHA_256(KDF_INFO) { \ + .base_cipher = { &_olm_cipher_aes_sha_256_ops },\ + .kdf_info = (uint8_t *)(KDF_INFO), \ + .kdf_info_length = sizeof(KDF_INFO) - 1 \ +} +#define OLM_CIPHER_BASE(CIPHER) \ + (&((CIPHER)->base_cipher)) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* OLM_CIPHER_H_ */ diff --git a/include/olm/cipher.hh b/include/olm/cipher.hh deleted file mode 100644 index c561972..0000000 --- a/include/olm/cipher.hh +++ /dev/null @@ -1,132 +0,0 @@ -/* Copyright 2015 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. - */ - -#ifndef OLM_CIPHER_HH_ -#define OLM_CIPHER_HH_ - -#include <cstdint> -#include <cstddef> - -namespace olm { - -class Cipher { -public: - virtual ~Cipher(); - - /** - * Returns the length of the message authentication code that will be - * appended to the output. - */ - virtual std::size_t mac_length() const = 0; - - /** - * Returns the length of cipher-text for a given length of plain-text. - */ - virtual std::size_t encrypt_ciphertext_length( - std::size_t plaintext_length - ) const = 0; - - /* - * Encrypts the plain-text into the output buffer and authenticates the - * contents of the output buffer covering both cipher-text and any other - * associated data in the output buffer. - * - * |---------------------------------------output_length-->| - * output |--ciphertext_length-->| |---mac_length-->| - * ciphertext - * - * The plain-text pointers and cipher-text pointers may be the same. - * - * Returns std::size_t(-1) if the length of the cipher-text or the output - * buffer is too small. Otherwise returns the length of the output buffer. - */ - virtual std::size_t encrypt( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t const * plaintext, std::size_t plaintext_length, - std::uint8_t * ciphertext, std::size_t ciphertext_length, - std::uint8_t * output, std::size_t output_length - ) const = 0; - - /** - * Returns the maximum length of plain-text that a given length of - * cipher-text can contain. - */ - virtual std::size_t decrypt_max_plaintext_length( - std::size_t ciphertext_length - ) const = 0; - - /** - * Authenticates the input and decrypts the cipher-text into the plain-text - * buffer. - * - * |----------------------------------------input_length-->| - * input |--ciphertext_length-->| |---mac_length-->| - * ciphertext - * - * The plain-text pointers and cipher-text pointers may be the same. - * - * Returns std::size_t(-1) if the length of the plain-text buffer is too - * small or if the authentication check fails. Otherwise returns the length - * of the plain text. - */ - virtual std::size_t decrypt( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t const * input, std::size_t input_length, - std::uint8_t const * ciphertext, std::size_t ciphertext_length, - std::uint8_t * plaintext, std::size_t max_plaintext_length - ) const = 0; -}; - - -class CipherAesSha256 : public Cipher { -public: - CipherAesSha256( - std::uint8_t const * kdf_info, std::size_t kdf_info_length - ); - - virtual std::size_t mac_length() const; - - virtual std::size_t encrypt_ciphertext_length( - std::size_t plaintext_length - ) const; - - virtual std::size_t encrypt( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t const * plaintext, std::size_t plaintext_length, - std::uint8_t * ciphertext, std::size_t ciphertext_length, - std::uint8_t * output, std::size_t output_length - ) const; - - virtual std::size_t decrypt_max_plaintext_length( - std::size_t ciphertext_length - ) const; - - virtual std::size_t decrypt( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t const * input, std::size_t input_length, - std::uint8_t const * ciphertext, std::size_t ciphertext_length, - std::uint8_t * plaintext, std::size_t max_plaintext_length - ) const; - -private: - std::uint8_t const * kdf_info; - std::size_t kdf_info_length; -}; - - -} // namespace - - -#endif /* OLM_CIPHER_HH_ */ diff --git a/include/olm/crypto.h b/include/olm/crypto.h new file mode 100644 index 0000000..dbf78ed --- /dev/null +++ b/include/olm/crypto.h @@ -0,0 +1,202 @@ +/* Copyright 2015 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. + */ + +/* C-compatible crpyto utility functions. At some point all of crypto.hh will + * move here. + */ + +#ifndef OLM_CRYPTO_H_ +#define OLM_CRYPTO_H_ + +#include <stdint.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** length of a sha256 hash */ +#define SHA256_OUTPUT_LENGTH 32 + +/** length of a public or private Curve25519 key */ +#define CURVE25519_KEY_LENGTH 32 + +/** length of the shared secret created by a Curve25519 ECDH operation */ +#define CURVE25519_SHARED_SECRET_LENGTH 32 + +/** amount of random data required to create a Curve25519 keypair */ +#define CURVE25519_RANDOM_LENGTH CURVE25519_KEY_LENGTH + +/** length of a public Ed25519 key */ +#define ED25519_PUBLIC_KEY_LENGTH 32 + +/** length of a private Ed25519 key */ +#define ED25519_PRIVATE_KEY_LENGTH 64 + +/** amount of random data required to create a Ed25519 keypair */ +#define ED25519_RANDOM_LENGTH 32 + +/** length of an Ed25519 signature */ +#define ED25519_SIGNATURE_LENGTH 64 + +/** length of an aes256 key */ +#define AES256_KEY_LENGTH 32 + +/** length of an aes256 initialisation vector */ +#define AES256_IV_LENGTH 16 + +struct _olm_aes256_key { + uint8_t key[AES256_KEY_LENGTH]; +}; + +struct _olm_aes256_iv { + uint8_t iv[AES256_IV_LENGTH]; +}; + + +struct _olm_curve25519_public_key { + uint8_t public_key[CURVE25519_KEY_LENGTH]; +}; + +struct _olm_curve25519_private_key { + uint8_t private_key[CURVE25519_KEY_LENGTH]; +}; + +struct _olm_curve25519_key_pair { + struct _olm_curve25519_public_key public_key; + struct _olm_curve25519_private_key private_key; +}; + +struct _olm_ed25519_public_key { + uint8_t public_key[ED25519_PUBLIC_KEY_LENGTH]; +}; + +struct _olm_ed25519_private_key { + uint8_t private_key[ED25519_PRIVATE_KEY_LENGTH]; +}; + +struct _olm_ed25519_key_pair { + struct _olm_ed25519_public_key public_key; + struct _olm_ed25519_private_key private_key; +}; + + +/** The length of output the aes_encrypt_cbc function will write */ +size_t _olm_crypto_aes_encrypt_cbc_length( + size_t input_length +); + +/** Encrypts the input using AES256 in CBC mode with PKCS#7 padding. + * The output buffer must be big enough to hold the output including padding */ +void _olm_crypto_aes_encrypt_cbc( + const struct _olm_aes256_key *key, + const struct _olm_aes256_iv *iv, + const uint8_t *input, size_t input_length, + uint8_t *output +); + +/** Decrypts the input using AES256 in CBC mode. The output buffer must be at + * least the same size as the input buffer. Returns the length of the plaintext + * without padding on success or std::size_t(-1) if the padding is invalid. + */ +size_t _olm_crypto_aes_decrypt_cbc( + const struct _olm_aes256_key *key, + const struct _olm_aes256_iv *iv, + uint8_t const * input, size_t input_length, + uint8_t * output +); + + +/** Computes SHA-256 of the input. The output buffer must be a least + * SHA256_OUTPUT_LENGTH (32) bytes long. */ +void _olm_crypto_sha256( + uint8_t const * input, size_t input_length, + uint8_t * output +); + +/** HMAC: Keyed-Hashing for Message Authentication + * http://tools.ietf.org/html/rfc2104 + * Computes HMAC-SHA-256 of the input for the key. The output buffer must + * be at least SHA256_OUTPUT_LENGTH (32) bytes long. */ +void _olm_crypto_hmac_sha256( + uint8_t const * key, size_t key_length, + uint8_t const * input, size_t input_length, + uint8_t * output +); + + +/** HMAC-based Key Derivation Function (HKDF) + * https://tools.ietf.org/html/rfc5869 + * Derives key material from the input bytes. */ +void _olm_crypto_hkdf_sha256( + uint8_t const * input, size_t input_length, + uint8_t const * info, size_t info_length, + uint8_t const * salt, size_t salt_length, + uint8_t * output, size_t output_length +); + + +/** Generate a curve25519 key pair + * random_32_bytes should be CURVE25519_RANDOM_LENGTH (32) bytes long. + */ +void _olm_crypto_curve25519_generate_key( + uint8_t const * random_32_bytes, + struct _olm_curve25519_key_pair *output +); + + +/** Create a shared secret using our private key and their public key. + * The output buffer must be at least CURVE25519_SHARED_SECRET_LENGTH (32) bytes long. + */ +void _olm_crypto_curve25519_shared_secret( + const struct _olm_curve25519_key_pair *our_key, + const struct _olm_curve25519_public_key *their_key, + uint8_t * output +); + +/** Generate an ed25519 key pair + * random_32_bytes should be ED25519_RANDOM_LENGTH (32) bytes long. + */ +void _olm_crypto_ed25519_generate_key( + uint8_t const * random_bytes, + struct _olm_ed25519_key_pair *output +); + +/** Signs the message using our private key. + * + * The output buffer must be at least ED25519_SIGNATURE_LENGTH (64) bytes + * long. */ +void _olm_crypto_ed25519_sign( + const struct _olm_ed25519_key_pair *our_key, + const uint8_t * message, size_t message_length, + uint8_t * output +); + +/** Verify an ed25519 signature + * The signature input buffer must be ED25519_SIGNATURE_LENGTH (64) bytes long. + * Returns non-zero if the signature is valid. */ +int _olm_crypto_ed25519_verify( + const struct _olm_ed25519_public_key *their_key, + const uint8_t * message, size_t message_length, + const uint8_t * signature +); + + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_CRYPTO_H_ */ diff --git a/include/olm/crypto.hh b/include/olm/crypto.hh deleted file mode 100644 index 7a05f8d..0000000 --- a/include/olm/crypto.hh +++ /dev/null @@ -1,179 +0,0 @@ -/* Copyright 2015 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. - */ -#ifndef OLM_CRYPTO_HH_ -#define OLM_CRYPTO_HH_ - -#include <cstdint> -#include <cstddef> - -namespace olm { - -static const std::size_t KEY_LENGTH = 32; -static const std::size_t SIGNATURE_LENGTH = 64; -static const std::size_t IV_LENGTH = 16; - -struct Curve25519PublicKey { - std::uint8_t public_key[KEY_LENGTH]; -}; - - -struct Curve25519KeyPair : public Curve25519PublicKey { - std::uint8_t private_key[KEY_LENGTH]; -}; - - -struct Ed25519PublicKey { - std::uint8_t public_key[KEY_LENGTH]; -}; - - -struct Ed25519KeyPair : public Ed25519PublicKey { - std::uint8_t private_key[KEY_LENGTH]; -}; - - -/** Generate a curve25519 key pair from 32 random bytes. */ -void curve25519_generate_key( - std::uint8_t const * random_32_bytes, - Curve25519KeyPair & key_pair -); - - -/** Create a shared secret using our private key and their public key. - * The output buffer must be at least 32 bytes long. */ -void curve25519_shared_secret( - Curve25519KeyPair const & our_key, - Curve25519PublicKey const & their_key, - std::uint8_t * output -); - - -/** Signs the message using our private key. - * The output buffer must be at least 64 bytes long. */ -void curve25519_sign( - Curve25519KeyPair const & our_key, - std::uint8_t const * message, std::size_t message_length, - std::uint8_t * output -); - - -/** Verify their message using their public key. - * The signature input buffer must be 64 bytes long. - * Returns true if the signature is valid. */ -bool curve25519_verify( - Curve25519PublicKey const & their_key, - std::uint8_t const * message, std::size_t message_length, - std::uint8_t const * signature -); - -/** Generate a curve25519 key pair from 32 random bytes. */ -void ed25519_generate_key( - std::uint8_t const * random_32_bytes, - Ed25519KeyPair & key_pair -); - - -/** Signs the message using our private key. - * The output buffer must be at least 64 bytes long. */ -void ed25519_sign( - Ed25519KeyPair const & our_key, - std::uint8_t const * message, std::size_t message_length, - std::uint8_t * output -); - - -/** Verify their message using their public key. - * The signature input buffer must be 64 bytes long. - * Returns true if the signature is valid. */ -bool ed25519_verify( - Ed25519PublicKey const & their_key, - std::uint8_t const * message, std::size_t message_length, - std::uint8_t const * signature -); - - -struct Aes256Key { - std::uint8_t key[KEY_LENGTH]; -}; - - -struct Aes256Iv { - std::uint8_t iv[IV_LENGTH]; -}; - - -/** The length of output the aes_encrypt_cbc function will write */ -std::size_t aes_encrypt_cbc_length( - std::size_t input_length -); - - -/** Encrypts the input using AES256 in CBC mode with PKCS#7 padding. - * The output buffer must be big enough to hold the output including padding */ -void aes_encrypt_cbc( - Aes256Key const & key, - Aes256Iv const & iv, - std::uint8_t const * input, std::size_t input_length, - std::uint8_t * output -); - - -/** Decrypts the input using AES256 in CBC mode. The output buffer must be at - * least the same size as the input buffer. Returns the length of the plaintext - * without padding on success or std::size_t(-1) if the padding is invalid. - */ -std::size_t aes_decrypt_cbc( - Aes256Key const & key, - Aes256Iv const & iv, - std::uint8_t const * input, std::size_t input_length, - std::uint8_t * output -); - - -/** Computes SHA-256 of the input. The output buffer must be a least 32 - * bytes long. */ -void sha256( - std::uint8_t const * input, std::size_t input_length, - std::uint8_t * output -); - - -const std::size_t SHA256_OUTPUT_LENGTH = 32; - - -/** HMAC: Keyed-Hashing for Message Authentication - * http://tools.ietf.org/html/rfc2104 - * Computes HMAC-SHA-256 of the input for the key. The output buffer must - * be at least 32 bytes long. */ -void hmac_sha256( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t const * input, std::size_t input_length, - std::uint8_t * output -); - - -/** HMAC-based Key Derivation Function (HKDF) - * https://tools.ietf.org/html/rfc5869 - * Derives key material from the input bytes. */ -void hkdf_sha256( - std::uint8_t const * input, std::size_t input_length, - std::uint8_t const * info, std::size_t info_length, - std::uint8_t const * salt, std::size_t salt_length, - std::uint8_t * output, std::size_t output_length -); - -} // namespace olm - -#endif /* OLM_CRYPTO_HH_ */ diff --git a/include/olm/error.h b/include/olm/error.h new file mode 100644 index 0000000..9d44a94 --- /dev/null +++ b/include/olm/error.h @@ -0,0 +1,65 @@ +/* Copyright 2015-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. + */ +#ifndef OLM_ERROR_H_ +#define OLM_ERROR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +enum OlmErrorCode { + OLM_SUCCESS = 0, /*!< There wasn't an error */ + OLM_NOT_ENOUGH_RANDOM = 1, /*!< Not enough entropy was supplied */ + OLM_OUTPUT_BUFFER_TOO_SMALL = 2, /*!< Supplied output buffer is too small */ + OLM_BAD_MESSAGE_VERSION = 3, /*!< The message version is unsupported */ + OLM_BAD_MESSAGE_FORMAT = 4, /*!< The message couldn't be decoded */ + OLM_BAD_MESSAGE_MAC = 5, /*!< The message couldn't be decrypted */ + OLM_BAD_MESSAGE_KEY_ID = 6, /*!< The message references an unknown key id */ + OLM_INVALID_BASE64 = 7, /*!< The input base64 was invalid */ + OLM_BAD_ACCOUNT_KEY = 8, /*!< The supplied account key is invalid */ + OLM_UNKNOWN_PICKLE_VERSION = 9, /*!< The pickled object is too new */ + OLM_CORRUPTED_PICKLE = 10, /*!< The pickled object couldn't be decoded */ + + OLM_BAD_SESSION_KEY = 11, /*!< Attempt to initialise an inbound group + session from an invalid session key */ + OLM_UNKNOWN_MESSAGE_INDEX = 12, /*!< Attempt to decode a message whose + * index is earlier than our earliest + * known session key. + */ + + /** + * Attempt to unpickle an account which uses pickle version 1 (which did + * not save enough space for the Ed25519 key; the key should be considered + * compromised. We don't let the user reload the account. + */ + OLM_BAD_LEGACY_ACCOUNT_PICKLE = 13, + + /** + * Received message had a bad signature + */ + OLM_BAD_SIGNATURE = 14, + + /* remember to update the list of string constants in error.c when updating + * this list. */ +}; + +/** get a string representation of the given error code. */ +const char * _olm_error_to_string(enum OlmErrorCode error); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_ERROR_H_ */ diff --git a/include/olm/error.hh b/include/olm/error.hh deleted file mode 100644 index b0d3764..0000000 --- a/include/olm/error.hh +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2015 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. - */ -#ifndef ERROR_HH_ -#define ERROR_HH_ - -namespace olm { - -enum struct ErrorCode { - SUCCESS = 0, /*!< There wasn't an error */ - NOT_ENOUGH_RANDOM = 1, /*!< Not enough entropy was supplied */ - OUTPUT_BUFFER_TOO_SMALL = 2, /*!< Supplied output buffer is too small */ - BAD_MESSAGE_VERSION = 3, /*!< The message version is unsupported */ - BAD_MESSAGE_FORMAT = 4, /*!< The message couldn't be decoded */ - BAD_MESSAGE_MAC = 5, /*!< The message couldn't be decrypted */ - BAD_MESSAGE_KEY_ID = 6, /*!< The message references an unknown key id */ - INVALID_BASE64 = 7, /*!< The input base64 was invalid */ - BAD_ACCOUNT_KEY = 8, /*!< The supplied account key is invalid */ - UNKNOWN_PICKLE_VERSION = 9, /*!< The pickled object is too new */ - CORRUPTED_PICKLE = 10, /*!< The pickled object couldn't be decoded */ -}; - -} // namespace olm - -#endif /* ERROR_HH_ */ diff --git a/include/olm/inbound_group_session.h b/include/olm/inbound_group_session.h new file mode 100644 index 0000000..59146c2 --- /dev/null +++ b/include/olm/inbound_group_session.h @@ -0,0 +1,172 @@ +/* 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. + */ +#ifndef OLM_INBOUND_GROUP_SESSION_H_ +#define OLM_INBOUND_GROUP_SESSION_H_ + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OlmInboundGroupSession OlmInboundGroupSession; + +/** get the size of an inbound group session, in bytes. */ +size_t olm_inbound_group_session_size(); + +/** + * Initialise an inbound group session object using the supplied memory + * The supplied memory should be at least olm_inbound_group_session_size() + * bytes. + */ +OlmInboundGroupSession * olm_inbound_group_session( + void *memory +); + +/** + * A null terminated string describing the most recent error to happen to a + * group session */ +const char *olm_inbound_group_session_last_error( + const OlmInboundGroupSession *session +); + +/** Clears the memory used to back this group session */ +size_t olm_clear_inbound_group_session( + OlmInboundGroupSession *session +); + +/** Returns the number of bytes needed to store an inbound group session */ +size_t olm_pickle_inbound_group_session_length( + const OlmInboundGroupSession *session +); + +/** + * Stores a group session as a base64 string. Encrypts the session using the + * supplied key. Returns the length of the session on success. + * + * Returns olm_error() on failure. If the pickle output buffer + * is smaller than olm_pickle_inbound_group_session_length() then + * olm_inbound_group_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" + */ +size_t olm_pickle_inbound_group_session( + OlmInboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + +/** + * Loads a group session from a pickled base64 string. Decrypts the session + * using the supplied key. + * + * Returns olm_error() on failure. If the key doesn't match the one used to + * encrypt the account then olm_inbound_group_session_last_error() will be + * "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then + * olm_inbound_group_session_last_error() will be "INVALID_BASE64". The input + * pickled buffer is destroyed + */ +size_t olm_unpickle_inbound_group_session( + OlmInboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + + +/** + * Start a new inbound group session, based on the parameters supplied. + * + * Returns olm_error() on failure. On failure last_error will be set with an + * error code. The last_error will be: + * + * * OLM_INVALID_BASE64 if the session_key is not valid base64 + * * OLM_BAD_SESSION_KEY if the session_key is invalid + */ +size_t olm_init_inbound_group_session( + OlmInboundGroupSession *session, + /* base64-encoded keys */ + uint8_t const * session_key, size_t session_key_length +); + +/** + * Get an upper bound on the number of bytes of plain-text the decrypt method + * will write for a given input message length. The actual size could be + * different due to padding. + * + * The input message buffer is destroyed. + * + * Returns olm_error() on failure. + */ +size_t olm_group_decrypt_max_plaintext_length( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length +); + +/** + * Decrypt a message. + * + * The input message buffer is destroyed. + * + * Returns the length of the decrypted plain-text, or olm_error() on failure. + * + * On failure last_error will be set with an error code. The last_error will + * be: + * * OLM_OUTPUT_BUFFER_TOO_SMALL if the plain-text buffer is too small + * * OLM_INVALID_BASE64 if the message is not valid base-64 + * * OLM_BAD_MESSAGE_VERSION if the message was encrypted with an unsupported + * version of the protocol + * * OLM_BAD_MESSAGE_FORMAT if the message headers could not be decoded + * * OLM_BAD_MESSAGE_MAC if the message could not be verified + * * OLM_UNKNOWN_MESSAGE_INDEX if we do not have a session key corresponding to the + * message's index (ie, it was sent before the session key was shared with + * us) + */ +size_t olm_group_decrypt( + OlmInboundGroupSession *session, + + /* input; note that it will be overwritten with the base64-decoded + message. */ + uint8_t * message, size_t message_length, + + /* output */ + uint8_t * plaintext, size_t max_plaintext_length +); + + +/** + * Get the number of bytes returned by olm_inbound_group_session_id() + */ +size_t olm_inbound_group_session_id_length( + const OlmInboundGroupSession *session +); + +/** + * Get a base64-encoded identifier for this session. + * + * Returns the length of the session id on success or olm_error() on + * failure. On failure last_error will be set with an error code. The + * last_error will be OUTPUT_BUFFER_TOO_SMALL if the id buffer was too + * small. + */ +size_t olm_inbound_group_session_id( + OlmInboundGroupSession *session, + uint8_t * id, size_t id_length +); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_INBOUND_GROUP_SESSION_H_ */ diff --git a/include/olm/megolm.h b/include/olm/megolm.h new file mode 100644 index 0000000..e4e5d0b --- /dev/null +++ b/include/olm/megolm.h @@ -0,0 +1,95 @@ +/* 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. + */ + +#ifndef OLM_MEGOLM_H_ +#define OLM_MEGOLM_H_ + +/** + * implementation of the Megolm multi-part ratchet used in group chats. + */ + +#include <stdint.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * number of bytes in each part of the ratchet; this should be the same as + * the length of the hash function used in the HMAC (32 bytes for us, as we + * use HMAC-SHA-256) + */ +#define MEGOLM_RATCHET_PART_LENGTH 32 /* SHA256_OUTPUT_LENGTH */ + +/** + * number of parts in the ratchet; the advance() implementations rely on + * this being 4. + */ +#define MEGOLM_RATCHET_PARTS 4 + +#define MEGOLM_RATCHET_LENGTH (MEGOLM_RATCHET_PARTS * MEGOLM_RATCHET_PART_LENGTH) + +typedef struct Megolm { + uint8_t data[MEGOLM_RATCHET_PARTS][MEGOLM_RATCHET_PART_LENGTH]; + uint32_t counter; +} Megolm; + + +/** + * The cipher used in megolm-backed conversations + * + * (AES256 + SHA256, with keys based on an HKDF with info of MEGOLM_KEYS) + */ +extern const struct _olm_cipher *megolm_cipher; + +/** + * initialize the megolm ratchet. random_data should be at least + * MEGOLM_RATCHET_LENGTH bytes of randomness. + */ +void megolm_init(Megolm *megolm, uint8_t const *random_data, uint32_t counter); + +/** Returns the number of bytes needed to store a megolm */ +size_t megolm_pickle_length(const Megolm *megolm); + +/** + * Pickle the megolm. Returns a pointer to the next free space in the buffer. + */ +uint8_t * megolm_pickle(const Megolm *megolm, uint8_t *pos); + +/** + * Unpickle the megolm. Returns a pointer to the next item in the buffer. + */ +const uint8_t * megolm_unpickle(Megolm *megolm, const uint8_t *pos, + const uint8_t *end); + + +/** advance the ratchet by one step */ +void megolm_advance(Megolm *megolm); + +/** + * get the key data in the ratchet. The returned data is + * MEGOLM_RATCHET_LENGTH bytes long. + */ +#define megolm_get_data(megolm) ((const uint8_t *)((megolm)->data)) + +/** advance the ratchet to a given count */ +void megolm_advance_to(Megolm *megolm, uint32_t advance_to); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_MEGOLM_H_ */ diff --git a/include/olm/memory.h b/include/olm/memory.h new file mode 100644 index 0000000..cc346d0 --- /dev/null +++ b/include/olm/memory.h @@ -0,0 +1,41 @@ +/* Copyright 2015, 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. + */ + +/* C bindings for memory functions */ + + +#ifndef OLM_MEMORY_H_ +#define OLM_MEMORY_H_ + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Clear the memory held in the buffer. This is more resilient to being + * optimised away than memset or bzero. + */ +void _olm_unset( + void volatile * buffer, size_t buffer_length +); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif /* OLM_MEMORY_H_ */ diff --git a/include/olm/memory.hh b/include/olm/memory.hh index 128990a..74ff9f8 100644 --- a/include/olm/memory.hh +++ b/include/olm/memory.hh @@ -1,4 +1,4 @@ -/* Copyright 2015 OpenMarket Ltd +/* Copyright 2015, 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. @@ -15,6 +15,9 @@ #include <cstddef> #include <cstdint> #include <cstring> +#include <iomanip> +#include <iostream> +#include <sstream> #include <type_traits> namespace olm { diff --git a/include/olm/message.h b/include/olm/message.h new file mode 100644 index 0000000..61012c9 --- /dev/null +++ b/include/olm/message.h @@ -0,0 +1,93 @@ +/* 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. + */ + +/** + * functions for encoding and decoding messages in the Olm protocol. + * + * Some of these functions have only C++ bindings, and are declared in + * message.hh; in time, they should probably be converted to plain C and + * declared here. + */ + +#ifndef OLM_MESSAGE_H_ +#define OLM_MESSAGE_H_ + +#include <stdint.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The length of the buffer needed to hold a group message. + */ +size_t _olm_encode_group_message_length( + uint32_t chain_index, + size_t ciphertext_length, + size_t mac_length, + size_t signature_length +); + +/** + * Writes the message headers into the output buffer. + * + * version: version number of the olm protocol + * message_index: message index + * ciphertext_length: length of the ciphertext + * output: where to write the output. Should be at least + * olm_encode_group_message_length() bytes long. + * ciphertext_ptr: returns the address that the ciphertext + * should be written to, followed by the MAC and the + * signature. + * + * Returns the size of the message, up to the MAC. + */ +size_t _olm_encode_group_message( + uint8_t version, + uint32_t message_index, + size_t ciphertext_length, + uint8_t *output, + uint8_t **ciphertext_ptr +); + + +struct _OlmDecodeGroupMessageResults { + uint8_t version; + uint32_t message_index; + int has_message_index; + const uint8_t *ciphertext; + size_t ciphertext_length; +}; + + +/** + * Reads the message headers from the input buffer. + */ +void _olm_decode_group_message( + const uint8_t *input, size_t input_length, + size_t mac_length, size_t signature_length, + + /* output structure: updated with results */ + struct _OlmDecodeGroupMessageResults *results +); + + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_MESSAGE_H_ */ diff --git a/include/olm/message.hh b/include/olm/message.hh index 5ce0a62..bd912d9 100644 --- a/include/olm/message.hh +++ b/include/olm/message.hh @@ -12,6 +12,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + + +/** + * functions for encoding and decoding messages in the Olm protocol. + * + * Some of these functions have plain-C bindings, and are declared in + * message.h; in time, all of the functions declared here should probably be + * converted to plain C and moved to message.h. + */ + +#include "message.h" + #include <cstddef> #include <cstdint> diff --git a/include/olm/olm.h b/include/olm/olm.h new file mode 100644 index 0000000..3257e53 --- /dev/null +++ b/include/olm/olm.h @@ -0,0 +1,449 @@ +/* Copyright 2015, 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. + */ + +#ifndef OLM_H_ +#define OLM_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "olm/inbound_group_session.h" +#include "olm/outbound_group_session.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const size_t OLM_MESSAGE_TYPE_PRE_KEY = 0; +static const size_t OLM_MESSAGE_TYPE_MESSAGE = 1; + +typedef struct OlmAccount OlmAccount; +typedef struct OlmSession OlmSession; +typedef struct OlmUtility OlmUtility; + +/** Get the version number of the library. + * Arguments will be updated if non-null. + */ +void olm_get_library_version(uint8_t *major, uint8_t *minor, uint8_t *patch); + +/** The size of an account object in bytes */ +size_t olm_account_size(); + +/** The size of a session object in bytes */ +size_t olm_session_size(); + +/** The size of a utility object in bytes */ +size_t olm_utility_size(); + +/** Initialise an account object using the supplied memory + * The supplied memory must be at least olm_account_size() bytes */ +OlmAccount * olm_account( + void * memory +); + +/** Initialise a session object using the supplied memory + * The supplied memory must be at least olm_session_size() bytes */ +OlmSession * olm_session( + void * memory +); + +/** Initialise a utility object using the supplied memory + * The supplied memory must be at least olm_utility_size() bytes */ +OlmUtility * olm_utility( + void * memory +); + +/** The value that olm will return from a function if there was an error */ +size_t olm_error(); + +/** A null terminated string describing the most recent error to happen to an + * account */ +const char * olm_account_last_error( + OlmAccount * account +); + +/** A null terminated string describing the most recent error to happen to a + * session */ +const char * olm_session_last_error( + OlmSession * session +); + +/** A null terminated string describing the most recent error to happen to a + * utility */ +const char * olm_utility_last_error( + OlmUtility * utility +); + +/** Clears the memory used to back this account */ +size_t olm_clear_account( + OlmAccount * account +); + +/** Clears the memory used to back this session */ +size_t olm_clear_session( + OlmSession * session +); + +/** Clears the memory used to back this utility */ +size_t olm_clear_utility( + OlmUtility * utility +); + +/** Returns the number of bytes needed to store an account */ +size_t olm_pickle_account_length( + OlmAccount * account +); + +/** Returns the number of bytes needed to store a session */ +size_t olm_pickle_session_length( + OlmSession * session +); + +/** Stores an account as a base64 string. Encrypts the account using the + * supplied key. Returns the length of the pickled account on success. + * Returns olm_error() on failure. If the pickle output buffer + * is smaller than olm_pickle_account_length() then + * olm_account_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" */ +size_t olm_pickle_account( + OlmAccount * account, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + +/** Stores a session as a base64 string. Encrypts the session using the + * supplied key. Returns the length of the pickled session on success. + * Returns olm_error() on failure. If the pickle output buffer + * is smaller than olm_pickle_session_length() then + * olm_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" */ +size_t olm_pickle_session( + OlmSession * session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + +/** Loads an account from a pickled base64 string. Decrypts the account using + * the supplied key. Returns olm_error() on failure. If the key doesn't + * match the one used to encrypt the account then olm_account_last_error() + * will be "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then + * olm_account_last_error() will be "INVALID_BASE64". The input pickled + * buffer is destroyed */ +size_t olm_unpickle_account( + OlmAccount * account, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + +/** Loads a session from a pickled base64 string. Decrypts the session using + * the supplied key. Returns olm_error() on failure. If the key doesn't + * match the one used to encrypt the account then olm_session_last_error() + * will be "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then + * olm_session_last_error() will be "INVALID_BASE64". The input pickled + * buffer is destroyed */ +size_t olm_unpickle_session( + OlmSession * session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + +/** The number of random bytes needed to create an account.*/ +size_t olm_create_account_random_length( + OlmAccount * account +); + +/** Creates a new account. Returns olm_error() on failure. If weren't + * enough random bytes then olm_account_last_error() will be + * "NOT_ENOUGH_RANDOM" */ +size_t olm_create_account( + OlmAccount * account, + void * random, size_t random_length +); + +/** The size of the output buffer needed to hold the identity keys */ +size_t olm_account_identity_keys_length( + OlmAccount * account +); + +/** Writes the public parts of the identity keys for the account into the + * identity_keys output buffer. Returns olm_error() on failure. If the + * identity_keys buffer was too small then olm_account_last_error() will be + * "OUTPUT_BUFFER_TOO_SMALL". */ +size_t olm_account_identity_keys( + OlmAccount * account, + void * identity_keys, size_t identity_key_length +); + + +/** The length of an ed25519 signature encoded as base64. */ +size_t olm_account_signature_length( + OlmAccount * account +); + +/** Signs a message with the ed25519 key for this account. Returns olm_error() + * on failure. If the signature buffer was too small then + * olm_account_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" */ +size_t olm_account_sign( + OlmAccount * account, + void const * message, size_t message_length, + void * signature, size_t signature_length +); + +/** The size of the output buffer needed to hold the one time keys */ +size_t olm_account_one_time_keys_length( + OlmAccount * account +); + +/** Writes the public parts of the unpublished one time keys for the account + * into the one_time_keys output buffer. + * <p> + * The returned data is a JSON-formatted object with the single property + * <tt>curve25519</tt>, which is itself an object mapping key id to + * base64-encoded Curve25519 key. For example: + * <pre> + * { + * curve25519: { + * "AAAAAA": "wo76WcYtb0Vk/pBOdmduiGJ0wIEjW4IBMbbQn7aSnTo", + * "AAAAAB": "LRvjo46L1X2vx69sS9QNFD29HWulxrmW11Up5AfAjgU" + * } + * } + * </pre> + * Returns olm_error() on failure. + * <p> + * If the one_time_keys buffer was too small then olm_account_last_error() + * will be "OUTPUT_BUFFER_TOO_SMALL". */ +size_t olm_account_one_time_keys( + OlmAccount * account, + void * one_time_keys, size_t one_time_keys_length +); + +/** Marks the current set of one time keys as being published. */ +size_t olm_account_mark_keys_as_published( + OlmAccount * account +); + +/** The largest number of one time keys this account can store. */ +size_t olm_account_max_number_of_one_time_keys( + OlmAccount * account +); + +/** The number of random bytes needed to generate a given number of new one + * time keys. */ +size_t olm_account_generate_one_time_keys_random_length( + OlmAccount * account, + size_t number_of_keys +); + +/** Generates a number of new one time keys. If the total number of keys stored + * by this account exceeds max_number_of_one_time_keys() then the old keys are + * discarded. Returns olm_error() on error. If the number of random bytes is + * too small then olm_account_last_error() will be "NOT_ENOUGH_RANDOM". */ +size_t olm_account_generate_one_time_keys( + OlmAccount * account, + size_t number_of_keys, + void * random, size_t random_length +); + +/** The number of random bytes needed to create an outbound session */ +size_t olm_create_outbound_session_random_length( + OlmSession * session +); + +/** Creates a new out-bound session for sending messages to a given identity_key + * and one_time_key. Returns olm_error() on failure. If the keys couldn't be + * decoded as base64 then olm_session_last_error() will be "INVALID_BASE64" + * If there weren't enough random bytes then olm_session_last_error() will + * be "NOT_ENOUGH_RANDOM". */ +size_t olm_create_outbound_session( + OlmSession * session, + OlmAccount * account, + void const * their_identity_key, size_t their_identity_key_length, + void const * their_one_time_key, size_t their_one_time_key_length, + void * random, size_t random_length +); + +/** Create a new in-bound session for sending/receiving messages from an + * incoming PRE_KEY message. Returns olm_error() on failure. If the base64 + * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". + * If the message was for an unsupported protocol version then + * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message + * couldn't be decoded then then olm_session_last_error() will be + * "BAD_MESSAGE_FORMAT". If the message refers to an unknown one time + * key then olm_session_last_error() will be "BAD_MESSAGE_KEY_ID". */ +size_t olm_create_inbound_session( + OlmSession * session, + OlmAccount * account, + void * one_time_key_message, size_t message_length +); + +/** Create a new in-bound session for sending/receiving messages from an + * incoming PRE_KEY message. Returns olm_error() on failure. If the base64 + * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". + * If the message was for an unsupported protocol version then + * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message + * couldn't be decoded then then olm_session_last_error() will be + * "BAD_MESSAGE_FORMAT". If the message refers to an unknown one time + * key then olm_session_last_error() will be "BAD_MESSAGE_KEY_ID". */ +size_t olm_create_inbound_session_from( + OlmSession * session, + OlmAccount * account, + void const * their_identity_key, size_t their_identity_key_length, + void * one_time_key_message, size_t message_length +); + +/** The length of the buffer needed to return the id for this session. */ +size_t olm_session_id_length( + OlmSession * session +); + +/** An identifier for this session. Will be the same for both ends of the + * conversation. If the id buffer is too small then olm_session_last_error() + * will be "OUTPUT_BUFFER_TOO_SMALL". */ +size_t olm_session_id( + OlmSession * session, + void * id, size_t id_length +); + +int olm_session_has_received_message( + OlmSession *session +); + +/** 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 + * message in reply. Returns olm_error() on failure. If the base64 + * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". + * If the message was for an unsupported protocol version then + * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message + * couldn't be decoded then then olm_session_last_error() will be + * "BAD_MESSAGE_FORMAT". */ +size_t olm_matches_inbound_session( + OlmSession * session, + void * one_time_key_message, size_t message_length +); + +/** 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 + * message in reply. Returns olm_error() on failure. If the base64 + * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". + * If the message was for an unsupported protocol version then + * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message + * couldn't be decoded then then olm_session_last_error() will be + * "BAD_MESSAGE_FORMAT". */ +size_t olm_matches_inbound_session_from( + OlmSession * session, + void const * their_identity_key, size_t their_identity_key_length, + void * one_time_key_message, size_t message_length +); + +/** Removes the one time keys that the session used from the account. Returns + * olm_error() on failure. If the account doesn't have any matching one time + * keys then olm_account_last_error() will be "BAD_MESSAGE_KEY_ID". */ +size_t olm_remove_one_time_keys( + OlmAccount * account, + OlmSession * session +); + +/** The type of the next message that olm_encrypt() will return. Returns + * OLM_MESSAGE_TYPE_PRE_KEY if the message will be a PRE_KEY message. + * Returns OLM_MESSAGE_TYPE_MESSAGE if the message will be a normal message. + * Returns olm_error on failure. */ +size_t olm_encrypt_message_type( + OlmSession * session +); + +/** The number of random bytes needed to encrypt the next message. */ +size_t olm_encrypt_random_length( + OlmSession * session +); + +/** The size of the next message in bytes for the given number of plain-text + * bytes. */ +size_t olm_encrypt_message_length( + OlmSession * session, + size_t plaintext_length +); + +/** Encrypts a message using the session. Returns the length of the message in + * bytes on success. Writes the message as base64 into the message buffer. + * Returns olm_error() on failure. If the message buffer is too small then + * olm_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL". If there + * weren't enough random bytes then olm_session_last_error() will be + * "NOT_ENOUGH_RANDOM". */ +size_t olm_encrypt( + OlmSession * session, + void const * plaintext, size_t plaintext_length, + void * random, size_t random_length, + void * message, size_t message_length +); + +/** The maximum number of bytes of plain-text a given message could decode to. + * The actual size could be different due to padding. The input message buffer + * is destroyed. Returns olm_error() on failure. If the message base64 + * couldn't be decoded then olm_session_last_error() will be + * "INVALID_BASE64". If the message is for an unsupported version of the + * protocol then olm_session_last_error() will be "BAD_MESSAGE_VERSION". + * If the message couldn't be decoded then olm_session_last_error() will be + * "BAD_MESSAGE_FORMAT". */ +size_t olm_decrypt_max_plaintext_length( + OlmSession * session, + size_t message_type, + void * message, size_t message_length +); + +/** Decrypts a message using the session. The input message buffer is destroyed. + * Returns the length of the plain-text on success. Returns olm_error() on + * failure. If the plain-text buffer is smaller than + * olm_decrypt_max_plaintext_length() then olm_session_last_error() + * will be "OUTPUT_BUFFER_TOO_SMALL". If the base64 couldn't be decoded then + * olm_session_last_error() will be "INVALID_BASE64". If the message is for + * an unsupported version of the protocol then olm_session_last_error() will + * be "BAD_MESSAGE_VERSION". If the message couldn't be decoded then + * olm_session_last_error() will be BAD_MESSAGE_FORMAT". + * If the MAC on the message was invalid then olm_session_last_error() will + * be "BAD_MESSAGE_MAC". */ +size_t olm_decrypt( + OlmSession * session, + size_t message_type, + void * message, size_t message_length, + void * plaintext, size_t max_plaintext_length +); + +/** The length of the buffer needed to hold the SHA-256 hash. */ +size_t olm_sha256_length( + OlmUtility * utility +); + +/** Calculates the SHA-256 hash of the input and encodes it as base64. If the + * output buffer is smaller than olm_sha256_length() then + * olm_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL". */ +size_t olm_sha256( + OlmUtility * utility, + void const * input, size_t input_length, + void * output, size_t output_length +); + +/** Verify an ed25519 signature. If the key was too small then + * olm_session_last_error will be "INVALID_BASE64". If the signature was invalid + * then olm_session_last_error() will be "BAD_MESSAGE_MAC". */ +size_t olm_ed25519_verify( + OlmUtility * utility, + void const * key, size_t key_length, + void const * message, size_t message_length, + void * signature, size_t signature_length +); + +#ifdef __cplusplus +} +#endif + +#endif /* OLM_HH_ */ diff --git a/include/olm/olm.hh b/include/olm/olm.hh index bee1ae4..5ca59c3 100644 --- a/include/olm/olm.hh +++ b/include/olm/olm.hh @@ -1,422 +1,4 @@ -/* Copyright 2015 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. +/* this file exists only for compatibility with existing applications. + * You should use "#include <olm/olm.h>" instead. */ -#ifndef OLM_HH_ -#define OLM_HH_ - -#include <stddef.h> -#include <stdint.h> - -#ifdef __cplusplus -extern "C" { -#endif - -static const size_t OLM_MESSAGE_TYPE_PRE_KEY = 0; -static const size_t OLM_MESSAGE_TYPE_MESSAGE = 1; - -typedef struct OlmAccount OlmAccount; -typedef struct OlmSession OlmSession; -typedef struct OlmUtility OlmUtility; - -/** The size of an account object in bytes */ -size_t olm_account_size(); - -/** The size of a session object in bytes */ -size_t olm_session_size(); - -/** The size of a utility object in bytes */ -size_t olm_utility_size(); - -/** Initialise an account object using the supplied memory - * The supplied memory must be at least olm_account_size() bytes */ -OlmAccount * olm_account( - void * memory -); - -/** Initialise a session object using the supplied memory - * The supplied memory must be at least olm_session_size() bytes */ -OlmSession * olm_session( - void * memory -); - -/** Initialise a utility object using the supplied memory - * The supplied memory must be at least olm_utility_size() bytes */ -OlmUtility * olm_utility( - void * memory -); - -/** The value that olm will return from a function if there was an error */ -size_t olm_error(); - -/** A null terminated string describing the most recent error to happen to an - * account */ -const char * olm_account_last_error( - OlmAccount * account -); - -/** A null terminated string describing the most recent error to happen to a - * session */ -const char * olm_session_last_error( - OlmSession * session -); - -/** A null terminated string describing the most recent error to happen to a - * utility */ -const char * olm_utility_last_error( - OlmUtility * utility -); - -/** Clears the memory used to back this account */ -size_t olm_clear_account( - OlmAccount * account -); - -/** Clears the memory used to back this session */ -size_t olm_clear_session( - OlmSession * session -); - -/** Clears the memory used to back this utility */ -size_t olm_clear_utility( - OlmUtility * utility -); - -/** Returns the number of bytes needed to store an account */ -size_t olm_pickle_account_length( - OlmAccount * account -); - -/** Returns the number of bytes needed to store a session */ -size_t olm_pickle_session_length( - OlmSession * session -); - -/** Stores an account as a base64 string. Encrypts the account using the - * supplied key. Returns the length of the pickled account on success. - * Returns olm_error() on failure. If the pickle output buffer - * is smaller than olm_pickle_account_length() then - * olm_account_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" */ -size_t olm_pickle_account( - OlmAccount * account, - void const * key, size_t key_length, - void * pickled, size_t pickled_length -); - -/** Stores a session as a base64 string. Encrypts the session using the - * supplied key. Returns the length of the pickled session on success. - * Returns olm_error() on failure. If the pickle output buffer - * is smaller than olm_pickle_session_length() then - * olm_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" */ -size_t olm_pickle_session( - OlmSession * session, - void const * key, size_t key_length, - void * pickled, size_t pickled_length -); - -/** Loads an account from a pickled base64 string. Decrypts the account using - * the supplied key. Returns olm_error() on failure. If the key doesn't - * match the one used to encrypt the account then olm_account_last_error() - * will be "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then - * olm_account_last_error() will be "INVALID_BASE64". The input pickled - * buffer is destroyed */ -size_t olm_unpickle_account( - OlmAccount * account, - void const * key, size_t key_length, - void * pickled, size_t pickled_length -); - -/** Loads a session from a pickled base64 string. Decrypts the session using - * the supplied key. Returns olm_error() on failure. If the key doesn't - * match the one used to encrypt the account then olm_session_last_error() - * will be "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then - * olm_session_last_error() will be "INVALID_BASE64". The input pickled - * buffer is destroyed */ -size_t olm_unpickle_session( - OlmSession * session, - void const * key, size_t key_length, - void * pickled, size_t pickled_length -); - -/** The number of random bytes needed to create an account.*/ -size_t olm_create_account_random_length( - OlmAccount * account -); - -/** Creates a new account. Returns olm_error() on failure. If weren't - * enough random bytes then olm_account_last_error() will be - * "NOT_ENOUGH_RANDOM" */ -size_t olm_create_account( - OlmAccount * account, - void * random, size_t random_length -); - -/** The size of the output buffer needed to hold the identity keys */ -size_t olm_account_identity_keys_length( - OlmAccount * account -); - -/** Writes the public parts of the identity keys for the account into the - * identity_keys output buffer. Returns olm_error() on failure. If the - * identity_keys buffer was too small then olm_account_last_error() will be - * "OUTPUT_BUFFER_TOO_SMALL". */ -size_t olm_account_identity_keys( - OlmAccount * account, - void * identity_keys, size_t identity_key_length -); - - -/** The length of an ed25519 signature encoded as base64. */ -size_t olm_account_signature_length( - OlmAccount * account -); - -/** Signs a message with the ed25519 key for this account. Returns olm_error() - * on failure. If the signature buffer was too small then - * olm_account_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" */ -size_t olm_account_sign( - OlmAccount * account, - void const * message, size_t message_length, - void * signature, size_t signature_length -); - -/** The size of the output buffer needed to hold the one time keys */ -size_t olm_account_one_time_keys_length( - OlmAccount * account -); - -/** Writes the public parts of the unpublished one time keys for the account - * into the one_time_keys output buffer. Returns olm_error() on failure. - * If the one_time_keys buffer was too small then olm_account_last_error() - * will be "OUTPUT_BUFFER_TOO_SMALL". */ -size_t olm_account_one_time_keys( - OlmAccount * account, - void * one_time_keys, size_t one_time_keys_length -); - -/** Marks the current set of one time keys as being published. */ -size_t olm_account_mark_keys_as_published( - OlmAccount * account -); - -/** The largest number of one time keys this account can store. */ -size_t olm_account_max_number_of_one_time_keys( - OlmAccount * account -); - -/** The number of random bytes needed to generate a given number of new one - * time keys. */ -size_t olm_account_generate_one_time_keys_random_length( - OlmAccount * account, - size_t number_of_keys -); - -/** Generates a number of new one time keys. If the total number of keys stored - * by this account exceeds max_number_of_one_time_keys() then the old keys are - * discarded. Returns olm_error() on error. If the number of random bytes is - * too small then olm_account_last_error() will be "NOT_ENOUGH_RANDOM". */ -size_t olm_account_generate_one_time_keys( - OlmAccount * account, - size_t number_of_keys, - void * random, size_t random_length -); - -/** The number of random bytes needed to create an outbound session */ -size_t olm_create_outbound_session_random_length( - OlmSession * session -); - -/** Creates a new out-bound session for sending messages to a given identity_key - * and one_time_key. Returns olm_error() on failure. If the keys couldn't be - * decoded as base64 then olm_session_last_error() will be "INVALID_BASE64" - * If there weren't enough random bytes then olm_session_last_error() will - * be "NOT_ENOUGH_RANDOM". */ -size_t olm_create_outbound_session( - OlmSession * session, - OlmAccount * account, - void const * their_identity_key, size_t their_identity_key_length, - void const * their_one_time_key, size_t their_one_time_key_length, - void * random, size_t random_length -); - -/** Create a new in-bound session for sending/receiving messages from an - * incoming PRE_KEY message. Returns olm_error() on failure. If the base64 - * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". - * If the message was for an unsupported protocol version then - * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message - * couldn't be decoded then then olm_session_last_error() will be - * "BAD_MESSAGE_FORMAT". If the message refers to an unknown one time - * key then olm_session_last_error() will be "BAD_MESSAGE_KEY_ID". */ -size_t olm_create_inbound_session( - OlmSession * session, - OlmAccount * account, - void * one_time_key_message, size_t message_length -); - -/** Create a new in-bound session for sending/receiving messages from an - * incoming PRE_KEY message. Returns olm_error() on failure. If the base64 - * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". - * If the message was for an unsupported protocol version then - * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message - * couldn't be decoded then then olm_session_last_error() will be - * "BAD_MESSAGE_FORMAT". If the message refers to an unknown one time - * key then olm_session_last_error() will be "BAD_MESSAGE_KEY_ID". */ -size_t olm_create_inbound_session_from( - OlmSession * session, - OlmAccount * account, - void const * their_identity_key, size_t their_identity_key_length, - void * one_time_key_message, size_t message_length -); - -/** The length of the buffer needed to return the id for this session. */ -size_t olm_session_id_length( - OlmSession * session -); - -/** An identifier for this session. Will be the same for both ends of the - * conversation. If the id buffer is too small then olm_session_last_error() - * will be "OUTPUT_BUFFER_TOO_SMALL". */ -size_t olm_session_id( - OlmSession * session, - void * id, size_t id_length -); - -/** 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 - * message in reply. Returns olm_error() on failure. If the base64 - * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". - * If the message was for an unsupported protocol version then - * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message - * couldn't be decoded then then olm_session_last_error() will be - * "BAD_MESSAGE_FORMAT". */ -size_t olm_matches_inbound_session( - OlmSession * session, - void * one_time_key_message, size_t message_length -); - -/** 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 - * message in reply. Returns olm_error() on failure. If the base64 - * couldn't be decoded then olm_session_last_error will be "INVALID_BASE64". - * If the message was for an unsupported protocol version then - * olm_session_last_error() will be "BAD_MESSAGE_VERSION". If the message - * couldn't be decoded then then olm_session_last_error() will be - * "BAD_MESSAGE_FORMAT". */ -size_t olm_matches_inbound_session_from( - OlmSession * session, - void const * their_identity_key, size_t their_identity_key_length, - void * one_time_key_message, size_t message_length -); - -/** Removes the one time keys that the session used from the account. Returns - * olm_error() on failure. If the account doesn't have any matching one time - * keys then olm_account_last_error() will be "BAD_MESSAGE_KEY_ID". */ -size_t olm_remove_one_time_keys( - OlmAccount * account, - OlmSession * session -); - -/** The type of the next message that olm_encrypt() will return. Returns - * OLM_MESSAGE_TYPE_PRE_KEY if the message will be a PRE_KEY message. - * Returns OLM_MESSAGE_TYPE_MESSAGE if the message will be a normal message. - * Returns olm_error on failure. */ -size_t olm_encrypt_message_type( - OlmSession * session -); - -/** The number of random bytes needed to encrypt the next message. */ -size_t olm_encrypt_random_length( - OlmSession * session -); - -/** The size of the next message in bytes for the given number of plain-text - * bytes. */ -size_t olm_encrypt_message_length( - OlmSession * session, - size_t plaintext_length -); - -/** Encrypts a message using the session. Returns the length of the message in - * bytes on success. Writes the message as base64 into the message buffer. - * Returns olm_error() on failure. If the message buffer is too small then - * olm_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL". If there - * weren't enough random bytes then olm_session_last_error() will be - * "NOT_ENOUGH_RANDOM". */ -size_t olm_encrypt( - OlmSession * session, - void const * plaintext, size_t plaintext_length, - void * random, size_t random_length, - void * message, size_t message_length -); - -/** The maximum number of bytes of plain-text a given message could decode to. - * The actual size could be different due to padding. The input message buffer - * is destroyed. Returns olm_error() on failure. If the message base64 - * couldn't be decoded then olm_session_last_error() will be - * "INVALID_BASE64". If the message is for an unsupported version of the - * protocol then olm_session_last_error() will be "BAD_MESSAGE_VERSION". - * If the message couldn't be decoded then olm_session_last_error() will be - * "BAD_MESSAGE_FORMAT". */ -size_t olm_decrypt_max_plaintext_length( - OlmSession * session, - size_t message_type, - void * message, size_t message_length -); - -/** Decrypts a message using the session. The input message buffer is destroyed. - * Returns the length of the plain-text on success. Returns olm_error() on - * failure. If the plain-text buffer is smaller than - * olm_decrypt_max_plaintext_length() then olm_session_last_error() - * will be "OUTPUT_BUFFER_TOO_SMALL". If the base64 couldn't be decoded then - * olm_session_last_error() will be "INVALID_BASE64". If the message is for - * an unsupported version of the protocol then olm_session_last_error() will - * be "BAD_MESSAGE_VERSION". If the message couldn't be decoded then - * olm_session_last_error() will be BAD_MESSAGE_FORMAT". - * If the MAC on the message was invalid then olm_session_last_error() will - * be "BAD_MESSAGE_MAC". */ -size_t olm_decrypt( - OlmSession * session, - size_t message_type, - void * message, size_t message_length, - void * plaintext, size_t max_plaintext_length -); - -/** The length of the buffer needed to hold the SHA-256 hash. */ -size_t olm_sha256_length( - OlmUtility * utility -); - -/** Calculates the SHA-256 hash of the input and encodes it as base64. If the - * output buffer is smaller than olm_sha256_length() then - * olm_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL". */ -size_t olm_sha256( - OlmUtility * utility, - void const * input, size_t input_length, - void * output, size_t output_length -); - -/** Verify an ed25519 signature. If the key was too small then - * olm_session_last_error will be "INVALID_BASE64". If the signature was invalid - * then olm_session_last_error() will be "BAD_MESSAGE_MAC". */ -size_t olm_ed25519_verify( - OlmUtility * utility, - void const * key, size_t key_length, - void const * message, size_t message_length, - void * signature, size_t signature_length -); - -#ifdef __cplusplus -} -#endif - -#endif /* OLM_HH_ */ +#include "olm/olm.h" diff --git a/include/olm/outbound_group_session.h b/include/olm/outbound_group_session.h new file mode 100644 index 0000000..90ccca3 --- /dev/null +++ b/include/olm/outbound_group_session.h @@ -0,0 +1,181 @@ +/* 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. + */ +#ifndef OLM_OUTBOUND_GROUP_SESSION_H_ +#define OLM_OUTBOUND_GROUP_SESSION_H_ + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OlmOutboundGroupSession OlmOutboundGroupSession; + +/** get the size of an outbound group session, in bytes. */ +size_t olm_outbound_group_session_size(); + +/** + * Initialise an outbound group session object using the supplied memory + * The supplied memory should be at least olm_outbound_group_session_size() + * bytes. + */ +OlmOutboundGroupSession * olm_outbound_group_session( + void *memory +); + +/** + * A null terminated string describing the most recent error to happen to a + * group session */ +const char *olm_outbound_group_session_last_error( + const OlmOutboundGroupSession *session +); + +/** Clears the memory used to back this group session */ +size_t olm_clear_outbound_group_session( + OlmOutboundGroupSession *session +); + +/** Returns the number of bytes needed to store an outbound group session */ +size_t olm_pickle_outbound_group_session_length( + const OlmOutboundGroupSession *session +); + +/** + * Stores a group session as a base64 string. Encrypts the session using the + * supplied key. Returns the length of the session on success. + * + * Returns olm_error() on failure. If the pickle output buffer + * is smaller than olm_pickle_outbound_group_session_length() then + * olm_outbound_group_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL" + */ +size_t olm_pickle_outbound_group_session( + OlmOutboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + +/** + * Loads a group session from a pickled base64 string. Decrypts the session + * using the supplied key. + * + * Returns olm_error() on failure. If the key doesn't match the one used to + * encrypt the account then olm_outbound_group_session_last_error() will be + * "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then + * olm_outbound_group_session_last_error() will be "INVALID_BASE64". The input + * pickled buffer is destroyed + */ +size_t olm_unpickle_outbound_group_session( + OlmOutboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +); + + +/** The number of random bytes needed to create an outbound group session */ +size_t olm_init_outbound_group_session_random_length( + const OlmOutboundGroupSession *session +); + +/** + * Start a new outbound group session. Returns olm_error() on failure. On + * failure last_error will be set with an error code. The last_error will be + * NOT_ENOUGH_RANDOM if the number of random bytes was too small. + */ +size_t olm_init_outbound_group_session( + OlmOutboundGroupSession *session, + uint8_t const * random, size_t random_length +); + +/** + * The number of bytes that will be created by encrypting a message + */ +size_t olm_group_encrypt_message_length( + OlmOutboundGroupSession *session, + size_t plaintext_length +); + +/** + * Encrypt some plain-text. Returns the length of the encrypted message or + * olm_error() on failure. On failure last_error will be set with an + * error code. The last_error will be OUTPUT_BUFFER_TOO_SMALL if the output + * buffer is too small. + */ +size_t olm_group_encrypt( + OlmOutboundGroupSession *session, + uint8_t const * plaintext, size_t plaintext_length, + uint8_t * message, size_t message_length +); + + +/** + * Get the number of bytes returned by olm_outbound_group_session_id() + */ +size_t olm_outbound_group_session_id_length( + const OlmOutboundGroupSession *session +); + +/** + * Get a base64-encoded identifier for this session. + * + * Returns the length of the session id on success or olm_error() on + * failure. On failure last_error will be set with an error code. The + * last_error will be OUTPUT_BUFFER_TOO_SMALL if the id buffer was too + * small. + */ +size_t olm_outbound_group_session_id( + OlmOutboundGroupSession *session, + uint8_t * id, size_t id_length +); + +/** + * Get the current message index for this session. + * + * Each message is sent with an increasing index; this returns the index for + * the next message. + */ +uint32_t olm_outbound_group_session_message_index( + OlmOutboundGroupSession *session +); + +/** + * Get the number of bytes returned by olm_outbound_group_session_key() + */ +size_t olm_outbound_group_session_key_length( + const OlmOutboundGroupSession *session +); + +/** + * Get the base64-encoded current ratchet key for this session. + * + * Each message is sent with a different ratchet key. This function returns the + * ratchet key that will be used for the next message. + * + * Returns the length of the ratchet key on success or olm_error() on + * failure. On failure last_error will be set with an error code. The + * last_error will be OUTPUT_BUFFER_TOO_SMALL if the buffer was too small. + */ +size_t olm_outbound_group_session_key( + OlmOutboundGroupSession *session, + uint8_t * key, size_t key_length +); + + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_OUTBOUND_GROUP_SESSION_H_ */ diff --git a/include/olm/pickle.h b/include/olm/pickle.h new file mode 100644 index 0000000..0e668bb --- /dev/null +++ b/include/olm/pickle.h @@ -0,0 +1,90 @@ +/* Copyright 2015-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. + */ +#ifndef OLM_PICKLE_H_ +#define OLM_PICKLE_H_ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct _olm_ed25519_public_key; +struct _olm_ed25519_key_pair; + + +#define _olm_pickle_uint32_length(value) 4 +uint8_t * _olm_pickle_uint32(uint8_t * pos, uint32_t value); +uint8_t const * _olm_unpickle_uint32( + uint8_t const * pos, uint8_t const * end, + uint32_t *value +); + + +#define _olm_pickle_bool_length(value) 1 +uint8_t * _olm_pickle_bool(uint8_t * pos, int value); +uint8_t const * _olm_unpickle_bool( + uint8_t const * pos, uint8_t const * end, + int *value +); + +#define _olm_pickle_bytes_length(bytes, bytes_length) (bytes_length) +uint8_t * _olm_pickle_bytes(uint8_t * pos, uint8_t const * bytes, + size_t bytes_length); +uint8_t const * _olm_unpickle_bytes(uint8_t const * pos, uint8_t const * end, + uint8_t * bytes, size_t bytes_length); + + +/** Get the number of bytes needed to pickle an ed25519 public key */ +size_t _olm_pickle_ed25519_public_key_length( + const struct _olm_ed25519_public_key * value +); + +/** Pickle the ed25519 public key. Returns a pointer to the next free space in + * the buffer. */ +uint8_t * _olm_pickle_ed25519_public_key( + uint8_t *pos, const struct _olm_ed25519_public_key * value +); + +/** Unpickle the ed25519 public key. Returns a pointer to the next item in the + * buffer. */ +const uint8_t * _olm_unpickle_ed25519_public_key( + const uint8_t *pos, const uint8_t *end, + struct _olm_ed25519_public_key * value +); + +/** Get the number of bytes needed to pickle an ed25519 key pair */ +size_t _olm_pickle_ed25519_key_pair_length( + const struct _olm_ed25519_key_pair * value +); + +/** Pickle the ed25519 key pair. Returns a pointer to the next free space in + * the buffer. */ +uint8_t * _olm_pickle_ed25519_key_pair( + uint8_t *pos, const struct _olm_ed25519_key_pair * value +); + +/** Unpickle the ed25519 key pair. Returns a pointer to the next item in the + * buffer. */ +const uint8_t * _olm_unpickle_ed25519_key_pair( + const uint8_t *pos, const uint8_t *end, + struct _olm_ed25519_key_pair * value +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_PICKLE_H */ diff --git a/include/olm/pickle.hh b/include/olm/pickle.hh index 27f1f26..4cf3f36 100644 --- a/include/olm/pickle.hh +++ b/include/olm/pickle.hh @@ -16,65 +16,45 @@ #define OLM_PICKLE_HH_ #include "olm/list.hh" -#include "olm/crypto.hh" +#include "olm/crypto.h" #include <cstring> #include <cstdint> namespace olm { -static std::size_t pickle_length( +inline std::size_t pickle_length( const std::uint32_t & value ) { return 4; } - -static std::uint8_t * pickle( +std::uint8_t * pickle( std::uint8_t * pos, std::uint32_t value -) { - pos += 4; - for (unsigned i = 4; i--;) { *(--pos) = value; value >>= 8; } - return pos + 4; -} - +); -static std::uint8_t const * unpickle( +std::uint8_t const * unpickle( std::uint8_t const * pos, std::uint8_t const * end, std::uint32_t & value -) { - value = 0; - if (end - pos < 4) return end; - for (unsigned i = 4; i--;) { value <<= 8; value |= *(pos++); } - return pos; -} +); + -static std::size_t pickle_length( +inline std::size_t pickle_length( const bool & value ) { return 1; } - -static std::uint8_t * pickle( +std::uint8_t * pickle( std::uint8_t * pos, bool value -) { - *(pos++) = value ? 1 : 0; - return pos; -} - +); -static std::uint8_t const * unpickle( +std::uint8_t const * unpickle( std::uint8_t const * pos, std::uint8_t const * end, bool & value -) { - if (pos == end) return end; - value = *(pos++); - return pos; -} - +); template<typename T, std::size_t max_size> @@ -117,90 +97,48 @@ std::uint8_t const * unpickle( } -static std::uint8_t * pickle_bytes( +std::uint8_t * pickle_bytes( std::uint8_t * pos, std::uint8_t const * bytes, std::size_t bytes_length -) { - std::memcpy(pos, bytes, bytes_length); - return pos + bytes_length; -} - - -static std::uint8_t const * unpickle_bytes( - std::uint8_t const * pos, std::uint8_t const * end, - std::uint8_t * bytes, std::size_t bytes_length -) { - if (end - pos < bytes_length) return end; - std::memcpy(bytes, pos, bytes_length); - return pos + bytes_length; -} - - -std::size_t pickle_length( - const Curve25519PublicKey & value ); - -std::uint8_t * pickle( - std::uint8_t * pos, - const Curve25519PublicKey & value -); - - -std::uint8_t const * unpickle( +std::uint8_t const * unpickle_bytes( std::uint8_t const * pos, std::uint8_t const * end, - Curve25519PublicKey & value -); - - -std::size_t pickle_length( - const Curve25519KeyPair & value -); - - -std::uint8_t * pickle( - std::uint8_t * pos, - const Curve25519KeyPair & value -); - - -std::uint8_t const * unpickle( - std::uint8_t const * pos, std::uint8_t const * end, - Curve25519KeyPair & value + std::uint8_t * bytes, std::size_t bytes_length ); std::size_t pickle_length( - const Ed25519PublicKey & value + const _olm_curve25519_public_key & value ); std::uint8_t * pickle( std::uint8_t * pos, - const Ed25519PublicKey & value + const _olm_curve25519_public_key & value ); std::uint8_t const * unpickle( std::uint8_t const * pos, std::uint8_t const * end, - Ed25519PublicKey & value + _olm_curve25519_public_key & value ); std::size_t pickle_length( - const Ed25519KeyPair & value + const _olm_curve25519_key_pair & value ); std::uint8_t * pickle( std::uint8_t * pos, - const Ed25519KeyPair & value + const _olm_curve25519_key_pair & value ); std::uint8_t const * unpickle( std::uint8_t const * pos, std::uint8_t const * end, - Ed25519KeyPair & value + _olm_curve25519_key_pair & value ); } // namespace olm diff --git a/include/olm/pickle_encoding.h b/include/olm/pickle_encoding.h new file mode 100644 index 0000000..03611df --- /dev/null +++ b/include/olm/pickle_encoding.h @@ -0,0 +1,76 @@ +/* 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. + */ + +/* functions for encrypting and decrypting pickled representations of objects */ + +#ifndef OLM_PICKLE_ENCODING_H_ +#define OLM_PICKLE_ENCODING_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "olm/error.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Get the number of bytes needed to encode a pickle of the length given + */ +size_t _olm_enc_output_length(size_t raw_length); + +/** + * Get the point in the output buffer that the raw pickle should be written to. + * + * In order that we can use the same buffer for the raw pickle, and the encoded + * pickle, the raw pickle needs to be written at the end of the buffer. (The + * base-64 encoding would otherwise overwrite the end of the input before it + * was encoded.) + */ + uint8_t *_olm_enc_output_pos(uint8_t * output, size_t raw_length); + +/** + * Encrypt and encode the given pickle in-situ. + * + * The raw pickle should have been written to enc_output_pos(pickle, + * raw_length). + * + * Returns the number of bytes in the encoded pickle. + */ +size_t _olm_enc_output( + uint8_t const * key, size_t key_length, + uint8_t *pickle, size_t raw_length +); + +/** + * Decode and decrypt the given pickle in-situ. + * + * Returns the number of bytes in the decoded pickle, or olm_error() on error, + * in which case *last_error will be updated, if last_error is non-NULL. + */ +size_t _olm_enc_input( + uint8_t const * key, size_t key_length, + uint8_t * input, size_t b64_length, + enum OlmErrorCode * last_error +); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OLM_PICKLE_ENCODING_H_ */ diff --git a/include/olm/ratchet.hh b/include/olm/ratchet.hh index 2393e5b..2e87e35 100644 --- a/include/olm/ratchet.hh +++ b/include/olm/ratchet.hh @@ -13,23 +13,29 @@ * limitations under the License. */ -#include "olm/crypto.hh" +#include <cstdint> + +#include "olm/crypto.h" #include "olm/list.hh" -#include "olm/error.hh" +#include "olm/error.h" -namespace olm { +struct _olm_cipher; -class Cipher; +namespace olm { -typedef std::uint8_t SharedKey[olm::KEY_LENGTH]; +/** length of a shared key: the root key R(i), chain key C(i,j), and message key + * M(i,j)). They are all only used to stuff into HMACs, so could be any length + * for that. The chain key and message key are both derived from SHA256 + * operations, so their length is determined by that. */ +const std::size_t OLM_SHARED_KEY_LENGTH = SHA256_OUTPUT_LENGTH; +typedef std::uint8_t SharedKey[OLM_SHARED_KEY_LENGTH]; struct ChainKey { std::uint32_t index; SharedKey key; }; - struct MessageKey { std::uint32_t index; SharedKey key; @@ -37,19 +43,19 @@ struct MessageKey { struct SenderChain { - Curve25519KeyPair ratchet_key; + _olm_curve25519_key_pair ratchet_key; ChainKey chain_key; }; struct ReceiverChain { - Curve25519PublicKey ratchet_key; + _olm_curve25519_public_key ratchet_key; ChainKey chain_key; }; struct SkippedMessageKey { - Curve25519PublicKey ratchet_key; + _olm_curve25519_public_key ratchet_key; MessageKey message_key; }; @@ -70,20 +76,20 @@ struct Ratchet { Ratchet( KdfInfo const & kdf_info, - Cipher const & ratchet_cipher + _olm_cipher const *ratchet_cipher ); /** A some strings identifying the application to feed into the KDF. */ KdfInfo const & kdf_info; /** The AEAD cipher to use for encrypting messages. */ - Cipher const & ratchet_cipher; + _olm_cipher const *ratchet_cipher; /** The last error that happened encrypting or decrypting a message. */ - ErrorCode last_error; + OlmErrorCode last_error; /** The root key is used to generate chain keys from the ephemeral keys. - * A new root_key derived each time a chain key is derived. */ + * A new root_key derived each time a new chain is started. */ SharedKey root_key; /** The sender chain is used to send messages. Each time a new ephemeral @@ -104,14 +110,14 @@ struct Ratchet { * remote's first ratchet key */ void initialise_as_bob( std::uint8_t const * shared_secret, std::size_t shared_secret_length, - Curve25519PublicKey const & their_ratchet_key + _olm_curve25519_public_key const & their_ratchet_key ); /** Initialise the session using a shared secret and the public/private key * pair for the first ratchet key */ void initialise_as_alice( std::uint8_t const * shared_secret, std::size_t shared_secret_length, - Curve25519KeyPair const & our_ratchet_key + _olm_curve25519_key_pair const & our_ratchet_key ); /** The number of bytes of output the encrypt method will write for @@ -170,7 +176,8 @@ std::uint8_t * pickle( std::uint8_t const * unpickle( std::uint8_t const * pos, std::uint8_t const * end, - Ratchet & value + Ratchet & value, + bool includes_chain_index ); diff --git a/include/olm/session.hh b/include/olm/session.hh index b21b0aa..9d44816 100644 --- a/include/olm/session.hh +++ b/include/olm/session.hh @@ -19,7 +19,7 @@ namespace olm { -class Account; +struct Account; enum struct MessageType { PRE_KEY = 0, @@ -31,13 +31,13 @@ struct Session { Session(); Ratchet ratchet; - ErrorCode last_error; + OlmErrorCode last_error; bool received_message; - Curve25519PublicKey alice_identity_key; - Curve25519PublicKey alice_base_key; - Curve25519PublicKey bob_one_time_key; + _olm_curve25519_public_key alice_identity_key; + _olm_curve25519_public_key alice_base_key; + _olm_curve25519_public_key bob_one_time_key; /** The number of random bytes that are needed to create a new outbound * session. This will be 64 bytes since two ephemeral keys are needed. */ @@ -48,8 +48,8 @@ struct Session { * NOT_ENOUGH_RANDOM if the number of random bytes was too small. */ std::size_t new_outbound_session( Account const & local_account, - Curve25519PublicKey const & identity_key, - Curve25519PublicKey const & one_time_key, + _olm_curve25519_public_key const & identity_key, + _olm_curve25519_public_key const & one_time_key, std::uint8_t const * random, std::size_t random_length ); @@ -59,7 +59,7 @@ struct Session { * the message headers could not be decoded. */ std::size_t new_inbound_session( Account & local_account, - Curve25519PublicKey const * their_identity_key, + _olm_curve25519_public_key const * their_identity_key, std::uint8_t const * pre_key_message, std::size_t message_length ); @@ -82,7 +82,7 @@ struct Session { * session does not match or the pre-key message could not be decoded. */ bool matches_inbound_session( - Curve25519PublicKey const * their_identity_key, + _olm_curve25519_public_key const * their_identity_key, std::uint8_t const * pre_key_message, std::size_t message_length ); diff --git a/include/olm/utility.hh b/include/olm/utility.hh index 5329a59..d650abc 100644 --- a/include/olm/utility.hh +++ b/include/olm/utility.hh @@ -16,20 +16,20 @@ #ifndef UTILITY_HH_ #define UTILITY_HH_ -#include "olm/error.hh" +#include "olm/error.h" #include <cstddef> #include <cstdint> -namespace olm { +struct _olm_ed25519_public_key; -class Ed25519PublicKey; +namespace olm { struct Utility { Utility(); - ErrorCode last_error; + OlmErrorCode last_error; /** The length of a SHA-256 hash in bytes. */ std::size_t sha256_length(); @@ -48,7 +48,7 @@ struct Utility { * last_error will be set with an error code. If the signature was too short * or was not a valid signature then last_error will be BAD_MESSAGE_MAC. */ std::size_t ed25519_verify( - Ed25519PublicKey const & key, + _olm_ed25519_public_key const & key, std::uint8_t const * message, std::size_t message_length, std::uint8_t const * signature, std::size_t signature_length ); diff --git a/javascript/.gitignore b/javascript/.gitignore new file mode 100644 index 0000000..603fe7c --- /dev/null +++ b/javascript/.gitignore @@ -0,0 +1,4 @@ +/exported_functions.json +/node_modules +/npm-debug.log +/olm.js diff --git a/javascript/README.md b/javascript/README.md index 6902e36..6ed9bbb 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -22,4 +22,21 @@ Example: bob_session.create_inbound(bob, bob_message); var plaintext = bob_session.decrypt(message_1.type, bob_message); - bob.remove_one_time_keys(bob_session);
\ No newline at end of file + bob.remove_one_time_keys(bob_session); + + +Group chat: + + var outbound_session = new Olm.OutboundGroupSession(); + outbound_session.create(); + + // exchange these over a secure channel + var session_id = group_session.session_id(); + var session_key = group_session.session_key(); + var message_index = group_session.message_index(); + + var inbound_session = new Olm.InboundGroupSession(); + inbound_session.create(message_index, session_key); + + var ciphertext = outbound_session.encrypt("Hello"); + var plaintext = inbound_session.decrypt(ciphertext); diff --git a/javascript/build.py b/javascript/build.py deleted file mode 100755 index 9766906..0000000 --- a/javascript/build.py +++ /dev/null @@ -1,72 +0,0 @@ -#! /usr/bin/python -# Copyright 2015 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. - -import subprocess -import glob -import os -import sys -import re -import json - -source_files = glob.glob("src/*.cpp") -pre_js, = glob.glob("javascript/*pre.js") -post_js, = glob.glob("javascript/*post.js") - -if not os.path.exists("build"): - os.mkdir("build") - -functions = set() -RE_FUNCTION=re.compile("(olm_[^( ]*)\\(") -with open("include/olm/olm.hh") as header: - for line in header: - match = RE_FUNCTION.search(line) - if match: - functions.add(match.groups()[0]) - - -exported_functions = os.path.abspath("build/exported_functions.json") -with open(exported_functions, "w") as json_file: - json.dump(["_" + function for function in functions], json_file) - - -emcc = os.environ.get("EMCC", "emcc") - -compile_args = [emcc] -compile_args += """ - -O3 - -Iinclude - -Ilib - -std=c++11 - --closure 1 - --memory-init-file 0 - -s NO_FILESYSTEM=1 - -s NO_BROWSER=1 - -s INVOKE_RUN=0 -""".split() -compile_args += source_files -compile_args += ("--pre-js", pre_js) -compile_args += ("--post-js", post_js) -compile_args += ("-s", "EXPORTED_FUNCTIONS=@" + exported_functions) -compile_args += sys.argv[1:] - -library = "build/olm.js" - -def run(args): - print args - print " ".join(args) - subprocess.check_call(args) - -run(compile_args + ["-o", library]) - diff --git a/javascript/demo/demo.css b/javascript/demo/demo.css new file mode 100644 index 0000000..bf07df1 --- /dev/null +++ b/javascript/demo/demo.css @@ -0,0 +1,8 @@ +div.user { + width: 500px; + float: left; + overflow: scroll; + margin: 0px 20px 0px 0px; + border: 1px solid black; + padding: 5px; +}
\ No newline at end of file diff --git a/javascript/demo/group_demo.html b/javascript/demo/group_demo.html new file mode 100644 index 0000000..31aacb0 --- /dev/null +++ b/javascript/demo/group_demo.html @@ -0,0 +1,61 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="demo.css"/> + <script src="../olm.js"></script> + <script src="group_demo.js"></script> + </head> +<body> +<div id="user1" class="user"> + <h1>User1</h1> + + <textarea class="user_plain_input"></textarea> + <button class="user_encrypt">Encrypt</button> + + <h2>Outgoing</h2> + + <h3>One-to-one output</h3> + <div class="user_cipher_output"></div> + + <h3>Group output</h3> + <div class="group_output"></div> + + <h2>Incoming</h2> + + <h3>One-to-one Received</h3> + <div class="user_cipher_input"></div> + + <h3>Group received</h3> + <div class="group_input"></div> + + <h2>Tasks</h2> + <div class="user_progress"></div> +</div> + +<div id="user2" class="user"> + <h1>User 2</h1> + + <textarea class="user_plain_input"></textarea> + <button class="user_encrypt">Encrypt</button> + + <h2>Outgoing</h2> + + <h3>One-to-one output</h3> + <div class="user_cipher_output"></div> + + <h3>Group output</h3> + <div class="group_output"></div> + + <h2>Incoming</h2> + + <h3>One-to-one Received</h3> + <div class="user_cipher_input"></div> + + <h3>Group received</h3> + <div class="group_input"></div> + + <h2>Tasks</h2> + <div class="user_progress"></div> +</div> + +</body> +</html> diff --git a/javascript/demo/group_demo.js b/javascript/demo/group_demo.js new file mode 100644 index 0000000..1b8f7ab --- /dev/null +++ b/javascript/demo/group_demo.js @@ -0,0 +1,492 @@ +/* Javascript parts of the group demo. To use, load group_demo.html in your + * browser. + */ + +function buttonElement(buttonLabel, clickHandler) { + var button = document.createElement("button"); + button.appendChild(document.createTextNode(buttonLabel)); + button.addEventListener("click", clickHandler, false); + return button; +} + +function buttonsAndText(textContent, buttonLabelToHandlerMap) { + var el = document.createElement("div"); + for (var label in buttonLabelToHandlerMap) { + if (!buttonLabelToHandlerMap.hasOwnProperty(label)) { + continue; + } + var handler = buttonLabelToHandlerMap[label]; + var button = buttonElement(label, handler); + el.appendChild(button); + } + + var message_element = document.createElement("tt"); + el.appendChild(message_element); + + var content = document.createTextNode(textContent); + message_element.appendChild(content); + + return el; +} + +function buttonAndTextElement(buttonLabel, textContent, clickHandler) { + var buttonMap = {}; + buttonMap[buttonLabel] = clickHandler; + return buttonsAndText(textContent, buttonMap); +} + +function DemoUser(name) { + this.name = name; + this.olmAccount = new Olm.Account(); + this.olmAccount.create(); + + this.idKey = this.getIdKeys()["curve25519"]; + + /* the people in our chat, indexed by their Curve25519 identity key. + */ + this.peers = {}; + + /* the Ed25519 signing key for each peer, indexed by their Curve25519 id key + */ + this.peerSigningKeys = {}; + + /* for each peer, a one-to-one session - indexed by id key and created on + * demand */ + this.peerSessions = {}; + + /* for each peer, info on their sender session - indexed by id key and + * session id */ + this.peerGroupSessions = {}; + + /* our outbound group session */ + this.groupSession = undefined; + + /* a list of pending tasks */ + this.tasks = []; + this.taskWorker = undefined; + + /* the operations our peers are allowed to do on us */ + var publicOps = [ + "getIdKeys", "getOneTimeKey", + "receiveOneToOne", "receiveGroup", + ]; + + this.remoteOps = {}; + for (var i=0; i<publicOps.length; i++) { + var op = publicOps[i]; + this.remoteOps[op] = this[op].bind(this); + } +} + +DemoUser.prototype._progress = function(message) { + var progress = this.progressElement; + + var message_element = document.createElement("pre"); + var start_content = document.createTextNode(message + "..."); + function start() { + message_element.appendChild(start_content); + progress.appendChild(message_element); + } + function done(res) { + var done_content = document.createTextNode(message + "..." + res); + message_element.replaceChild(done_content, start_content); + } + return {start:start, done:done}; +}; + +DemoUser.prototype._do_tasks = function() { + var self = this; + var task = self.tasks.shift(); + var desc = task[0]; + var func = task[1]; + var callback = task[2]; + + var p = self._progress(desc); + p.start(); + + function done() { + p.done("Done"); + + if (callback) { + try { + callback.apply(undefined, arguments) + } catch (e) { + console.error("Uncaught exception in callback", e.stack || e); + } + } + + start_tasks(); + } + + // sleep 50ms before actually doing the task + self.taskWorker = window.setTimeout(function() { + try { + task[1](done); + } catch (e) { + console.error("Uncaught exception in task", e.stack || e); + p.done("Failed: "+e); + start_tasks(); + } + }, 50); + + + function start_tasks() { + if (self.tasks.length == 0) { + self.taskWorker = undefined; + return; + } + + self.taskWorker = window.setTimeout(self._do_tasks.bind(self), 50); + } +} + +/** + * add a function "task" to this user's queue of things to do. + * + * task is called with a single argument 'done' which is a function to call + * once the task is complete. + * + * 'callback' is called once the task is complete, with any arguments that + * were passed to 'done'. + */ +DemoUser.prototype.addTask = function(description, task, callback) { + this.tasks.push([description, task, callback]); + if(!this.taskWorker) { + this._do_tasks(); + } +}; + +DemoUser.prototype.addPeer = function(peerOps) { + var keys = peerOps.getIdKeys(); + var id = keys["curve25519"]; + this.peers[id] = peerOps; + this.peerSigningKeys[id] = keys["ed25519"]; +}; + +DemoUser.prototype.getIdKeys = function() { + return JSON.parse(this.olmAccount.identity_keys()); +}; + +DemoUser.prototype.generateKeys = function(callback) { + var self = this; + this.addTask("generate one time key", function(done) { + self.olmAccount.generate_one_time_keys(1); + done(); + }, callback); +}; + +DemoUser.prototype.getOneTimeKey = function() { + var self = this; + var keys = JSON.parse(self.olmAccount.one_time_keys()) + .curve25519; + for (key_id in keys) { + if (keys.hasOwnProperty(key_id)) { + return keys[key_id]; + } + } + throw new Error("No one-time-keys generated"); +}; + +/* ************************************************************************ + * + * one-to-one messaging + */ + +/** + * retrieve, or initiate, a one-to-one session to a given peer + */ +DemoUser.prototype.getPeerSession = function(peerId, callback) { + var self = this; + + if (this.peerSessions[peerId]) { + callback(this.peerSessions[peerId]); + return; + } + + var peer = this.peers[peerId]; + this.addTask("get peer keys", function(done) { + key = peer.getOneTimeKey(); + done(key); + }, function(ot_key) { + self.addTask("create peer session", function(done) { + var session = new Olm.Session(); + session.create_outbound(self.olmAccount, peerId, ot_key); + self.peerSessions[peerId] = session; + done(session); + }, callback); + }); +}; + +/** + * encrypt a one-to-one message and prepare it for sending to a peer + */ +DemoUser.prototype.sendToPeer = function(peerId, message, callback) { + var self = this; + this.getPeerSession(peerId, function(session) { + self.addTask("encrypt one-to-one message", function(done) { + var encrypted = session.encrypt(message); + var packet = { + sender_key: self.idKey, + ciphertext: encrypted, + }; + var json = JSON.stringify(packet); + + var el = buttonAndTextElement("send", json, function(ev) { + self.peers[peerId].receiveOneToOne(json); + }); + self.cipherOutputDiv.appendChild(el); + done(); + }, callback); + }); +}; + +/** + * handler for receiving a one-to-one message + */ +DemoUser.prototype.receiveOneToOne = function(jsonpacket) { + var self = this; + var el = buttonAndTextElement("decrypt", jsonpacket, function(ev) { + var sender = JSON.parse(jsonpacket).sender_key; + self.decryptOneToOne(jsonpacket, function(result) { + + var el2 = document.createElement("tt"); + el.appendChild(el2); + + var content = document.createTextNode(" -> "+result); + el2.appendChild(content); + + var body = JSON.parse(result); + + // create a new inbound session if we don't yet have one + if (!self.peerGroupSessions[sender] || + !self.peerGroupSessions[sender][body.session_id]) { + self.createInboundSession( + sender, body.session_id, body.session_key + ); + } + }); + }); + this.cipherInputDiv.appendChild(el); +}; + +/** + * add a task to decrypt a one-to-one message. Calls the callback with the + * decrypted plaintext + */ +DemoUser.prototype.decryptOneToOne = function(jsonpacket, callback) { + var self = this; + self.addTask("decrypt one-to-one message", function(done) { + var packet = JSON.parse(jsonpacket); + var peerId = packet.sender_key; + + var session = self.peerSessions[peerId]; + var plaintext; + if (session) { + plaintext = session.decrypt(packet.ciphertext.type, packet.ciphertext.body); + done(plaintext); + return; + } + + if (packet.ciphertext.type != 0) { + throw new Error("Unknown one-to-one session"); + } + + session = new Olm.Session(); + session.create_inbound(self.olmAccount, packet.ciphertext.body); + self.peerSessions[peerId] = session; + plaintext = session.decrypt(packet.ciphertext.type, packet.ciphertext.body); + done(plaintext); + }, callback) +}; + +/* ************************************************************************ + * + * group messaging + */ + + +/** + * retrieve, or initiate, an outbound group session + */ +DemoUser.prototype.getGroupSession = function() { + if (this.groupSession) { + return this.groupSession; + } + + this.groupSession = new Olm.OutboundGroupSession(); + this.groupSession.create(); + + var keymsg = { + "session_id": this.groupSession.session_id(), + "session_key": this.groupSession.session_key(), + "message_index": this.groupSession.message_index(), + }; + var jsonmsg = JSON.stringify(keymsg); + + for (var peer in this.peers) { + if (!this.peers.hasOwnProperty(peer)) { + continue; + } + this.sendToPeer(peer, jsonmsg); + } + + return this.groupSession; +}; + +/** + * add a task to create an inbound group session + */ +DemoUser.prototype.createInboundSession = function( + peer_id, session_id, session_key, callback +) { + var self = this; + this.addTask("init inbound session", function(done) { + session = new Olm.InboundGroupSession(); + session.create(session_key); + if (!self.peerGroupSessions[peer_id]) { + self.peerGroupSessions[peer_id] = {}; + } + if (session_id != session.session_id()) { + throw new Error("Mismatched session_ids"); + } + self.peerGroupSessions[peer_id][session_id] = session; + done(session); + }, callback); +}; + +/** + * handler for receiving a group message + */ +DemoUser.prototype.receiveGroup = function(jsonpacket) { + var self = this; + var el = buttonAndTextElement("decrypt", jsonpacket, function(ev) { + self.decryptGroup(jsonpacket, function(result) { + var el2 = document.createElement("tt"); + el.appendChild(el2); + + var content = document.createTextNode(" -> "+result); + el2.appendChild(content); + }); + }); + this.groupInputDiv.appendChild(el); +}; + +/** + * add a task to decrypt a received group message. Calls the callback with the + * decrypted plaintext + */ +DemoUser.prototype.decryptGroup = function(jsonpacket, callback) { + var self = this; + this.addTask("decrypt group message", function(done) { + var packet = JSON.parse(jsonpacket); + + var sender = packet.sender_key; + var session_id = packet.session_id; + + var sender_signing_key = self.peerSigningKeys[sender]; + if (!sender_signing_key) { + throw new Error("No known signing key for sender "+sender); + } + + var olmUtility = new Olm.Utility(); + olmUtility.ed25519_verify( + sender_signing_key, packet.body, packet.signature + ); + + var peer_sessions = self.peerGroupSessions[sender]; + if (!peer_sessions) { + throw new Error("No sessions for sender "+sender); + } + + var session = peer_sessions[session_id]; + if (!session) { + throw new Error("Unknown session id " + session_id); + } + + var plaintext = session.decrypt(packet.body); + done(plaintext); + }, callback); +}; + + + +/** + * add a task to encrypt, and prepare for sending, a group message. + * + * Will create a group session if necessary + */ +DemoUser.prototype.encrypt = function(message) { + var self = this; + var session = this.getGroupSession(); + + function sendJsonToPeers(json) { + for (var peer in self.peers) { + if (!self.peers.hasOwnProperty(peer)) { + continue; + } + self.peers[peer].receiveGroup(json); + } + } + + + self.addTask("encrypt group message", function(done) { + var encrypted = session.encrypt(message); + var signature = self.olmAccount.sign(encrypted); + + var packet = { + sender_key: self.idKey, + session_id: session.session_id(), + body: encrypted, + signature: signature, + }; + var json = JSON.stringify(packet); + + var el = buttonsAndText(json, { + send: function(ev) { + sendJsonToPeers(json); + }, + "send corrupted": function(ev) { + var p = JSON.parse(json); + p.body += " "; + sendJsonToPeers(JSON.stringify(p)); + }, + }); + self.groupOutputDiv.appendChild(el); + done(); + }); +}; + + +/* ************************************************************************** */ + +function initUserDiv(demoUser, div) { + demoUser.progressElement = div.getElementsByClassName("user_progress")[0]; + demoUser.cipherOutputDiv = div.getElementsByClassName("user_cipher_output")[0]; + demoUser.cipherInputDiv = div.getElementsByClassName("user_cipher_input")[0]; + demoUser.groupOutputDiv = div.getElementsByClassName("group_output")[0]; + demoUser.groupInputDiv = div.getElementsByClassName("group_input")[0]; + + var plain_input = div.getElementsByClassName("user_plain_input")[0]; + var encrypt = div.getElementsByClassName("user_encrypt")[0]; + + encrypt.addEventListener("click", function() { + demoUser.encrypt(plain_input.value); + }, false); + +} + +function startDemo() { + var user1 = new DemoUser(); + initUserDiv(user1, document.getElementById("user1")); + user1.generateKeys(); + + var user2 = new DemoUser(); + initUserDiv(user2, document.getElementById("user2")); + user2.generateKeys(); + + user1.addPeer(user2.remoteOps); + user2.addPeer(user1.remoteOps); +} + + +document.addEventListener("DOMContentLoaded", startDemo, false); diff --git a/javascript/demo.html b/javascript/demo/one_to_one_demo.html index 20e780b..fc7e654 100644 --- a/javascript/demo.html +++ b/javascript/demo/one_to_one_demo.html @@ -1,8 +1,7 @@ <html> <head> -<script src="../build/olm.js"></script> +<script src="../olm.js"></script> <script> - document.addEventListener("DOMContentLoaded", function (event) { function progress(who, message) { var message_element = document.createElement("pre"); diff --git a/javascript/olm_inbound_group_session.js b/javascript/olm_inbound_group_session.js new file mode 100644 index 0000000..6058233 --- /dev/null +++ b/javascript/olm_inbound_group_session.js @@ -0,0 +1,103 @@ +/* The 'length' argument to Pointer_stringify doesn't work if the input includes + * characters >= 128; we therefore need to add a NULL character to all of our + * strings. This acts as a symbolic constant to help show what we're doing. + */ +var NULL_BYTE_PADDING_LENGTH = 1; + +function InboundGroupSession() { + var size = Module['_olm_inbound_group_session_size'](); + this.buf = malloc(size); + this.ptr = Module['_olm_inbound_group_session'](this.buf); +} + +function inbound_group_session_method(wrapped) { + return function() { + var result = wrapped.apply(this, arguments); + if (result === OLM_ERROR) { + var message = Pointer_stringify( + Module['_olm_inbound_group_session_last_error'](arguments[0]) + ); + throw new Error("OLM." + message); + } + return result; + } +} + +InboundGroupSession.prototype['free'] = function() { + Module['_olm_clear_inbound_group_session'](this.ptr); + free(this.ptr); +} + +InboundGroupSession.prototype['pickle'] = restore_stack(function(key) { + var key_array = array_from_string(key); + var pickle_length = inbound_group_session_method( + Module['_olm_pickle_inbound_group_session_length'] + )(this.ptr); + var key_buffer = stack(key_array); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); + inbound_group_session_method(Module['_olm_pickle_inbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length + ); + return Pointer_stringify(pickle_buffer); +}); + +InboundGroupSession.prototype['unpickle'] = restore_stack(function(key, pickle) { + var key_array = array_from_string(key); + var key_buffer = stack(key_array); + var pickle_array = array_from_string(pickle); + var pickle_buffer = stack(pickle_array); + inbound_group_session_method(Module['_olm_unpickle_inbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, + pickle_array.length + ); +}); + +InboundGroupSession.prototype['create'] = restore_stack(function(session_key) { + var key_array = array_from_string(session_key); + var key_buffer = stack(key_array); + + inbound_group_session_method(Module['_olm_init_inbound_group_session'])( + this.ptr, key_buffer, key_array.length + ); +}); + +InboundGroupSession.prototype['decrypt'] = restore_stack(function( + message +) { + var message_array = array_from_string(message); + var message_buffer = stack(message_array); + var max_plaintext_length = inbound_group_session_method( + Module['_olm_group_decrypt_max_plaintext_length'] + )(this.ptr, message_buffer, message_array.length); + // caculating the length destroys the input buffer. + // So we copy the array to a new buffer + var message_buffer = stack(message_array); + var plaintext_buffer = stack(max_plaintext_length + NULL_BYTE_PADDING_LENGTH); + var plaintext_length = inbound_group_session_method(Module["_olm_group_decrypt"])( + this.ptr, + message_buffer, message_array.length, + plaintext_buffer, max_plaintext_length + ); + + // Pointer_stringify requires a null-terminated argument (the optional + // 'len' argument doesn't work for UTF-8 data). + Module['setValue']( + plaintext_buffer+plaintext_length, + 0, "i8" + ); + + return Pointer_stringify(plaintext_buffer); +}); + +InboundGroupSession.prototype['session_id'] = restore_stack(function() { + var length = inbound_group_session_method( + Module['_olm_inbound_group_session_id_length'] + )(this.ptr); + var session_id = stack(length + NULL_BYTE_PADDING_LENGTH); + inbound_group_session_method(Module['_olm_inbound_group_session_id'])( + this.ptr, session_id, length + ); + return Pointer_stringify(session_id); +}); + +olm_exports['InboundGroupSession'] = InboundGroupSession; diff --git a/javascript/olm_outbound_group_session.js b/javascript/olm_outbound_group_session.js new file mode 100644 index 0000000..88a441d --- /dev/null +++ b/javascript/olm_outbound_group_session.js @@ -0,0 +1,110 @@ +/* The 'length' argument to Pointer_stringify doesn't work if the input includes + * characters >= 128; we therefore need to add a NULL character to all of our + * strings. This acts as a symbolic constant to help show what we're doing. + */ +var NULL_BYTE_PADDING_LENGTH = 1; + + +function OutboundGroupSession() { + var size = Module['_olm_outbound_group_session_size'](); + this.buf = malloc(size); + this.ptr = Module['_olm_outbound_group_session'](this.buf); +} + +function outbound_group_session_method(wrapped) { + return function() { + var result = wrapped.apply(this, arguments); + if (result === OLM_ERROR) { + var message = Pointer_stringify( + Module['_olm_outbound_group_session_last_error'](arguments[0]) + ); + throw new Error("OLM." + message); + } + return result; + } +} + +OutboundGroupSession.prototype['free'] = function() { + Module['_olm_clear_outbound_group_session'](this.ptr); + free(this.ptr); +} + +OutboundGroupSession.prototype['pickle'] = restore_stack(function(key) { + var key_array = array_from_string(key); + var pickle_length = outbound_group_session_method( + Module['_olm_pickle_outbound_group_session_length'] + )(this.ptr); + var key_buffer = stack(key_array); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_pickle_outbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length + ); + return Pointer_stringify(pickle_buffer); +}); + +OutboundGroupSession.prototype['unpickle'] = restore_stack(function(key, pickle) { + var key_array = array_from_string(key); + var key_buffer = stack(key_array); + var pickle_array = array_from_string(pickle); + var pickle_buffer = stack(pickle_array); + outbound_group_session_method(Module['_olm_unpickle_outbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, + pickle_array.length + ); +}); + +OutboundGroupSession.prototype['create'] = restore_stack(function() { + var random_length = outbound_group_session_method( + Module['_olm_init_outbound_group_session_random_length'] + )(this.ptr); + var random = random_stack(random_length); + outbound_group_session_method(Module['_olm_init_outbound_group_session'])( + this.ptr, random, random_length + ); +}); + +OutboundGroupSession.prototype['encrypt'] = restore_stack(function(plaintext) { + var plaintext_array = array_from_string(plaintext); + var message_length = outbound_group_session_method( + Module['_olm_group_encrypt_message_length'] + )(this.ptr, plaintext_array.length); + var plaintext_buffer = stack(plaintext_array); + var message_buffer = stack(message_length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_group_encrypt'])( + this.ptr, + plaintext_buffer, plaintext_array.length, + message_buffer, message_length + ); + return Pointer_stringify(message_buffer); +}); + +OutboundGroupSession.prototype['session_id'] = restore_stack(function() { + var length = outbound_group_session_method( + Module['_olm_outbound_group_session_id_length'] + )(this.ptr); + var session_id = stack(length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_outbound_group_session_id'])( + this.ptr, session_id, length + ); + return Pointer_stringify(session_id); +}); + +OutboundGroupSession.prototype['session_key'] = restore_stack(function() { + var key_length = outbound_group_session_method( + Module['_olm_outbound_group_session_key_length'] + )(this.ptr); + var key = stack(key_length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_outbound_group_session_key'])( + this.ptr, key, key_length + ); + return Pointer_stringify(key); +}); + +OutboundGroupSession.prototype['message_index'] = function() { + var idx = outbound_group_session_method( + Module['_olm_outbound_group_session_message_index'] + )(this.ptr); + return idx; +}; + +olm_exports['OutboundGroupSession'] = OutboundGroupSession; diff --git a/javascript/olm_post.js b/javascript/olm_post.js index dbb4862..8951c11 100644 --- a/javascript/olm_post.js +++ b/javascript/olm_post.js @@ -4,6 +4,16 @@ var free = Module['_free']; var Pointer_stringify = Module['Pointer_stringify']; var OLM_ERROR = Module['_olm_error'](); +/* The 'length' argument to Pointer_stringify doesn't work if the input includes + * characters >= 128; we therefore need to add a NULL character to all of our + * strings. This acts as a symbolic constant to help show what we're doing. + */ +var NULL_BYTE_PADDING_LENGTH = 1; + +/* allocate a number of bytes of storage on the stack. + * + * If size_or_array is a Number, allocates that number of zero-initialised bytes. + */ function stack(size_or_array) { return Module['allocate'](size_or_array, 'i8', Module['ALLOC_STACK']); } @@ -68,11 +78,11 @@ Account.prototype['identity_keys'] = restore_stack(function() { var keys_length = account_method( Module['_olm_account_identity_keys_length'] )(this.ptr); - var keys = stack(keys_length); + var keys = stack(keys_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_account_identity_keys'])( this.ptr, keys, keys_length ); - return Pointer_stringify(keys, keys_length); + return Pointer_stringify(keys); }); Account.prototype['sign'] = restore_stack(function(message) { @@ -81,24 +91,24 @@ Account.prototype['sign'] = restore_stack(function(message) { )(this.ptr); var message_array = array_from_string(message); var message_buffer = stack(message_array); - var signature_buffer = stack(signature_length); + var signature_buffer = stack(signature_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_account_sign'])( this.ptr, message_buffer, message_array.length, signature_buffer, signature_length ); - return Pointer_stringify(signature_buffer, signature_length); + return Pointer_stringify(signature_buffer); }); Account.prototype['one_time_keys'] = restore_stack(function() { var keys_length = account_method( Module['_olm_account_one_time_keys_length'] )(this.ptr); - var keys = stack(keys_length); + var keys = stack(keys_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_account_one_time_keys'])( this.ptr, keys, keys_length ); - return Pointer_stringify(keys, keys_length); + return Pointer_stringify(keys); }); Account.prototype['mark_keys_as_published'] = restore_stack(function() { @@ -135,11 +145,11 @@ Account.prototype['pickle'] = restore_stack(function(key) { Module['_olm_pickle_account_length'] )(this.ptr); var key_buffer = stack(key_array); - var pickle_buffer = stack(pickle_length); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_pickle_account'])( this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length ); - return Pointer_stringify(pickle_buffer, pickle_length); + return Pointer_stringify(pickle_buffer); }); Account.prototype['unpickle'] = restore_stack(function(key, pickle) { @@ -183,11 +193,11 @@ Session.prototype['pickle'] = restore_stack(function(key) { Module['_olm_pickle_session_length'] )(this.ptr); var key_buffer = stack(key_array); - var pickle_buffer = stack(pickle_length); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); session_method(Module['_olm_pickle_session'])( this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length ); - return Pointer_stringify(pickle_buffer, pickle_length); + return Pointer_stringify(pickle_buffer); }); Session.prototype['unpickle'] = restore_stack(function(key, pickle) { @@ -246,13 +256,20 @@ Session.prototype['create_inbound_from'] = restore_stack(function( Session.prototype['session_id'] = restore_stack(function() { var id_length = session_method(Module['_olm_session_id_length'])(this.ptr); - var id_buffer = stack(id_length); + var id_buffer = stack(id_length + NULL_BYTE_PADDING_LENGTH); session_method(Module['_olm_session_id'])( this.ptr, id_buffer, id_length ); - return Pointer_stringify(id_buffer, id_length); + return Pointer_stringify(id_buffer); }); +Session.prototype['has_received_message'] = function() { + return session_method(Module['_olm_session_has_received_message'])( + this.ptr + ) ? true : false; +}; + + Session.prototype['matches_inbound'] = restore_stack(function( one_time_key_message ) { @@ -292,7 +309,7 @@ Session.prototype['encrypt'] = restore_stack(function( )(this.ptr, plaintext_array.length); var random = random_stack(random_length); var plaintext_buffer = stack(plaintext_array); - var message_buffer = stack(message_length); + var message_buffer = stack(message_length + NULL_BYTE_PADDING_LENGTH); session_method(Module['_olm_encrypt'])( this.ptr, plaintext_buffer, plaintext_array.length, @@ -301,7 +318,7 @@ Session.prototype['encrypt'] = restore_stack(function( ); return { "type": message_type, - "body": Pointer_stringify(message_buffer, message_length) + "body": Pointer_stringify(message_buffer) }; }); @@ -316,13 +333,23 @@ Session.prototype['decrypt'] = restore_stack(function( // caculating the length destroys the input buffer. // So we copy the array to a new buffer var message_buffer = stack(message_array); - var plaintext_buffer = stack(max_plaintext_length); + var plaintext_buffer = stack( + max_plaintext_length + NULL_BYTE_PADDING_LENGTH + ); var plaintext_length = session_method(Module["_olm_decrypt"])( this.ptr, message_type, message_buffer, message.length, plaintext_buffer, max_plaintext_length ); - return Pointer_stringify(plaintext_buffer, plaintext_length); + + // Pointer_stringify requires a null-terminated argument (the optional + // 'len' argument doesn't work for UTF-8 data). + Module['setValue']( + plaintext_buffer+plaintext_length, + 0, "i8" + ); + + return Pointer_stringify(plaintext_buffer); }); function Utility() { @@ -353,13 +380,13 @@ Utility.prototype['sha256'] = restore_stack(function(input) { var output_length = utility_method(Module['_olm_sha256_length'])(this.ptr); var input_array = array_from_string(input); var input_buffer = stack(input_array); - var output_buffer = stack(output_length); + var output_buffer = stack(output_length + NULL_BYTE_PADDING_LENGTH); utility_method(Module['_olm_sha2516'])( this.ptr, input_buffer, input_array.length(), output_buffer, output_length ); - return Pointer_stringify(output_buffer, output_length); + return Pointer_stringify(output_buffer); }); Utility.prototype['ed25519_verify'] = restore_stack(function( @@ -383,4 +410,13 @@ olm_exports["Account"] = Account; olm_exports["Session"] = Session; olm_exports["Utility"] = Utility; +olm_exports["get_library_version"] = restore_stack(function() { + var buf = stack(3); + Module['_olm_get_library_version'](buf, buf+1, buf+2); + return [ + getValue(buf, 'i8'), + getValue(buf+1, 'i8'), + getValue(buf+2, 'i8'), + ]; +}); }(); diff --git a/javascript/olm_pre.js b/javascript/olm_pre.js index 7706687..50bf8c2 100644 --- a/javascript/olm_pre.js +++ b/javascript/olm_pre.js @@ -2,7 +2,7 @@ var olm_exports = {}; var get_random_values; var process; // Shadow the process object so that emscripten won't get // confused by browserify -if (global && global["window"]) { +if (typeof(global) !== 'undefined' && global["window"]) { // We're running with browserify module["exports"] = olm_exports; global["window"]["Olm"] = olm_exports; diff --git a/javascript/package.json b/javascript/package.json index efcdaa5..df43ce1 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -1,9 +1,14 @@ { "name": "olm", - "version": "0.1.0", - "description": "An implementation of a well known cryptographic ratchet", + "version": "1.3.0", + "description": "An implementation of the Double Ratchet cryptographic ratchet", "main": "olm.js", + "files": [ + "olm.js", + "README.md" + ], "scripts": { + "build": "make -C .. js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { diff --git a/jenkins.sh b/jenkins.sh new file mode 100755 index 0000000..3b6fb1f --- /dev/null +++ b/jenkins.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +make clean +rm -f olm-*.tgz + +make lib +make test +./python/test_olm.sh + +. ~/.emsdk_set_env.sh +make js +npm pack javascript diff --git a/lib/curve25519-donna.h b/lib/curve25519-donna.h new file mode 100644 index 0000000..3c53d4a --- /dev/null +++ b/lib/curve25519-donna.h @@ -0,0 +1,18 @@ +/* header file for the curve25519-donna implementation, because the + * authors of that project don't supply one. + */ +#ifndef CURVE25519_DONNA_H +#define CURVE25519_DONNA_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern int curve25519_donna(unsigned char *output, const unsigned char *a, + const unsigned char *b); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/curve25519-donna/python-src/curve25519/test/test_curve25519.py b/lib/curve25519-donna/python-src/curve25519/test/test_curve25519.py index 2ecbd47..b3a5447 100755 --- a/lib/curve25519-donna/python-src/curve25519/test/test_curve25519.py +++ b/lib/curve25519-donna/python-src/curve25519/test/test_curve25519.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#! /usr/bin/env python import unittest diff --git a/lib/curve25519-donna/python-src/curve25519/test/test_speed.py b/lib/curve25519-donna/python-src/curve25519/test/test_speed.py index 87952fa..4d7e0c8 100755 --- a/lib/curve25519-donna/python-src/curve25519/test/test_speed.py +++ b/lib/curve25519-donna/python-src/curve25519/test/test_speed.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#! /usr/bin/env python from time import time from curve25519 import Private diff --git a/lib/curve25519-donna/setup.py b/lib/curve25519-donna/setup.py index dc1b8eb..df5cbfd 100755 --- a/lib/curve25519-donna/setup.py +++ b/lib/curve25519-donna/setup.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#! /usr/bin/env python from subprocess import Popen, PIPE from distutils.core import setup, Extension diff --git a/lib/ed25519_additions.c b/lib/ed25519_additions.c deleted file mode 100644 index 5fa0c68..0000000 --- a/lib/ed25519_additions.c +++ /dev/null @@ -1,43 +0,0 @@ -void convert_curve25519_to_ed25519( - unsigned char * public_key, - unsigned char * signature -) { - fe mont_x, mont_x_minus_one, mont_x_plus_one, inv_mont_x_plus_one; - fe one; - fe ed_y; - - fe_frombytes(mont_x, public_key); - fe_1(one); - fe_sub(mont_x_minus_one, mont_x, one); - fe_add(mont_x_plus_one, mont_x, one); - fe_invert(inv_mont_x_plus_one, mont_x_plus_one); - fe_mul(ed_y, mont_x_minus_one, inv_mont_x_plus_one); - fe_tobytes(public_key, ed_y); - - public_key[31] &= 0x7F; - public_key[31] |= (signature[63] & 0x80); - signature[63] &= 0x7F; -} - - -void convert_ed25519_to_curve25519( - unsigned char const * public_key, - unsigned char * signature -) { - unsigned char sign_bit = public_key[31] & 0x80; - signature[63] &= 0x7F; - signature[63] |= sign_bit; -} - - -void ed25519_keypair( - unsigned char * private_key, - unsigned char * public_key -) { - ge_p3 A; - private_key[0] &= 248; - private_key[31] &= 63; - private_key[31] |= 64; - ge_scalarmult_base(&A, private_key); - ge_p3_tobytes(public_key, &A); -} diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..b8ca4f7 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,4 @@ +*.pyc +/*.account +/*.session +/*.group_session diff --git a/python/olm.py b/python/olm.py deleted file mode 100755 index 4bd85f0..0000000 --- a/python/olm.py +++ /dev/null @@ -1,542 +0,0 @@ -#! /usr/bin/python -from ctypes import * -import json -import os - -lib = cdll.LoadLibrary(os.path.join( - os.path.dirname(__file__), "..", "build", "libolm.so") -) - - -lib.olm_error.argtypes = [] -lib.olm_error.restypes = c_size_t - -ERR = lib.olm_error() - -class OlmError(Exception): - pass - - -lib.olm_account_size.argtypes = [] -lib.olm_account_size.restype = c_size_t - -lib.olm_account.argtypes = [c_void_p] -lib.olm_account.restype = c_void_p - -lib.olm_account_last_error.argtypes = [c_void_p] -lib.olm_account_last_error.restype = c_char_p - -def account_errcheck(res, func, args): - if res == ERR: - raise OlmError("%s: %s" % ( - func.__name__, lib.olm_account_last_error(args[0]) - )) - return res - - -def account_function(func, *types): - func.argtypes = (c_void_p,) + types - func.restypes = c_size_t - func.errcheck = account_errcheck - - -account_function( - lib.olm_pickle_account, c_void_p, c_size_t, c_void_p, c_size_t -) -account_function( - lib.olm_unpickle_account, c_void_p, c_size_t, c_void_p, c_size_t -) -account_function(lib.olm_create_account_random_length) -account_function(lib.olm_create_account, c_void_p, c_size_t) -account_function(lib.olm_account_identity_keys_length) -account_function(lib.olm_account_identity_keys, c_void_p, c_size_t) -account_function(lib.olm_account_signature_length) -account_function(lib.olm_account_sign, c_void_p, c_size_t, c_void_p, c_size_t) -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_mark_keys_as_published) -account_function(lib.olm_account_max_number_of_one_time_keys) -account_function( - lib.olm_account_generate_one_time_keys_random_length, - c_size_t -) -account_function( - lib.olm_account_generate_one_time_keys, - c_size_t, - c_void_p, c_size_t -) - - -def read_random(n): - with open("/dev/urandom", "rb") as f: - return f.read(n) - -class Account(object): - def __init__(self): - self.buf = create_string_buffer(lib.olm_account_size()) - self.ptr = lib.olm_account(self.buf) - - def create(self): - random_length = lib.olm_create_account_random_length(self.ptr) - random = read_random(random_length) - random_buffer = create_string_buffer(random) - lib.olm_create_account(self.ptr, random_buffer, random_length) - - def pickle(self, key): - key_buffer = create_string_buffer(key) - pickle_length = lib.olm_pickle_account_length(self.ptr) - pickle_buffer = create_string_buffer(pickle_length) - lib.olm_pickle_account( - self.ptr, key_buffer, len(key), pickle_buffer, pickle_length - ) - return pickle_buffer.raw - - def unpickle(self, key, pickle): - key_buffer = create_string_buffer(key) - pickle_buffer = create_string_buffer(pickle) - lib.olm_unpickle_account( - self.ptr, key_buffer, len(key), pickle_buffer, len(pickle) - ) - - def identity_keys(self): - out_length = lib.olm_account_identity_keys_length(self.ptr) - out_buffer = create_string_buffer(out_length) - lib.olm_account_identity_keys( - self.ptr, - out_buffer, out_length - ) - return json.loads(out_buffer.raw) - - def sign(self, message): - out_length = lib.olm_account_signature_length(self.ptr) - message_buffer = create_string_buffer(message) - out_buffer = create_string_buffer(out_length) - lib.olm_account_sign( - self.ptr, message_buffer, len(message), out_buffer, out_length - ) - return out_buffer.raw - - def one_time_keys(self): - out_length = lib.olm_account_one_time_keys_length(self.ptr) - out_buffer = create_string_buffer(out_length) - lib.olm_account_one_time_keys(self.ptr, out_buffer, out_length) - return json.loads(out_buffer.raw) - - def mark_keys_as_published(self): - lib.olm_account_mark_keys_as_published(self.ptr) - - def max_number_of_one_time_keys(self): - return lib.olm_account_max_number_of_one_time_keys(self.ptr) - - def generate_one_time_keys(self, count): - random_length = lib.olm_account_generate_one_time_keys_random_length( - self.ptr, count - ) - random = read_random(random_length) - random_buffer = create_string_buffer(random) - lib.olm_account_generate_one_time_keys( - self.ptr, count, random_buffer, random_length - ) - - def clear(self): - pass - - -lib.olm_session_size.argtypes = [] -lib.olm_session_size.restype = c_size_t - -lib.olm_session.argtypes = [c_void_p] -lib.olm_session.restype = c_void_p - -lib.olm_session_last_error.argtypes = [c_void_p] -lib.olm_session_last_error.restype = c_char_p - - -def session_errcheck(res, func, args): - if res == ERR: - raise OlmError("%s: %s" % ( - func.__name__, lib.olm_session_last_error(args[0]) - )) - return res - - -def session_function(func, *types): - func.argtypes = (c_void_p,) + types - func.restypes = c_size_t - func.errcheck = session_errcheck - -session_function(lib.olm_session_last_error) -session_function( - lib.olm_pickle_session, c_void_p, c_size_t, c_void_p, c_size_t -) -session_function( - lib.olm_unpickle_session, c_void_p, c_size_t, c_void_p, c_size_t -) -session_function(lib.olm_create_outbound_session_random_length) -session_function( - lib.olm_create_outbound_session, - c_void_p, # Account - c_void_p, c_size_t, # Identity Key - c_void_p, c_size_t, # One Time Key - c_void_p, c_size_t, # Random -) -session_function( - lib.olm_create_inbound_session, - c_void_p, # Account - c_void_p, c_size_t, # Pre Key Message -) -session_function( - lib.olm_create_inbound_session_from, - c_void_p, # Account - c_void_p, c_size_t, # Identity Key - c_void_p, c_size_t, # Pre Key Message -) -session_function(lib.olm_session_id_length) -session_function(lib.olm_session_id, c_void_p, c_size_t) -session_function(lib.olm_matches_inbound_session, c_void_p, c_size_t) -session_function( - lib.olm_matches_inbound_session_from, - c_void_p, c_size_t, # Identity Key - c_void_p, c_size_t, # Pre Key Message -) -session_function(lib.olm_encrypt_message_type) -session_function(lib.olm_encrypt_random_length) -session_function(lib.olm_encrypt_message_length, c_size_t) -session_function( - lib.olm_encrypt, - c_void_p, c_size_t, # Plaintext - c_void_p, c_size_t, # Random - c_void_p, c_size_t, # Message -); -session_function( - lib.olm_decrypt_max_plaintext_length, - c_size_t, # Message Type - c_void_p, c_size_t, # Message -) -session_function( - lib.olm_decrypt, - c_size_t, # Message Type - c_void_p, c_size_t, # Message - c_void_p, c_size_t, # Plaintext -) - -class Session(object): - def __init__(self): - self.buf = create_string_buffer(lib.olm_session_size()) - self.ptr = lib.olm_session(self.buf) - - def pickle(self, key): - key_buffer = create_string_buffer(key) - pickle_length = lib.olm_pickle_session_length(self.ptr) - pickle_buffer = create_string_buffer(pickle_length) - lib.olm_pickle_session( - self.ptr, key_buffer, len(key), pickle_buffer, pickle_length - ) - return pickle_buffer.raw - - def unpickle(self, key, pickle): - key_buffer = create_string_buffer(key) - pickle_buffer = create_string_buffer(pickle) - lib.olm_unpickle_session( - self.ptr, key_buffer, len(key), pickle_buffer, len(pickle) - ) - - def create_outbound(self, account, identity_key, one_time_key): - r_length = lib.olm_create_outbound_session_random_length(self.ptr) - random = read_random(r_length) - random_buffer = create_string_buffer(random) - identity_key_buffer = create_string_buffer(identity_key) - one_time_key_buffer = create_string_buffer(one_time_key) - lib.olm_create_outbound_session( - self.ptr, - account.ptr, - identity_key_buffer, len(identity_key), - one_time_key_buffer, len(one_time_key), - random_buffer, r_length - ) - - def create_inbound(self, account, one_time_key_message): - one_time_key_message_buffer = create_string_buffer(one_time_key_message) - lib.olm_create_inbound_session( - self.ptr, - account.ptr, - one_time_key_message_buffer, len(one_time_key_message) - ) - - def create_inbound_from(self, account, identity_key, one_time_key_message): - identity_key_buffer = create_string_buffer(identity_key) - one_time_key_message_buffer = create_string_buffer(one_time_key_message) - lib.olm_create_inbound_session_from( - self.ptr, - account.ptr, - identity_key_buffer, len(identity_key), - one_time_key_message_buffer, len(one_time_key_message) - ) - - def session_id(self): - id_length = lib.olm_session_id_length(self.ptr) - id_buffer = create_string_buffer(id_length) - lib.olm_session_id(self.ptr, id_buffer, id_length); - return id_buffer.raw - - def matches_inbound(self, one_time_key_message): - one_time_key_message_buffer = create_string_buffer(one_time_key_message) - return bool(lib.olm_matches_inbound_session( - self.ptr, - one_time_key_message_buffer, len(one_time_key_message) - )) - - def matches_inbound_from(self, identity_key, one_time_key_message): - identity_key_buffer = create_string_buffer(identity_key) - one_time_key_message_buffer = create_string_buffer(one_time_key_message) - return bool(lib.olm_matches_inbound_session( - self.ptr, - identity_key_buffer, len(identity_key), - one_time_key_message_buffer, len(one_time_key_message) - )) - - def encrypt(self, plaintext): - r_length = lib.olm_encrypt_random_length(self.ptr) - random = read_random(r_length) - random_buffer = create_string_buffer(random) - - message_type = lib.olm_encrypt_message_type(self.ptr) - message_length = lib.olm_encrypt_message_length( - self.ptr, len(plaintext) - ) - message_buffer = create_string_buffer(message_length) - - plaintext_buffer = create_string_buffer(plaintext) - - lib.olm_encrypt( - self.ptr, - plaintext_buffer, len(plaintext), - random_buffer, r_length, - message_buffer, message_length, - ) - return message_type, message_buffer.raw - - def decrypt(self, message_type, message): - message_buffer = create_string_buffer(message) - max_plaintext_length = lib.olm_decrypt_max_plaintext_length( - self.ptr, message_type, message_buffer, len(message) - ) - plaintext_buffer = create_string_buffer(max_plaintext_length) - message_buffer = create_string_buffer(message) - plaintext_length = lib.olm_decrypt( - self.ptr, message_type, message_buffer, len(message), - plaintext_buffer, max_plaintext_length - ) - return plaintext_buffer.raw[:plaintext_length] - - def clear(self): - pass - - -if __name__ == '__main__': - import argparse - import sys - import os - import yaml - - parser = argparse.ArgumentParser() - parser.add_argument("--key", help="Account encryption key", default="") - commands = parser.add_subparsers() - - create_account = commands.add_parser("create_account", help="Create a new account") - create_account.add_argument("account_file", help="Local account file") - - def do_create_account(args): - if os.path.exists(args.account_file): - sys.stderr.write("Account %r file already exists" % ( - args.account_file, - )) - sys.exit(1) - account = Account() - account.create() - with open(args.account_file, "wb") as f: - f.write(account.pickle(args.key)) - - create_account.set_defaults(func=do_create_account) - - keys = commands.add_parser("keys", help="List public keys for an account") - keys.add_argument("account_file", help="Local account file") - keys.add_argument("--json", action="store_true", help="Output as JSON") - - def do_keys(args): - account = Account() - with open(args.account_file, "rb") as f: - account.unpickle(args.key, f.read()) - result = { - "account_keys": account.identity_keys(), - "one_time_keys": account.one_time_keys(), - } - try: - if args.json: - json.dump(result, sys.stdout, indent=4) - else: - yaml.safe_dump(result, sys.stdout, default_flow_style=False) - except: - pass - - keys.set_defaults(func=do_keys) - - sign = commands.add_parser("sign", help="Sign a message") - sign.add_argument("account_file", help="Local account file") - sign.add_argument("message_file", help="Message to sign") - sign.add_argument("signature_file", help="Signature to output") - - def do_sign(args): - account = Account() - with open(args.account_file, "rb") as f: - account.unpickle(args.key, f.read()) - with open_in(args.message_file) as f: - message = f.read() - signature = account.sign(message) - with open_out(args.signature_file) as f: - f.write(signature) - - sign.set_defaults(func=do_sign) - - - generate_keys = commands.add_parser("generate_keys", help="Generate one time keys") - generate_keys.add_argument("account_file", help="Local account file") - generate_keys.add_argument("count", type=int, help="Number of keys to generate") - - def do_generate_keys(args): - account = Account() - with open(args.account_file, "rb") as f: - account.unpickle(args.key, f.read()) - account.generate_one_time_keys(args.count) - with open(args.account_file, "wb") as f: - f.write(account.pickle(args.key)) - - generate_keys.set_defaults(func=do_generate_keys) - - - outbound = commands.add_parser("outbound", help="Create an outbound session") - outbound.add_argument("account_file", help="Local account file") - outbound.add_argument("session_file", help="Local session file") - outbound.add_argument("identity_key", help="Remote identity key") - outbound.add_argument("one_time_key", help="Remote one time key") - - def do_outbound(args): - if os.path.exists(args.session_file): - sys.stderr.write("Session %r file already exists" % ( - args.session_file, - )) - sys.exit(1) - account = Account() - with open(args.account_file, "rb") as f: - account.unpickle(args.key, f.read()) - session = Session() - session.create_outbound( - account, args.identity_key, args.one_time_key - ) - with open(args.session_file, "wb") as f: - f.write(session.pickle(args.key)) - - outbound.set_defaults(func=do_outbound) - - def open_in(path): - if path == "-": - return sys.stdin - else: - return open(path, "rb") - - def open_out(path): - if path == "-": - return sys.stdout - else: - return open(path, "wb") - - inbound = commands.add_parser("inbound", help="Create an inbound session") - inbound.add_argument("account_file", help="Local account file") - inbound.add_argument("session_file", help="Local session file") - inbound.add_argument("message_file", help="Message", default="-") - inbound.add_argument("plaintext_file", help="Plaintext", default="-") - - def do_inbound(args): - if os.path.exists(args.session_file): - sys.stderr.write("Session %r file already exists" % ( - args.session_file, - )) - sys.exit(1) - account = Account() - with open(args.account_file, "rb") as f: - account.unpickle(args.key, f.read()) - with open_in(args.message_file) as f: - message_type = f.read(8) - message = f.read() - if message_type != "PRE_KEY ": - sys.stderr.write("Expecting a PRE_KEY message") - sys.exit(1) - session = Session() - session.create_inbound(account, message) - plaintext = session.decrypt(0, message) - with open(args.session_file, "wb") as f: - f.write(session.pickle(args.key)) - with open_out(args.plaintext_file) as f: - f.write(plaintext) - - inbound.set_defaults(func=do_inbound) - - session_id = commands.add_parser("session_id", help="Session ID") - session_id.add_argument("session_file", help="Local session file") - - def do_session_id(args): - session = Session() - with open(args.session_file, "rb") as f: - session.unpickle(args.key, f.read()) - sys.stdout.write(session.session_id() + "\n") - - session_id.set_defaults(func=do_session_id) - - encrypt = commands.add_parser("encrypt", help="Encrypt a message") - encrypt.add_argument("session_file", help="Local session file") - encrypt.add_argument("plaintext_file", help="Plaintext", default="-") - encrypt.add_argument("message_file", help="Message", default="-") - - def do_encrypt(args): - session = Session() - with open(args.session_file, "rb") as f: - session.unpickle(args.key, f.read()) - with open_in(args.plaintext_file) as f: - plaintext = f.read() - message_type, message = session.encrypt(plaintext) - with open(args.session_file, "wb") as f: - f.write(session.pickle(args.key)) - with open_out(args.message_file) as f: - f.write(["PRE_KEY ", "MESSAGE "][message_type]) - f.write(message) - - encrypt.set_defaults(func=do_encrypt) - - decrypt = commands.add_parser("decrypt", help="Decrypt a message") - decrypt.add_argument("session_file", help="Local session file") - decrypt.add_argument("message_file", help="Message", default="-") - decrypt.add_argument("plaintext_file", help="Plaintext", default="-") - - def do_decrypt(args): - session = Session() - with open(args.session_file, "rb") as f: - session.unpickle(args.key, f.read()) - with open_in(args.message_file) as f: - message_type = f.read(8) - message = f.read() - if message_type not in {"PRE_KEY ", "MESSAGE "}: - sys.stderr.write("Expecting a PRE_KEY or MESSAGE message") - sys.exit(1) - message_type = 1 if message_type == "MESSAGE " else 0 - plaintext = session.decrypt(message_type, message) - with open(args.session_file, "wb") as f: - f.write(session.pickle(args.key)) - with open_out(args.plaintext_file) as f: - f.write(plaintext) - - decrypt.set_defaults(func=do_decrypt) - - args = parser.parse_args() - args.func(args) - - diff --git a/python/olm/__init__.py b/python/olm/__init__.py new file mode 100644 index 0000000..31b29b9 --- /dev/null +++ b/python/olm/__init__.py @@ -0,0 +1,4 @@ +from .account import Account +from .session import Session +from .outbound_group_session import OutboundGroupSession +from .inbound_group_session import InboundGroupSession diff --git a/python/olm/__main__.py b/python/olm/__main__.py new file mode 100755 index 0000000..cf9158d --- /dev/null +++ b/python/olm/__main__.py @@ -0,0 +1,339 @@ +#! /usr/bin/env python + +from __future__ import print_function + +import argparse +import json +import os +import sys +import yaml + +from . import * + +def read_base64_file(filename): + """Read a base64 file, dropping any CR/LF characters""" + with open(filename, "rb") as f: + return f.read().translate(None, "\r\n") + +def build_arg_parser(): + parser = argparse.ArgumentParser() + parser.add_argument("--key", help="Account encryption key", default="") + commands = parser.add_subparsers() + + create_account = commands.add_parser("create_account", help="Create a new account") + create_account.add_argument("account_file", help="Local account file") + + def do_create_account(args): + if os.path.exists(args.account_file): + sys.stderr.write("Account %r file already exists" % ( + args.account_file, + )) + sys.exit(1) + account = Account() + account.create() + with open(args.account_file, "wb") as f: + f.write(account.pickle(args.key)) + + create_account.set_defaults(func=do_create_account) + + keys = commands.add_parser("keys", help="List public keys for an account") + keys.add_argument("account_file", help="Local account file") + keys.add_argument("--json", action="store_true", help="Output as JSON") + + def do_keys(args): + account = Account() + account.unpickle(args.key, read_base64_file(args.account_file)) + result = { + "account_keys": account.identity_keys(), + "one_time_keys": account.one_time_keys(), + } + try: + if args.json: + json.dump(result, sys.stdout, indent=4) + else: + yaml.safe_dump(result, sys.stdout, default_flow_style=False) + except: + pass + + keys.set_defaults(func=do_keys) + + def do_id_key(args): + account = Account() + account.unpickle(args.key, read_base64_file(args.account_file)) + print(account.identity_keys()['curve25519']) + + id_key = commands.add_parser("identity_key", help="Get the identity key for an account") + id_key.add_argument("account_file", help="Local account file") + id_key.set_defaults(func=do_id_key) + + def do_one_time_key(args): + account = Account() + account.unpickle(args.key, read_base64_file(args.account_file)) + keys = account.one_time_keys()['curve25519'].values() + key_num = args.key_num + if key_num < 1 or key_num > len(keys): + print( + "Invalid key number %i: %i keys available" % + (key_num, len(keys)), + file=sys.stderr + ) + sys.exit(1) + print (keys[key_num-1]) + + one_time_key = commands.add_parser("one_time_key", + help="Get a one-time key for the account") + one_time_key.add_argument("account_file", help="Local account file") + one_time_key.add_argument("--key-num", "-n", type=int, default=1, + help="Index of key to retrieve (default: 1)") + one_time_key.set_defaults(func=do_one_time_key) + + + sign = commands.add_parser("sign", help="Sign a message") + sign.add_argument("account_file", help="Local account file") + sign.add_argument("message_file", help="Message to sign") + sign.add_argument("signature_file", help="Signature to output") + + def do_sign(args): + account = Account() + account.unpickle(args.key, read_base64_file(args.account_file)) + with open_in(args.message_file) as f: + message = f.read() + signature = account.sign(message) + with open_out(args.signature_file) as f: + f.write(signature) + + sign.set_defaults(func=do_sign) + + + generate_keys = commands.add_parser("generate_keys", help="Generate one time keys") + generate_keys.add_argument("account_file", help="Local account file") + generate_keys.add_argument("count", type=int, help="Number of keys to generate") + + def do_generate_keys(args): + account = Account() + account.unpickle(args.key, read_base64_file(args.account_file)) + account.generate_one_time_keys(args.count) + with open(args.account_file, "wb") as f: + f.write(account.pickle(args.key)) + + generate_keys.set_defaults(func=do_generate_keys) + + + outbound = commands.add_parser("outbound", help="Create an outbound session") + outbound.add_argument("account_file", help="Local account file") + outbound.add_argument("session_file", help="Local session file") + outbound.add_argument("identity_key", help="Remote identity key") + outbound.add_argument("one_time_key", help="Remote one time key") + + def do_outbound(args): + if os.path.exists(args.session_file): + sys.stderr.write("Session %r file already exists" % ( + args.session_file, + )) + sys.exit(1) + account = Account() + account.unpickle(args.key, read_base64_file(args.account_file)) + session = Session() + session.create_outbound( + account, args.identity_key, args.one_time_key + ) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + + outbound.set_defaults(func=do_outbound) + + def open_in(path): + if path == "-": + return sys.stdin + else: + return open(path, "rb") + + def open_out(path): + if path == "-": + return sys.stdout + else: + return open(path, "wb") + + inbound = commands.add_parser("inbound", help="Create an inbound session") + inbound.add_argument("account_file", help="Local account file") + inbound.add_argument("session_file", help="Local session file") + inbound.add_argument("message_file", help="Message", default="-") + inbound.add_argument("plaintext_file", help="Plaintext", default="-") + + def do_inbound(args): + if os.path.exists(args.session_file): + sys.stderr.write("Session %r file already exists" % ( + args.session_file, + )) + sys.exit(1) + account = Account() + account.unpickle(args.key, read_base64_file(args.account_file)) + with open_in(args.message_file) as f: + message_type = f.read(8) + message = f.read() + if message_type != "PRE_KEY ": + sys.stderr.write("Expecting a PRE_KEY message") + sys.exit(1) + session = Session() + session.create_inbound(account, message) + plaintext = session.decrypt(0, message) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + with open_out(args.plaintext_file) as f: + f.write(plaintext) + + inbound.set_defaults(func=do_inbound) + + session_id = commands.add_parser("session_id", help="Session ID") + session_id.add_argument("session_file", help="Local session file") + + def do_session_id(args): + session = Session() + session.unpickle(args.key, read_base64_file(args.session_file)) + sys.stdout.write(session.session_id() + "\n") + + session_id.set_defaults(func=do_session_id) + + encrypt = commands.add_parser("encrypt", help="Encrypt a message") + encrypt.add_argument("session_file", help="Local session file") + encrypt.add_argument("plaintext_file", help="Plaintext", default="-") + encrypt.add_argument("message_file", help="Message", default="-") + + def do_encrypt(args): + session = Session() + session.unpickle(args.key, read_base64_file(args.session_file)) + with open_in(args.plaintext_file) as f: + plaintext = f.read() + message_type, message = session.encrypt(plaintext) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + with open_out(args.message_file) as f: + f.write(["PRE_KEY ", "MESSAGE "][message_type]) + f.write(message) + + encrypt.set_defaults(func=do_encrypt) + + decrypt = commands.add_parser("decrypt", help="Decrypt a message") + decrypt.add_argument("session_file", help="Local session file") + decrypt.add_argument("message_file", help="Message", default="-") + decrypt.add_argument("plaintext_file", help="Plaintext", default="-") + + def do_decrypt(args): + session = Session() + session.unpickle(args.key, read_base64_file(args.session_file)) + with open_in(args.message_file) as f: + message_type = f.read(8) + message = f.read() + if message_type not in {"PRE_KEY ", "MESSAGE "}: + sys.stderr.write("Expecting a PRE_KEY or MESSAGE message") + sys.exit(1) + message_type = 1 if message_type == "MESSAGE " else 0 + plaintext = session.decrypt(message_type, message) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + with open_out(args.plaintext_file) as f: + f.write(plaintext) + + decrypt.set_defaults(func=do_decrypt) + + outbound_group = commands.add_parser("outbound_group", help="Create an outbound group session") + outbound_group.add_argument("session_file", help="Local group session file") + outbound_group.set_defaults(func=do_outbound_group) + + group_credentials = commands.add_parser("group_credentials", help="Export the current outbound group session credentials") + group_credentials.add_argument("session_file", help="Local outbound group session file") + group_credentials.add_argument("credentials_file", help="File to write credentials to (default stdout)", + type=argparse.FileType('w'), nargs='?', + default=sys.stdout) + group_credentials.set_defaults(func=do_group_credentials) + + group_encrypt = commands.add_parser("group_encrypt", help="Encrypt a group message") + group_encrypt.add_argument("session_file", help="Local outbound group session file") + group_encrypt.add_argument("plaintext_file", help="Plaintext file (default stdin)", + type=argparse.FileType('rb'), nargs='?', + default=sys.stdin) + group_encrypt.add_argument("message_file", help="Message file (default stdout)", + type=argparse.FileType('w'), nargs='?', + default=sys.stdout) + group_encrypt.set_defaults(func=do_group_encrypt) + + inbound_group = commands.add_parser( + "inbound_group", + help=("Create an inbound group session based on credentials from an "+ + "outbound group session")) + inbound_group.add_argument("session_file", help="Local inbound group session file") + inbound_group.add_argument("credentials_file", + help="File to read credentials from (default stdin)", + type=argparse.FileType('r'), nargs='?', + default=sys.stdin) + inbound_group.set_defaults(func=do_inbound_group) + + group_decrypt = commands.add_parser("group_decrypt", help="Decrypt a group message") + group_decrypt.add_argument("session_file", help="Local inbound group session file") + group_decrypt.add_argument("message_file", help="Message file (default stdin)", + type=argparse.FileType('r'), nargs='?', + default=sys.stdin) + group_decrypt.add_argument("plaintext_file", help="Plaintext file (default stdout)", + type=argparse.FileType('wb'), nargs='?', + default=sys.stdout) + group_decrypt.set_defaults(func=do_group_decrypt) + return parser + +def do_outbound_group(args): + if os.path.exists(args.session_file): + sys.stderr.write("Session %r file already exists" % ( + args.session_file, + )) + sys.exit(1) + session = OutboundGroupSession() + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + +def do_group_encrypt(args): + session = OutboundGroupSession() + session.unpickle(args.key, read_base64_file(args.session_file)) + plaintext = args.plaintext_file.read() + message = session.encrypt(plaintext) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + args.message_file.write(message) + +def do_group_credentials(args): + session = OutboundGroupSession() + session.unpickle(args.key, read_base64_file(args.session_file)) + result = { + 'message_index': session.message_index(), + 'session_key': session.session_key(), + } + json.dump(result, args.credentials_file, indent=4) + +def do_inbound_group(args): + if os.path.exists(args.session_file): + sys.stderr.write("Session %r file already exists\n" % ( + args.session_file, + )) + sys.exit(1) + credentials = json.load(args.credentials_file) + for k in ('session_key', ): + if not k in credentials: + sys.stderr.write("Credentials file is missing %s\n" % k) + sys.exit(1); + + session = InboundGroupSession() + session.init(credentials['session_key']) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + +def do_group_decrypt(args): + session = InboundGroupSession() + session.unpickle(args.key, read_base64_file(args.session_file)) + message = args.message_file.read() + plaintext = session.decrypt(message) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + args.plaintext_file.write(plaintext) + +if __name__ == '__main__': + parser = build_arg_parser() + args = parser.parse_args() + args.func(args) diff --git a/python/olm/_base.py b/python/olm/_base.py new file mode 100644 index 0000000..bc5c206 --- /dev/null +++ b/python/olm/_base.py @@ -0,0 +1,19 @@ +import os.path + +from ctypes import * + +def read_random(n): + with open("/dev/urandom", "rb") as f: + return f.read(n) + +lib = cdll.LoadLibrary(os.path.join( + os.path.dirname(__file__), "..", "..", "build", "libolm.so.1") +) + +lib.olm_error.argtypes = [] +lib.olm_error.restypes = c_size_t + +ERR = lib.olm_error() + +class OlmError(Exception): + pass diff --git a/python/olm/account.py b/python/olm/account.py new file mode 100644 index 0000000..7673329 --- /dev/null +++ b/python/olm/account.py @@ -0,0 +1,121 @@ +import json + +from ._base import * + +lib.olm_account_size.argtypes = [] +lib.olm_account_size.restype = c_size_t + +lib.olm_account.argtypes = [c_void_p] +lib.olm_account.restype = c_void_p + +lib.olm_account_last_error.argtypes = [c_void_p] +lib.olm_account_last_error.restype = c_char_p + +def account_errcheck(res, func, args): + if res == ERR: + raise OlmError("%s: %s" % ( + func.__name__, lib.olm_account_last_error(args[0]) + )) + return res + + +def account_function(func, *types): + func.argtypes = (c_void_p,) + types + func.restypes = c_size_t + func.errcheck = account_errcheck + + +account_function( + lib.olm_pickle_account, c_void_p, c_size_t, c_void_p, c_size_t +) +account_function( + lib.olm_unpickle_account, c_void_p, c_size_t, c_void_p, c_size_t +) +account_function(lib.olm_create_account_random_length) +account_function(lib.olm_create_account, c_void_p, c_size_t) +account_function(lib.olm_account_identity_keys_length) +account_function(lib.olm_account_identity_keys, c_void_p, c_size_t) +account_function(lib.olm_account_signature_length) +account_function(lib.olm_account_sign, c_void_p, c_size_t, c_void_p, c_size_t) +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_mark_keys_as_published) +account_function(lib.olm_account_max_number_of_one_time_keys) +account_function( + lib.olm_account_generate_one_time_keys_random_length, + c_size_t +) +account_function( + lib.olm_account_generate_one_time_keys, + c_size_t, + c_void_p, c_size_t +) +class Account(object): + def __init__(self): + self.buf = create_string_buffer(lib.olm_account_size()) + self.ptr = lib.olm_account(self.buf) + + def create(self): + random_length = lib.olm_create_account_random_length(self.ptr) + random = read_random(random_length) + random_buffer = create_string_buffer(random) + lib.olm_create_account(self.ptr, random_buffer, random_length) + + def pickle(self, key): + key_buffer = create_string_buffer(key) + pickle_length = lib.olm_pickle_account_length(self.ptr) + pickle_buffer = create_string_buffer(pickle_length) + lib.olm_pickle_account( + self.ptr, key_buffer, len(key), pickle_buffer, pickle_length + ) + return pickle_buffer.raw + + def unpickle(self, key, pickle): + key_buffer = create_string_buffer(key) + pickle_buffer = create_string_buffer(pickle) + lib.olm_unpickle_account( + self.ptr, key_buffer, len(key), pickle_buffer, len(pickle) + ) + + def identity_keys(self): + out_length = lib.olm_account_identity_keys_length(self.ptr) + out_buffer = create_string_buffer(out_length) + lib.olm_account_identity_keys( + self.ptr, + out_buffer, out_length + ) + return json.loads(out_buffer.raw) + + def sign(self, message): + out_length = lib.olm_account_signature_length(self.ptr) + message_buffer = create_string_buffer(message) + out_buffer = create_string_buffer(out_length) + lib.olm_account_sign( + self.ptr, message_buffer, len(message), out_buffer, out_length + ) + return out_buffer.raw + + def one_time_keys(self): + out_length = lib.olm_account_one_time_keys_length(self.ptr) + out_buffer = create_string_buffer(out_length) + lib.olm_account_one_time_keys(self.ptr, out_buffer, out_length) + return json.loads(out_buffer.raw) + + def mark_keys_as_published(self): + lib.olm_account_mark_keys_as_published(self.ptr) + + def max_number_of_one_time_keys(self): + return lib.olm_account_max_number_of_one_time_keys(self.ptr) + + def generate_one_time_keys(self, count): + random_length = lib.olm_account_generate_one_time_keys_random_length( + self.ptr, count + ) + random = read_random(random_length) + random_buffer = create_string_buffer(random) + lib.olm_account_generate_one_time_keys( + self.ptr, count, random_buffer, random_length + ) + + def clear(self): + pass diff --git a/python/olm/inbound_group_session.py b/python/olm/inbound_group_session.py new file mode 100644 index 0000000..d5547fd --- /dev/null +++ b/python/olm/inbound_group_session.py @@ -0,0 +1,95 @@ +import json + +from ._base import * + +lib.olm_inbound_group_session_size.argtypes = [] +lib.olm_inbound_group_session_size.restype = c_size_t + +lib.olm_inbound_group_session.argtypes = [c_void_p] +lib.olm_inbound_group_session.restype = c_void_p + +lib.olm_inbound_group_session_last_error.argtypes = [c_void_p] +lib.olm_inbound_group_session_last_error.restype = c_char_p + +def inbound_group_session_errcheck(res, func, args): + if res == ERR: + raise OlmError("%s: %s" % ( + func.__name__, lib.olm_inbound_group_session_last_error(args[0]) + )) + return res + + +def inbound_group_session_function(func, *types): + func.argtypes = (c_void_p,) + types + func.restypes = c_size_t + func.errcheck = inbound_group_session_errcheck + + +inbound_group_session_function( + lib.olm_pickle_inbound_group_session, c_void_p, c_size_t, c_void_p, c_size_t +) +inbound_group_session_function( + lib.olm_unpickle_inbound_group_session, c_void_p, c_size_t, c_void_p, c_size_t +) + +inbound_group_session_function( + lib.olm_init_inbound_group_session, c_void_p, c_size_t +) + +inbound_group_session_function( + lib.olm_group_decrypt_max_plaintext_length, c_void_p, c_size_t +) +inbound_group_session_function( + lib.olm_group_decrypt, + c_void_p, c_size_t, # message + c_void_p, c_size_t, # plaintext +) + +inbound_group_session_function(lib.olm_inbound_group_session_id_length) +inbound_group_session_function(lib.olm_inbound_group_session_id, c_void_p, c_size_t) + +class InboundGroupSession(object): + def __init__(self): + self.buf = create_string_buffer(lib.olm_inbound_group_session_size()) + self.ptr = lib.olm_inbound_group_session(self.buf) + + def pickle(self, key): + key_buffer = create_string_buffer(key) + pickle_length = lib.olm_pickle_inbound_group_session_length(self.ptr) + pickle_buffer = create_string_buffer(pickle_length) + lib.olm_pickle_inbound_group_session( + self.ptr, key_buffer, len(key), pickle_buffer, pickle_length + ) + return pickle_buffer.raw + + def unpickle(self, key, pickle): + key_buffer = create_string_buffer(key) + pickle_buffer = create_string_buffer(pickle) + lib.olm_unpickle_inbound_group_session( + self.ptr, key_buffer, len(key), pickle_buffer, len(pickle) + ) + + def init(self, session_key): + key_buffer = create_string_buffer(session_key) + lib.olm_init_inbound_group_session( + self.ptr, key_buffer, len(session_key) + ) + + def decrypt(self, message): + message_buffer = create_string_buffer(message) + max_plaintext_length = lib.olm_group_decrypt_max_plaintext_length( + self.ptr, message_buffer, len(message) + ) + plaintext_buffer = create_string_buffer(max_plaintext_length) + message_buffer = create_string_buffer(message) + plaintext_length = lib.olm_group_decrypt( + self.ptr, message_buffer, len(message), + plaintext_buffer, max_plaintext_length + ) + return plaintext_buffer.raw[:plaintext_length] + + def session_id(self): + id_length = lib.olm_inbound_group_session_id_length(self.ptr) + id_buffer = create_string_buffer(id_length) + lib.olm_inbound_group_session_id(self.ptr, id_buffer, id_length); + return id_buffer.raw diff --git a/python/olm/outbound_group_session.py b/python/olm/outbound_group_session.py new file mode 100644 index 0000000..56f0962 --- /dev/null +++ b/python/olm/outbound_group_session.py @@ -0,0 +1,107 @@ +import json + +from ._base import * + +lib.olm_outbound_group_session_size.argtypes = [] +lib.olm_outbound_group_session_size.restype = c_size_t + +lib.olm_outbound_group_session.argtypes = [c_void_p] +lib.olm_outbound_group_session.restype = c_void_p + +lib.olm_outbound_group_session_last_error.argtypes = [c_void_p] +lib.olm_outbound_group_session_last_error.restype = c_char_p + +def outbound_group_session_errcheck(res, func, args): + if res == ERR: + raise OlmError("%s: %s" % ( + func.__name__, lib.olm_outbound_group_session_last_error(args[0]) + )) + return res + + +def outbound_group_session_function(func, *types): + func.argtypes = (c_void_p,) + types + func.restypes = c_size_t + func.errcheck = outbound_group_session_errcheck + + +outbound_group_session_function( + lib.olm_pickle_outbound_group_session, c_void_p, c_size_t, c_void_p, c_size_t +) +outbound_group_session_function( + lib.olm_unpickle_outbound_group_session, c_void_p, c_size_t, c_void_p, c_size_t +) + +outbound_group_session_function(lib.olm_init_outbound_group_session_random_length) +outbound_group_session_function(lib.olm_init_outbound_group_session, c_void_p, c_size_t) + +lib.olm_outbound_group_session_message_index.argtypes = [c_void_p] +lib.olm_outbound_group_session_message_index.restype = c_uint32 + +outbound_group_session_function(lib.olm_group_encrypt_message_length, c_size_t) +outbound_group_session_function(lib.olm_group_encrypt, + c_void_p, c_size_t, # Plaintext + c_void_p, c_size_t, # Message +) + +outbound_group_session_function(lib.olm_outbound_group_session_id_length) +outbound_group_session_function(lib.olm_outbound_group_session_id, c_void_p, c_size_t) +outbound_group_session_function(lib.olm_outbound_group_session_key_length) +outbound_group_session_function(lib.olm_outbound_group_session_key, c_void_p, c_size_t) + + +class OutboundGroupSession(object): + def __init__(self): + self.buf = create_string_buffer(lib.olm_outbound_group_session_size()) + self.ptr = lib.olm_outbound_group_session(self.buf) + + random_length = lib.olm_init_outbound_group_session_random_length(self.ptr) + random = read_random(random_length) + random_buffer = create_string_buffer(random) + lib.olm_init_outbound_group_session(self.ptr, random_buffer, random_length) + + def pickle(self, key): + key_buffer = create_string_buffer(key) + pickle_length = lib.olm_pickle_outbound_group_session_length(self.ptr) + pickle_buffer = create_string_buffer(pickle_length) + lib.olm_pickle_outbound_group_session( + self.ptr, key_buffer, len(key), pickle_buffer, pickle_length + ) + return pickle_buffer.raw + + def unpickle(self, key, pickle): + key_buffer = create_string_buffer(key) + pickle_buffer = create_string_buffer(pickle) + lib.olm_unpickle_outbound_group_session( + self.ptr, key_buffer, len(key), pickle_buffer, len(pickle) + ) + + def encrypt(self, plaintext): + message_length = lib.olm_group_encrypt_message_length( + self.ptr, len(plaintext) + ) + message_buffer = create_string_buffer(message_length) + + plaintext_buffer = create_string_buffer(plaintext) + + lib.olm_group_encrypt( + self.ptr, + plaintext_buffer, len(plaintext), + message_buffer, message_length, + ) + return message_buffer.raw + + def session_id(self): + id_length = lib.olm_outbound_group_session_id_length(self.ptr) + id_buffer = create_string_buffer(id_length) + lib.olm_outbound_group_session_id(self.ptr, id_buffer, id_length); + return id_buffer.raw + + def message_index(self): + return lib.olm_outbound_group_session_message_index(self.ptr) + + def session_key(self): + key_length = lib.olm_outbound_group_session_key_length(self.ptr) + key_buffer = create_string_buffer(key_length) + lib.olm_outbound_group_session_key(self.ptr, key_buffer, key_length); + return key_buffer.raw diff --git a/python/olm/session.py b/python/olm/session.py new file mode 100644 index 0000000..308f220 --- /dev/null +++ b/python/olm/session.py @@ -0,0 +1,192 @@ +from ._base import * + + +lib.olm_session_size.argtypes = [] +lib.olm_session_size.restype = c_size_t + +lib.olm_session.argtypes = [c_void_p] +lib.olm_session.restype = c_void_p + +lib.olm_session_last_error.argtypes = [c_void_p] +lib.olm_session_last_error.restype = c_char_p + + +def session_errcheck(res, func, args): + if res == ERR: + raise OlmError("%s: %s" % ( + func.__name__, lib.olm_session_last_error(args[0]) + )) + return res + + +def session_function(func, *types): + func.argtypes = (c_void_p,) + types + func.restypes = c_size_t + func.errcheck = session_errcheck + +session_function(lib.olm_session_last_error) +session_function( + lib.olm_pickle_session, c_void_p, c_size_t, c_void_p, c_size_t +) +session_function( + lib.olm_unpickle_session, c_void_p, c_size_t, c_void_p, c_size_t +) +session_function(lib.olm_create_outbound_session_random_length) +session_function( + lib.olm_create_outbound_session, + c_void_p, # Account + c_void_p, c_size_t, # Identity Key + c_void_p, c_size_t, # One Time Key + c_void_p, c_size_t, # Random +) +session_function( + lib.olm_create_inbound_session, + c_void_p, # Account + c_void_p, c_size_t, # Pre Key Message +) +session_function( + lib.olm_create_inbound_session_from, + c_void_p, # Account + c_void_p, c_size_t, # Identity Key + c_void_p, c_size_t, # Pre Key Message +) +session_function(lib.olm_session_id_length) +session_function(lib.olm_session_id, c_void_p, c_size_t) +session_function(lib.olm_matches_inbound_session, c_void_p, c_size_t) +session_function( + lib.olm_matches_inbound_session_from, + c_void_p, c_size_t, # Identity Key + c_void_p, c_size_t, # Pre Key Message +) +session_function(lib.olm_encrypt_message_type) +session_function(lib.olm_encrypt_random_length) +session_function(lib.olm_encrypt_message_length, c_size_t) +session_function( + lib.olm_encrypt, + c_void_p, c_size_t, # Plaintext + c_void_p, c_size_t, # Random + c_void_p, c_size_t, # Message +); +session_function( + lib.olm_decrypt_max_plaintext_length, + c_size_t, # Message Type + c_void_p, c_size_t, # Message +) +session_function( + lib.olm_decrypt, + c_size_t, # Message Type + c_void_p, c_size_t, # Message + c_void_p, c_size_t, # Plaintext +) + +class Session(object): + def __init__(self): + self.buf = create_string_buffer(lib.olm_session_size()) + self.ptr = lib.olm_session(self.buf) + + def pickle(self, key): + key_buffer = create_string_buffer(key) + pickle_length = lib.olm_pickle_session_length(self.ptr) + pickle_buffer = create_string_buffer(pickle_length) + lib.olm_pickle_session( + self.ptr, key_buffer, len(key), pickle_buffer, pickle_length + ) + return pickle_buffer.raw + + def unpickle(self, key, pickle): + key_buffer = create_string_buffer(key) + pickle_buffer = create_string_buffer(pickle) + lib.olm_unpickle_session( + self.ptr, key_buffer, len(key), pickle_buffer, len(pickle) + ) + + def create_outbound(self, account, identity_key, one_time_key): + r_length = lib.olm_create_outbound_session_random_length(self.ptr) + random = read_random(r_length) + random_buffer = create_string_buffer(random) + identity_key_buffer = create_string_buffer(identity_key) + one_time_key_buffer = create_string_buffer(one_time_key) + lib.olm_create_outbound_session( + self.ptr, + account.ptr, + identity_key_buffer, len(identity_key), + one_time_key_buffer, len(one_time_key), + random_buffer, r_length + ) + + def create_inbound(self, account, one_time_key_message): + one_time_key_message_buffer = create_string_buffer(one_time_key_message) + lib.olm_create_inbound_session( + self.ptr, + account.ptr, + one_time_key_message_buffer, len(one_time_key_message) + ) + + def create_inbound_from(self, account, identity_key, one_time_key_message): + identity_key_buffer = create_string_buffer(identity_key) + one_time_key_message_buffer = create_string_buffer(one_time_key_message) + lib.olm_create_inbound_session_from( + self.ptr, + account.ptr, + identity_key_buffer, len(identity_key), + one_time_key_message_buffer, len(one_time_key_message) + ) + + def session_id(self): + id_length = lib.olm_session_id_length(self.ptr) + id_buffer = create_string_buffer(id_length) + lib.olm_session_id(self.ptr, id_buffer, id_length); + return id_buffer.raw + + def matches_inbound(self, one_time_key_message): + one_time_key_message_buffer = create_string_buffer(one_time_key_message) + return bool(lib.olm_matches_inbound_session( + self.ptr, + one_time_key_message_buffer, len(one_time_key_message) + )) + + def matches_inbound_from(self, identity_key, one_time_key_message): + identity_key_buffer = create_string_buffer(identity_key) + one_time_key_message_buffer = create_string_buffer(one_time_key_message) + return bool(lib.olm_matches_inbound_session( + self.ptr, + identity_key_buffer, len(identity_key), + one_time_key_message_buffer, len(one_time_key_message) + )) + + def encrypt(self, plaintext): + r_length = lib.olm_encrypt_random_length(self.ptr) + random = read_random(r_length) + random_buffer = create_string_buffer(random) + + message_type = lib.olm_encrypt_message_type(self.ptr) + message_length = lib.olm_encrypt_message_length( + self.ptr, len(plaintext) + ) + message_buffer = create_string_buffer(message_length) + + plaintext_buffer = create_string_buffer(plaintext) + + lib.olm_encrypt( + self.ptr, + plaintext_buffer, len(plaintext), + random_buffer, r_length, + message_buffer, message_length, + ) + return message_type, message_buffer.raw + + def decrypt(self, message_type, message): + message_buffer = create_string_buffer(message) + max_plaintext_length = lib.olm_decrypt_max_plaintext_length( + self.ptr, message_type, message_buffer, len(message) + ) + plaintext_buffer = create_string_buffer(max_plaintext_length) + message_buffer = create_string_buffer(message) + plaintext_length = lib.olm_decrypt( + self.ptr, message_type, message_buffer, len(message), + plaintext_buffer, max_plaintext_length + ) + return plaintext_buffer.raw[:plaintext_length] + + def clear(self): + pass diff --git a/python/test_olm.sh b/python/test_olm.sh index 78cd3c2..989e166 100755 --- a/python/test_olm.sh +++ b/python/test_olm.sh @@ -1,22 +1,34 @@ #! /bin/bash -OLM="$(dirname $0)/olm.py" +cd `dirname $0` + +OLM="python -m olm" ALICE_ACCOUNT=alice.account ALICE_SESSION=alice.session +ALICE_GROUP_SESSION=alice.group_session BOB_ACCOUNT=bob.account BOB_SESSION=bob.session +BOB_GROUP_SESSION=bob.group_session rm $ALICE_ACCOUNT $BOB_ACCOUNT rm $ALICE_SESSION $BOB_SESSION +rm $ALICE_GROUP_SESSION $BOB_GROUP_SESSION $OLM create_account $ALICE_ACCOUNT $OLM create_account $BOB_ACCOUNT $OLM generate_keys $BOB_ACCOUNT 1 -BOB_IDENTITY_KEY="$($OLM keys --json $BOB_ACCOUNT | jq -r .account_keys.curve25519)" -BOB_ONE_TIME_KEY="$($OLM keys --json $BOB_ACCOUNT | jq -r '.one_time_keys.curve25519|to_entries[0].value')" +BOB_IDENTITY_KEY="$($OLM identity_key $BOB_ACCOUNT)" +BOB_ONE_TIME_KEY="$($OLM one_time_key $BOB_ACCOUNT)" $OLM outbound $ALICE_ACCOUNT $ALICE_SESSION "$BOB_IDENTITY_KEY" "$BOB_ONE_TIME_KEY" echo "Hello world" | $OLM encrypt $ALICE_SESSION - - | $OLM inbound $BOB_ACCOUNT $BOB_SESSION - - + + +### group sessions + +$OLM outbound_group $ALICE_GROUP_SESSION +$OLM group_credentials $ALICE_GROUP_SESSION | $OLM inbound_group $BOB_GROUP_SESSION +echo "Hello group" | $OLM group_encrypt $ALICE_GROUP_SESSION - - | $OLM group_decrypt $BOB_GROUP_SESSION diff --git a/src/account.cpp b/src/account.cpp index 43033c8..248f3d1 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015 OpenMarket Ltd +/* Copyright 2015, 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. @@ -14,20 +14,21 @@ */ #include "olm/account.hh" #include "olm/base64.hh" +#include "olm/pickle.h" #include "olm/pickle.hh" #include "olm/memory.hh" olm::Account::Account( ) : next_one_time_key_id(0), - last_error(olm::ErrorCode::SUCCESS) { + last_error(OlmErrorCode::OLM_SUCCESS) { } olm::OneTimeKey const * olm::Account::lookup_key( - olm::Curve25519PublicKey const & public_key + _olm_curve25519_public_key const & public_key ) { for (olm::OneTimeKey const & key : one_time_keys) { - if (olm::array_equal(key.key.public_key, public_key.public_key)) { + if (olm::array_equal(key.key.public_key.public_key, public_key.public_key)) { return &key; } } @@ -35,11 +36,11 @@ olm::OneTimeKey const * olm::Account::lookup_key( } std::size_t olm::Account::remove_key( - olm::Curve25519PublicKey const & public_key + _olm_curve25519_public_key const & public_key ) { OneTimeKey * i; for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) { - if (olm::array_equal(i->key.public_key, public_key.public_key)) { + if (olm::array_equal(i->key.public_key.public_key, public_key.public_key)) { std::uint32_t id = i->id; one_time_keys.erase(i); return id; @@ -49,20 +50,20 @@ std::size_t olm::Account::remove_key( } std::size_t olm::Account::new_account_random_length() { - return 2 * olm::KEY_LENGTH; + return ED25519_RANDOM_LENGTH + CURVE25519_RANDOM_LENGTH; } std::size_t olm::Account::new_account( uint8_t const * random, std::size_t random_length ) { if (random_length < new_account_random_length()) { - last_error = olm::ErrorCode::NOT_ENOUGH_RANDOM; + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; return std::size_t(-1); } - olm::ed25519_generate_key(random, identity_keys.ed25519_key); - random += KEY_LENGTH; - olm::curve25519_generate_key(random, identity_keys.curve25519_key); + _olm_crypto_ed25519_generate_key(random, &identity_keys.ed25519_key); + random += ED25519_RANDOM_LENGTH; + _olm_crypto_curve25519_generate_key(random, &identity_keys.curve25519_key); return 0; } @@ -110,7 +111,7 @@ std::size_t olm::Account::get_identity_json( size_t expected_length = get_identity_json_length(); if (identity_json_length < expected_length) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } @@ -118,16 +119,16 @@ std::size_t olm::Account::get_identity_json( pos = write_string(pos, KEY_JSON_CURVE25519); *(pos++) = '\"'; pos = olm::encode_base64( - identity_keys.curve25519_key.public_key, - sizeof(identity_keys.curve25519_key.public_key), + identity_keys.curve25519_key.public_key.public_key, + sizeof(identity_keys.curve25519_key.public_key.public_key), pos ); *(pos++) = '\"'; *(pos++) = ','; pos = write_string(pos, KEY_JSON_ED25519); *(pos++) = '\"'; pos = olm::encode_base64( - identity_keys.ed25519_key.public_key, - sizeof(identity_keys.ed25519_key.public_key), + identity_keys.ed25519_key.public_key.public_key, + sizeof(identity_keys.ed25519_key.public_key.public_key), pos ); *(pos++) = '\"'; *(pos++) = '}'; @@ -137,7 +138,7 @@ std::size_t olm::Account::get_identity_json( std::size_t olm::Account::signature_length( ) { - return olm::SIGNATURE_LENGTH; + return ED25519_SIGNATURE_LENGTH; } @@ -146,11 +147,11 @@ std::size_t olm::Account::sign( std::uint8_t * signature, std::size_t signature_length ) { if (signature_length < this->signature_length()) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } - olm::ed25519_sign( - identity_keys.ed25519_key, message, message_length, signature + _olm_crypto_ed25519_sign( + &identity_keys.ed25519_key, message, message_length, signature ); return this->signature_length(); } @@ -185,7 +186,7 @@ std::size_t olm::Account::get_one_time_keys_json( ) { std::uint8_t * pos = one_time_json; if (one_time_json_length < get_one_time_keys_json_length()) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } *(pos++) = '{'; @@ -202,7 +203,7 @@ std::size_t olm::Account::get_one_time_keys_json( pos = olm::encode_base64(key_id, sizeof(key_id), pos); *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"'; pos = olm::encode_base64( - key.key.public_key, sizeof(key.key.public_key), pos + key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos ); *(pos++) = '\"'; sep = ','; @@ -238,7 +239,7 @@ std::size_t olm::Account::max_number_of_one_time_keys( std::size_t olm::Account::generate_one_time_keys_random_length( std::size_t number_of_keys ) { - return olm::KEY_LENGTH * number_of_keys; + return CURVE25519_RANDOM_LENGTH * number_of_keys; } std::size_t olm::Account::generate_one_time_keys( @@ -246,15 +247,15 @@ std::size_t olm::Account::generate_one_time_keys( std::uint8_t const * random, std::size_t random_length ) { if (random_length < generate_one_time_keys_random_length(number_of_keys)) { - last_error = olm::ErrorCode::NOT_ENOUGH_RANDOM; + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; return std::size_t(-1); } for (unsigned i = 0; i < number_of_keys; ++i) { OneTimeKey & key = *one_time_keys.insert(one_time_keys.begin()); key.id = ++next_one_time_key_id; key.published = false; - olm::curve25519_generate_key(random, key.key); - random += olm::KEY_LENGTH; + _olm_crypto_curve25519_generate_key(random, &key.key); + random += CURVE25519_RANDOM_LENGTH; } return number_of_keys; } @@ -265,7 +266,7 @@ static std::size_t pickle_length( olm::IdentityKeys const & value ) { size_t length = 0; - length += olm::pickle_length(value.ed25519_key); + length += _olm_pickle_ed25519_key_pair_length(&value.ed25519_key); length += olm::pickle_length(value.curve25519_key); return length; } @@ -275,7 +276,7 @@ static std::uint8_t * pickle( std::uint8_t * pos, olm::IdentityKeys const & value ) { - pos = olm::pickle(pos, value.ed25519_key); + pos = _olm_pickle_ed25519_key_pair(pos, &value.ed25519_key); pos = olm::pickle(pos, value.curve25519_key); return pos; } @@ -285,7 +286,7 @@ static std::uint8_t const * unpickle( std::uint8_t const * pos, std::uint8_t const * end, olm::IdentityKeys & value ) { - pos = olm::unpickle(pos, end, value.ed25519_key); + pos = _olm_unpickle_ed25519_key_pair(pos, end, &value.ed25519_key); pos = olm::unpickle(pos, end, value.curve25519_key); return pos; } @@ -326,7 +327,9 @@ static std::uint8_t const * unpickle( } // namespace olm namespace { -static const std::uint32_t ACCOUNT_PICKLE_VERSION = 1; +// pickle version 1 used only 32 bytes for the ed25519 private key. +// Any keys thus used should be considered compromised. +static const std::uint32_t ACCOUNT_PICKLE_VERSION = 2; } @@ -360,9 +363,15 @@ std::uint8_t const * olm::unpickle( ) { uint32_t pickle_version; pos = olm::unpickle(pos, end, pickle_version); - if (pickle_version != ACCOUNT_PICKLE_VERSION) { - value.last_error = olm::ErrorCode::UNKNOWN_PICKLE_VERSION; - return end; + switch (pickle_version) { + case ACCOUNT_PICKLE_VERSION: + break; + case 1: + value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE; + return end; + default: + value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION; + return end; } pos = olm::unpickle(pos, end, value.identity_keys); pos = olm::unpickle(pos, end, value.one_time_keys); diff --git a/src/base64.cpp b/src/base64.cpp index bf8492e..bbfb210 100644 --- a/src/base64.cpp +++ b/src/base64.cpp @@ -12,9 +12,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "olm/base64.h" #include "olm/base64.hh" - namespace { static const std::uint8_t ENCODE_BASE64[64] = { @@ -45,6 +45,12 @@ static const std::uint8_t DECODE_BASE64[128] = { } // namespace +std::size_t olm::encode_base64_length( + std::size_t input_length +) { + return 4 * ((input_length + 2) / 3) + (input_length + 2) % 3 - 2; +} + std::uint8_t * olm::encode_base64( std::uint8_t const * input, std::size_t input_length, std::uint8_t * output @@ -128,3 +134,34 @@ std::uint8_t const * olm::decode_base64( } return input + input_length; } + + +// implementations of base64.h + +size_t _olm_encode_base64_length( + size_t input_length +) { + return olm::encode_base64_length(input_length); +} + +size_t _olm_encode_base64( + uint8_t const * input, size_t input_length, + uint8_t * output +) { + uint8_t * r = olm::encode_base64(input, input_length, output); + return r - output; +} + +size_t _olm_decode_base64_length( + size_t input_length +) { + return olm::decode_base64_length(input_length); +} + +size_t _olm_decode_base64( + uint8_t const * input, size_t input_length, + uint8_t * output +) { + olm::decode_base64(input, input_length, output); + return olm::decode_base64_length(input_length); +} diff --git a/src/cipher.cpp b/src/cipher.cpp index 7bb11b8..6b53690 100644 --- a/src/cipher.cpp +++ b/src/cipher.cpp @@ -12,21 +12,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "olm/cipher.hh" -#include "olm/crypto.hh" +#include "olm/cipher.h" +#include "olm/crypto.h" #include "olm/memory.hh" #include <cstring> -olm::Cipher::~Cipher() { - -} +const std::size_t HMAC_KEY_LENGTH = 32; namespace { struct DerivedKeys { - olm::Aes256Key aes_key; - std::uint8_t mac_key[olm::KEY_LENGTH]; - olm::Aes256Iv aes_iv; + _olm_aes256_key aes_key; + std::uint8_t mac_key[HMAC_KEY_LENGTH]; + _olm_aes256_iv aes_iv; }; @@ -35,8 +33,10 @@ static void derive_keys( std::uint8_t const * key, std::size_t key_length, DerivedKeys & keys ) { - std::uint8_t derived_secrets[2 * olm::KEY_LENGTH + olm::IV_LENGTH]; - olm::hkdf_sha256( + std::uint8_t derived_secrets[ + AES256_KEY_LENGTH + HMAC_KEY_LENGTH + AES256_IV_LENGTH + ]; + _olm_crypto_hkdf_sha256( key, key_length, nullptr, 0, kdf_info, kdf_info_length, @@ -51,48 +51,41 @@ static void derive_keys( static const std::size_t MAC_LENGTH = 8; -} // namespace - - -olm::CipherAesSha256::CipherAesSha256( - std::uint8_t const * kdf_info, std::size_t kdf_info_length -) : kdf_info(kdf_info), kdf_info_length(kdf_info_length) { - -} - - -std::size_t olm::CipherAesSha256::mac_length() const { +size_t aes_sha_256_cipher_mac_length(const struct _olm_cipher *cipher) { return MAC_LENGTH; } - -std::size_t olm::CipherAesSha256::encrypt_ciphertext_length( - std::size_t plaintext_length -) const { - return olm::aes_encrypt_cbc_length(plaintext_length); +size_t aes_sha_256_cipher_encrypt_ciphertext_length( + const struct _olm_cipher *cipher, size_t plaintext_length +) { + return _olm_crypto_aes_encrypt_cbc_length(plaintext_length); } +size_t aes_sha_256_cipher_encrypt( + const struct _olm_cipher *cipher, + uint8_t const * key, size_t key_length, + uint8_t const * plaintext, size_t plaintext_length, + uint8_t * ciphertext, size_t ciphertext_length, + uint8_t * output, size_t output_length +) { + auto *c = reinterpret_cast<const _olm_cipher_aes_sha_256 *>(cipher); -std::size_t olm::CipherAesSha256::encrypt( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t const * plaintext, std::size_t plaintext_length, - std::uint8_t * ciphertext, std::size_t ciphertext_length, - std::uint8_t * output, std::size_t output_length -) const { - if (encrypt_ciphertext_length(plaintext_length) < ciphertext_length) { + if (aes_sha_256_cipher_encrypt_ciphertext_length(cipher, plaintext_length) + < ciphertext_length) { return std::size_t(-1); } + struct DerivedKeys keys; - std::uint8_t mac[olm::SHA256_OUTPUT_LENGTH]; + std::uint8_t mac[SHA256_OUTPUT_LENGTH]; - derive_keys(kdf_info, kdf_info_length, key, key_length, keys); + derive_keys(c->kdf_info, c->kdf_info_length, key, key_length, keys); - olm::aes_encrypt_cbc( - keys.aes_key, keys.aes_iv, plaintext, plaintext_length, ciphertext + _olm_crypto_aes_encrypt_cbc( + &keys.aes_key, &keys.aes_iv, plaintext, plaintext_length, ciphertext ); - olm::hmac_sha256( - keys.mac_key, olm::KEY_LENGTH, output, output_length - MAC_LENGTH, mac + _olm_crypto_hmac_sha256( + keys.mac_key, HMAC_KEY_LENGTH, output, output_length - MAC_LENGTH, mac ); std::memcpy(output + output_length - MAC_LENGTH, mac, MAC_LENGTH); @@ -102,25 +95,29 @@ std::size_t olm::CipherAesSha256::encrypt( } -std::size_t olm::CipherAesSha256::decrypt_max_plaintext_length( - std::size_t ciphertext_length -) const { +size_t aes_sha_256_cipher_decrypt_max_plaintext_length( + const struct _olm_cipher *cipher, + size_t ciphertext_length +) { return ciphertext_length; } -std::size_t olm::CipherAesSha256::decrypt( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t const * input, std::size_t input_length, - std::uint8_t const * ciphertext, std::size_t ciphertext_length, - std::uint8_t * plaintext, std::size_t max_plaintext_length -) const { +size_t aes_sha_256_cipher_decrypt( + const struct _olm_cipher *cipher, + uint8_t const * key, size_t key_length, + uint8_t const * input, size_t input_length, + uint8_t const * ciphertext, size_t ciphertext_length, + uint8_t * plaintext, size_t max_plaintext_length +) { + auto *c = reinterpret_cast<const _olm_cipher_aes_sha_256 *>(cipher); + DerivedKeys keys; - std::uint8_t mac[olm::SHA256_OUTPUT_LENGTH]; + std::uint8_t mac[SHA256_OUTPUT_LENGTH]; - derive_keys(kdf_info, kdf_info_length, key, key_length, keys); + derive_keys(c->kdf_info, c->kdf_info_length, key, key_length, keys); - olm::hmac_sha256( - keys.mac_key, olm::KEY_LENGTH, input, input_length - MAC_LENGTH, mac + _olm_crypto_hmac_sha256( + keys.mac_key, HMAC_KEY_LENGTH, input, input_length - MAC_LENGTH, mac ); std::uint8_t const * input_mac = input + input_length - MAC_LENGTH; @@ -129,10 +126,20 @@ std::size_t olm::CipherAesSha256::decrypt( return std::size_t(-1); } - std::size_t plaintext_length = olm::aes_decrypt_cbc( - keys.aes_key, keys.aes_iv, ciphertext, ciphertext_length, plaintext + std::size_t plaintext_length = _olm_crypto_aes_decrypt_cbc( + &keys.aes_key, &keys.aes_iv, ciphertext, ciphertext_length, plaintext ); olm::unset(keys); return plaintext_length; } + +} // namespace + +const struct _olm_cipher_ops _olm_cipher_aes_sha_256_ops = { + aes_sha_256_cipher_mac_length, + aes_sha_256_cipher_encrypt_ciphertext_length, + aes_sha_256_cipher_encrypt, + aes_sha_256_cipher_decrypt_max_plaintext_length, + aes_sha_256_cipher_decrypt, +}; diff --git a/src/crypto.cpp b/src/crypto.cpp index 8024355..5095c79 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -12,62 +12,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "olm/crypto.hh" +#include "olm/crypto.h" #include "olm/memory.hh" #include <cstring> extern "C" { -int curve25519_donna( - uint8_t * output, - const uint8_t * secret, - const uint8_t * basepoint -); - #include "crypto-algorithms/aes.h" #include "crypto-algorithms/sha256.h" -int ed25519_sign( - unsigned char *signature, - const unsigned char *message, size_t message_len, - const unsigned char *public_key, - const unsigned char *private_key -); - - -int ed25519_verify( - const unsigned char *signature, - const unsigned char *message, size_t message_len, - const unsigned char *public_key -); - - -void convert_curve25519_to_ed25519( - unsigned char * public_key, - unsigned char * signature -); - - -void convert_ed25519_to_curve25519( - unsigned char const * public_key, - unsigned char * signature -); - - -void ed25519_keypair( - unsigned char * private_key, - unsigned char * public_key -); - } +#include "ed25519/src/ed25519.h" +#include "curve25519-donna.h" namespace { static const std::uint8_t CURVE25519_BASEPOINT[32] = {9}; static const std::size_t AES_KEY_SCHEDULE_LENGTH = 60; -static const std::size_t AES_KEY_BITS = 8 * olm::KEY_LENGTH; +static const std::size_t AES_KEY_BITS = 8 * AES256_KEY_LENGTH; static const std::size_t AES_BLOCK_LENGTH = 16; static const std::size_t SHA256_BLOCK_LENGTH = 64; static const std::uint8_t HKDF_DEFAULT_SALT[32] = {}; @@ -120,7 +84,7 @@ inline static void hmac_sha256_final( std::uint8_t const * hmac_key, std::uint8_t * output ) { - std::uint8_t o_pad[SHA256_BLOCK_LENGTH + olm::SHA256_OUTPUT_LENGTH]; + std::uint8_t o_pad[SHA256_BLOCK_LENGTH + SHA256_OUTPUT_LENGTH]; std::memcpy(o_pad, hmac_key, SHA256_BLOCK_LENGTH); for (std::size_t i = 0; i < SHA256_BLOCK_LENGTH; ++i) { o_pad[i] ^= 0x5C; @@ -136,115 +100,86 @@ inline static void hmac_sha256_final( } // namespace - -void olm::curve25519_generate_key( - std::uint8_t const * random_32_bytes, - olm::Curve25519KeyPair & key_pair +void _olm_crypto_curve25519_generate_key( + uint8_t const * random_32_bytes, + struct _olm_curve25519_key_pair *key_pair ) { - std::memcpy(key_pair.private_key, random_32_bytes, KEY_LENGTH); + std::memcpy( + key_pair->private_key.private_key, random_32_bytes, + CURVE25519_KEY_LENGTH + ); ::curve25519_donna( - key_pair.public_key, key_pair.private_key, CURVE25519_BASEPOINT + key_pair->public_key.public_key, + key_pair->private_key.private_key, + CURVE25519_BASEPOINT ); } -void olm::curve25519_shared_secret( - olm::Curve25519KeyPair const & our_key, - olm::Curve25519PublicKey const & their_key, - std::uint8_t * output -) { - ::curve25519_donna(output, our_key.private_key, their_key.public_key); -} - - -void olm::curve25519_sign( - olm::Curve25519KeyPair const & our_key, - std::uint8_t const * message, std::size_t message_length, +void _olm_crypto_curve25519_shared_secret( + const struct _olm_curve25519_key_pair *our_key, + const struct _olm_curve25519_public_key * their_key, std::uint8_t * output ) { - std::uint8_t private_key[KEY_LENGTH]; - std::uint8_t public_key[KEY_LENGTH]; - std::memcpy(private_key, our_key.private_key, KEY_LENGTH); - ::ed25519_keypair(private_key, public_key); - ::ed25519_sign( - output, - message, message_length, - public_key, private_key - ); - ::convert_ed25519_to_curve25519(public_key, output); + ::curve25519_donna(output, our_key->private_key.private_key, their_key->public_key); } -bool olm::curve25519_verify( - olm::Curve25519PublicKey const & their_key, - std::uint8_t const * message, std::size_t message_length, - std::uint8_t const * signature -) { - std::uint8_t public_key[KEY_LENGTH]; - std::uint8_t signature_buffer[SIGNATURE_LENGTH]; - std::memcpy(public_key, their_key.public_key, KEY_LENGTH); - std::memcpy(signature_buffer, signature, SIGNATURE_LENGTH); - ::convert_curve25519_to_ed25519(public_key, signature_buffer); - return 0 != ::ed25519_verify( - signature, - message, message_length, - public_key - ); -} - - -void olm::ed25519_generate_key( +void _olm_crypto_ed25519_generate_key( std::uint8_t const * random_32_bytes, - olm::Ed25519KeyPair & key_pair + struct _olm_ed25519_key_pair *key_pair ) { - std::memcpy(key_pair.private_key, random_32_bytes, KEY_LENGTH); - ::ed25519_keypair(key_pair.private_key, key_pair.public_key); + ::ed25519_create_keypair( + key_pair->public_key.public_key, key_pair->private_key.private_key, + random_32_bytes + ); } -void olm::ed25519_sign( - olm::Ed25519KeyPair const & our_key, +void _olm_crypto_ed25519_sign( + const struct _olm_ed25519_key_pair *our_key, std::uint8_t const * message, std::size_t message_length, std::uint8_t * output ) { ::ed25519_sign( output, message, message_length, - our_key.public_key, our_key.private_key + our_key->public_key.public_key, + our_key->private_key.private_key ); } -bool olm::ed25519_verify( - olm::Ed25519PublicKey const & their_key, +int _olm_crypto_ed25519_verify( + const struct _olm_ed25519_public_key *their_key, std::uint8_t const * message, std::size_t message_length, std::uint8_t const * signature ) { return 0 != ::ed25519_verify( signature, message, message_length, - their_key.public_key + their_key->public_key ); } -std::size_t olm::aes_encrypt_cbc_length( +std::size_t _olm_crypto_aes_encrypt_cbc_length( std::size_t input_length ) { return input_length + AES_BLOCK_LENGTH - input_length % AES_BLOCK_LENGTH; } -void olm::aes_encrypt_cbc( - olm::Aes256Key const & key, - olm::Aes256Iv const & iv, +void _olm_crypto_aes_encrypt_cbc( + _olm_aes256_key const *key, + _olm_aes256_iv const *iv, std::uint8_t const * input, std::size_t input_length, std::uint8_t * output ) { std::uint32_t key_schedule[AES_KEY_SCHEDULE_LENGTH]; - ::aes_key_setup(key.key, key_schedule, AES_KEY_BITS); + ::aes_key_setup(key->key, key_schedule, AES_KEY_BITS); std::uint8_t input_block[AES_BLOCK_LENGTH]; - std::memcpy(input_block, iv.iv, AES_BLOCK_LENGTH); + std::memcpy(input_block, iv->iv, AES_BLOCK_LENGTH); while (input_length >= AES_BLOCK_LENGTH) { xor_block<AES_BLOCK_LENGTH>(input_block, input); ::aes_encrypt(input_block, output, key_schedule, AES_KEY_BITS); @@ -266,17 +201,17 @@ void olm::aes_encrypt_cbc( } -std::size_t olm::aes_decrypt_cbc( - olm::Aes256Key const & key, - olm::Aes256Iv const & iv, +std::size_t _olm_crypto_aes_decrypt_cbc( + _olm_aes256_key const *key, + _olm_aes256_iv const *iv, std::uint8_t const * input, std::size_t input_length, std::uint8_t * output ) { std::uint32_t key_schedule[AES_KEY_SCHEDULE_LENGTH]; - ::aes_key_setup(key.key, key_schedule, AES_KEY_BITS); + ::aes_key_setup(key->key, key_schedule, AES_KEY_BITS); std::uint8_t block1[AES_BLOCK_LENGTH]; std::uint8_t block2[AES_BLOCK_LENGTH]; - std::memcpy(block1, iv.iv, AES_BLOCK_LENGTH); + std::memcpy(block1, iv->iv, AES_BLOCK_LENGTH); for (std::size_t i = 0; i < input_length; i += AES_BLOCK_LENGTH) { std::memcpy(block2, &input[i], AES_BLOCK_LENGTH); ::aes_decrypt(&input[i], &output[i], key_schedule, AES_KEY_BITS); @@ -291,7 +226,7 @@ std::size_t olm::aes_decrypt_cbc( } -void olm::sha256( +void _olm_crypto_sha256( std::uint8_t const * input, std::size_t input_length, std::uint8_t * output ) { @@ -303,7 +238,7 @@ void olm::sha256( } -void olm::hmac_sha256( +void _olm_crypto_hmac_sha256( std::uint8_t const * key, std::size_t key_length, std::uint8_t const * input, std::size_t input_length, std::uint8_t * output @@ -319,7 +254,7 @@ void olm::hmac_sha256( } -void olm::hkdf_sha256( +void _olm_crypto_hkdf_sha256( std::uint8_t const * input, std::size_t input_length, std::uint8_t const * salt, std::size_t salt_length, std::uint8_t const * info, std::size_t info_length, @@ -327,32 +262,32 @@ void olm::hkdf_sha256( ) { ::SHA256_CTX context; std::uint8_t hmac_key[SHA256_BLOCK_LENGTH]; - std::uint8_t step_result[olm::SHA256_OUTPUT_LENGTH]; + std::uint8_t step_result[SHA256_OUTPUT_LENGTH]; std::size_t bytes_remaining = output_length; std::uint8_t iteration = 1; if (!salt) { salt = HKDF_DEFAULT_SALT; salt_length = sizeof(HKDF_DEFAULT_SALT); } - /* Expand */ + /* Extract */ hmac_sha256_key(salt, salt_length, hmac_key); hmac_sha256_init(&context, hmac_key); ::sha256_update(&context, input, input_length); hmac_sha256_final(&context, hmac_key, step_result); - hmac_sha256_key(step_result, olm::SHA256_OUTPUT_LENGTH, hmac_key); + hmac_sha256_key(step_result, SHA256_OUTPUT_LENGTH, hmac_key); - /* Extract */ + /* Expand */ hmac_sha256_init(&context, hmac_key); ::sha256_update(&context, info, info_length); ::sha256_update(&context, &iteration, 1); hmac_sha256_final(&context, hmac_key, step_result); - while (bytes_remaining > olm::SHA256_OUTPUT_LENGTH) { - std::memcpy(output, step_result, olm::SHA256_OUTPUT_LENGTH); - output += olm::SHA256_OUTPUT_LENGTH; - bytes_remaining -= olm::SHA256_OUTPUT_LENGTH; + while (bytes_remaining > SHA256_OUTPUT_LENGTH) { + std::memcpy(output, step_result, SHA256_OUTPUT_LENGTH); + output += SHA256_OUTPUT_LENGTH; + bytes_remaining -= SHA256_OUTPUT_LENGTH; iteration ++; hmac_sha256_init(&context, hmac_key); - ::sha256_update(&context, step_result, olm::SHA256_OUTPUT_LENGTH); + ::sha256_update(&context, step_result, SHA256_OUTPUT_LENGTH); ::sha256_update(&context, info, info_length); ::sha256_update(&context, &iteration, 1); hmac_sha256_final(&context, hmac_key, step_result); diff --git a/src/libs.cpp b/src/ed25519.c index 6757574..c7a1a8e 100644 --- a/src/libs.cpp +++ b/src/ed25519.c @@ -1,4 +1,4 @@ -/* Copyright 2015 OpenMarket Ltd +/* Copyright 2015-6 OpenMarket Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,16 +12,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -extern "C" { -#include "crypto-algorithms/sha256.c" -#include "crypto-algorithms/aes.c" -#include "curve25519-donna/curve25519-donna.c" #define select ed25519_select #include "ed25519/src/fe.c" #include "ed25519/src/sc.c" #include "ed25519/src/ge.c" +#include "ed25519/src/keypair.c" #include "ed25519/src/sha512.c" #include "ed25519/src/verify.c" #include "ed25519/src/sign.c" -#include "ed25519_additions.c" -} diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..f541a93 --- /dev/null +++ b/src/error.c @@ -0,0 +1,43 @@ +/* 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. + */ + +#include "olm/error.h" + +static const char * ERRORS[] = { + "SUCCESS", + "NOT_ENOUGH_RANDOM", + "OUTPUT_BUFFER_TOO_SMALL", + "BAD_MESSAGE_VERSION", + "BAD_MESSAGE_FORMAT", + "BAD_MESSAGE_MAC", + "BAD_MESSAGE_KEY_ID", + "INVALID_BASE64", + "BAD_ACCOUNT_KEY", + "UNKNOWN_PICKLE_VERSION", + "CORRUPTED_PICKLE", + "BAD_SESSION_KEY", + "UNKNOWN_MESSAGE_INDEX", + "BAD_LEGACY_ACCOUNT_PICKLE", + "BAD_SIGNATURE", +}; + +const char * _olm_error_to_string(enum OlmErrorCode error) +{ + if (error < (sizeof(ERRORS)/sizeof(ERRORS[0]))) { + return ERRORS[error]; + } else { + return "UNKNOWN_ERROR"; + } +} diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c new file mode 100644 index 0000000..bf00008 --- /dev/null +++ b/src/inbound_group_session.c @@ -0,0 +1,386 @@ +/* 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. + */ + +#include "olm/inbound_group_session.h" + +#include <string.h> + +#include "olm/base64.h" +#include "olm/cipher.h" +#include "olm/crypto.h" +#include "olm/error.h" +#include "olm/megolm.h" +#include "olm/memory.h" +#include "olm/message.h" +#include "olm/pickle.h" +#include "olm/pickle_encoding.h" + + +#define OLM_PROTOCOL_VERSION 3 +#define GROUP_SESSION_ID_LENGTH ED25519_PUBLIC_KEY_LENGTH +#define PICKLE_VERSION 1 +#define SESSION_KEY_VERSION 2 + +struct OlmInboundGroupSession { + /** our earliest known ratchet value */ + Megolm initial_ratchet; + + /** The most recent ratchet value */ + Megolm latest_ratchet; + + /** The ed25519 signing key */ + struct _olm_ed25519_public_key signing_key; + + enum OlmErrorCode last_error; +}; + +size_t olm_inbound_group_session_size() { + return sizeof(OlmInboundGroupSession); +} + +OlmInboundGroupSession * olm_inbound_group_session( + void *memory +) { + OlmInboundGroupSession *session = memory; + olm_clear_inbound_group_session(session); + return session; +} + +const char *olm_inbound_group_session_last_error( + const OlmInboundGroupSession *session +) { + return _olm_error_to_string(session->last_error); +} + +size_t olm_clear_inbound_group_session( + OlmInboundGroupSession *session +) { + _olm_unset(session, sizeof(OlmInboundGroupSession)); + return sizeof(OlmInboundGroupSession); +} + +#define SESSION_KEY_RAW_LENGTH \ + (1 + 4 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH\ + + ED25519_SIGNATURE_LENGTH) + +/** init the session keys from the un-base64-ed session keys */ +static size_t _init_group_session_keys( + OlmInboundGroupSession *session, + const uint8_t *key_buf +) { + const uint8_t *ptr = key_buf; + size_t version = *ptr++; + + if (version != SESSION_KEY_VERSION) { + session->last_error = OLM_BAD_SESSION_KEY; + return (size_t)-1; + } + + uint32_t counter = 0; + // Decode counter as a big endian 32-bit number. + for (unsigned i = 0; i < 4; i++) { + counter <<= 8; counter |= *ptr++; + } + + megolm_init(&session->initial_ratchet, ptr, counter); + megolm_init(&session->latest_ratchet, ptr, counter); + + ptr += MEGOLM_RATCHET_LENGTH; + memcpy( + session->signing_key.public_key, ptr, ED25519_PUBLIC_KEY_LENGTH + ); + ptr += ED25519_PUBLIC_KEY_LENGTH; + + if (!_olm_crypto_ed25519_verify( + &session->signing_key, key_buf, ptr - key_buf, ptr + )) { + session->last_error = OLM_BAD_SIGNATURE; + return (size_t)-1; + } + return 0; +} + +size_t olm_init_inbound_group_session( + OlmInboundGroupSession *session, + const uint8_t * session_key, size_t session_key_length +) { + uint8_t key_buf[SESSION_KEY_RAW_LENGTH]; + size_t raw_length = _olm_decode_base64_length(session_key_length); + size_t result; + + if (raw_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + if (raw_length != SESSION_KEY_RAW_LENGTH) { + session->last_error = OLM_BAD_SESSION_KEY; + return (size_t)-1; + } + + _olm_decode_base64(session_key, session_key_length, key_buf); + result = _init_group_session_keys(session, key_buf); + _olm_unset(key_buf, SESSION_KEY_RAW_LENGTH); + return result; +} + +static size_t raw_pickle_length( + const OlmInboundGroupSession *session +) { + size_t length = 0; + length += _olm_pickle_uint32_length(PICKLE_VERSION); + length += megolm_pickle_length(&session->initial_ratchet); + length += megolm_pickle_length(&session->latest_ratchet); + length += _olm_pickle_ed25519_public_key_length(&session->signing_key); + return length; +} + +size_t olm_pickle_inbound_group_session_length( + const OlmInboundGroupSession *session +) { + return _olm_enc_output_length(raw_pickle_length(session)); +} + +size_t olm_pickle_inbound_group_session( + OlmInboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +) { + size_t raw_length = raw_pickle_length(session); + uint8_t *pos; + + if (pickled_length < _olm_enc_output_length(raw_length)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + pos = _olm_enc_output_pos(pickled, raw_length); + pos = _olm_pickle_uint32(pos, PICKLE_VERSION); + pos = megolm_pickle(&session->initial_ratchet, pos); + pos = megolm_pickle(&session->latest_ratchet, pos); + pos = _olm_pickle_ed25519_public_key(pos, &session->signing_key); + + return _olm_enc_output(key, key_length, pickled, raw_length); +} + +size_t olm_unpickle_inbound_group_session( + OlmInboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +) { + const uint8_t *pos; + const uint8_t *end; + uint32_t pickle_version; + + size_t raw_length = _olm_enc_input( + key, key_length, pickled, pickled_length, &(session->last_error) + ); + if (raw_length == (size_t)-1) { + return raw_length; + } + + pos = pickled; + end = pos + raw_length; + pos = _olm_unpickle_uint32(pos, end, &pickle_version); + if (pickle_version != PICKLE_VERSION) { + session->last_error = OLM_UNKNOWN_PICKLE_VERSION; + return (size_t)-1; + } + pos = megolm_unpickle(&session->initial_ratchet, pos, end); + pos = megolm_unpickle(&session->latest_ratchet, pos, end); + pos = _olm_unpickle_ed25519_public_key(pos, end, &session->signing_key); + + if (end != pos) { + /* We had the wrong number of bytes in the input. */ + session->last_error = OLM_CORRUPTED_PICKLE; + return (size_t)-1; + } + + return pickled_length; +} + +/** + * get the max plaintext length in an un-base64-ed message + */ +static size_t _decrypt_max_plaintext_length( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length +) { + struct _OlmDecodeGroupMessageResults decoded_results; + + _olm_decode_group_message( + message, message_length, + megolm_cipher->ops->mac_length(megolm_cipher), + ED25519_SIGNATURE_LENGTH, + &decoded_results); + + if (decoded_results.version != OLM_PROTOCOL_VERSION) { + session->last_error = OLM_BAD_MESSAGE_VERSION; + return (size_t)-1; + } + + if (!decoded_results.ciphertext) { + session->last_error = OLM_BAD_MESSAGE_FORMAT; + return (size_t)-1; + } + + return megolm_cipher->ops->decrypt_max_plaintext_length( + megolm_cipher, decoded_results.ciphertext_length); +} + +size_t olm_group_decrypt_max_plaintext_length( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length +) { + size_t raw_length; + + raw_length = _olm_decode_base64(message, message_length, message); + if (raw_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + return _decrypt_max_plaintext_length( + session, message, raw_length + ); +} + +/** + * decrypt an un-base64-ed message + */ +static size_t _decrypt( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length, + uint8_t * plaintext, size_t max_plaintext_length +) { + struct _OlmDecodeGroupMessageResults decoded_results; + size_t max_length, r; + Megolm *megolm; + Megolm tmp_megolm; + + _olm_decode_group_message( + message, message_length, + megolm_cipher->ops->mac_length(megolm_cipher), + ED25519_SIGNATURE_LENGTH, + &decoded_results); + + if (decoded_results.version != OLM_PROTOCOL_VERSION) { + session->last_error = OLM_BAD_MESSAGE_VERSION; + return (size_t)-1; + } + + if (!decoded_results.has_message_index || !decoded_results.ciphertext) { + session->last_error = OLM_BAD_MESSAGE_FORMAT; + return (size_t)-1; + } + + /* verify the signature. We could do this before decoding the message, but + * we allow for the possibility of future protocol versions which use a + * different signing mechanism; we would rather throw "BAD_MESSAGE_VERSION" + * than "BAD_SIGNATURE" in this case. + */ + message_length -= ED25519_SIGNATURE_LENGTH; + r = _olm_crypto_ed25519_verify( + &session->signing_key, + message, message_length, + message + message_length + ); + if (!r) { + session->last_error = OLM_BAD_SIGNATURE; + return (size_t)-1; + } + + max_length = megolm_cipher->ops->decrypt_max_plaintext_length( + megolm_cipher, + decoded_results.ciphertext_length + ); + if (max_plaintext_length < max_length) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + /* pick a megolm instance to use. If we're at or beyond the latest ratchet + * value, use that */ + if ((decoded_results.message_index - session->latest_ratchet.counter) < (1U << 31)) { + megolm = &session->latest_ratchet; + } else if ((decoded_results.message_index - session->initial_ratchet.counter) >= (1U << 31)) { + /* the counter is before our intial ratchet - we can't decode this. */ + session->last_error = OLM_UNKNOWN_MESSAGE_INDEX; + return (size_t)-1; + } else { + /* otherwise, start from the initial megolm. Take a copy so that we + * don't overwrite the initial megolm */ + tmp_megolm = session->initial_ratchet; + megolm = &tmp_megolm; + } + + megolm_advance_to(megolm, decoded_results.message_index); + + /* now try checking the mac, and decrypting */ + r = megolm_cipher->ops->decrypt( + megolm_cipher, + megolm_get_data(megolm), MEGOLM_RATCHET_LENGTH, + message, message_length, + decoded_results.ciphertext, decoded_results.ciphertext_length, + plaintext, max_plaintext_length + ); + + _olm_unset(&tmp_megolm, sizeof(tmp_megolm)); + if (r == (size_t)-1) { + session->last_error = OLM_BAD_MESSAGE_MAC; + return r; + } + + return r; +} + +size_t olm_group_decrypt( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length, + uint8_t * plaintext, size_t max_plaintext_length +) { + size_t raw_message_length; + + raw_message_length = _olm_decode_base64(message, message_length, message); + if (raw_message_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + return _decrypt( + session, message, raw_message_length, + plaintext, max_plaintext_length + ); +} + +size_t olm_inbound_group_session_id_length( + const OlmInboundGroupSession *session +) { + return _olm_encode_base64_length(GROUP_SESSION_ID_LENGTH); +} + +size_t olm_inbound_group_session_id( + OlmInboundGroupSession *session, + uint8_t * id, size_t id_length +) { + if (id_length < olm_inbound_group_session_id_length(session)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + return _olm_encode_base64( + session->signing_key.public_key, GROUP_SESSION_ID_LENGTH, id + ); +} diff --git a/src/megolm.c b/src/megolm.c new file mode 100644 index 0000000..3395449 --- /dev/null +++ b/src/megolm.c @@ -0,0 +1,150 @@ +/* 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. + */ + + +#include "olm/megolm.h" + +#include <string.h> + +#include "olm/cipher.h" +#include "olm/crypto.h" +#include "olm/pickle.h" + +static const struct _olm_cipher_aes_sha_256 MEGOLM_CIPHER = + OLM_CIPHER_INIT_AES_SHA_256("MEGOLM_KEYS"); +const struct _olm_cipher *megolm_cipher = OLM_CIPHER_BASE(&MEGOLM_CIPHER); + +/* the seeds used in the HMAC-SHA-256 functions for each part of the ratchet. + */ +#define HASH_KEY_SEED_LENGTH 1 +static uint8_t HASH_KEY_SEEDS[MEGOLM_RATCHET_PARTS][HASH_KEY_SEED_LENGTH] = { + {0x00}, + {0x01}, + {0x02}, + {0x03} +}; + +static void rehash_part( + uint8_t data[MEGOLM_RATCHET_PARTS][MEGOLM_RATCHET_PART_LENGTH], + int rehash_from_part, int rehash_to_part +) { + _olm_crypto_hmac_sha256( + data[rehash_from_part], + MEGOLM_RATCHET_PART_LENGTH, + HASH_KEY_SEEDS[rehash_to_part], HASH_KEY_SEED_LENGTH, + data[rehash_to_part] + ); +} + + + +void megolm_init(Megolm *megolm, uint8_t const *random_data, uint32_t counter) { + megolm->counter = counter; + memcpy(megolm->data, random_data, MEGOLM_RATCHET_LENGTH); +} + +size_t megolm_pickle_length(const Megolm *megolm) { + size_t length = 0; + length += _olm_pickle_bytes_length(megolm_get_data(megolm), MEGOLM_RATCHET_LENGTH); + length += _olm_pickle_uint32_length(megolm->counter); + return length; + +} + +uint8_t * megolm_pickle(const Megolm *megolm, uint8_t *pos) { + pos = _olm_pickle_bytes(pos, megolm_get_data(megolm), MEGOLM_RATCHET_LENGTH); + pos = _olm_pickle_uint32(pos, megolm->counter); + return pos; +} + +const uint8_t * megolm_unpickle(Megolm *megolm, const uint8_t *pos, + const uint8_t *end) { + pos = _olm_unpickle_bytes(pos, end, (uint8_t *)(megolm->data), + MEGOLM_RATCHET_LENGTH); + pos = _olm_unpickle_uint32(pos, end, &megolm->counter); + return pos; +} + +/* simplistic implementation for a single step */ +void megolm_advance(Megolm *megolm) { + uint32_t mask = 0x00FFFFFF; + int h = 0; + int i; + + megolm->counter++; + + /* figure out how much we need to rekey */ + while (h < (int)MEGOLM_RATCHET_PARTS) { + if (!(megolm->counter & mask)) + break; + h++; + mask >>= 8; + } + + /* now update R(h)...R(3) based on R(h) */ + for (i = MEGOLM_RATCHET_PARTS-1; i >= h; i--) { + rehash_part(megolm->data, h, i); + } +} + +void megolm_advance_to(Megolm *megolm, uint32_t advance_to) { + int j; + + /* starting with R0, see if we need to update each part of the hash */ + for (j = 0; j < (int)MEGOLM_RATCHET_PARTS; j++) { + int shift = (MEGOLM_RATCHET_PARTS-j-1) * 8; + uint32_t mask = (~(uint32_t)0) << shift; + int k; + + /* how many times do we need to rehash this part? + * + * '& 0xff' ensures we handle integer wraparound correctly + */ + unsigned int steps = + ((advance_to >> shift) - (megolm->counter >> shift)) & 0xff; + + if (steps == 0) { + /* deal with the edge case where megolm->counter is slightly larger + * than advance_to. This should only happen for R(0), and implies + * that advance_to has wrapped around and we need to advance R(0) + * 256 times. + */ + if (advance_to < megolm->counter) { + steps = 0x100; + } else { + continue; + } + } + + /* for all but the last step, we can just bump R(j) without regard + * to R(j+1)...R(3). + */ + while (steps > 1) { + rehash_part(megolm->data, j, j); + steps --; + } + + /* on the last step we also need to bump R(j+1)...R(3). + * + * (Theoretically, we could skip bumping R(j+2) if we're going to bump + * R(j+1) again, but the code to figure that out is a bit baroque and + * doesn't save us much). + */ + for (k = 3; k >= j; k--) { + rehash_part(megolm->data, j, k); + } + megolm->counter = advance_to & mask; + } +} diff --git a/src/memory.cpp b/src/memory.cpp index 16a4683..20e0683 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -13,7 +13,13 @@ * limitations under the License. */ #include "olm/memory.hh" +#include "olm/memory.h" +void _olm_unset( + void volatile * buffer, size_t buffer_length +) { + olm::unset(buffer, buffer_length); +} void olm::unset( void volatile * buffer, std::size_t buffer_length diff --git a/src/message.cpp b/src/message.cpp index 05f707f..05fe2c7 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015 OpenMarket Ltd +/* Copyright 2015-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. @@ -14,6 +14,8 @@ */ #include "olm/message.hh" +#include "olm/memory.hh" + namespace { template<typename T> @@ -131,7 +133,7 @@ static std::uint8_t const * decode( std::uint8_t const * len_start = pos; pos = varint_skip(pos, end); std::size_t len = varint_decode<std::size_t>(len_start, pos); - if (len > end - pos) return end; + if (len > std::size_t(end - pos)) return end; value = pos; value_length = len; pos += len; @@ -152,7 +154,7 @@ static std::uint8_t const * skip_unknown( std::uint8_t const * len_start = pos; pos = varint_skip(pos, end); std::size_t len = varint_decode<std::size_t>(len_start, pos); - if (len > end - pos) return end; + if (len > std::size_t(end - pos)) return end; pos += len; } else { return end; @@ -323,3 +325,74 @@ void olm::decode_one_time_key_message( unknown = pos; } } + + + +static const std::uint8_t GROUP_MESSAGE_INDEX_TAG = 010; +static const std::uint8_t GROUP_CIPHERTEXT_TAG = 022; + +size_t _olm_encode_group_message_length( + uint32_t message_index, + size_t ciphertext_length, + size_t mac_length, + size_t signature_length +) { + size_t length = VERSION_LENGTH; + length += 1 + varint_length(message_index); + length += 1 + varstring_length(ciphertext_length); + length += mac_length; + length += signature_length; + return length; +} + + +size_t _olm_encode_group_message( + uint8_t version, + uint32_t message_index, + size_t ciphertext_length, + uint8_t *output, + uint8_t **ciphertext_ptr +) { + std::uint8_t * pos = output; + + *(pos++) = version; + pos = encode(pos, GROUP_MESSAGE_INDEX_TAG, message_index); + pos = encode(pos, GROUP_CIPHERTEXT_TAG, *ciphertext_ptr, ciphertext_length); + return pos-output; +} + +void _olm_decode_group_message( + const uint8_t *input, size_t input_length, + size_t mac_length, size_t signature_length, + struct _OlmDecodeGroupMessageResults *results +) { + std::uint8_t const * pos = input; + std::size_t trailer_length = mac_length + signature_length; + std::uint8_t const * end = input + input_length - trailer_length; + std::uint8_t const * unknown = nullptr; + + bool has_message_index = false; + results->message_index = 0; + results->ciphertext = nullptr; + results->ciphertext_length = 0; + + if (input_length < trailer_length) return; + results->version = *(pos++); + + while (pos != end) { + pos = decode( + pos, end, GROUP_MESSAGE_INDEX_TAG, + results->message_index, has_message_index + ); + pos = decode( + pos, end, GROUP_CIPHERTEXT_TAG, + results->ciphertext, results->ciphertext_length + ); + if (unknown == pos) { + pos = skip_unknown(pos, end); + } + unknown = pos; + } + + results->has_message_index = (int)has_message_index; +} diff --git a/src/olm.cpp b/src/olm.cpp index 63f3d83..d3af19c 100644 --- a/src/olm.cpp +++ b/src/olm.cpp @@ -12,12 +12,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "olm/olm.hh" +#include "olm/olm.h" #include "olm/session.hh" #include "olm/account.hh" +#include "olm/cipher.h" +#include "olm/pickle_encoding.h" #include "olm/utility.hh" #include "olm/base64.hh" -#include "olm/cipher.hh" #include "olm/memory.hh" #include <new> @@ -57,75 +58,6 @@ static std::uint8_t const * from_c(void const * bytes) { return reinterpret_cast<std::uint8_t const *>(bytes); } -static const std::uint8_t CIPHER_KDF_INFO[] = "Pickle"; - -static const olm::CipherAesSha256 PICKLE_CIPHER( - CIPHER_KDF_INFO, sizeof(CIPHER_KDF_INFO) -1 -); - -std::size_t enc_output_length( - size_t raw_length -) { - std::size_t length = PICKLE_CIPHER.encrypt_ciphertext_length(raw_length); - length += PICKLE_CIPHER.mac_length(); - return olm::encode_base64_length(length); -} - - -std::uint8_t * enc_output_pos( - std::uint8_t * output, - size_t raw_length -) { - std::size_t length = PICKLE_CIPHER.encrypt_ciphertext_length(raw_length); - length += PICKLE_CIPHER.mac_length(); - return output + olm::encode_base64_length(length) - length; -} - -std::size_t enc_output( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t * output, size_t raw_length -) { - std::size_t ciphertext_length = PICKLE_CIPHER.encrypt_ciphertext_length( - raw_length - ); - std::size_t length = ciphertext_length + PICKLE_CIPHER.mac_length(); - std::size_t base64_length = olm::encode_base64_length(length); - std::uint8_t * raw_output = output + base64_length - length; - PICKLE_CIPHER.encrypt( - key, key_length, - raw_output, raw_length, - raw_output, ciphertext_length, - raw_output, length - ); - olm::encode_base64(raw_output, length, output); - return raw_length; -} - -std::size_t enc_input( - std::uint8_t const * key, std::size_t key_length, - std::uint8_t * input, size_t b64_length, - olm::ErrorCode & last_error -) { - std::size_t enc_length = olm::decode_base64_length(b64_length); - if (enc_length == std::size_t(-1)) { - last_error = olm::ErrorCode::INVALID_BASE64; - return std::size_t(-1); - } - olm::decode_base64(input, b64_length, input); - std::size_t raw_length = enc_length - PICKLE_CIPHER.mac_length(); - std::size_t result = PICKLE_CIPHER.decrypt( - key, key_length, - input, enc_length, - input, raw_length, - input, raw_length - ); - if (result == std::size_t(-1)) { - last_error = olm::ErrorCode::BAD_ACCOUNT_KEY; - } - return result; -} - - std::size_t b64_output_length( size_t raw_length ) { @@ -150,36 +82,27 @@ std::size_t b64_output( std::size_t b64_input( std::uint8_t * input, size_t b64_length, - olm::ErrorCode & last_error + OlmErrorCode & last_error ) { std::size_t raw_length = olm::decode_base64_length(b64_length); if (raw_length == std::size_t(-1)) { - last_error = olm::ErrorCode::INVALID_BASE64; + last_error = OlmErrorCode::OLM_INVALID_BASE64; return std::size_t(-1); } olm::decode_base64(input, b64_length, input); return raw_length; } -static const char * ERRORS[11] { - "SUCCESS", - "NOT_ENOUGH_RANDOM", - "OUTPUT_BUFFER_TOO_SMALL", - "BAD_MESSAGE_VERSION", - "BAD_MESSAGE_FORMAT", - "BAD_MESSAGE_MAC", - "BAD_MESSAGE_KEY_ID", - "INVALID_BASE64", - "BAD_ACCOUNT_KEY", - "UNKNOWN_PICKLE_VERSION", - "CORRUPTED_PICKLE", -}; - } // namespace extern "C" { +void olm_get_library_version(uint8_t *major, uint8_t *minor, uint8_t *patch) { + if (major != NULL) *major = OLMLIB_VERSION_MAJOR; + if (minor != NULL) *minor = OLMLIB_VERSION_MINOR; + if (patch != NULL) *patch = OLMLIB_VERSION_PATCH; +} size_t olm_error() { return std::size_t(-1); @@ -189,35 +112,23 @@ size_t olm_error() { const char * olm_account_last_error( OlmAccount * account ) { - unsigned error = unsigned(from_c(account)->last_error); - if (error < sizeof(ERRORS)) { - return ERRORS[error]; - } else { - return "UNKNOWN_ERROR"; - } + auto error = from_c(account)->last_error; + return _olm_error_to_string(error); } const char * olm_session_last_error( OlmSession * session ) { - unsigned error = unsigned(from_c(session)->last_error); - if (error < sizeof(ERRORS)) { - return ERRORS[error]; - } else { - return "UNKNOWN_ERROR"; - } + auto error = from_c(session)->last_error; + return _olm_error_to_string(error); } const char * olm_utility_last_error( OlmUtility * utility ) { - unsigned error = unsigned(from_c(utility)->last_error); - if (error < sizeof(ERRORS)) { - return ERRORS[error]; - } else { - return "UNKNOWN_ERROR"; - } + auto error = from_c(utility)->last_error; + return _olm_error_to_string(error); } size_t olm_account_size() { @@ -293,14 +204,14 @@ size_t olm_clear_utility( size_t olm_pickle_account_length( OlmAccount * account ) { - return enc_output_length(pickle_length(*from_c(account))); + return _olm_enc_output_length(pickle_length(*from_c(account))); } size_t olm_pickle_session_length( OlmSession * session ) { - return enc_output_length(pickle_length(*from_c(session))); + return _olm_enc_output_length(pickle_length(*from_c(session))); } @@ -311,12 +222,12 @@ size_t olm_pickle_account( ) { olm::Account & object = *from_c(account); std::size_t raw_length = pickle_length(object); - if (pickled_length < enc_output_length(raw_length)) { - object.last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + if (pickled_length < _olm_enc_output_length(raw_length)) { + object.last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return size_t(-1); } - pickle(enc_output_pos(from_c(pickled), raw_length), object); - return enc_output(from_c(key), key_length, from_c(pickled), raw_length); + pickle(_olm_enc_output_pos(from_c(pickled), raw_length), object); + return _olm_enc_output(from_c(key), key_length, from_c(pickled), raw_length); } @@ -327,12 +238,12 @@ size_t olm_pickle_session( ) { olm::Session & object = *from_c(session); std::size_t raw_length = pickle_length(object); - if (pickled_length < enc_output_length(raw_length)) { - object.last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + if (pickled_length < _olm_enc_output_length(raw_length)) { + object.last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return size_t(-1); } - pickle(enc_output_pos(from_c(pickled), raw_length), object); - return enc_output(from_c(key), key_length, from_c(pickled), raw_length); + pickle(_olm_enc_output_pos(from_c(pickled), raw_length), object); + return _olm_enc_output(from_c(key), key_length, from_c(pickled), raw_length); } @@ -343,8 +254,8 @@ size_t olm_unpickle_account( ) { olm::Account & object = *from_c(account); std::uint8_t * const pos = from_c(pickled); - std::size_t raw_length = enc_input( - from_c(key), key_length, pos, pickled_length, object.last_error + std::size_t raw_length = _olm_enc_input( + from_c(key), key_length, pos, pickled_length, &object.last_error ); if (raw_length == std::size_t(-1)) { return std::size_t(-1); @@ -355,8 +266,8 @@ size_t olm_unpickle_account( * (pos + raw_length). On error unpickle will return (pos + raw_length + 1). */ if (end != unpickle(pos, end + 1, object)) { - if (object.last_error == olm::ErrorCode::SUCCESS) { - object.last_error = olm::ErrorCode::CORRUPTED_PICKLE; + if (object.last_error == OlmErrorCode::OLM_SUCCESS) { + object.last_error = OlmErrorCode::OLM_CORRUPTED_PICKLE; } return std::size_t(-1); } @@ -371,8 +282,8 @@ size_t olm_unpickle_session( ) { olm::Session & object = *from_c(session); std::uint8_t * const pos = from_c(pickled); - std::size_t raw_length = enc_input( - from_c(key), key_length, pos, pickled_length, object.last_error + std::size_t raw_length = _olm_enc_input( + from_c(key), key_length, pos, pickled_length, &object.last_error ); if (raw_length == std::size_t(-1)) { return std::size_t(-1); @@ -384,8 +295,8 @@ size_t olm_unpickle_session( * (pos + raw_length). On error unpickle will return (pos + raw_length + 1). */ if (end != unpickle(pos, end + 1, object)) { - if (object.last_error == olm::ErrorCode::SUCCESS) { - object.last_error = olm::ErrorCode::CORRUPTED_PICKLE; + if (object.last_error == OlmErrorCode::OLM_SUCCESS) { + object.last_error = OlmErrorCode::OLM_CORRUPTED_PICKLE; } return std::size_t(-1); } @@ -442,7 +353,7 @@ size_t olm_account_sign( std::size_t raw_length = from_c(account)->signature_length(); if (signature_length < b64_output_length(raw_length)) { from_c(account)->last_error = - olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } from_c(account)->sign( @@ -525,14 +436,14 @@ size_t olm_create_outbound_session( std::size_t id_key_length = their_identity_key_length; std::size_t ot_key_length = their_one_time_key_length; - if (olm::decode_base64_length(id_key_length) != olm::KEY_LENGTH - || olm::decode_base64_length(ot_key_length) != olm::KEY_LENGTH + if (olm::decode_base64_length(id_key_length) != CURVE25519_KEY_LENGTH + || olm::decode_base64_length(ot_key_length) != CURVE25519_KEY_LENGTH ) { - from_c(session)->last_error = olm::ErrorCode::INVALID_BASE64; + from_c(session)->last_error = OlmErrorCode::OLM_INVALID_BASE64; return std::size_t(-1); } - olm::Curve25519PublicKey identity_key; - olm::Curve25519PublicKey one_time_key; + _olm_curve25519_public_key identity_key; + _olm_curve25519_public_key one_time_key; olm::decode_base64(id_key, id_key_length, identity_key.public_key); olm::decode_base64(ot_key, ot_key_length, one_time_key.public_key); @@ -572,11 +483,11 @@ size_t olm_create_inbound_session_from( std::uint8_t const * id_key = from_c(their_identity_key); std::size_t id_key_length = their_identity_key_length; - if (olm::decode_base64_length(id_key_length) != olm::KEY_LENGTH) { - from_c(session)->last_error = olm::ErrorCode::INVALID_BASE64; + if (olm::decode_base64_length(id_key_length) != CURVE25519_KEY_LENGTH) { + from_c(session)->last_error = OlmErrorCode::OLM_INVALID_BASE64; return std::size_t(-1); } - olm::Curve25519PublicKey identity_key; + _olm_curve25519_public_key identity_key; olm::decode_base64(id_key, id_key_length, identity_key.public_key); std::size_t raw_length = b64_input( @@ -605,7 +516,7 @@ size_t olm_session_id( std::size_t raw_length = from_c(session)->session_id_length(); if (id_length < b64_output_length(raw_length)) { from_c(session)->last_error = - olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } std::size_t result = from_c(session)->session_id( @@ -618,6 +529,12 @@ size_t olm_session_id( } +int olm_session_has_received_message( + OlmSession * session +) { + return from_c(session)->received_message; +} + size_t olm_matches_inbound_session( OlmSession * session, void * one_time_key_message, size_t message_length @@ -643,11 +560,11 @@ size_t olm_matches_inbound_session_from( std::uint8_t const * id_key = from_c(their_identity_key); std::size_t id_key_length = their_identity_key_length; - if (olm::decode_base64_length(id_key_length) != olm::KEY_LENGTH) { - from_c(session)->last_error = olm::ErrorCode::INVALID_BASE64; + if (olm::decode_base64_length(id_key_length) != CURVE25519_KEY_LENGTH) { + from_c(session)->last_error = OlmErrorCode::OLM_INVALID_BASE64; return std::size_t(-1); } - olm::Curve25519PublicKey identity_key; + _olm_curve25519_public_key identity_key; olm::decode_base64(id_key, id_key_length, identity_key.public_key); std::size_t raw_length = b64_input( @@ -671,7 +588,7 @@ size_t olm_remove_one_time_keys( from_c(session)->bob_one_time_key ); if (result == std::size_t(-1)) { - from_c(account)->last_error = olm::ErrorCode::BAD_MESSAGE_KEY_ID; + from_c(account)->last_error = OlmErrorCode::OLM_BAD_MESSAGE_KEY_ID; } return result; } @@ -712,7 +629,7 @@ size_t olm_encrypt( ); if (message_length < b64_output_length(raw_length)) { from_c(session)->last_error = - olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } std::size_t result = from_c(session)->encrypt( @@ -779,7 +696,7 @@ size_t olm_sha256( std::size_t raw_length = from_c(utility)->sha256_length(); if (output_length < b64_output_length(raw_length)) { from_c(utility)->last_error = - olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } std::size_t result = from_c(utility)->sha256( @@ -799,11 +716,11 @@ size_t olm_ed25519_verify( void const * message, size_t message_length, void * signature, size_t signature_length ) { - if (olm::decode_base64_length(key_length) != olm::KEY_LENGTH) { - from_c(utility)->last_error = olm::ErrorCode::INVALID_BASE64; + if (olm::decode_base64_length(key_length) != CURVE25519_KEY_LENGTH) { + from_c(utility)->last_error = OlmErrorCode::OLM_INVALID_BASE64; return std::size_t(-1); } - olm::Ed25519PublicKey verify_key; + _olm_ed25519_public_key verify_key; olm::decode_base64(from_c(key), key_length, verify_key.public_key); std::size_t raw_signature_length = b64_input( from_c(signature), signature_length, from_c(utility)->last_error diff --git a/src/outbound_group_session.c b/src/outbound_group_session.c new file mode 100644 index 0000000..4e4561a --- /dev/null +++ b/src/outbound_group_session.c @@ -0,0 +1,361 @@ +/* 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. + */ + +#include "olm/outbound_group_session.h" + +#include <string.h> +#include <sys/time.h> + +#include "olm/base64.h" +#include "olm/cipher.h" +#include "olm/crypto.h" +#include "olm/error.h" +#include "olm/megolm.h" +#include "olm/memory.h" +#include "olm/message.h" +#include "olm/pickle.h" +#include "olm/pickle_encoding.h" + +#define OLM_PROTOCOL_VERSION 3 +#define GROUP_SESSION_ID_LENGTH ED25519_PUBLIC_KEY_LENGTH +#define PICKLE_VERSION 1 +#define SESSION_KEY_VERSION 2 + +struct OlmOutboundGroupSession { + /** the Megolm ratchet providing the encryption keys */ + Megolm ratchet; + + /** The ed25519 keypair used for signing the messages */ + struct _olm_ed25519_key_pair signing_key; + + enum OlmErrorCode last_error; +}; + + +size_t olm_outbound_group_session_size() { + return sizeof(OlmOutboundGroupSession); +} + +OlmOutboundGroupSession * olm_outbound_group_session( + void *memory +) { + OlmOutboundGroupSession *session = memory; + olm_clear_outbound_group_session(session); + return session; +} + +const char *olm_outbound_group_session_last_error( + const OlmOutboundGroupSession *session +) { + return _olm_error_to_string(session->last_error); +} + +size_t olm_clear_outbound_group_session( + OlmOutboundGroupSession *session +) { + _olm_unset(session, sizeof(OlmOutboundGroupSession)); + return sizeof(OlmOutboundGroupSession); +} + +static size_t raw_pickle_length( + const OlmOutboundGroupSession *session +) { + size_t length = 0; + length += _olm_pickle_uint32_length(PICKLE_VERSION); + length += megolm_pickle_length(&(session->ratchet)); + length += _olm_pickle_ed25519_key_pair_length(&(session->signing_key)); + return length; +} + +size_t olm_pickle_outbound_group_session_length( + const OlmOutboundGroupSession *session +) { + return _olm_enc_output_length(raw_pickle_length(session)); +} + +size_t olm_pickle_outbound_group_session( + OlmOutboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +) { + size_t raw_length = raw_pickle_length(session); + uint8_t *pos; + + if (pickled_length < _olm_enc_output_length(raw_length)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + pos = _olm_enc_output_pos(pickled, raw_length); + pos = _olm_pickle_uint32(pos, PICKLE_VERSION); + pos = megolm_pickle(&(session->ratchet), pos); + pos = _olm_pickle_ed25519_key_pair(pos, &(session->signing_key)); + + return _olm_enc_output(key, key_length, pickled, raw_length); +} + +size_t olm_unpickle_outbound_group_session( + OlmOutboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +) { + const uint8_t *pos; + const uint8_t *end; + uint32_t pickle_version; + + size_t raw_length = _olm_enc_input( + key, key_length, pickled, pickled_length, &(session->last_error) + ); + if (raw_length == (size_t)-1) { + return raw_length; + } + + pos = pickled; + end = pos + raw_length; + pos = _olm_unpickle_uint32(pos, end, &pickle_version); + if (pickle_version != PICKLE_VERSION) { + session->last_error = OLM_UNKNOWN_PICKLE_VERSION; + return (size_t)-1; + } + pos = megolm_unpickle(&(session->ratchet), pos, end); + pos = _olm_unpickle_ed25519_key_pair(pos, end, &(session->signing_key)); + + if (end != pos) { + /* We had the wrong number of bytes in the input. */ + session->last_error = OLM_CORRUPTED_PICKLE; + return (size_t)-1; + } + + return pickled_length; +} + + +size_t olm_init_outbound_group_session_random_length( + const OlmOutboundGroupSession *session +) { + /* we need data to initialize the megolm ratchet, plus some more for the + * session id. + */ + return MEGOLM_RATCHET_LENGTH + + ED25519_RANDOM_LENGTH; +} + +size_t olm_init_outbound_group_session( + OlmOutboundGroupSession *session, + uint8_t const * random, size_t random_length +) { + if (random_length < olm_init_outbound_group_session_random_length(session)) { + /* Insufficient random data for new session */ + session->last_error = OLM_NOT_ENOUGH_RANDOM; + return (size_t)-1; + } + + megolm_init(&(session->ratchet), random, 0); + random += MEGOLM_RATCHET_LENGTH; + + _olm_crypto_ed25519_generate_key(random, &(session->signing_key)); + random += ED25519_RANDOM_LENGTH; + + return 0; +} + +static size_t raw_message_length( + OlmOutboundGroupSession *session, + size_t plaintext_length) +{ + size_t ciphertext_length, mac_length; + + ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length( + megolm_cipher, plaintext_length + ); + + mac_length = megolm_cipher->ops->mac_length(megolm_cipher); + + return _olm_encode_group_message_length( + session->ratchet.counter, + ciphertext_length, mac_length, ED25519_SIGNATURE_LENGTH + ); +} + +size_t olm_group_encrypt_message_length( + OlmOutboundGroupSession *session, + size_t plaintext_length +) { + size_t message_length = raw_message_length(session, plaintext_length); + return _olm_encode_base64_length(message_length); +} + +/** write an un-base64-ed message to the buffer */ +static size_t _encrypt( + OlmOutboundGroupSession *session, uint8_t const * plaintext, size_t plaintext_length, + uint8_t * buffer +) { + size_t ciphertext_length, mac_length, message_length; + size_t result; + uint8_t *ciphertext_ptr; + + ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length( + megolm_cipher, + plaintext_length + ); + + mac_length = megolm_cipher->ops->mac_length(megolm_cipher); + + /* first we build the message structure, then we encrypt + * the plaintext into it. + */ + message_length = _olm_encode_group_message( + OLM_PROTOCOL_VERSION, + session->ratchet.counter, + ciphertext_length, + buffer, + &ciphertext_ptr); + + message_length += mac_length; + + result = megolm_cipher->ops->encrypt( + megolm_cipher, + megolm_get_data(&(session->ratchet)), MEGOLM_RATCHET_LENGTH, + plaintext, plaintext_length, + ciphertext_ptr, ciphertext_length, + buffer, message_length + ); + + if (result == (size_t)-1) { + return result; + } + + megolm_advance(&(session->ratchet)); + + /* sign the whole thing with the ed25519 key. */ + _olm_crypto_ed25519_sign( + &(session->signing_key), + buffer, message_length, + buffer + message_length + ); + + return result; +} + +size_t olm_group_encrypt( + OlmOutboundGroupSession *session, + uint8_t const * plaintext, size_t plaintext_length, + uint8_t * message, size_t max_message_length +) { + size_t rawmsglen; + size_t result; + uint8_t *message_pos; + + rawmsglen = raw_message_length(session, plaintext_length); + + if (max_message_length < _olm_encode_base64_length(rawmsglen)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + /* we construct the message at the end of the buffer, so that + * we have room to base64-encode it once we're done. + */ + message_pos = message + _olm_encode_base64_length(rawmsglen) - rawmsglen; + + /* write the message, and encrypt it, at message_pos */ + result = _encrypt(session, plaintext, plaintext_length, message_pos); + if (result == (size_t)-1) { + return result; + } + + /* bas64-encode it */ + return _olm_encode_base64( + message_pos, rawmsglen, message + ); +} + + +size_t olm_outbound_group_session_id_length( + const OlmOutboundGroupSession *session +) { + return _olm_encode_base64_length(GROUP_SESSION_ID_LENGTH); +} + +size_t olm_outbound_group_session_id( + OlmOutboundGroupSession *session, + uint8_t * id, size_t id_length +) { + if (id_length < olm_outbound_group_session_id_length(session)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + return _olm_encode_base64( + session->signing_key.public_key.public_key, GROUP_SESSION_ID_LENGTH, id + ); +} + +uint32_t olm_outbound_group_session_message_index( + OlmOutboundGroupSession *session +) { + return session->ratchet.counter; +} + +#define SESSION_KEY_RAW_LENGTH \ + (1 + 4 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH\ + + ED25519_SIGNATURE_LENGTH) + +size_t olm_outbound_group_session_key_length( + const OlmOutboundGroupSession *session +) { + return _olm_encode_base64_length(SESSION_KEY_RAW_LENGTH); +} + +size_t olm_outbound_group_session_key( + OlmOutboundGroupSession *session, + uint8_t * key, size_t key_length +) { + uint8_t *raw; + uint8_t *ptr; + size_t encoded_length = olm_outbound_group_session_key_length(session); + + if (key_length < encoded_length) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + /* put the raw data at the end of the output buffer. */ + raw = ptr = key + encoded_length - SESSION_KEY_RAW_LENGTH; + *ptr++ = SESSION_KEY_VERSION; + + uint32_t counter = session->ratchet.counter; + // Encode counter as a big endian 32-bit number. + for (unsigned i = 0; i < 4; i++) { + *ptr++ = 0xFF & (counter >> 24); counter <<= 8; + } + + memcpy(ptr, megolm_get_data(&session->ratchet), MEGOLM_RATCHET_LENGTH); + ptr += MEGOLM_RATCHET_LENGTH; + + memcpy( + ptr, session->signing_key.public_key.public_key, + ED25519_PUBLIC_KEY_LENGTH + ); + ptr += ED25519_PUBLIC_KEY_LENGTH; + + /* sign the whole thing with the ed25519 key. */ + _olm_crypto_ed25519_sign( + &(session->signing_key), + raw, ptr - raw, ptr + ); + + return _olm_encode_base64(raw, SESSION_KEY_RAW_LENGTH, key); +} diff --git a/src/pickle.cpp b/src/pickle.cpp index 25580d2..b6ee4c5 100644 --- a/src/pickle.cpp +++ b/src/pickle.cpp @@ -13,10 +13,65 @@ * limitations under the License. */ #include "olm/pickle.hh" +#include "olm/pickle.h" + +std::uint8_t * olm::pickle( + std::uint8_t * pos, + std::uint32_t value +) { + pos += 4; + for (unsigned i = 4; i--;) { *(--pos) = value; value >>= 8; } + return pos + 4; +} + + +std::uint8_t const * olm::unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + std::uint32_t & value +) { + value = 0; + if (end < pos + 4) return end; + for (unsigned i = 4; i--;) { value <<= 8; value |= *(pos++); } + return pos; +} + +std::uint8_t * olm::pickle( + std::uint8_t * pos, + bool value +) { + *(pos++) = value ? 1 : 0; + return pos; +} + +std::uint8_t const * olm::unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + bool & value +) { + if (pos == end) return end; + value = *(pos++); + return pos; +} + +std::uint8_t * olm::pickle_bytes( + std::uint8_t * pos, + std::uint8_t const * bytes, std::size_t bytes_length +) { + std::memcpy(pos, bytes, bytes_length); + return pos + bytes_length; +} + +std::uint8_t const * olm::unpickle_bytes( + std::uint8_t const * pos, std::uint8_t const * end, + std::uint8_t * bytes, std::size_t bytes_length +) { + if (end < pos + bytes_length) return end; + std::memcpy(bytes, pos, bytes_length); + return pos + bytes_length; +} std::size_t olm::pickle_length( - const olm::Curve25519PublicKey & value + const _olm_curve25519_public_key & value ) { return sizeof(value.public_key); } @@ -24,7 +79,7 @@ std::size_t olm::pickle_length( std::uint8_t * olm::pickle( std::uint8_t * pos, - const olm::Curve25519PublicKey & value + const _olm_curve25519_public_key & value ) { pos = olm::pickle_bytes( pos, value.public_key, sizeof(value.public_key) @@ -35,7 +90,7 @@ std::uint8_t * olm::pickle( std::uint8_t const * olm::unpickle( std::uint8_t const * pos, std::uint8_t const * end, - olm::Curve25519PublicKey & value + _olm_curve25519_public_key & value ) { pos = olm::unpickle_bytes( pos, end, value.public_key, sizeof(value.public_key) @@ -46,21 +101,24 @@ std::uint8_t const * olm::unpickle( std::size_t olm::pickle_length( - const olm::Curve25519KeyPair & value + const _olm_curve25519_key_pair & value ) { - return sizeof(value.public_key) + sizeof(value.private_key); + return sizeof(value.public_key.public_key) + + sizeof(value.private_key.private_key); } std::uint8_t * olm::pickle( std::uint8_t * pos, - const olm::Curve25519KeyPair & value + const _olm_curve25519_key_pair & value ) { pos = olm::pickle_bytes( - pos, value.public_key, sizeof(value.public_key) + pos, value.public_key.public_key, + sizeof(value.public_key.public_key) ); pos = olm::pickle_bytes( - pos, value.private_key, sizeof(value.private_key) + pos, value.private_key.private_key, + sizeof(value.private_key.private_key) ); return pos; } @@ -68,77 +126,117 @@ std::uint8_t * olm::pickle( std::uint8_t const * olm::unpickle( std::uint8_t const * pos, std::uint8_t const * end, - olm::Curve25519KeyPair & value + _olm_curve25519_key_pair & value ) { pos = olm::unpickle_bytes( - pos, end, value.public_key, sizeof(value.public_key) + pos, end, value.public_key.public_key, + sizeof(value.public_key.public_key) ); pos = olm::unpickle_bytes( - pos, end, value.private_key, sizeof(value.private_key) + pos, end, value.private_key.private_key, + sizeof(value.private_key.private_key) ); return pos; } -std::size_t olm::pickle_length( - const olm::Ed25519PublicKey & value +////// pickle.h implementations + +std::size_t _olm_pickle_ed25519_public_key_length( + const _olm_ed25519_public_key * value ) { - return sizeof(value.public_key); + return sizeof(value->public_key); } -std::uint8_t * olm::pickle( +std::uint8_t * _olm_pickle_ed25519_public_key( std::uint8_t * pos, - const olm::Ed25519PublicKey & value + const _olm_ed25519_public_key *value ) { pos = olm::pickle_bytes( - pos, value.public_key, sizeof(value.public_key) + pos, value->public_key, sizeof(value->public_key) ); return pos; } -std::uint8_t const * olm::unpickle( +std::uint8_t const * _olm_unpickle_ed25519_public_key( std::uint8_t const * pos, std::uint8_t const * end, - olm::Ed25519PublicKey & value + _olm_ed25519_public_key * value ) { pos = olm::unpickle_bytes( - pos, end, value.public_key, sizeof(value.public_key) + pos, end, value->public_key, sizeof(value->public_key) ); return pos; - } -std::size_t olm::pickle_length( - const olm::Ed25519KeyPair & value +std::size_t _olm_pickle_ed25519_key_pair_length( + const _olm_ed25519_key_pair *value ) { - return sizeof(value.public_key) + sizeof(value.private_key); + return sizeof(value->public_key.public_key) + + sizeof(value->private_key.private_key); } -std::uint8_t * olm::pickle( +std::uint8_t * _olm_pickle_ed25519_key_pair( std::uint8_t * pos, - const olm::Ed25519KeyPair & value + const _olm_ed25519_key_pair *value ) { pos = olm::pickle_bytes( - pos, value.public_key, sizeof(value.public_key) + pos, value->public_key.public_key, + sizeof(value->public_key.public_key) ); pos = olm::pickle_bytes( - pos, value.private_key, sizeof(value.private_key) + pos, value->private_key.private_key, + sizeof(value->private_key.private_key) ); return pos; } -std::uint8_t const * olm::unpickle( +std::uint8_t const * _olm_unpickle_ed25519_key_pair( std::uint8_t const * pos, std::uint8_t const * end, - olm::Ed25519KeyPair & value + _olm_ed25519_key_pair *value ) { pos = olm::unpickle_bytes( - pos, end, value.public_key, sizeof(value.public_key) + pos, end, value->public_key.public_key, + sizeof(value->public_key.public_key) ); pos = olm::unpickle_bytes( - pos, end, value.private_key, sizeof(value.private_key) + pos, end, value->private_key.private_key, + sizeof(value->private_key.private_key) ); return pos; } + +uint8_t * _olm_pickle_uint32(uint8_t * pos, uint32_t value) { + return olm::pickle(pos, value); +} + +uint8_t const * _olm_unpickle_uint32( + uint8_t const * pos, uint8_t const * end, + uint32_t *value +) { + return olm::unpickle(pos, end, *value); +} + +uint8_t * _olm_pickle_bool(uint8_t * pos, int value) { + return olm::pickle(pos, (bool)value); +} + +uint8_t const * _olm_unpickle_bool( + uint8_t const * pos, uint8_t const * end, + int *value +) { + return olm::unpickle(pos, end, *reinterpret_cast<bool *>(value)); +} + +uint8_t * _olm_pickle_bytes(uint8_t * pos, uint8_t const * bytes, + size_t bytes_length) { + return olm::pickle_bytes(pos, bytes, bytes_length); +} + +uint8_t const * _olm_unpickle_bytes(uint8_t const * pos, uint8_t const * end, + uint8_t * bytes, size_t bytes_length) { + return olm::unpickle_bytes(pos, end, bytes, bytes_length); +} diff --git a/src/pickle_encoding.c b/src/pickle_encoding.c new file mode 100644 index 0000000..5d5f8d7 --- /dev/null +++ b/src/pickle_encoding.c @@ -0,0 +1,92 @@ +/* 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. + */ + +#include "olm/pickle_encoding.h" + +#include "olm/base64.h" +#include "olm/cipher.h" +#include "olm/olm.h" + +static const struct _olm_cipher_aes_sha_256 PICKLE_CIPHER = + OLM_CIPHER_INIT_AES_SHA_256("Pickle"); + +size_t _olm_enc_output_length( + size_t raw_length +) { + const struct _olm_cipher *cipher = OLM_CIPHER_BASE(&PICKLE_CIPHER); + size_t length = cipher->ops->encrypt_ciphertext_length(cipher, raw_length); + length += cipher->ops->mac_length(cipher); + return _olm_encode_base64_length(length); +} + +uint8_t * _olm_enc_output_pos( + uint8_t * output, + size_t raw_length +) { + const struct _olm_cipher *cipher = OLM_CIPHER_BASE(&PICKLE_CIPHER); + size_t length = cipher->ops->encrypt_ciphertext_length(cipher, raw_length); + length += cipher->ops->mac_length(cipher); + return output + _olm_encode_base64_length(length) - length; +} + +size_t _olm_enc_output( + uint8_t const * key, size_t key_length, + uint8_t * output, size_t raw_length +) { + const struct _olm_cipher *cipher = OLM_CIPHER_BASE(&PICKLE_CIPHER); + size_t ciphertext_length = cipher->ops->encrypt_ciphertext_length( + cipher, raw_length + ); + size_t length = ciphertext_length + cipher->ops->mac_length(cipher); + size_t base64_length = _olm_encode_base64_length(length); + uint8_t * raw_output = output + base64_length - length; + cipher->ops->encrypt( + cipher, + key, key_length, + raw_output, raw_length, + raw_output, ciphertext_length, + raw_output, length + ); + _olm_encode_base64(raw_output, length, output); + return raw_length; +} + + +size_t _olm_enc_input(uint8_t const * key, size_t key_length, + uint8_t * input, size_t b64_length, + enum OlmErrorCode * last_error +) { + size_t enc_length = _olm_decode_base64_length(b64_length); + if (enc_length == (size_t)-1) { + if (last_error) { + *last_error = OLM_INVALID_BASE64; + } + return (size_t)-1; + } + _olm_decode_base64(input, b64_length, input); + const struct _olm_cipher *cipher = OLM_CIPHER_BASE(&PICKLE_CIPHER); + size_t raw_length = enc_length - cipher->ops->mac_length(cipher); + size_t result = cipher->ops->decrypt( + cipher, + key, key_length, + input, enc_length, + input, raw_length, + input, raw_length + ); + if (result == (size_t)-1 && last_error) { + *last_error = OLM_BAD_ACCOUNT_KEY; + } + return result; +} diff --git a/src/ratchet.cpp b/src/ratchet.cpp index 5ef1e56..279c4c0 100644 --- a/src/ratchet.cpp +++ b/src/ratchet.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015 OpenMarket Ltd +/* Copyright 2015, 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. @@ -15,10 +15,9 @@ #include "olm/ratchet.hh" #include "olm/message.hh" #include "olm/memory.hh" -#include "olm/cipher.hh" +#include "olm/cipher.h" #include "olm/pickle.hh" - #include <cstring> namespace { @@ -28,18 +27,30 @@ static const std::uint8_t MESSAGE_KEY_SEED[1] = {0x01}; static const std::uint8_t CHAIN_KEY_SEED[1] = {0x02}; static const std::size_t MAX_MESSAGE_GAP = 2000; + +/** + * Advance the root key, creating a new message chain. + * + * @param root_key previous root key R(n-1) + * @param our_key our new ratchet key T(n) + * @param their_key their most recent ratchet key T(n-1) + * @param info table of constants for the ratchet function + * @param new_root_key[out] returns the new root key R(n) + * @param new_chain_key[out] returns the first chain key in the new chain + * C(n,0) + */ static void create_chain_key( olm::SharedKey const & root_key, - olm::Curve25519KeyPair const & our_key, - olm::Curve25519PublicKey const & their_key, + _olm_curve25519_key_pair const & our_key, + _olm_curve25519_public_key const & their_key, olm::KdfInfo const & info, olm::SharedKey & new_root_key, olm::ChainKey & new_chain_key ) { olm::SharedKey secret; - olm::curve25519_shared_secret(our_key, their_key, secret); - std::uint8_t derived_secrets[2 * olm::KEY_LENGTH]; - olm::hkdf_sha256( + _olm_crypto_curve25519_shared_secret(&our_key, &their_key, secret); + std::uint8_t derived_secrets[2 * olm::OLM_SHARED_KEY_LENGTH]; + _olm_crypto_hkdf_sha256( secret, sizeof(secret), root_key, sizeof(root_key), info.ratchet_info, info.ratchet_info_length, @@ -58,7 +69,7 @@ static void advance_chain_key( olm::ChainKey const & chain_key, olm::ChainKey & new_chain_key ) { - olm::hmac_sha256( + _olm_crypto_hmac_sha256( chain_key.key, sizeof(chain_key.key), CHAIN_KEY_SEED, sizeof(CHAIN_KEY_SEED), new_chain_key.key @@ -70,9 +81,8 @@ static void advance_chain_key( static void create_message_keys( olm::ChainKey const & chain_key, olm::KdfInfo const & info, - olm::MessageKey & message_key -) { - olm::hmac_sha256( + olm::MessageKey & message_key) { + _olm_crypto_hmac_sha256( chain_key.key, sizeof(chain_key.key), MESSAGE_KEY_SEED, sizeof(MESSAGE_KEY_SEED), message_key.key @@ -82,12 +92,13 @@ static void create_message_keys( static std::size_t verify_mac_and_decrypt( - olm::Cipher const & cipher, + _olm_cipher const *cipher, olm::MessageKey const & message_key, olm::MessageReader const & reader, std::uint8_t * plaintext, std::size_t max_plaintext_length ) { - return cipher.decrypt( + return cipher->ops->decrypt( + cipher, message_key.key, sizeof(message_key.key), reader.input, reader.input_length, reader.ciphertext, reader.ciphertext_length, @@ -155,7 +166,6 @@ static std::size_t verify_mac_and_decrypt_for_new_chain( new_chain.ratchet_key, session.kdf_info, new_root_key, new_chain.chain_key ); - std::size_t result = verify_mac_and_decrypt_for_existing_chain( session, new_chain.chain_key, reader, plaintext, max_plaintext_length @@ -170,19 +180,19 @@ static std::size_t verify_mac_and_decrypt_for_new_chain( olm::Ratchet::Ratchet( olm::KdfInfo const & kdf_info, - Cipher const & ratchet_cipher + _olm_cipher const * ratchet_cipher ) : kdf_info(kdf_info), ratchet_cipher(ratchet_cipher), - last_error(olm::ErrorCode::SUCCESS) { + last_error(OlmErrorCode::OLM_SUCCESS) { } void olm::Ratchet::initialise_as_bob( std::uint8_t const * shared_secret, std::size_t shared_secret_length, - olm::Curve25519PublicKey const & their_ratchet_key + _olm_curve25519_public_key const & their_ratchet_key ) { - std::uint8_t derived_secrets[2 * olm::KEY_LENGTH]; - olm::hkdf_sha256( + std::uint8_t derived_secrets[2 * olm::OLM_SHARED_KEY_LENGTH]; + _olm_crypto_hkdf_sha256( shared_secret, shared_secret_length, nullptr, 0, kdf_info.root_info, kdf_info.root_info_length, @@ -200,10 +210,10 @@ void olm::Ratchet::initialise_as_bob( void olm::Ratchet::initialise_as_alice( std::uint8_t const * shared_secret, std::size_t shared_secret_length, - olm::Curve25519KeyPair const & our_ratchet_key + _olm_curve25519_key_pair const & our_ratchet_key ) { - std::uint8_t derived_secrets[2 * olm::KEY_LENGTH]; - olm::hkdf_sha256( + std::uint8_t derived_secrets[2 * olm::OLM_SHARED_KEY_LENGTH]; + _olm_crypto_hkdf_sha256( shared_secret, shared_secret_length, nullptr, 0, kdf_info.root_info, kdf_info.root_info_length, @@ -224,7 +234,7 @@ namespace olm { static std::size_t pickle_length( const olm::SharedKey & value ) { - return olm::KEY_LENGTH; + return olm::OLM_SHARED_KEY_LENGTH; } @@ -232,7 +242,7 @@ static std::uint8_t * pickle( std::uint8_t * pos, const olm::SharedKey & value ) { - return olm::pickle_bytes(pos, value, olm::KEY_LENGTH); + return olm::pickle_bytes(pos, value, olm::OLM_SHARED_KEY_LENGTH); } @@ -240,7 +250,7 @@ static std::uint8_t const * unpickle( std::uint8_t const * pos, std::uint8_t const * end, olm::SharedKey & value ) { - return olm::unpickle_bytes(pos, end, value, olm::KEY_LENGTH); + return olm::unpickle_bytes(pos, end, value, olm::OLM_SHARED_KEY_LENGTH); } @@ -349,7 +359,7 @@ std::size_t olm::pickle_length( olm::Ratchet const & value ) { std::size_t length = 0; - length += olm::KEY_LENGTH; + length += olm::OLM_SHARED_KEY_LENGTH; length += olm::pickle_length(value.sender_chain); length += olm::pickle_length(value.receiver_chains); length += olm::pickle_length(value.skipped_message_keys); @@ -370,12 +380,19 @@ std::uint8_t * olm::pickle( std::uint8_t const * olm::unpickle( std::uint8_t const * pos, std::uint8_t const * end, - olm::Ratchet & value + olm::Ratchet & value, + bool includes_chain_index ) { pos = unpickle(pos, end, value.root_key); pos = unpickle(pos, end, value.sender_chain); pos = unpickle(pos, end, value.receiver_chains); pos = unpickle(pos, end, value.skipped_message_keys); + + // pickle v 0x80000001 includes a chain index; pickle v1 does not. + if (includes_chain_index) { + std::uint32_t dummy; + pos = unpickle(pos, end, dummy); + } return pos; } @@ -387,17 +404,18 @@ std::size_t olm::Ratchet::encrypt_output_length( if (!sender_chain.empty()) { counter = sender_chain[0].chain_key.index; } - std::size_t padded = ratchet_cipher.encrypt_ciphertext_length( + std::size_t padded = ratchet_cipher->ops->encrypt_ciphertext_length( + ratchet_cipher, plaintext_length ); return olm::encode_message_length( - counter, olm::KEY_LENGTH, padded, ratchet_cipher.mac_length() + counter, CURVE25519_KEY_LENGTH, padded, ratchet_cipher->ops->mac_length(ratchet_cipher) ); } std::size_t olm::Ratchet::encrypt_random_length() { - return sender_chain.empty() ? olm::KEY_LENGTH : 0; + return sender_chain.empty() ? CURVE25519_RANDOM_LENGTH : 0; } @@ -409,17 +427,17 @@ std::size_t olm::Ratchet::encrypt( std::size_t output_length = encrypt_output_length(plaintext_length); if (random_length < encrypt_random_length()) { - last_error = olm::ErrorCode::NOT_ENOUGH_RANDOM; + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; return std::size_t(-1); } if (max_output_length < output_length) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } if (sender_chain.empty()) { sender_chain.insert(); - olm::curve25519_generate_key(random, sender_chain[0].ratchet_key); + _olm_crypto_curve25519_generate_key(random, &sender_chain[0].ratchet_key); create_chain_key( root_key, sender_chain[0].ratchet_key, @@ -433,22 +451,26 @@ std::size_t olm::Ratchet::encrypt( create_message_keys(sender_chain[0].chain_key, kdf_info, keys); advance_chain_key(sender_chain[0].chain_key, sender_chain[0].chain_key); - std::size_t ciphertext_length = ratchet_cipher.encrypt_ciphertext_length( + std::size_t ciphertext_length = ratchet_cipher->ops->encrypt_ciphertext_length( + ratchet_cipher, plaintext_length ); std::uint32_t counter = keys.index; - Curve25519PublicKey const & ratchet_key = sender_chain[0].ratchet_key; + _olm_curve25519_public_key const & ratchet_key = + sender_chain[0].ratchet_key.public_key; olm::MessageWriter writer; olm::encode_message( - writer, PROTOCOL_VERSION, counter, olm::KEY_LENGTH, ciphertext_length, + writer, PROTOCOL_VERSION, counter, CURVE25519_KEY_LENGTH, + ciphertext_length, output ); olm::store_array(writer.ratchet_key, ratchet_key.public_key); - ratchet_cipher.encrypt( + ratchet_cipher->ops->encrypt( + ratchet_cipher, keys.key, sizeof(keys.key), plaintext, plaintext_length, writer.ciphertext, ciphertext_length, @@ -465,15 +487,17 @@ std::size_t olm::Ratchet::decrypt_max_plaintext_length( ) { olm::MessageReader reader; olm::decode_message( - reader, input, input_length, ratchet_cipher.mac_length() + reader, input, input_length, + ratchet_cipher->ops->mac_length(ratchet_cipher) ); if (!reader.ciphertext) { - last_error = olm::ErrorCode::BAD_MESSAGE_FORMAT; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; return std::size_t(-1); } - return ratchet_cipher.decrypt_max_plaintext_length(reader.ciphertext_length); + return ratchet_cipher->ops->decrypt_max_plaintext_length( + ratchet_cipher, reader.ciphertext_length); } @@ -483,38 +507,41 @@ std::size_t olm::Ratchet::decrypt( ) { olm::MessageReader reader; olm::decode_message( - reader, input, input_length, ratchet_cipher.mac_length() + reader, input, input_length, + ratchet_cipher->ops->mac_length(ratchet_cipher) ); if (reader.version != PROTOCOL_VERSION) { - last_error = olm::ErrorCode::BAD_MESSAGE_VERSION; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_VERSION; return std::size_t(-1); } if (!reader.has_counter || !reader.ratchet_key || !reader.ciphertext) { - last_error = olm::ErrorCode::BAD_MESSAGE_FORMAT; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; return std::size_t(-1); } - std::size_t max_length = ratchet_cipher.decrypt_max_plaintext_length( + std::size_t max_length = ratchet_cipher->ops->decrypt_max_plaintext_length( + ratchet_cipher, reader.ciphertext_length ); if (max_plaintext_length < max_length) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } - if (reader.ratchet_key_length != olm::KEY_LENGTH) { - last_error = olm::ErrorCode::BAD_MESSAGE_FORMAT; + if (reader.ratchet_key_length != CURVE25519_KEY_LENGTH) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; return std::size_t(-1); } ReceiverChain * chain = nullptr; + for (olm::ReceiverChain & receiver_chain : receiver_chains) { if (0 == std::memcmp( receiver_chain.ratchet_key.public_key, reader.ratchet_key, - olm::KEY_LENGTH + CURVE25519_KEY_LENGTH )) { chain = &receiver_chain; break; @@ -534,7 +561,7 @@ std::size_t olm::Ratchet::decrypt( if (reader.counter == skipped.message_key.index && 0 == std::memcmp( skipped.ratchet_key.public_key, reader.ratchet_key, - olm::KEY_LENGTH + CURVE25519_KEY_LENGTH ) ) { /* Found the key for this message. Check the MAC. */ @@ -555,26 +582,32 @@ std::size_t olm::Ratchet::decrypt( } } else { result = verify_mac_and_decrypt_for_existing_chain( - *this, chain->chain_key, reader, plaintext, max_plaintext_length + *this, chain->chain_key, + reader, plaintext, max_plaintext_length ); } if (result == std::size_t(-1)) { - last_error = olm::ErrorCode::BAD_MESSAGE_MAC; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_MAC; return std::size_t(-1); } if (!chain) { - /* They have started using a new empheral ratchet key. + /* They have started using a new ephemeral ratchet key. * We need to derive a new set of chain keys. * We can discard our previous empheral ratchet key. * We will generate a new key when we send the next message. */ + chain = receiver_chains.insert(); olm::load_array(chain->ratchet_key.public_key, reader.ratchet_key); + + // TODO: we've already done this once, in + // verify_mac_and_decrypt_for_new_chain(). we could reuse the result. create_chain_key( root_key, sender_chain[0].ratchet_key, chain->ratchet_key, kdf_info, root_key, chain->chain_key ); + olm::unset(sender_chain[0]); sender_chain.erase(sender_chain.begin()); } diff --git a/src/session.cpp b/src/session.cpp index 71c80e4..f1bc5a7 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015 OpenMarket Ltd +/* Copyright 2015, 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. @@ -13,8 +13,8 @@ * limitations under the License. */ #include "olm/session.hh" -#include "olm/cipher.hh" -#include "olm/crypto.hh" +#include "olm/cipher.h" +#include "olm/crypto.h" #include "olm/account.hh" #include "olm/memory.hh" #include "olm/message.hh" @@ -30,64 +30,64 @@ static const std::uint8_t ROOT_KDF_INFO[] = "OLM_ROOT"; static const std::uint8_t RATCHET_KDF_INFO[] = "OLM_RATCHET"; static const std::uint8_t CIPHER_KDF_INFO[] = "OLM_KEYS"; -static const olm::CipherAesSha256 OLM_CIPHER( - CIPHER_KDF_INFO, sizeof(CIPHER_KDF_INFO) -1 -); - static const olm::KdfInfo OLM_KDF_INFO = { ROOT_KDF_INFO, sizeof(ROOT_KDF_INFO) - 1, RATCHET_KDF_INFO, sizeof(RATCHET_KDF_INFO) - 1 }; +static const struct _olm_cipher_aes_sha_256 OLM_CIPHER = + OLM_CIPHER_INIT_AES_SHA_256(CIPHER_KDF_INFO); + } // namespace olm::Session::Session( -) : ratchet(OLM_KDF_INFO, OLM_CIPHER), - last_error(olm::ErrorCode::SUCCESS), +) : ratchet(OLM_KDF_INFO, OLM_CIPHER_BASE(&OLM_CIPHER)), + last_error(OlmErrorCode::OLM_SUCCESS), received_message(false) { } std::size_t olm::Session::new_outbound_session_random_length() { - return olm::KEY_LENGTH * 2; + return CURVE25519_RANDOM_LENGTH * 2; } std::size_t olm::Session::new_outbound_session( olm::Account const & local_account, - olm::Curve25519PublicKey const & identity_key, - olm::Curve25519PublicKey const & one_time_key, + _olm_curve25519_public_key const & identity_key, + _olm_curve25519_public_key const & one_time_key, std::uint8_t const * random, std::size_t random_length ) { if (random_length < new_outbound_session_random_length()) { - last_error = olm::ErrorCode::NOT_ENOUGH_RANDOM; + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; return std::size_t(-1); } - olm::Curve25519KeyPair base_key; - olm::curve25519_generate_key(random, base_key); + _olm_curve25519_key_pair base_key; + _olm_crypto_curve25519_generate_key(random, &base_key); - olm::Curve25519KeyPair ratchet_key; - olm::curve25519_generate_key(random + olm::KEY_LENGTH, ratchet_key); + _olm_curve25519_key_pair ratchet_key; + _olm_crypto_curve25519_generate_key(random + CURVE25519_RANDOM_LENGTH, &ratchet_key); - olm::Curve25519KeyPair const & alice_identity_key_pair = ( + _olm_curve25519_key_pair const & alice_identity_key_pair = ( local_account.identity_keys.curve25519_key ); received_message = false; - alice_identity_key = alice_identity_key_pair; - alice_base_key = base_key; + alice_identity_key = alice_identity_key_pair.public_key; + alice_base_key = base_key.public_key; bob_one_time_key = one_time_key; - std::uint8_t secret[3 * olm::KEY_LENGTH]; + // Calculate the shared secret S via triple DH + std::uint8_t secret[3 * CURVE25519_SHARED_SECRET_LENGTH]; std::uint8_t * pos = secret; - olm::curve25519_shared_secret(alice_identity_key_pair, one_time_key, pos); - pos += olm::KEY_LENGTH; - olm::curve25519_shared_secret(base_key, identity_key, pos); - pos += olm::KEY_LENGTH; - olm::curve25519_shared_secret(base_key, one_time_key, pos); + _olm_crypto_curve25519_shared_secret(&alice_identity_key_pair, &one_time_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&base_key, &identity_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&base_key, &one_time_key, pos); ratchet.initialise_as_alice(secret, sizeof(secret), ratchet_key); @@ -106,13 +106,13 @@ static bool check_message_fields( bool ok = true; ok = ok && (have_their_identity_key || reader.identity_key); if (reader.identity_key) { - ok = ok && reader.identity_key_length == olm::KEY_LENGTH; + ok = ok && reader.identity_key_length == CURVE25519_KEY_LENGTH; } ok = ok && reader.message; ok = ok && reader.base_key; - ok = ok && reader.base_key_length == olm::KEY_LENGTH; + ok = ok && reader.base_key_length == CURVE25519_KEY_LENGTH; ok = ok && reader.one_time_key; - ok = ok && reader.one_time_key_length == olm::KEY_LENGTH; + ok = ok && reader.one_time_key_length == CURVE25519_KEY_LENGTH; return ok; } @@ -121,43 +121,44 @@ static bool check_message_fields( std::size_t olm::Session::new_inbound_session( olm::Account & local_account, - olm::Curve25519PublicKey const * their_identity_key, + _olm_curve25519_public_key const * their_identity_key, std::uint8_t const * one_time_key_message, std::size_t message_length ) { olm::PreKeyMessageReader reader; decode_one_time_key_message(reader, one_time_key_message, message_length); if (!check_message_fields(reader, their_identity_key)) { - last_error = olm::ErrorCode::BAD_MESSAGE_FORMAT; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; return std::size_t(-1); } if (reader.identity_key && their_identity_key) { bool same = 0 == std::memcmp( - their_identity_key->public_key, reader.identity_key, olm::KEY_LENGTH + their_identity_key->public_key, reader.identity_key, CURVE25519_KEY_LENGTH ); if (!same) { - last_error = olm::ErrorCode::BAD_MESSAGE_KEY_ID; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_KEY_ID; return std::size_t(-1); } } + olm::load_array(alice_identity_key.public_key, reader.identity_key); + olm::load_array(alice_base_key.public_key, reader.base_key); + olm::load_array(bob_one_time_key.public_key, reader.one_time_key); + olm::MessageReader message_reader; decode_message( message_reader, reader.message, reader.message_length, - ratchet.ratchet_cipher.mac_length() + ratchet.ratchet_cipher->ops->mac_length(ratchet.ratchet_cipher) ); if (!message_reader.ratchet_key - || message_reader.ratchet_key_length != olm::KEY_LENGTH) { - last_error = olm::ErrorCode::BAD_MESSAGE_FORMAT; + || message_reader.ratchet_key_length != CURVE25519_KEY_LENGTH) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; return std::size_t(-1); } - olm::load_array(alice_identity_key.public_key, reader.identity_key); - olm::load_array(alice_base_key.public_key, reader.base_key); - olm::load_array(bob_one_time_key.public_key, reader.one_time_key); - olm::Curve25519PublicKey ratchet_key; + _olm_curve25519_public_key ratchet_key; olm::load_array(ratchet_key.public_key, message_reader.ratchet_key); olm::OneTimeKey const * our_one_time_key = local_account.lookup_key( @@ -165,32 +166,34 @@ std::size_t olm::Session::new_inbound_session( ); if (!our_one_time_key) { - last_error = olm::ErrorCode::BAD_MESSAGE_KEY_ID; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_KEY_ID; return std::size_t(-1); } - olm::Curve25519KeyPair const & bob_identity_key = ( + _olm_curve25519_key_pair const & bob_identity_key = ( local_account.identity_keys.curve25519_key ); - olm::Curve25519KeyPair const & bob_one_time_key = our_one_time_key->key; + _olm_curve25519_key_pair const & bob_one_time_key = our_one_time_key->key; - std::uint8_t secret[olm::KEY_LENGTH * 3]; + // Calculate the shared secret S via triple DH + std::uint8_t secret[CURVE25519_SHARED_SECRET_LENGTH * 3]; std::uint8_t * pos = secret; - olm::curve25519_shared_secret(bob_one_time_key, alice_identity_key, pos); - pos += olm::KEY_LENGTH; - olm::curve25519_shared_secret(bob_identity_key, alice_base_key, pos); - pos += olm::KEY_LENGTH; - olm::curve25519_shared_secret(bob_one_time_key, alice_base_key, pos); + _olm_crypto_curve25519_shared_secret(&bob_one_time_key, &alice_identity_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&bob_identity_key, &alice_base_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&bob_one_time_key, &alice_base_key, pos); ratchet.initialise_as_bob(secret, sizeof(secret), ratchet_key); olm::unset(secret); + return std::size_t(0); } std::size_t olm::Session::session_id_length() { - return olm::SHA256_OUTPUT_LENGTH; + return SHA256_OUTPUT_LENGTH; } @@ -198,21 +201,21 @@ std::size_t olm::Session::session_id( std::uint8_t * id, std::size_t id_length ) { if (id_length < session_id_length()) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } - std::uint8_t tmp[olm::KEY_LENGTH * 3]; + std::uint8_t tmp[CURVE25519_KEY_LENGTH * 3]; std::uint8_t * pos = tmp; pos = olm::store_array(pos, alice_identity_key.public_key); pos = olm::store_array(pos, alice_base_key.public_key); pos = olm::store_array(pos, bob_one_time_key.public_key); - olm::sha256(tmp, sizeof(tmp), id); + _olm_crypto_sha256(tmp, sizeof(tmp), id); return session_id_length(); } bool olm::Session::matches_inbound_session( - olm::Curve25519PublicKey const * their_identity_key, + _olm_curve25519_public_key const * their_identity_key, std::uint8_t const * one_time_key_message, std::size_t message_length ) { olm::PreKeyMessageReader reader; @@ -225,20 +228,20 @@ bool olm::Session::matches_inbound_session( bool same = true; if (reader.identity_key) { same = same && 0 == std::memcmp( - reader.identity_key, alice_identity_key.public_key, olm::KEY_LENGTH + reader.identity_key, alice_identity_key.public_key, CURVE25519_KEY_LENGTH ); } if (their_identity_key) { same = same && 0 == std::memcmp( their_identity_key->public_key, alice_identity_key.public_key, - olm::KEY_LENGTH + CURVE25519_KEY_LENGTH ); } same = same && 0 == std::memcmp( - reader.base_key, alice_base_key.public_key, olm::KEY_LENGTH + reader.base_key, alice_base_key.public_key, CURVE25519_KEY_LENGTH ); same = same && 0 == std::memcmp( - reader.one_time_key, bob_one_time_key.public_key, olm::KEY_LENGTH + reader.one_time_key, bob_one_time_key.public_key, CURVE25519_KEY_LENGTH ); return same; } @@ -265,9 +268,9 @@ std::size_t olm::Session::encrypt_message_length( } return encode_one_time_key_message_length( - olm::KEY_LENGTH, - olm::KEY_LENGTH, - olm::KEY_LENGTH, + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, message_length ); } @@ -284,7 +287,7 @@ std::size_t olm::Session::encrypt( std::uint8_t * message, std::size_t message_length ) { if (message_length < encrypt_message_length(plaintext_length)) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } std::uint8_t * message_body; @@ -299,9 +302,9 @@ std::size_t olm::Session::encrypt( encode_one_time_key_message( writer, PROTOCOL_VERSION, - olm::KEY_LENGTH, - olm::KEY_LENGTH, - olm::KEY_LENGTH, + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, message_body_length, message ); @@ -319,8 +322,10 @@ std::size_t olm::Session::encrypt( if (result == std::size_t(-1)) { last_error = ratchet.last_error; - ratchet.last_error = olm::ErrorCode::SUCCESS; + ratchet.last_error = OlmErrorCode::OLM_SUCCESS; + return result; } + return result; } @@ -338,7 +343,7 @@ std::size_t olm::Session::decrypt_max_plaintext_length( olm::PreKeyMessageReader reader; decode_one_time_key_message(reader, message, message_length); if (!reader.message) { - last_error = olm::ErrorCode::BAD_MESSAGE_FORMAT; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; return std::size_t(-1); } message_body = reader.message; @@ -351,7 +356,7 @@ std::size_t olm::Session::decrypt_max_plaintext_length( if (result == std::size_t(-1)) { last_error = ratchet.last_error; - ratchet.last_error = olm::ErrorCode::SUCCESS; + ratchet.last_error = OlmErrorCode::OLM_SUCCESS; } return result; } @@ -371,7 +376,7 @@ std::size_t olm::Session::decrypt( olm::PreKeyMessageReader reader; decode_one_time_key_message(reader, message, message_length); if (!reader.message) { - last_error = olm::ErrorCode::BAD_MESSAGE_FORMAT; + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; return std::size_t(-1); } message_body = reader.message; @@ -384,14 +389,17 @@ std::size_t olm::Session::decrypt( if (result == std::size_t(-1)) { last_error = ratchet.last_error; - ratchet.last_error = olm::ErrorCode::SUCCESS; - } else { - received_message = true; + ratchet.last_error = OlmErrorCode::OLM_SUCCESS; + return result; } + + received_message = true; return result; } namespace { +// the master branch writes pickle version 1; the logging_enabled branch writes +// 0x80000001. static const std::uint32_t SESSION_PICKLE_VERSION = 1; } @@ -429,15 +437,26 @@ std::uint8_t const * olm::unpickle( ) { uint32_t pickle_version; pos = olm::unpickle(pos, end, pickle_version); - if (pickle_version != SESSION_PICKLE_VERSION) { - value.last_error = olm::ErrorCode::UNKNOWN_PICKLE_VERSION; - return end; + + bool includes_chain_index; + switch (pickle_version) { + case 1: + includes_chain_index = false; + break; + + case 0x80000001UL: + includes_chain_index = true; + break; + + default: + value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION; + return end; } + pos = olm::unpickle(pos, end, value.received_message); pos = olm::unpickle(pos, end, value.alice_identity_key); pos = olm::unpickle(pos, end, value.alice_base_key); pos = olm::unpickle(pos, end, value.bob_one_time_key); - pos = olm::unpickle(pos, end, value.ratchet); + pos = olm::unpickle(pos, end, value.ratchet, includes_chain_index); return pos; } - diff --git a/src/utility.cpp b/src/utility.cpp index bc51cff..e9688de 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -14,16 +14,16 @@ */ #include "olm/utility.hh" -#include "olm/crypto.hh" +#include "olm/crypto.h" olm::Utility::Utility( -) : last_error(olm::ErrorCode::SUCCESS) { +) : last_error(OlmErrorCode::OLM_SUCCESS) { } size_t olm::Utility::sha256_length() { - return olm::SHA256_OUTPUT_LENGTH; + return SHA256_OUTPUT_LENGTH; } @@ -32,25 +32,25 @@ size_t olm::Utility::sha256( std::uint8_t * output, std::size_t output_length ) { if (output_length < sha256_length()) { - last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL; + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; return std::size_t(-1); } - olm::sha256(input, input_length, output); - return olm::SHA256_OUTPUT_LENGTH; + _olm_crypto_sha256(input, input_length, output); + return SHA256_OUTPUT_LENGTH; } size_t olm::Utility::ed25519_verify( - Ed25519PublicKey const & key, + _olm_ed25519_public_key const & key, std::uint8_t const * message, std::size_t message_length, std::uint8_t const * signature, std::size_t signature_length ) { - if (signature_length < olm::SIGNATURE_LENGTH) { - last_error = olm::ErrorCode::BAD_MESSAGE_MAC; + if (signature_length < ED25519_SIGNATURE_LENGTH) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_MAC; return std::size_t(-1); } - if (!olm::ed25519_verify(key, message, message_length, signature)) { - last_error = olm::ErrorCode::BAD_MESSAGE_MAC; + if (!_olm_crypto_ed25519_verify(&key, message, message_length, signature)) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_MAC; return std::size_t(-1); } return std::size_t(0); diff --git a/test.py b/test.py deleted file mode 100755 index e1ef5c9..0000000 --- a/test.py +++ /dev/null @@ -1,36 +0,0 @@ -#! /usr/bin/python -# Copyright 2015 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. - -import subprocess -import glob -import os - -if not os.path.exists("build"): - os.mkdir("build") - -test_files = glob.glob("tests/test_*.cpp") -source_files = glob.glob("src/*.cpp") - -compile_args = "g++ -g -O0 -Itests/include -Iinclude -Ilib --std=c++11".split() -compile_args += source_files - -def run(args): - print " ".join(args) - subprocess.check_call(args) - -for test_file in test_files: - exe_file = "build/" + test_file[5:-4] - run(compile_args + [test_file, "-o", exe_file]) - run([exe_file]) diff --git a/tests/test_base64.cpp b/tests/test_base64.cpp index 5bae2f9..c95e3c9 100644 --- a/tests/test_base64.cpp +++ b/tests/test_base64.cpp @@ -1,10 +1,11 @@ #include "olm/base64.hh" +#include "olm/base64.h" #include "unittest.hh" int main() { { /* Base64 encode test */ -TestCase test_case("Base64 encode test"); +TestCase test_case("Base64 C++ binding encode test"); std::uint8_t input[] = "Hello World"; std::uint8_t expected_output[] = "SGVsbG8gV29ybGQ"; @@ -18,8 +19,24 @@ olm::encode_base64(input, input_length, output); assert_equals(expected_output, output, output_length); } +{ +TestCase test_case("Base64 C binding encode test"); + +std::uint8_t input[] = "Hello World"; +std::uint8_t expected_output[] = "SGVsbG8gV29ybGQ"; +std::size_t input_length = sizeof(input) - 1; + +std::size_t output_length = ::_olm_encode_base64_length(input_length); +assert_equals(std::size_t(15), output_length); + +std::uint8_t output[output_length]; +output_length = ::_olm_encode_base64(input, input_length, output); +assert_equals(std::size_t(15), output_length); +assert_equals(expected_output, output, output_length); +} + { /* Base64 decode test */ -TestCase test_case("Base64 decode test"); +TestCase test_case("Base64 C++ binding decode test"); std::uint8_t input[] = "SGVsbG8gV29ybGQ"; std::uint8_t expected_output[] = "Hello World"; @@ -33,4 +50,21 @@ olm::decode_base64(input, input_length, output); assert_equals(expected_output, output, output_length); } +{ +TestCase test_case("Base64 C binding decode test"); + +std::uint8_t input[] = "SGVsbG8gV29ybGQ"; +std::uint8_t expected_output[] = "Hello World"; +std::size_t input_length = sizeof(input) - 1; + +std::size_t output_length = ::_olm_decode_base64_length(input_length); +assert_equals(std::size_t(11), output_length); + +std::uint8_t output[output_length]; +output_length = ::_olm_decode_base64(input, input_length, output); +assert_equals(std::size_t(11), output_length); +assert_equals(expected_output, output, output_length); +} + + } diff --git a/tests/test_crypto.cpp b/tests/test_crypto.cpp index 4606c52..7dad892 100644 --- a/tests/test_crypto.cpp +++ b/tests/test_crypto.cpp @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "olm/crypto.hh" +#include "olm/crypto.h" #include "unittest.hh" @@ -58,60 +58,31 @@ std::uint8_t expected_agreement[32] = { 0x76, 0xF0, 0x9B, 0x3C, 0x1E, 0x16, 0x17, 0x42 }; -olm::Curve25519KeyPair alice_pair; -olm::curve25519_generate_key(alice_private, alice_pair); +_olm_curve25519_key_pair alice_pair; +_olm_crypto_curve25519_generate_key(alice_private, &alice_pair); -assert_equals(alice_private, alice_pair.private_key, 32); -assert_equals(alice_public, alice_pair.public_key, 32); +assert_equals(alice_private, alice_pair.private_key.private_key, 32); +assert_equals(alice_public, alice_pair.public_key.public_key, 32); -olm::Curve25519KeyPair bob_pair; -olm::curve25519_generate_key(bob_private, bob_pair); +_olm_curve25519_key_pair bob_pair; +_olm_crypto_curve25519_generate_key(bob_private, &bob_pair); -assert_equals(bob_private, bob_pair.private_key, 32); -assert_equals(bob_public, bob_pair.public_key, 32); +assert_equals(bob_private, bob_pair.private_key.private_key, 32); +assert_equals(bob_public, bob_pair.public_key.public_key, 32); -std::uint8_t actual_agreement[olm::KEY_LENGTH] = {}; +std::uint8_t actual_agreement[CURVE25519_SHARED_SECRET_LENGTH] = {}; -olm::curve25519_shared_secret(alice_pair, bob_pair, actual_agreement); +_olm_crypto_curve25519_shared_secret(&alice_pair, &bob_pair.public_key, actual_agreement); assert_equals(expected_agreement, actual_agreement, 32); -olm::curve25519_shared_secret(bob_pair, alice_pair, actual_agreement); +_olm_crypto_curve25519_shared_secret(&bob_pair, &alice_pair.public_key, actual_agreement); assert_equals(expected_agreement, actual_agreement, 32); } /* Curve25529 Test Case 1 */ -{ /* Curve25519 Signature Test Case 1 */ -TestCase test_case("Curve25519 Signature Test Case 1"); - -std::uint8_t private_key[33] = "This key is a string of 32 bytes"; -std::uint8_t message[] = "message"; -std::size_t message_length = sizeof(message) - 1; - -olm::Curve25519KeyPair key_pair; -olm::curve25519_generate_key(private_key, key_pair); - -std::uint8_t signature[64]; - -olm::curve25519_sign( - key_pair, message, message_length, signature -); - -bool result = olm::curve25519_verify( - key_pair, message, message_length, signature -); -assert_equals(true, result); - -message[0] = 'n'; -result = olm::curve25519_verify( - key_pair, message, message_length, signature -); -assert_equals(false, result); - -} /* Curve25519 Signature Test Case 1 */ - { TestCase test_case("Ed25519 Signature Test Case 1"); std::uint8_t private_key[33] = "This key is a string of 32 bytes"; @@ -119,22 +90,22 @@ std::uint8_t private_key[33] = "This key is a string of 32 bytes"; std::uint8_t message[] = "Hello, World"; std::size_t message_length = sizeof(message) - 1; -olm::Ed25519KeyPair key_pair; -olm::ed25519_generate_key(private_key, key_pair); +_olm_ed25519_key_pair key_pair; +_olm_crypto_ed25519_generate_key(private_key, &key_pair); std::uint8_t signature[64]; -olm::ed25519_sign( - key_pair, message, message_length, signature +_olm_crypto_ed25519_sign( + &key_pair, message, message_length, signature ); -bool result = olm::ed25519_verify( - key_pair, message, message_length, signature +bool result = _olm_crypto_ed25519_verify( + &key_pair.public_key, message, message_length, signature ); assert_equals(true, result); message[0] = 'n'; -result = olm::ed25519_verify( - key_pair, message, message_length, signature +result = _olm_crypto_ed25519_verify( + &key_pair.public_key, message, message_length, signature ); assert_equals(false, result); } @@ -144,8 +115,8 @@ assert_equals(false, result); TestCase test_case("AES Test Case 1"); -olm::Aes256Key key = {}; -olm::Aes256Iv iv = {}; +_olm_aes256_key key = {}; +_olm_aes256_iv iv = {}; std::uint8_t input[16] = {}; std::uint8_t expected[32] = { @@ -155,16 +126,16 @@ std::uint8_t expected[32] = { 0x4B, 0xAE, 0xDF, 0xFC, 0x3D, 0x21, 0x4C, 0x38 }; -std::size_t length = olm::aes_encrypt_cbc_length(sizeof(input)); +std::size_t length = _olm_crypto_aes_encrypt_cbc_length(sizeof(input)); assert_equals(std::size_t(32), length); std::uint8_t actual[32] = {}; -olm::aes_encrypt_cbc(key, iv, input, sizeof(input), actual); +_olm_crypto_aes_encrypt_cbc(&key, &iv, input, sizeof(input), actual); assert_equals(expected, actual, 32); -length = olm::aes_decrypt_cbc(key, iv, expected, sizeof(expected), actual); +length = _olm_crypto_aes_decrypt_cbc(&key, &iv, expected, sizeof(expected), actual); assert_equals(std::size_t(16), length); assert_equals(input, actual, length); @@ -186,7 +157,7 @@ std::uint8_t expected[32] = { std::uint8_t actual[32]; -olm::sha256(input, sizeof(input), actual); +_olm_crypto_sha256(input, sizeof(input), actual); assert_equals(expected, actual, 32); @@ -207,7 +178,7 @@ std::uint8_t expected[32] = { std::uint8_t actual[32]; -olm::hmac_sha256(input, sizeof(input), input, sizeof(input), actual); +_olm_crypto_hmac_sha256(input, sizeof(input), input, sizeof(input), actual); assert_equals(expected, actual, 32); @@ -242,7 +213,7 @@ std::uint8_t hmac_expected_output[32] = { std::uint8_t hmac_actual_output[32] = {}; -olm::hmac_sha256( +_olm_crypto_hmac_sha256( salt, sizeof(salt), input, sizeof(input), hmac_actual_output @@ -261,7 +232,7 @@ std::uint8_t hkdf_expected_output[42] = { std::uint8_t hkdf_actual_output[42] = {}; -olm::hkdf_sha256( +_olm_crypto_hkdf_sha256( input, sizeof(input), salt, sizeof(salt), info, sizeof(info), diff --git a/tests/test_group_session.cpp b/tests/test_group_session.cpp new file mode 100644 index 0000000..9930927 --- /dev/null +++ b/tests/test_group_session.cpp @@ -0,0 +1,240 @@ +/* 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. + */ +#include "olm/inbound_group_session.h" +#include "olm/outbound_group_session.h" +#include "unittest.hh" + + +int main() { + +{ + TestCase test_case("Pickle outbound group session"); + + size_t size = olm_outbound_group_session_size(); + uint8_t memory[size]; + OlmOutboundGroupSession *session = olm_outbound_group_session(memory); + + size_t pickle_length = olm_pickle_outbound_group_session_length(session); + uint8_t pickle1[pickle_length]; + olm_pickle_outbound_group_session(session, + "secret_key", 10, + pickle1, pickle_length); + uint8_t pickle2[pickle_length]; + memcpy(pickle2, pickle1, pickle_length); + + uint8_t buffer2[size]; + OlmOutboundGroupSession *session2 = olm_outbound_group_session(buffer2); + size_t res = olm_unpickle_outbound_group_session(session2, + "secret_key", 10, + pickle2, pickle_length); + assert_not_equals((size_t)-1, res); + assert_equals(pickle_length, + olm_pickle_outbound_group_session_length(session2)); + olm_pickle_outbound_group_session(session2, + "secret_key", 10, + pickle2, pickle_length); + + assert_equals(pickle1, pickle2, pickle_length); +} + + +{ + TestCase test_case("Pickle inbound group session"); + + size_t size = olm_inbound_group_session_size(); + uint8_t memory[size]; + OlmInboundGroupSession *session = olm_inbound_group_session(memory); + + size_t pickle_length = olm_pickle_inbound_group_session_length(session); + uint8_t pickle1[pickle_length]; + olm_pickle_inbound_group_session(session, + "secret_key", 10, + pickle1, pickle_length); + uint8_t pickle2[pickle_length]; + memcpy(pickle2, pickle1, pickle_length); + + uint8_t buffer2[size]; + OlmInboundGroupSession *session2 = olm_inbound_group_session(buffer2); + size_t res = olm_unpickle_inbound_group_session(session2, + "secret_key", 10, + pickle2, pickle_length); + assert_not_equals((size_t)-1, res); + assert_equals(pickle_length, + olm_pickle_inbound_group_session_length(session2)); + olm_pickle_inbound_group_session(session2, + "secret_key", 10, + pickle2, pickle_length); + + assert_equals(pickle1, pickle2, pickle_length); +} + +{ + TestCase test_case("Group message send/receive"); + + uint8_t random_bytes[] = + "0123456789ABDEF0123456789ABCDEF" + "0123456789ABDEF0123456789ABCDEF" + "0123456789ABDEF0123456789ABCDEF" + "0123456789ABDEF0123456789ABCDEF" + "0123456789ABDEF0123456789ABCDEF" + "0123456789ABDEF0123456789ABCDEF"; + + + /* build the outbound session */ + size_t size = olm_outbound_group_session_size(); + uint8_t memory[size]; + OlmOutboundGroupSession *session = olm_outbound_group_session(memory); + + assert_equals((size_t)160, + olm_init_outbound_group_session_random_length(session)); + + size_t res = olm_init_outbound_group_session( + session, random_bytes, sizeof(random_bytes)); + assert_equals((size_t)0, res); + + assert_equals(0U, olm_outbound_group_session_message_index(session)); + size_t session_key_len = olm_outbound_group_session_key_length(session); + uint8_t session_key[session_key_len]; + olm_outbound_group_session_key(session, session_key, session_key_len); + + /* encode the message */ + uint8_t plaintext[] = "Message"; + size_t plaintext_length = sizeof(plaintext) - 1; + + size_t msglen = olm_group_encrypt_message_length( + session, plaintext_length); + + uint8_t msg[msglen]; + res = olm_group_encrypt(session, plaintext, plaintext_length, + msg, msglen); + assert_equals(msglen, res); + assert_equals(1U, olm_outbound_group_session_message_index(session)); + + + /* build the inbound session */ + size = olm_inbound_group_session_size(); + uint8_t inbound_session_memory[size]; + OlmInboundGroupSession *inbound_session = + olm_inbound_group_session(inbound_session_memory); + + res = olm_init_inbound_group_session( + inbound_session, session_key, session_key_len); + assert_equals((size_t)0, res); + + + /* Check the session ids */ + + size_t out_session_id_len = olm_outbound_group_session_id_length(session); + uint8_t out_session_id[out_session_id_len]; + assert_equals(out_session_id_len, olm_outbound_group_session_id( + session, out_session_id, out_session_id_len + )); + + size_t in_session_id_len = olm_inbound_group_session_id_length( + inbound_session + ); + uint8_t in_session_id[in_session_id_len]; + assert_equals(in_session_id_len, olm_inbound_group_session_id( + inbound_session, in_session_id, in_session_id_len + )); + + assert_equals(in_session_id_len, out_session_id_len); + assert_equals(out_session_id, in_session_id, in_session_id_len); + + /* decode the message */ + + /* olm_group_decrypt_max_plaintext_length destroys the input so we have to + copy it. */ + uint8_t msgcopy[msglen]; + memcpy(msgcopy, msg, msglen); + size = olm_group_decrypt_max_plaintext_length(inbound_session, msgcopy, msglen); + uint8_t plaintext_buf[size]; + res = olm_group_decrypt(inbound_session, msg, msglen, + plaintext_buf, size); + assert_equals(plaintext_length, res); + assert_equals(plaintext, plaintext_buf, res); +} + +{ + TestCase test_case("Invalid signature group message"); + + uint8_t plaintext[] = "Message"; + size_t plaintext_length = sizeof(plaintext) - 1; + + uint8_t session_key[] = + "AgAAAAAwMTIzNDU2Nzg5QUJERUYwMTIzNDU2Nzg5QUJDREVGMDEyMzQ1Njc4OUFCREVGM" + "DEyMzQ1Njc4OUFCQ0RFRjAxMjM0NTY3ODlBQkRFRjAxMjM0NTY3ODlBQkNERUYwMTIzND" + "U2Nzg5QUJERUYwMTIzNDU2Nzg5QUJDREVGMDEyMztqJ7zOtqQtYqOo0CpvDXNlMhV3HeJ" + "DpjrASKGLWdop4lx1cSN3Xv1TgfLPW8rhGiW+hHiMxd36nRuxscNv9k4oJA/KP+o0mi1w" + "v44StrEJ1wwx9WZHBUIWkQbaBSuBDw"; + + uint8_t message[] = + "AwgAEhAcbh6UpbByoyZxufQ+h2B+8XHMjhR69G8nP4pNZGl/3QMgrzCZPmP+F2aPLyKPz" + "xRPBMUkeXRJ6Iqm5NeOdx2eERgTW7P20CM+lL3Xpk+ZUOOPvsSQNaAL"; + size_t msglen = sizeof(message)-1; + + /* build the inbound session */ + size_t size = olm_inbound_group_session_size(); + uint8_t inbound_session_memory[size]; + OlmInboundGroupSession *inbound_session = + olm_inbound_group_session(inbound_session_memory); + + size_t res = olm_init_inbound_group_session( + inbound_session, session_key, sizeof(session_key)-1 + ); + assert_equals((size_t)0, res); + + /* decode the message */ + + /* olm_group_decrypt_max_plaintext_length destroys the input so we have to + copy it. */ + uint8_t msgcopy[msglen]; + memcpy(msgcopy, message, msglen); + size = olm_group_decrypt_max_plaintext_length( + inbound_session, msgcopy, msglen + ); + + memcpy(msgcopy, message, msglen); + uint8_t plaintext_buf[size]; + res = olm_group_decrypt( + inbound_session, msgcopy, msglen, plaintext_buf, size + ); + assert_equals(plaintext_length, res); + assert_equals(plaintext, plaintext_buf, res); + + /* now twiddle the signature */ + message[msglen-1] = 'E'; + memcpy(msgcopy, message, msglen); + assert_equals( + size, + olm_group_decrypt_max_plaintext_length( + inbound_session, msgcopy, msglen + ) + ); + + memcpy(msgcopy, message, msglen); + res = olm_group_decrypt( + inbound_session, msgcopy, msglen, + plaintext_buf, size + ); + assert_equals((size_t)-1, res); + assert_equals( + std::string("BAD_SIGNATURE"), + std::string(olm_inbound_group_session_last_error(inbound_session)) + ); +} + + +} diff --git a/tests/test_megolm.cpp b/tests/test_megolm.cpp new file mode 100644 index 0000000..3048fa3 --- /dev/null +++ b/tests/test_megolm.cpp @@ -0,0 +1,134 @@ +/* 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. + */ +#include "olm/megolm.h" +#include "olm/memory.hh" + +#include "unittest.hh" + + +int main() { + +std::uint8_t random_bytes[] = + "0123456789ABCDEF0123456789ABCDEF" + "0123456789ABCDEF0123456789ABCDEF" + "0123456789ABCDEF0123456789ABCDEF" + "0123456789ABCDEF0123456789ABCDEF"; + +{ + TestCase test_case("Megolm::advance"); + + Megolm mr; + + megolm_init(&mr, random_bytes, 0); + // single-step advance + megolm_advance(&mr); + const std::uint8_t expected1[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0xba, 0x9c, 0xd9, 0x55, 0x74, 0x1d, 0x1c, 0x16, 0x23, 0x23, 0xec, 0x82, 0x5e, 0x7c, 0x5c, 0xe8, + 0x89, 0xbb, 0xb4, 0x23, 0xa1, 0x8f, 0x23, 0x82, 0x8f, 0xb2, 0x09, 0x0d, 0x6e, 0x2a, 0xf8, 0x6a + }; + assert_equals(1U, mr.counter); + assert_equals(expected1, megolm_get_data(&mr), MEGOLM_RATCHET_LENGTH); + + // repeat with complex advance + megolm_init(&mr, random_bytes, 0); + megolm_advance_to(&mr, 1); + assert_equals(1U, mr.counter); + assert_equals(expected1, megolm_get_data(&mr), MEGOLM_RATCHET_LENGTH); + + megolm_advance_to(&mr, 0x1000000); + const std::uint8_t expected2[] = { + 0x54, 0x02, 0x2d, 0x7d, 0xc0, 0x29, 0x8e, 0x16, 0x37, 0xe2, 0x1c, 0x97, 0x15, 0x30, 0x92, 0xf9, + 0x33, 0xc0, 0x56, 0xff, 0x74, 0xfe, 0x1b, 0x92, 0x2d, 0x97, 0x1f, 0x24, 0x82, 0xc2, 0x85, 0x9c, + 0x70, 0x04, 0xc0, 0x1e, 0xe4, 0x9b, 0xd6, 0xef, 0xe0, 0x07, 0x35, 0x25, 0xaf, 0x9b, 0x16, 0x32, + 0xc5, 0xbe, 0x72, 0x6d, 0x12, 0x34, 0x9c, 0xc5, 0xbd, 0x47, 0x2b, 0xdc, 0x2d, 0xf6, 0x54, 0x0f, + 0x31, 0x12, 0x59, 0x11, 0x94, 0xfd, 0xa6, 0x17, 0xe5, 0x68, 0xc6, 0x83, 0x10, 0x1e, 0xae, 0xcd, + 0x7e, 0xdd, 0xd6, 0xde, 0x1f, 0xbc, 0x07, 0x67, 0xae, 0x34, 0xda, 0x1a, 0x09, 0xa5, 0x4e, 0xab, + 0xba, 0x9c, 0xd9, 0x55, 0x74, 0x1d, 0x1c, 0x16, 0x23, 0x23, 0xec, 0x82, 0x5e, 0x7c, 0x5c, 0xe8, + 0x89, 0xbb, 0xb4, 0x23, 0xa1, 0x8f, 0x23, 0x82, 0x8f, 0xb2, 0x09, 0x0d, 0x6e, 0x2a, 0xf8, 0x6a, + }; + assert_equals(0x1000000U, mr.counter); + assert_equals(expected2, megolm_get_data(&mr), MEGOLM_RATCHET_LENGTH); + + megolm_advance_to(&mr, 0x1041506); + const std::uint8_t expected3[] = { + 0x54, 0x02, 0x2d, 0x7d, 0xc0, 0x29, 0x8e, 0x16, 0x37, 0xe2, 0x1c, 0x97, 0x15, 0x30, 0x92, 0xf9, + 0x33, 0xc0, 0x56, 0xff, 0x74, 0xfe, 0x1b, 0x92, 0x2d, 0x97, 0x1f, 0x24, 0x82, 0xc2, 0x85, 0x9c, + 0x55, 0x58, 0x8d, 0xf5, 0xb7, 0xa4, 0x88, 0x78, 0x42, 0x89, 0x27, 0x86, 0x81, 0x64, 0x58, 0x9f, + 0x36, 0x63, 0x44, 0x7b, 0x51, 0xed, 0xc3, 0x59, 0x5b, 0x03, 0x6c, 0xa6, 0x04, 0xc4, 0x6d, 0xcd, + 0x5c, 0x54, 0x85, 0x0b, 0xfa, 0x98, 0xa1, 0xfd, 0x79, 0xa9, 0xdf, 0x1c, 0xbe, 0x8f, 0xc5, 0x68, + 0x19, 0x37, 0xd3, 0x0c, 0x85, 0xc8, 0xc3, 0x1f, 0x7b, 0xb8, 0x28, 0x81, 0x6c, 0xf9, 0xff, 0x3b, + 0x95, 0x6c, 0xbf, 0x80, 0x7e, 0x65, 0x12, 0x6a, 0x49, 0x55, 0x8d, 0x45, 0xc8, 0x4a, 0x2e, 0x4c, + 0xd5, 0x6f, 0x03, 0xe2, 0x44, 0x16, 0xb9, 0x8e, 0x1c, 0xfd, 0x97, 0xc2, 0x06, 0xaa, 0x90, 0x7a + }; + assert_equals(0x1041506U, mr.counter); + assert_equals(expected3, megolm_get_data(&mr), MEGOLM_RATCHET_LENGTH); +} + +{ + TestCase test_case("Megolm::advance wraparound"); + + Megolm mr1, mr2; + + megolm_init(&mr1, random_bytes, 0xffffffffUL); + megolm_advance_to(&mr1, 0x1000000); + assert_equals(0x1000000U, mr1.counter); + + megolm_init(&mr2, random_bytes, 0); + megolm_advance_to(&mr2, 0x2000000); + assert_equals(0x2000000U, mr2.counter); + + assert_equals(megolm_get_data(&mr2), megolm_get_data(&mr1), MEGOLM_RATCHET_LENGTH); +} + +{ + TestCase test_case("Megolm::advance overflow by one"); + + Megolm mr1, mr2; + + megolm_init(&mr1, random_bytes, 0xffffffffUL); + megolm_advance_to(&mr1, 0x0); + assert_equals(0x0U, mr1.counter); + + megolm_init(&mr2, random_bytes, 0xffffffffUL); + megolm_advance(&mr2); + assert_equals(0x0U, mr2.counter); + + assert_equals(megolm_get_data(&mr2), megolm_get_data(&mr1), MEGOLM_RATCHET_LENGTH); +} + +{ + TestCase test_case("Megolm::advance overflow"); + + Megolm mr1, mr2; + + megolm_init(&mr1, random_bytes, 0x1UL); + megolm_advance_to(&mr1, 0x80000000UL); + megolm_advance_to(&mr1, 0x0); + assert_equals(0x0U, mr1.counter); + + megolm_init(&mr2, random_bytes, 0x1UL); + megolm_advance_to(&mr2, 0x0UL); + assert_equals(0x0U, mr2.counter); + + assert_equals(megolm_get_data(&mr2), megolm_get_data(&mr1), MEGOLM_RATCHET_LENGTH); +} + +} diff --git a/tests/test_message.cpp b/tests/test_message.cpp index ff14649..25693f5 100644 --- a/tests/test_message.cpp +++ b/tests/test_message.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015 OpenMarket Ltd +/* Copyright 2015-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. @@ -62,4 +62,51 @@ assert_equals(message2, output, 35); } /* Message encode test */ + +{ /* group message encode test */ + + TestCase test_case("Group message encode test"); + + size_t length = _olm_encode_group_message_length(200, 10, 8, 64); + size_t expected_length = 1 + (1+2) + (2+10) + 8 + 64; + assert_equals(expected_length, length); + + uint8_t output[50]; + uint8_t *ciphertext_ptr; + + _olm_encode_group_message( + 3, + 200, // counter + 10, // ciphertext length + output, + &ciphertext_ptr + ); + + uint8_t expected[] = + "\x03" + "\x08\xC8\x01" + "\x12\x0A"; + + assert_equals(expected, output, sizeof(expected)-1); + assert_equals(output+sizeof(expected)-1, ciphertext_ptr); +} /* group message encode test */ + +{ + TestCase test_case("Group message decode test"); + + struct _OlmDecodeGroupMessageResults results; + std::uint8_t message[] = + "\x03" + "\x08\xC8\x01" + "\x12\x0A" "ciphertext" + "hmacsha2" + "ed25519signature"; + + _olm_decode_group_message(message, sizeof(message)-1, 8, 16, &results); + assert_equals(std::uint8_t(3), results.version); + assert_equals(1, results.has_message_index); + assert_equals(std::uint32_t(200), results.message_index); + assert_equals(std::size_t(10), results.ciphertext_length); + assert_equals(ciphertext, results.ciphertext, 10); +} /* group message decode test */ } diff --git a/tests/test_olm.cpp b/tests/test_olm.cpp index fbc14cf..af2c9f7 100644 --- a/tests/test_olm.cpp +++ b/tests/test_olm.cpp @@ -1,4 +1,4 @@ -#include "olm/olm.hh" +#include "olm/olm.h" #include "unittest.hh" #include <cstddef> @@ -65,6 +65,33 @@ assert_equals(pickle1, pickle2, pickle_length); } + +{ + TestCase test_case("Old account unpickle test"); + + // this uses the old pickle format, which did not use enough space + // for the Ed25519 key. We should reject it. + std::uint8_t pickle[] = + "x3h9er86ygvq56pM1yesdAxZou4ResPQC9Rszk/fhEL9JY/umtZ2N/foL/SUgVXS" + "v0IxHHZTafYjDdzJU9xr8dQeBoOTGfV9E/lCqDGBnIlu7SZndqjEKXtzGyQr4sP4" + "K/A/8TOu9iK2hDFszy6xETiousHnHgh2ZGbRUh4pQx+YMm8ZdNZeRnwFGLnrWyf9" + "O5TmXua1FcU"; + + std::uint8_t account_buffer[::olm_account_size()]; + ::OlmAccount *account = ::olm_account(account_buffer); + assert_equals( + std::size_t(-1), + ::olm_unpickle_account( + account, "", 0, pickle, sizeof(pickle)-1 + ) + ); + assert_equals( + std::string("BAD_LEGACY_ACCOUNT_PICKLE"), + std::string(::olm_account_last_error(account)) + ); +} + + { /** Pickle session test */ TestCase test_case("Pickle session test"); diff --git a/tests/test_olm_decrypt.cpp b/tests/test_olm_decrypt.cpp index 2a2db98..4a1fb97 100644 --- a/tests/test_olm_decrypt.cpp +++ b/tests/test_olm_decrypt.cpp @@ -1,11 +1,16 @@ -#include "olm/olm.hh" +#include "olm/olm.h" #include "unittest.hh" -const char * test_cases[] = { - "41776f", - "7fff6f0101346d671201", - "ee776f41496f674177804177778041776f6716670a677d6f670a67c2677d", - "e9e9c9c1e9e9c9e9c9c1e9e9c9c1", +struct test_case { + const char *msghex; + const char *expected_error; +}; + +const test_case test_cases[] = { + { "41776f", "BAD_MESSAGE_FORMAT" }, + { "7fff6f0101346d671201", "BAD_MESSAGE_FORMAT" }, + { "ee776f41496f674177804177778041776f6716670a677d6f670a67c2677d", "BAD_MESSAGE_FORMAT" }, + { "e9e9c9c1e9e9c9e9c9c1e9e9c9c1", "BAD_MESSAGE_FORMAT" }, }; @@ -31,29 +36,39 @@ void decode_hex( } } -void decrypt_case(int message_type, const char * test_case) { +void decrypt_case(int message_type, const test_case * test_case) { std::uint8_t session_memory[olm_session_size()]; ::OlmSession * session = ::olm_session(session_memory); std::uint8_t pickled[strlen(session_data)]; ::memcpy(pickled, session_data, sizeof(pickled)); - ::olm_unpickle_session(session, "", 0, pickled, sizeof(pickled)); + assert_not_equals( + ::olm_error(), + ::olm_unpickle_session(session, "", 0, pickled, sizeof(pickled)) + ); - std::size_t message_length = strlen(test_case) / 2; + std::size_t message_length = strlen(test_case->msghex) / 2; std::uint8_t * message = (std::uint8_t *) ::malloc(message_length); - decode_hex(test_case, message, message_length); + decode_hex(test_case->msghex, message, message_length); size_t max_length = olm_decrypt_max_plaintext_length( session, message_type, message, message_length ); - if (max_length == std::size_t(-1)) { + if (test_case->expected_error) { + assert_equals(::olm_error(), max_length); + assert_equals( + std::string(test_case->expected_error), + std::string(::olm_session_last_error(session)) + ); free(message); return; } + assert_not_equals(::olm_error(), max_length); + uint8_t plaintext[max_length]; - decode_hex(test_case, message, message_length); + decode_hex(test_case->msghex, message, message_length); olm_decrypt( session, message_type, message, message_length, @@ -67,8 +82,8 @@ int main() { { TestCase my_test("Olm decrypt test"); -for (int i = 0; i < sizeof(test_cases)/ sizeof(const char *); ++i) { - decrypt_case(0, test_cases[i]); +for (unsigned int i = 0; i < sizeof(test_cases)/ sizeof(test_cases[0]); ++i) { + decrypt_case(0, &test_cases[i]); } } diff --git a/tests/test_olm_sha256.cpp b/tests/test_olm_sha256.cpp index fe5bf42..c6d0242 100644 --- a/tests/test_olm_sha256.cpp +++ b/tests/test_olm_sha256.cpp @@ -1,4 +1,4 @@ -#include "olm/olm.hh" +#include "olm/olm.h" #include "unittest.hh" int main() { diff --git a/tests/test_olm_signature.cpp b/tests/test_olm_signature.cpp index a7cce63..d7259de 100644 --- a/tests/test_olm_signature.cpp +++ b/tests/test_olm_signature.cpp @@ -1,4 +1,4 @@ -#include "olm/olm.hh" +#include "olm/olm.h" #include "unittest.hh" #include <cstddef> diff --git a/tests/test_olm_using_malloc.cpp b/tests/test_olm_using_malloc.cpp index 84fbbd7..fff3ea2 100644 --- a/tests/test_olm_using_malloc.cpp +++ b/tests/test_olm_using_malloc.cpp @@ -1,4 +1,4 @@ -#include "olm/olm.hh" +#include "olm/olm.h" #include "unittest.hh" #include <cstddef> diff --git a/tests/test_ratchet.cpp b/tests/test_ratchet.cpp index cbb3c52..fb60ba9 100644 --- a/tests/test_ratchet.cpp +++ b/tests/test_ratchet.cpp @@ -13,7 +13,7 @@ * limitations under the License. */ #include "olm/ratchet.hh" -#include "olm/cipher.hh" +#include "olm/cipher.h" #include "unittest.hh" @@ -28,13 +28,12 @@ olm::KdfInfo kdf_info = { ratchet_info, sizeof(ratchet_info) - 1 }; -olm::CipherAesSha256 cipher( - message_info, sizeof(message_info) - 1 -); +_olm_cipher_aes_sha_256 cipher0 = OLM_CIPHER_INIT_AES_SHA_256(message_info); +_olm_cipher *cipher = OLM_CIPHER_BASE(&cipher0); std::uint8_t random_bytes[] = "0123456789ABDEF0123456789ABCDEF"; -olm::Curve25519KeyPair alice_key; -olm::curve25519_generate_key(random_bytes, alice_key); +_olm_curve25519_key_pair alice_key; +_olm_crypto_curve25519_generate_key(random_bytes, &alice_key); std::uint8_t shared_secret[] = "A secret"; @@ -45,7 +44,7 @@ olm::Ratchet alice(kdf_info, cipher); olm::Ratchet bob(kdf_info, cipher); alice.initialise_as_alice(shared_secret, sizeof(shared_secret) - 1, alice_key); -bob.initialise_as_bob(shared_secret, sizeof(shared_secret) - 1, alice_key); +bob.initialise_as_bob(shared_secret, sizeof(shared_secret) - 1, alice_key.public_key); std::uint8_t plaintext[] = "Message"; std::size_t plaintext_length = sizeof(plaintext) - 1; @@ -114,7 +113,7 @@ olm::Ratchet alice(kdf_info, cipher); olm::Ratchet bob(kdf_info, cipher); alice.initialise_as_alice(shared_secret, sizeof(shared_secret) - 1, alice_key); -bob.initialise_as_bob(shared_secret, sizeof(shared_secret) - 1, alice_key); +bob.initialise_as_bob(shared_secret, sizeof(shared_secret) - 1, alice_key.public_key); std::uint8_t plaintext_1[] = "First Message"; std::size_t plaintext_1_length = sizeof(plaintext_1) - 1; @@ -186,7 +185,7 @@ olm::Ratchet alice(kdf_info, cipher); olm::Ratchet bob(kdf_info, cipher); alice.initialise_as_alice(shared_secret, sizeof(shared_secret) - 1, alice_key); -bob.initialise_as_bob(shared_secret, sizeof(shared_secret) - 1, alice_key); +bob.initialise_as_bob(shared_secret, sizeof(shared_secret) - 1, alice_key.public_key); std::uint8_t plaintext[] = "These 15 bytes"; assert_equals(std::size_t(15), sizeof(plaintext)); @@ -195,7 +194,7 @@ std::uint8_t random[] = "This is a random 32 byte string"; for (unsigned i = 0; i < 8; ++i) { { std::uint8_t msg[alice.encrypt_output_length(sizeof(plaintext))]; - std::uint8_t encrypt_length = alice.encrypt( + alice.encrypt( plaintext, 15, random, 32, msg, sizeof(msg) ); std::uint8_t output[bob.decrypt_max_plaintext_length(msg, sizeof(msg))]; @@ -206,7 +205,7 @@ for (unsigned i = 0; i < 8; ++i) { random[31]++; { std::uint8_t msg[bob.encrypt_output_length(sizeof(plaintext))]; - std::uint8_t encrypt_length = bob.encrypt( + bob.encrypt( plaintext, 15, random, 32, msg, sizeof(msg) ); std::uint8_t output[alice.decrypt_max_plaintext_length(msg, sizeof(msg))]; diff --git a/tests/test_session.cpp b/tests/test_session.cpp new file mode 100644 index 0000000..e2c3199 --- /dev/null +++ b/tests/test_session.cpp @@ -0,0 +1,144 @@ +#include "olm/session.hh" +#include "olm/pickle_encoding.h" + +#include "unittest.hh" + +/* decode into a buffer, which is returned */ +std::uint8_t *decode_hex( + const char * input +) { + static std::uint8_t buf[256]; + std::uint8_t *p = buf; + while (*input != '\0') { + char high = *(input++); + char low = *(input++); + if (high >= 'a') high -= 'a' - ('9' + 1); + if (low >= 'a') low -= 'a' - ('9' + 1); + uint8_t value = ((high - '0') << 4) | (low - '0'); + *p++ = value; + } + return buf; +} + +void check_session(const olm::Session &session) { + assert_equals( + decode_hex("49d640dc96b80176694af69fc4b8ca9fac49aecbd697d01fd8bee1ed2693b6c9"), + session.ratchet.root_key, 32 + ); + + assert_equals( + std::size_t(1), + session.ratchet.sender_chain.size() + ); + + assert_equals( + decode_hex("f77a03eaa9b301fa7d2a5aa6b50286906de12cc96044f526dbbcb12839ad7003"), + session.ratchet.sender_chain[0].ratchet_key.public_key.public_key, 32 + ); + + assert_equals( + decode_hex("d945c6ed4c7c277117adf11fb133a7936d287afe97c0b3ac989644b4490d4f31"), + session.ratchet.sender_chain[0].ratchet_key.private_key.private_key, 32 + ); + + assert_equals( + std::uint32_t(0), + session.ratchet.sender_chain[0].chain_key.index + ); + + assert_equals( + std::size_t(0), + session.ratchet.receiver_chains.size() + ); + + assert_equals( + std::size_t(0), + session.ratchet.skipped_message_keys.size() + ); + + assert_equals(OLM_SUCCESS, session.last_error); + assert_equals(false, session.received_message); + + assert_equals( + decode_hex("7326b58623a3f7bd8da11a1bab51f432c02a7430241b326e9fc8916a21eb257e"), + session.alice_identity_key.public_key, 32 + ); + + assert_equals( + decode_hex("0ab4b30bde20bd374ceccc72861660f0fd046f7516900796c3e5de41c598316c"), + session.alice_base_key.public_key, 32 + ); + + assert_equals( + decode_hex("585dba930b10d90d81702c715f4085d07c42b0cd2d676010bb6086c86c4cc618"), + session.bob_one_time_key.public_key, 32 + ); +} + +int main() { + +{ + TestCase test_case("V1 session pickle"); + + const uint8_t *PICKLE_KEY=(uint8_t *)"secret_key"; + uint8_t pickled[] = + "wkEpwMgiAqD7B1/Lw2cKYYDcUZVOd9QHes7ZroWxr/Rp/nWEAySgRsIu/a54YhO67rwitr" + "Lpos7tFxxK9IZ7pKB1qrR1coVWIt78V9lp9WgmBAvxHBSY+tu1lkL/JjLi963/yFdPancZ" + "+WHMVfaKlV3gWGpo7EfNK6qAOxI1Ea/eCsE2sYrsHEDvLLGlKAA9E56rmmoe2w6TKzsQjs" + "ZM2/XT2eJ82EgMO9pL02iLElXWmGNv72Ut7DouR0pQIT50HIEEKcFxYcoTb3WCfJD76Coe" + "sE4kx+TA6d45Xu1bwQNNkTGF+nCCu/GmKY+sECXbz9U6WhxG0YdF9Z4T8YkWYAgpKNS0FW" + "RV"; + size_t pickle_len = _olm_enc_input( + PICKLE_KEY, strlen((char *)PICKLE_KEY), + pickled, strlen((char *)pickled), NULL + ); + + olm::Session session; + const uint8_t *unpickle_res = olm::unpickle(pickled, pickled+sizeof(pickled), session); + assert_equals( + pickle_len, (size_t)(unpickle_res - pickled) + ); + + check_session(session); + +#if 0 + size_t rawlen = olm::pickle_length(session); + uint8_t *r1 = _olm_enc_output_pos(pickled, rawlen); + olm::pickle(r1, session); + _olm_enc_output( + PICKLE_KEY, strlen((char *)PICKLE_KEY), + pickled, rawlen); + printf("%s\n", pickled); +#endif +} + +{ + TestCase test_case("V2 session pickle"); + + const uint8_t *PICKLE_KEY=(uint8_t *)"secret_key"; + uint8_t pickled[] = + "m+DS/q34MXpw2xp50ZD0B7val1mlMpQXo0mx+VPje0weFYRRuuZQBdJgcFPEpi2MVSpA4c" + "qgqHyj2/bU7/lz+BXkEBrCFVx0BJidxXfOLDW4TNtRhLS1YHJNGP8GvTg1+dCytBTLsCdm" + "5f945Eq1U/pY3Cg96YTUufFP6EYrfRoDbAsRHc+h+wKKftQv+W44yUmRhcCemGHtpxk3UQ" + "AMCI7EBv9BvveyZMy3p9qZ3xvFK34Hef+R7gjtFycz7Nk/4UF46sT3cTmUlXz9iFW4uz2F" + "rTI1Wjym+l0DadsbSpHSUjmp9zt4qRP2UjwfZ5QNLv+cdObIfqFsiThGu/PlKigdF4SLHr" + "nG"; + + size_t pickle_len = _olm_enc_input( + PICKLE_KEY, strlen((char *)PICKLE_KEY), + pickled, strlen((char *)pickled), NULL + ); + + olm::Session session; + const uint8_t *unpickle_res = olm::unpickle(pickled, pickled+sizeof(pickled), session); + assert_equals( + pickle_len, (size_t)(unpickle_res - pickled) + ); + + check_session(session); +} + + + +return 0; +} diff --git a/tracing/graph.py b/tracing/graph.py index ac121aa..aa51b6a 100755 --- a/tracing/graph.py +++ b/tracing/graph.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#! /usr/bin/env python import sys import yaml diff --git a/version_script.ver b/version_script.ver new file mode 100644 index 0000000..3aec5f6 --- /dev/null +++ b/version_script.ver @@ -0,0 +1,9 @@ +# this is a 'version script' for the linker which tells it to only export +# symbols starting 'olm_'. + +{ + global: + olm_*; + local: + *; +}; |