Merge pull request #86 from matrix-org/add_python_pk_signing

add python bindings for PK signing
This commit is contained in:
Hubert Chathi 2019-04-12 19:21:17 -04:00 committed by GitHub
commit 0d0169c839
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 122 additions and 4 deletions

View file

@ -40,7 +40,9 @@ from .pk import (
PkMessage, PkMessage,
PkEncryption, PkEncryption,
PkDecryption, PkDecryption,
PkSigning,
PkEncryptionError, PkEncryptionError,
PkDecryptionError PkDecryptionError,
PkSigningError
) )
from .sas import Sas, OlmSasError from .sas import Sas, OlmSasError

View file

@ -17,7 +17,8 @@
This module contains bindings to the PK part of the Olm library. This module contains bindings to the PK part of the Olm library.
It contains two classes PkDecryption and PkEncryption that are used to 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: Examples:
>>> decryption = PkDecryption() >>> decryption = PkDecryption()
@ -25,16 +26,22 @@ Examples:
>>> plaintext = "It's a secret to everybody." >>> plaintext = "It's a secret to everybody."
>>> message = encryption.encrypt(plaintext) >>> message = encryption.encrypt(plaintext)
>>> decrypted_plaintext = decryption.decrypt(message) >>> decrypted_plaintext = decryption.decrypt(message)
>>> seed = PkSigning.generate_seed()
>>> signing = PkSigning(seed)
>>> signature = signing.sign(plaintext)
>>> ed25519_verify(signing.public_key, plaintext, signature)
""" """
from builtins import super from builtins import super
from typing import AnyStr, Type from typing import AnyStr, Type
from future.utils import bytes_to_native_str from future.utils import bytes_to_native_str
from _libolm import ffi, lib # type: ignore from _libolm import ffi, lib # type: ignore
from ._finalize import track_for_finalization
from ._compat import URANDOM, to_bytearray from ._compat import URANDOM, to_bytearray
from ._finalize import track_for_finalization
class PkEncryptionError(Exception): class PkEncryptionError(Exception):
@ -45,6 +52,10 @@ class PkDecryptionError(Exception):
"""libolm Pk decryption exception.""" """libolm Pk decryption exception."""
class PkSigningError(Exception):
"""libolm Pk signing exception."""
def _clear_pk_encryption(pk_struct): def _clear_pk_encryption(pk_struct):
lib.olm_clear_pk_encryption(pk_struct) lib.olm_clear_pk_encryption(pk_struct)
@ -344,3 +355,100 @@ class PkDecryption(object):
lib.memset(plaintext_buffer, 0, max_plaintext_length) lib.memset(plaintext_buffer, 0, max_plaintext_length)
return bytes_to_native_str(plaintext) 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)
)

View file

@ -1,6 +1,7 @@
import pytest import pytest
from olm import PkDecryption, PkDecryptionError, PkEncryption from olm import (PkDecryption, PkDecryptionError, PkEncryption, PkSigning,
ed25519_verify)
class TestClass(object): class TestClass(object):
@ -47,3 +48,10 @@ class TestClass(object):
with pytest.raises(PkDecryptionError): with pytest.raises(PkDecryptionError):
PkDecryption.from_pickle(pickle, "Not secret") 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)