Merge pull request #86 from matrix-org/add_python_pk_signing
add python bindings for PK signing
This commit is contained in:
commit
0d0169c839
3 changed files with 122 additions and 4 deletions
|
@ -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
|
||||||
|
|
112
python/olm/pk.py
112
python/olm/pk.py
|
@ -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)
|
||||||
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue