aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore20
-rw-r--r--CHANGELOG.rst69
-rw-r--r--Makefile242
-rw-r--r--README.rst54
-rwxr-xr-xbuild_shared_library.py36
-rw-r--r--docs/megolm.rst290
-rw-r--r--docs/olm.rst143
-rw-r--r--fuzzers/fuzz_decode_message.cpp14
-rw-r--r--fuzzers/fuzz_decrypt.cpp65
-rw-r--r--fuzzers/fuzz_group_decrypt.cpp71
-rw-r--r--fuzzers/fuzz_unpickle_account.cpp14
-rw-r--r--fuzzers/fuzz_unpickle_session.cpp14
-rw-r--r--fuzzers/include/fuzzing.hh82
-rw-r--r--include/olm/account.hh16
-rw-r--r--include/olm/base64.h77
-rw-r--r--include/olm/base64.hh6
-rw-r--r--include/olm/cipher.h138
-rw-r--r--include/olm/cipher.hh132
-rw-r--r--include/olm/crypto.h202
-rw-r--r--include/olm/crypto.hh179
-rw-r--r--include/olm/error.h65
-rw-r--r--include/olm/error.hh36
-rw-r--r--include/olm/inbound_group_session.h172
-rw-r--r--include/olm/megolm.h95
-rw-r--r--include/olm/memory.h41
-rw-r--r--include/olm/memory.hh5
-rw-r--r--include/olm/message.h93
-rw-r--r--include/olm/message.hh12
-rw-r--r--include/olm/olm.h449
-rw-r--r--include/olm/olm.hh424
-rw-r--r--include/olm/outbound_group_session.h181
-rw-r--r--include/olm/pickle.h90
-rw-r--r--include/olm/pickle.hh104
-rw-r--r--include/olm/pickle_encoding.h76
-rw-r--r--include/olm/ratchet.hh39
-rw-r--r--include/olm/session.hh18
-rw-r--r--include/olm/utility.hh10
-rw-r--r--javascript/.gitignore4
-rw-r--r--javascript/README.md19
-rwxr-xr-xjavascript/build.py72
-rw-r--r--javascript/demo/demo.css8
-rw-r--r--javascript/demo/group_demo.html61
-rw-r--r--javascript/demo/group_demo.js492
-rw-r--r--javascript/demo/one_to_one_demo.html (renamed from javascript/demo.html)3
-rw-r--r--javascript/olm_inbound_group_session.js103
-rw-r--r--javascript/olm_outbound_group_session.js110
-rw-r--r--javascript/olm_post.js72
-rw-r--r--javascript/olm_pre.js2
-rw-r--r--javascript/package.json9
-rwxr-xr-xjenkins.sh14
-rw-r--r--lib/curve25519-donna.h18
-rwxr-xr-xlib/curve25519-donna/python-src/curve25519/test/test_curve25519.py2
-rwxr-xr-xlib/curve25519-donna/python-src/curve25519/test/test_speed.py2
-rwxr-xr-xlib/curve25519-donna/setup.py2
-rw-r--r--lib/ed25519_additions.c43
-rw-r--r--python/.gitignore4
-rwxr-xr-xpython/olm.py542
-rw-r--r--python/olm/__init__.py4
-rwxr-xr-xpython/olm/__main__.py339
-rw-r--r--python/olm/_base.py19
-rw-r--r--python/olm/account.py121
-rw-r--r--python/olm/inbound_group_session.py95
-rw-r--r--python/olm/outbound_group_session.py107
-rw-r--r--python/olm/session.py192
-rwxr-xr-xpython/test_olm.sh18
-rw-r--r--src/account.cpp75
-rw-r--r--src/base64.cpp39
-rw-r--r--src/cipher.cpp115
-rw-r--r--src/crypto.cpp175
-rw-r--r--src/ed25519.c (renamed from src/libs.cpp)9
-rw-r--r--src/error.c43
-rw-r--r--src/inbound_group_session.c386
-rw-r--r--src/megolm.c150
-rw-r--r--src/memory.cpp6
-rw-r--r--src/message.cpp79
-rw-r--r--src/olm.cpp201
-rw-r--r--src/outbound_group_session.c361
-rw-r--r--src/pickle.cpp162
-rw-r--r--src/pickle_encoding.c92
-rw-r--r--src/ratchet.cpp139
-rw-r--r--src/session.cpp173
-rw-r--r--src/utility.cpp22
-rwxr-xr-xtest.py36
-rw-r--r--tests/test_base64.cpp38
-rw-r--r--tests/test_crypto.cpp87
-rw-r--r--tests/test_group_session.cpp240
-rw-r--r--tests/test_megolm.cpp134
-rw-r--r--tests/test_message.cpp49
-rw-r--r--tests/test_olm.cpp29
-rw-r--r--tests/test_olm_decrypt.cpp43
-rw-r--r--tests/test_olm_sha256.cpp2
-rw-r--r--tests/test_olm_signature.cpp2
-rw-r--r--tests/test_olm_using_malloc.cpp2
-rw-r--r--tests/test_ratchet.cpp21
-rw-r--r--tests/test_session.cpp144
-rwxr-xr-xtracing/graph.py2
-rw-r--r--version_script.ver9
97 files changed, 6965 insertions, 2351 deletions
diff --git a/.gitignore b/.gitignore
index 1a56246..067636a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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)
diff --git a/README.rst b/README.rst
index 8ce1385..21ae529 100644
--- a/README.rst
+++ b/README.rst
@@ -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:
+ *;
+};