From 74e9300daf0b5537749d5bbe6500281ba19d6e88 Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Mon, 8 Apr 2019 17:19:47 -0400
Subject: add python bindings for PK signing

---
 python/olm/__init__.py  |   4 +-
 python/olm/pk.py        | 108 +++++++++++++++++++++++++++++++++++++++++++++++-
 python/tests/pk_test.py |  15 ++++++-
 3 files changed, 124 insertions(+), 3 deletions(-)

diff --git a/python/olm/__init__.py b/python/olm/__init__.py
index 7b7423b..1168886 100644
--- a/python/olm/__init__.py
+++ b/python/olm/__init__.py
@@ -40,6 +40,8 @@ from .pk import (
     PkMessage,
     PkEncryption,
     PkDecryption,
+    PkSigning,
     PkEncryptionError,
-    PkDecryptionError
+    PkDecryptionError,
+    PkSigningError
 )
diff --git a/python/olm/pk.py b/python/olm/pk.py
index b67d5a4..6c91b98 100644
--- a/python/olm/pk.py
+++ b/python/olm/pk.py
@@ -17,7 +17,8 @@
 
 This module contains bindings to the PK part of the Olm library.
 It contains two classes PkDecryption and PkEncryption that are used to
-establish an encrypted communication channel using public key encryption.
+establish an encrypted communication channel using public key encryption,
+as well as a class PkSigning that is used to sign a message.
 
 Examples:
     >>> decryption = PkDecryption()
@@ -25,6 +26,10 @@ Examples:
     >>> plaintext = "It's a secret to everybody."
     >>> message = encryption.encrypt(plaintext)
     >>> decrypted_plaintext = decryption.decrypt(message)
+    >>> seed = PkSigning.generate_seed()
+    >>> signing = PkSigning(seed)
+    >>> signature = signing.sign(plaintext)
+    >>> ed25519_verify(signing.public_key, plaintext, signature)
 
 """
 
@@ -45,6 +50,10 @@ class PkDecryptionError(Exception):
     """libolm Pk decryption exception."""
 
 
+class PkSigningError(Exception):
+    """libolm Pk signing exception."""
+
+
 def _clear_pk_encryption(pk_struct):
     lib.olm_clear_pk_encryption(pk_struct)
 
@@ -344,3 +353,100 @@ class PkDecryption(object):
         lib.memset(plaintext_buffer, 0, max_plaintext_length)
 
         return bytes_to_native_str(plaintext)
+
+
+def _clear_pk_signing(pk_struct):
+    lib.olm_clear_pk_signing(pk_struct)
+
+
+class PkSigning(object):
+    """PkSigning class.
+
+    Signs messages using public key cryptography.
+
+    Attributes:
+        public_key (str): The public key of the PkSigning object, can be
+            shared and used to verify using Utility.ed25519_verify.
+
+    """
+
+    def __init__(self, seed):
+        # type: (bytes) -> None
+        """Create a new signing object.
+
+        Args:
+            seed(bytes): the seed to use as the private key for signing.  The
+                seed must have the same length as the seeds generated by
+                PkSigning.generate_seed().
+        """
+        if not seed:
+            raise ValueError("seed can't be empty")
+
+        self._buf = ffi.new("char[]", lib.olm_pk_signing_size())
+        self._pk_signing = lib.olm_pk_signing(self._buf)
+        track_for_finalization(self, self._pk_signing, _clear_pk_signing)
+
+        seed_buffer = ffi.new("char[]", seed)
+
+        pubkey_length = lib.olm_pk_signing_public_key_length()
+        pubkey_buffer = ffi.new("char[]", pubkey_length)
+
+        ret = lib.olm_pk_signing_key_from_seed(
+            self._pk_signing,
+            pubkey_buffer, pubkey_length,
+            seed_buffer, len(seed)
+        )
+
+        # zero out copies of the seed
+        lib.memset(seed_buffer, 0, len(seed))
+
+        self._check_error(ret)
+
+        self.public_key = bytes_to_native_str(
+            ffi.unpack(pubkey_buffer, pubkey_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_pk_signing_last_error(self._pk_signing)))
+
+        raise PkSigningError(last_error)
+
+    @classmethod
+    def generate_seed(cls):
+        # type: () -> bytes
+        """Generate a random seed.
+        """
+        random_length = lib.olm_pk_signing_seed_length()
+        random = URANDOM(random_length)
+
+        return random
+
+    def sign(self, message):
+        # type: (AnyStr) -> str
+        """Sign a message
+
+        Returns the signature.
+        Raises PkSigningError on failure.
+
+        Args:
+            message(str): the message to sign.
+        """
+        bytes_message = to_bytearray(message)
+
+        signature_length = lib.olm_pk_signature_length()
+        signature_buffer = ffi.new("char[]", signature_length)
+
+        ret = lib.olm_pk_sign(
+            self._pk_signing,
+            ffi.from_buffer(bytes_message), len(bytes_message),
+            signature_buffer, signature_length)
+        self._check_error(ret)
+
+        return bytes_to_native_str(
+            ffi.unpack(signature_buffer, signature_length)
+        )
diff --git a/python/tests/pk_test.py b/python/tests/pk_test.py
index f2aa147..096b6a8 100644
--- a/python/tests/pk_test.py
+++ b/python/tests/pk_test.py
@@ -1,6 +1,12 @@
 import pytest
 
-from olm import PkDecryption, PkDecryptionError, PkEncryption
+from olm import (
+    ed25519_verify,
+    PkDecryption,
+    PkDecryptionError,
+    PkEncryption,
+    PkSigning
+)
 
 
 class TestClass(object):
@@ -47,3 +53,10 @@ class TestClass(object):
 
         with pytest.raises(PkDecryptionError):
             PkDecryption.from_pickle(pickle, "Not secret")
+
+    def test_signing(self):
+        seed = PkSigning.generate_seed()
+        signing = PkSigning(seed)
+        message = "This statement is true"
+        signature = signing.sign(message)
+        ed25519_verify(signing.public_key, message, signature)
-- 
cgit v1.2.3-70-g09d2


From 107adba241dc2cd6483bddc1d10087af5abe98ae Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Fri, 12 Apr 2019 13:02:57 -0400
Subject: isort python/olm/pk.py

---
 python/olm/pk.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/python/olm/pk.py b/python/olm/pk.py
index 6c91b98..193aba5 100644
--- a/python/olm/pk.py
+++ b/python/olm/pk.py
@@ -35,11 +35,13 @@ Examples:
 
 from builtins import super
 from typing import AnyStr, Type
+
 from future.utils import bytes_to_native_str
 
 from _libolm import ffi, lib  # type: ignore
-from ._finalize import track_for_finalization
+
 from ._compat import URANDOM, to_bytearray
+from ._finalize import track_for_finalization
 
 
 class PkEncryptionError(Exception):
-- 
cgit v1.2.3-70-g09d2


From ab6e8d5086894ae73c33b124a0c04df4b0195a46 Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Fri, 12 Apr 2019 19:17:06 -0400
Subject: more isort

---
 python/tests/pk_test.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/python/tests/pk_test.py b/python/tests/pk_test.py
index 096b6a8..fe3b4b6 100644
--- a/python/tests/pk_test.py
+++ b/python/tests/pk_test.py
@@ -1,12 +1,7 @@
 import pytest
 
-from olm import (
-    ed25519_verify,
-    PkDecryption,
-    PkDecryptionError,
-    PkEncryption,
-    PkSigning
-)
+from olm import (PkDecryption, PkDecryptionError, PkEncryption, PkSigning,
+                 ed25519_verify)
 
 
 class TestClass(object):
-- 
cgit v1.2.3-70-g09d2