Skip to content

Commit

Permalink
Use new Secure Message API in PyThemis (#401)
Browse files Browse the repository at this point in the history
* Use new Secure Message API in PyThemis

Straightforward changes to use new C API of Secure Message. We just
need to call different functions and that's it.

Keep Python method naming the same, we'll update it later separately.

* Improve key kind verification and tests

Previous implementation allowed to (ab)use SecureMessage class in
sign/verify mode by not specifying one of the keys. It is not possible
now since we're using the new C API. Now we require both public and
private key to be specified at Secure Message construction.

Update the tests to verify the new requirement and add more error
checking to constructor to ensure that both keys are provided and
that they have correct kinds.

* Early key kind checks for sign/verify mode

Check key kinds in sign/verify mode as well and produce user-friendly
error messages.

Update the tests to expect ThemisError now if the key is None.
Previously this failed on len() method call, now we check the type
correctly.
  • Loading branch information
ilammy authored Mar 4, 2019
1 parent 9fcc423 commit 6cebec3
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 23 deletions.
90 changes: 68 additions & 22 deletions src/wrappers/themis/python/pythemis/smessage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import warnings
from ctypes import cdll, create_string_buffer, c_int, string_at, byref
from ctypes.util import find_library
from enum import IntEnum

from .exception import ThemisError, THEMIS_CODES

Expand All @@ -24,82 +25,127 @@

class SMessage(object):
def __init__(self, private_key, peer_public_key):
if not (private_key and peer_public_key):
if not private_key:
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message failed creating")
"Secure Message: missing private key")
if not peer_public_key:
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message: missing public key")
if not _private_key(private_key):
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message: invalid private key")
if not _public_key(peer_public_key):
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message: invalid public key")
self.private_key = private_key
self.peer_public_key = peer_public_key

def wrap(self, message):
encrypted_message_length = c_int(0)
res = themis.themis_secure_message_wrap(
res = themis.themis_secure_message_encrypt(
self.private_key, len(self.private_key),
self.peer_public_key, len(self.peer_public_key),
message, len(message), None, byref(encrypted_message_length))
if res != THEMIS_CODES.BUFFER_TOO_SMALL:
raise ThemisError(res, "Secure Message failed encrypting")
raise ThemisError(res, "Secure Message failed to encrypt")
encrypted_message = create_string_buffer(encrypted_message_length.value)
res = themis.themis_secure_message_wrap(
res = themis.themis_secure_message_encrypt(
self.private_key, len(self.private_key),
self.peer_public_key, len(self.peer_public_key),
message, len(message), encrypted_message,
byref(encrypted_message_length))
if res != THEMIS_CODES.SUCCESS:
raise ThemisError(res, "Secure Message failed encrypting")
raise ThemisError(res, "Secure Message failed to encrypt")

return string_at(encrypted_message, encrypted_message_length.value)

def unwrap(self, message):
plain_message_length = c_int(0)
res = themis.themis_secure_message_unwrap(
res = themis.themis_secure_message_decrypt(
self.private_key, len(self.private_key),
self.peer_public_key, len(self.peer_public_key),
message, len(message), None, byref(plain_message_length))
if res != THEMIS_CODES.BUFFER_TOO_SMALL:
raise ThemisError(res, "Secure Message failed decrypting")
raise ThemisError(res, "Secure Message failed to decrypt")
plain_message = create_string_buffer(plain_message_length.value)
res = themis.themis_secure_message_unwrap(
res = themis.themis_secure_message_decrypt(
self.private_key, len(self.private_key),
self.peer_public_key, len(self.peer_public_key),
message, len(message), plain_message, byref(plain_message_length))
if res != THEMIS_CODES.SUCCESS:
raise ThemisError(res, "Secure Message failed decrypting")
raise ThemisError(res, "Secure Message failed to decrypt")
return string_at(plain_message, plain_message_length.value)


def ssign(private_key, message):
if not private_key:
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message: missing private key")
if not _private_key(private_key):
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message: invalid private key")
encrypted_message_length = c_int(0)
res = themis.themis_secure_message_wrap(
private_key, len(private_key), None, 0, message, len(message),
res = themis.themis_secure_message_sign(
private_key, len(private_key), message, len(message),
None, byref(encrypted_message_length))
if res != THEMIS_CODES.BUFFER_TOO_SMALL:
raise ThemisError(res, "Secure Message failed singing")
raise ThemisError(res, "Secure Message failed to sign")
encrypted_message = create_string_buffer(encrypted_message_length.value)
res = themis.themis_secure_message_wrap(
private_key, len(private_key), None, 0, message, len(message),
res = themis.themis_secure_message_sign(
private_key, len(private_key), message, len(message),
encrypted_message, byref(encrypted_message_length))
if res != THEMIS_CODES.SUCCESS:
raise ThemisError(res, "Secure Message failed singing")
raise ThemisError(res, "Secure Message failed to sign")
return string_at(encrypted_message, encrypted_message_length.value)


def sverify(public_key, message):
if not public_key:
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message: missing public key")
if not _public_key(public_key):
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Message: invalid public key")
plain_message_length = c_int(0)
res = themis.themis_secure_message_unwrap(
None, 0, public_key, len(public_key), message, len(message),
res = themis.themis_secure_message_verify(
public_key, len(public_key), message, len(message),
None, byref(plain_message_length))
if res != THEMIS_CODES.BUFFER_TOO_SMALL:
raise ThemisError(res, "Secure Message failed verifying")
raise ThemisError(res, "Secure Message failed to verify")
plain_message = create_string_buffer(plain_message_length.value)
res = themis.themis_secure_message_unwrap(
None, 0, public_key, len(public_key), message, len(message),
res = themis.themis_secure_message_verify(
public_key, len(public_key), message, len(message),
plain_message, byref(plain_message_length))
if res != THEMIS_CODES.SUCCESS:
raise ThemisError(res, "Secure Message failed verifying")
raise ThemisError(res, "Secure Message failed to verify")

return string_at(plain_message, plain_message_length.value)


class THEMIS_KEY(IntEnum):
INVALID = 0
RSA_PRIVATE = 1
RSA_PUBLIC = 2
EC_PRIVATE = 3
EC_PUBLIC = 4


def _public_key(key):
res = themis.themis_is_valid_asym_key(key, len(key))
if res != THEMIS_CODES.SUCCESS:
return False
kind = themis.themis_get_asym_key_kind(key, len(key))
return kind in [THEMIS_KEY.RSA_PUBLIC, THEMIS_KEY.EC_PUBLIC]


def _private_key(key):
res = themis.themis_is_valid_asym_key(key, len(key))
if res != THEMIS_CODES.SUCCESS:
return False
kind = themis.themis_get_asym_key_kind(key, len(key))
return kind in [THEMIS_KEY.RSA_PRIVATE, THEMIS_KEY.EC_PRIVATE]


class smessage(SMessage):
def __init__(self, *args, **kwargs):
warnings.warn("smessage is deprecated in favor of SMessage.")
Expand Down
11 changes: 10 additions & 1 deletion tests/pythemis/test_smessage.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def encrypt_decrypt(self, private, public):
with self.assertRaises(ThemisError):
smessage.SMessage(private, "")

with self.assertRaises(ThemisError):
smessage.SMessage(public, private)

encryptor = smessage.SMessage(private, public)
with self.assertRaises(ThemisError):
encryptor.wrap("")
Expand All @@ -57,16 +60,22 @@ def sign_verify(self, private, public):
with self.assertRaises(ThemisError):
smessage.ssign("", "")

with self.assertRaises(TypeError):
with self.assertRaises(ThemisError):
smessage.ssign(None, self.message)

with self.assertRaises(ThemisError):
smessage.ssign(private, "")

with self.assertRaises(ThemisError):
smessage.ssign(public, "message")

encrypted_message = smessage.ssign(private, self.message)
with self.assertRaises(ThemisError):
smessage.sverify(public, "")

with self.assertRaises(ThemisError):
smessage.sverify(private, encrypted_message)

with self.assertRaises(ThemisError):
smessage.sverify(public, b"".join([b"11", encrypted_message]))

Expand Down

0 comments on commit 6cebec3

Please sign in to comment.