Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use new Secure Message API in PyThemis #401

Merged
merged 3 commits into from
Mar 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add private_key length check and error message here as well? I think it would be nice addition to this PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it makes sense to check keys for sign/verify mode as well. I'll add the checks.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar check

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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to make this function public to allow use it for apps

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and better to rename to something like validate_public_key because _public_key looks like getter function

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm... I'm not against it but I think there's a better way.

These two functions are effectively internal helpers which implement a particular check specifically for Secure Message needs. However, the user may have different needs that that. I think that for general use it would be better to export C functions themis_is_valid_asym_key and themis_get_asym_key_kind which provide a bit more information about the keys. Then the user could implement their of is_valid_public_key function the way they want.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be great. but not only is_valid_public_key, I think is_valid_private_key will be useful for users too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea overall, but let's do it later if you don't mind: #409

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

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