Commit e3d66733 authored by poljar's avatar poljar Committed by Hubert Chathi

python: Import improved python bindings.

This commit imports the python bindings from:
    https://github.com/poljar/python-olm

The bindings are imported at commit c44b145818520d69eaaa350fb95afcb846125e0f

Minor modifications were made while importing:
    - Removed travis config
    - Removed Arch Linux PKGBUILD
    - Removed the html docs, they can be rebuild by running make html in
      the docs folder
    - Slightly modified the README

The new bindings feature some improvements over the old ones:
    - Python 2 and 3 support
    - Automatic memory management
    - Automatic memory clearing before it is freed
    - Type signatures via the python typing module
    - Full test coverage
    - Properties are utilized where it makes sense (e.g. account.id)
Signed-off-by: poljar's avatarDamir Jelić <[email protected]>
parent 2fccf440
.coverage
.mypy_cache/
.ropeproject/
.pytest_cache/
packages/
python_olm.egg-info/
_libolm*
__pycache__
*.pyc
.hypothesis/
.tox/
This diff is collapsed.
include olm.h
include Makefile
include olm_build.py
PYTHON ?= python
all: olm
olm:
$(PYTHON) setup.py build
install: olm
$(PYTHON) setup.py install --skip-build -O1 --root=$(DESTDIR)
test: develop py2develop
python3 -m pytest
python2 -m pytest
python3 -m pytest --flake8 --benchmark-disable
python3 -m pytest --isort --benchmark-disable
python3 -m pytest --cov --cov-branch --benchmark-disable
clean:
-rm -r python_olm.egg-info/ dist/ __pycache__/
-rm *.so _libolm.o
-rm -r packages/
-rm -r build/
develop: _libolm.o
py2develop: _libolm.so
_libolm.so: include/olm/olm.h olm_build.py
python2 olm_build.py
-rm _libolm.c
_libolm.o: include/olm/olm.h olm_build.py
python3 olm_build.py
-rm _libolm.c
.PHONY: all olm install clean test develop
python-olm
==========
[![Travis Build Status](https://travis-ci.org/poljar/python-olm.svg?branch=master)](https://travis-ci.org/poljar/python-olm)
[![Codecov Coverage Status](https://codecov.io/gh/poljar/python-olm/branch/master/graph/badge.svg)](https://codecov.io/gh/poljar/python-olm)
Python bindings for Olm.
The specification of the Olm cryptographic ratchet which is used for peer to
peer sessions of this library can be found [here][4].
The specification of the Megolm cryptographic ratchet which is used for group
sessions of this library can be found [here][5].
An example of the implementation of the Olm and Megolm cryptographic protocol
can be found in the Matrix protocol for which the implementation guide can be
found [here][6].
The full API reference can be found [here][7].
# Accounts
Accounts create and hold the central identity of the Olm protocol, they consist of a fingerprint and identity
key pair. They also produce one time keys that are used to start peer to peer
encrypted communication channels.
## Account Creation
A new account is created with the Account class, it creates a new Olm key pair.
The public parts of the key pair are available using the identity_keys property
of the class.
```python
>>> alice = Account()
>>> alice.identity_keys
{'curve25519': '2PytGagXercwHjzQETLcMa3JOsaU2qkPIESaqoi59zE',
'ed25519': 'HHpOuFYdHwoa54GxSttz9YmaTmbuVU3js92UTUjYJgM'}
```
## One Time keys
One time keys need to be generated before people can start an encrypted peer to
peer channel to an account.
```python
>>> alice.generate_one_time_keys(1)
>>> alice.one_time_keys
{'curve25519': {'AAAAAQ': 'KiHoW6CIy905UC4V1Frmwr3VW8bTWkBL4uWtWFFllxM'}}
```
After the one time keys are published they should be marked as such so they
aren't reused.
```python
>>> alice.mark_keys_as_published()
>>> alice.one_time_keys
{'curve25519': {}}
```
## Pickling
Accounts should be stored for later reuse, storing an account is done with the
pickle method while the restoring step is done with the from_pickle class
method.
```python
>>> pickle = alice.pickle()
>>> restored = Account.from_pickle(pickle)
```
# Sessions
Sessions are used to create an encrypted peer to peer communication channel
between two accounts.
## Session Creation
```python
>>> alice = Account()
>>> bob = Account()
>>> bob.generate_one_time_keys(1)
>>> id_key = bob.identity_keys["curve25519"]
>>> one_time = list(bob.one_time_keys["curve25519"].values())[0]
>>> alice_session = OutboundSession(alice, id_key, one_time)
```
## Encryption
After an outbound session is created an encrypted message can be exchanged:
```python
>>> message = alice_session.encrypt("It's a secret to everybody")
>>> message.ciphertext
'AwogkL7RoakT9gnjcZMra+y39WXKRmnxBPEaEp6OSueIA0cSIJxGpBoP8YZ+CGweXQ10LujbXMgK88
xG/JZMQJ5ulK9ZGiC8TYrezNYr3qyIBLlecXr/9wnegvJaSFDmWDVOcf4XfyI/AwogqIZfAklRXGC5b
ZJcZxVxQGgJ8Dz4OQII8k0Dp8msUXwQACIQvagY1dO55Qvnk5PZ2GF+wdKnvj6Zxl2g'
>>> message.message_type
0
```
After the message is transfered, bob can create an InboundSession to decrypt the
message.
```python
>>> bob_session = InboundSession(bob, message)
>>> bob_session.decrypt(message)
"It's a secret to everybody"
```
## Pickling
Sessions like accounts can be stored for later use the API is the same as for
accounts.
```python
>>> pickle = session.pickle()
>>> restored = Session.from_pickle(pickle)
```
# Group Sessions
Group Sessions are used to create a one-to-many encrypted communication channel.
The group session key needs to be shared with all participants that should be able
to decrypt the group messages. Another thing to notice is that, since the group
session key is ratcheted every time a message is encrypted, the session key should
be shared before any messages are encrypted.
## Group Session Creation
Group sessions aren't bound to an account like peer-to-peer sessions so their
creation is straightforward.
```python
>>> alice_group = OutboundGroupSession()
>>> bob_inbound_group = InboundGroupSession(alice_group.session_key)
```
## Group Encryption
Group encryption is pretty simple. The important part is to share the session
key with all participants over a secure channel (e.g. peer-to-peer Olm
sessions).
```python
>>> message = alice_group.encrypt("It's a secret to everybody")
>>> bob_inbound_group.decrypt(message)
("It's a secret to everybody", 0)
```
## Pickling
Pickling works the same way as for peer-to-peer Olm sessions.
```python
>>> pickle = session.pickle()
>>> restored = InboundGroupSession.from_pickle(pickle)
```
[1]: https://git.matrix.org/git/olm/about/
[2]: https://git.matrix.org/git/olm/tree/python?id=f8c61b8f8432d0b0b38d57f513c5048fb42f22ab
[3]: https://cffi.readthedocs.io/en/latest/
[4]: https://git.matrix.org/git/olm/about/docs/olm.rst
[5]: https://git.matrix.org/git/olm/about/docs/megolm.rst
[6]: https://matrix.org/docs/guides/e2e_implementation.html
[7]: https://poljar.github.io/python-olm/html/index.html
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = olm
SOURCEDIR = .
BUILDDIR = .
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M [email protected] "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('../'))
# -- Project information -----------------------------------------------------
project = 'python-olm'
copyright = '2018, Damir Jelić'
author = 'Damir Jelić'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '2.2'
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
'sphinx.ext.napoleon',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'olmdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'olm.tex', 'olm Documentation',
'Damir Jelić', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'olm', 'olm Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'olm', 'olm Documentation',
author, 'olm', 'One line description of project.',
'Miscellaneous'),
]
# -- Extension configuration -------------------------------------------------
<meta http-equiv="refresh" content="0; url=./html/index.html" />
.. olm documentation master file, created by
sphinx-quickstart on Sun Jun 17 15:57:08 2018.
Welcome to olm's documentation!
===============================
.. toctree::
Olm API reference <olm.rst>
:maxdepth: 2
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=olm
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
olm package
===========
olm.account module
------------------
.. automodule:: olm.account
:members:
:undoc-members:
:show-inheritance:
olm.group\_session module
-------------------------
.. automodule:: olm.group_session
:members:
:undoc-members:
:show-inheritance:
olm.session module
------------------
.. automodule:: olm.session
:members:
:undoc-members:
:show-inheritance:
olm.utility module
------------------
.. automodule:: olm.utility
:members:
:undoc-members:
:show-inheritance:
This diff is collapsed.
# -*- coding: utf-8 -*-
# libolm python bindings
# Copyright © 2015-2017 OpenMarket Ltd
# Copyright © 2018 Damir Jelić <[email protected]>
"""
Olm Python bindings
~~~~~~~~~~~~~~~~~~~~~
| This package implements python bindings for the libolm C library.
| © Copyright 2015-2017 by OpenMarket Ltd
| © Copyright 2018 by Damir Jelić
"""
from .utility import ed25519_verify, OlmVerifyError
from .account import Account, OlmAccountError
from .session import (
Session,
InboundSession,
OutboundSession,
OlmSessionError,
OlmMessage,
OlmPreKeyMessage
)
from .group_session import (
InboundGroupSession,
OutboundGroupSession,
OlmGroupSessionError
)
__title__ = "python-olm"
__description__ = ("python CFFI bindings for the olm "
"cryptographic ratchet library")
__url__ = "https://github.com/poljar/python-olm"
__version__ = "0.1"
__author__ = "Damir Jelić"
__author_email__ = "[email protected]"
__license__ = "Apache 2.0"
__copyright__ = "Copyright 2018 Damir Jelić"
# -*- coding: utf-8 -*-
# libolm python bindings
# Copyright © 2015-2017 OpenMarket Ltd
# Copyright © 2018 Damir Jelić <[email protected]>
from builtins import bytes, str
from typing import AnyStr
try:
import secrets
URANDOM = secrets.token_bytes # pragma: no cover
except ImportError: # pragma: no cover
from os import urandom
URANDOM = urandom # type: ignore
def to_bytes(string):
# type: (AnyStr) -> bytes
if isinstance(string, bytes):
return string
elif isinstance(string, str):
return bytes(string, "utf-8")
raise TypeError("Invalid type {}".format(type(string)))
# The MIT License (MIT)
# Copyright (c) 2010 Benjamin Peterson <[email protected]>
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
"""Finalization with weakrefs
This is designed for avoiding __del__.
"""
from __future__ import print_function
import sys
import traceback
import weakref
__author__ = "Benjamin Peterson <[email protected]>"
class OwnerRef(weakref.ref):
"""A simple weakref.ref subclass, so attributes can be added."""
pass
def _run_finalizer(ref):
"""Internal weakref callback to run finalizers"""
del _finalize_refs[id(ref)]
finalizer = ref.finalizer
item = ref.item
try:
finalizer(item)
except Exception: # pragma: no cover
print("Exception running {}:".format(finalizer), file=sys.stderr)
traceback.print_exc()
_finalize_refs = {}
def track_for_finalization(owner, item, finalizer):
"""Register an object for finalization.
``owner`` is the the object which is responsible for ``item``.
``finalizer`` will be called with ``item`` as its only argument when
``owner`` is destroyed by the garbage collector.
"""
ref = OwnerRef(owner, _run_finalizer)
ref.item = item
ref.finalizer = finalizer
_finalize_refs[id(ref)] = ref
# -*- coding: utf-8 -*-
# libolm python bindings
# Copyright © 2015-2017 OpenMarket Ltd
# Copyright © 2018 Damir Jelić <[email protected]>
"""libolm Account module.
This module contains the account part of the Olm library. It contains a single
Account class which handles the creation of new accounts as well as the storing
and restoring of them.
Examples:
>>> acc = Account()
>>> account.identity_keys()
>>> account.generate_one_time_keys(1)
"""
import json
# pylint: disable=redefined-builtin,unused-import
from builtins import bytes, super
from typing import AnyStr, Dict, Optional, Type
from future.utils import bytes_to_native_str
# pylint: disable=no-name-in-module
from _libolm import ffi, lib # type: ignore
from ._compat import URANDOM, to_bytes
from ._finalize import track_for_finalization
# This is imported only for type checking purposes
if False:
from .session import Session # pragma: no cover
def _clear_account(account):
# type: (ffi.cdata) -> None
lib.olm_clear_account(account)
class OlmAccountError(Exception):
"""libolm Account error exception."""
class Account(object):
"""libolm Account class."""
def __new__(cls):
# type: (Type[Account]) -> Account
obj = super().__new__(cls)
obj._buf = ffi.new("char[]", lib.olm_account_size())
obj._account = lib.olm_account(obj._buf)
track_for_finalization(obj, obj._account, _clear_account)
return obj
def __init__(self):
# type: () -> None
"""Create a new Olm account.
Creates a new account and its matching identity key pair.
Raises OlmAccountError on failure. If there weren't enough random bytes
for the account creation the error message for the exception will be
NOT_ENOUGH_RANDOM.
"""
# This is needed to silence mypy not knowing the type of _account.
# There has to be a better way for this.
if False: # pragma: no cover
self._account = self._account # type: ffi.cdata
random_length = lib.olm_create_account_random_length(self._account)
random = URANDOM(random_length)
random_buffer = ffi.new("char[]", random)
self._check_error(
lib.olm_create_account(self._account, random_buffer,
random_length))
def _check_error(self, ret):
# type: (int) -> None
if ret != lib.olm_error():
return
last_error = bytes_to_native_str(
ffi.string((lib.olm_account_last_error(self._account))))
raise OlmAccountError(last_error)
def pickle(self, passphrase=""):
# type: (Optional[str]) -> bytes
"""Store an Olm account.
Stores an account as a base64 string. Encrypts the account using the
supplied passphrase. Returns a byte object containing the base64
encoded string of the pickled account. Raises OlmAccountError on
failure.
Args:
passphrase(str, optional): The passphrase to be used to encrypt
the account.
"""
byte_key = bytes(passphrase, "utf-8") if passphrase else b""
key_buffer = ffi.new("char[]", byte_key)
pickle_length = lib.olm_pickle_account_length(self._account)
pickle_buffer = ffi.new("char[]", pickle_length)
self._check_error(
lib.olm_pickle_account(self._account, key_buffer, len(byte_key),
pickle_buffer, pickle_length))
return ffi.unpack(pickle_buffer, pickle_length)
@classmethod
def from_pickle(cls, pickle, passphrase=""):
# type: (bytes, Optional[str]) -> Account
"""Load a previously stored olm account.
Loads an account from a pickled base64-encoded string and returns an
Account object. Decrypts the account using the supplied passphrase.
Raises OlmAccountError on failure. If the passphrase doesn't match the
one used to encrypt the account then the error message for the
exception will be "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded
then the error message will be "INVALID_BASE64".