From e545ad7eaf55ac8b7dc7d37c046c541e35cef542 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 17 May 2016 18:55:39 +0100 Subject: Outbound group session support in the python wrappers --- python/olm/__init__.py | 1 + python/olm/__main__.py | 38 ++++++++++++++++- python/olm/outbound_group_session.py | 83 ++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 python/olm/outbound_group_session.py (limited to 'python/olm') diff --git a/python/olm/__init__.py b/python/olm/__init__.py index 5520132..8681d12 100644 --- a/python/olm/__init__.py +++ b/python/olm/__init__.py @@ -1,2 +1,3 @@ from .account import Account from .session import Session +from .outbound_group_session import OutboundGroupSession diff --git a/python/olm/__main__.py b/python/olm/__main__.py index d2b0d38..8bb2419 100755 --- a/python/olm/__main__.py +++ b/python/olm/__main__.py @@ -8,7 +8,7 @@ import yaml from . import * -if __name__ == '__main__': +def build_arg_parser(): parser = argparse.ArgumentParser() parser.add_argument("--key", help="Account encryption key", default="") commands = parser.add_subparsers() @@ -206,5 +206,41 @@ if __name__ == '__main__': 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_encrypt = commands.add_parser("group_encrypt", help="Encrypt a group message") + group_encrypt.add_argument("session_file", help="Local group session file") + group_encrypt.add_argument("plaintext_file", help="Plaintext", + type=argparse.FileType('rb'), default=sys.stdin) + group_encrypt.add_argument("message_file", help="Message", + type=argparse.FileType('wb'), default=sys.stdout) + group_encrypt.set_defaults(func=do_group_encrypt) + + 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() + with open(args.session_file, "rb") as f: + session.unpickle(args.key, f.read()) + 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) + +if __name__ == '__main__': + parser = build_arg_parser() args = parser.parse_args() args.func(args) diff --git a/python/olm/outbound_group_session.py b/python/olm/outbound_group_session.py new file mode 100644 index 0000000..6182647 --- /dev/null +++ b/python/olm/outbound_group_session.py @@ -0,0 +1,83 @@ +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) +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 +) + + +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 -- cgit v1.2.3 From 8b1514c0a653ccc3f49db70131d7d4f7524f1f9b Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 18 May 2016 17:20:06 +0100 Subject: Implement functions to get the state of outbound session We need to be able to inspect an outbound session so that we can tell our peer how to set up an inbound session. --- python/olm/outbound_group_session.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'python/olm') diff --git a/python/olm/outbound_group_session.py b/python/olm/outbound_group_session.py index 6182647..56f0962 100644 --- a/python/olm/outbound_group_session.py +++ b/python/olm/outbound_group_session.py @@ -34,12 +34,21 @@ outbound_group_session_function( 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): @@ -81,3 +90,18 @@ class OutboundGroupSession(object): 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 -- cgit v1.2.3 From 846ab858a6dd2e962d3e110147f4274416026f5a Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 19 May 2016 12:01:15 +0100 Subject: Python wrapper: support for inbound group sessions --- python/olm/__init__.py | 1 + python/olm/__main__.py | 76 +++++++++++++++++++++++++++++--- python/olm/inbound_group_session.py | 86 +++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 python/olm/inbound_group_session.py (limited to 'python/olm') diff --git a/python/olm/__init__.py b/python/olm/__init__.py index 8681d12..31b29b9 100644 --- a/python/olm/__init__.py +++ b/python/olm/__init__.py @@ -1,3 +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 index 8bb2419..3fcf24d 100755 --- a/python/olm/__main__.py +++ b/python/olm/__main__.py @@ -210,14 +210,43 @@ def build_arg_parser(): 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 group session file") - group_encrypt.add_argument("plaintext_file", help="Plaintext", - type=argparse.FileType('rb'), default=sys.stdin) - group_encrypt.add_argument("message_file", help="Message", - type=argparse.FileType('wb'), default=sys.stdout) + 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): @@ -240,6 +269,43 @@ def do_group_encrypt(args): f.write(session.pickle(args.key)) args.message_file.write(message) +def do_group_credentials(args): + session = OutboundGroupSession() + with open(args.session_file, "rb") as f: + session.unpickle(args.key, f.read()) + 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 ('message_index', '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['message_index'], credentials['session_key']) + with open(args.session_file, "wb") as f: + f.write(session.pickle(args.key)) + +def do_group_decrypt(args): + session = InboundGroupSession() + with open(args.session_file, "rb") as f: + session.unpickle(args.key, f.read()) + 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() diff --git a/python/olm/inbound_group_session.py b/python/olm/inbound_group_session.py new file mode 100644 index 0000000..6c01095 --- /dev/null +++ b/python/olm/inbound_group_session.py @@ -0,0 +1,86 @@ +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_uint32, 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 +) + +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, message_index, session_key): + key_buffer = create_string_buffer(session_key) + lib.olm_init_inbound_group_session( + self.ptr, message_index, 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] -- cgit v1.2.3