Skip to content

Commit

Permalink
rsa bindings (#511)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Graeb <[email protected]>
  • Loading branch information
DmitriyMusatkin and graebm authored Oct 19, 2023
1 parent 2ea0145 commit 00e826a
Show file tree
Hide file tree
Showing 10 changed files with 639 additions and 34 deletions.
223 changes: 193 additions & 30 deletions .gitignore

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions awscrt/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# SPDX-License-Identifier: Apache-2.0.

import _awscrt
from awscrt import NativeResource
from typing import Union
from enum import IntEnum


class Hash:
Expand Down Expand Up @@ -59,3 +62,89 @@ def update(self, to_hmac):

def digest(self, truncate_to=0):
return _awscrt.hmac_digest(self._hmac, truncate_to)


class RSAEncryptionAlgorithm(IntEnum):
"""RSA Encryption Algorithm"""

PKCS1_5 = 0
"""
PKCSv1.5 padding
"""

OAEP_SHA256 = 1
"""
OAEP padding with sha256 hash function
"""

OAEP_SHA512 = 2
"""
OAEP padding with sha512 hash function
"""


class RSASignatureAlgorithm(IntEnum):
"""RSA Encryption Algorithm"""

PKCS1_5_SHA256 = 0
"""
PKCSv1.5 padding with sha256 hash function
"""

PSS_SHA256 = 1
"""
PSS padding with sha256 hash function
"""


class RSA(NativeResource):
def __init__(self, binding):
super().__init__()
self._binding = binding

@staticmethod
def new_private_key_from_pem_data(pem_data: Union[str, bytes, bytearray, memoryview]) -> 'RSA':
"""
Creates a new instance of private RSA key pair from pem data.
Raises ValueError if pem does not have private key object.
"""
return RSA(binding=_awscrt.rsa_private_key_from_pem_data(pem_data))

@staticmethod
def new_public_key_from_pem_data(pem_data: Union[str, bytes, bytearray, memoryview]) -> 'RSA':
"""
Creates a new instance of public RSA key pair from pem data.
Raises ValueError if pem does not have public key object.
"""
return RSA(binding=_awscrt.rsa_public_key_from_pem_data(pem_data))

def encrypt(self, encryption_algorithm: RSAEncryptionAlgorithm,
plaintext: Union[bytes, bytearray, memoryview]) -> bytes:
"""
Encrypts data using a given algorithm.
"""
return _awscrt.rsa_encrypt(self._binding, encryption_algorithm, plaintext)

def decrypt(self, encryption_algorithm: RSAEncryptionAlgorithm,
ciphertext: Union[bytes, bytearray, memoryview]) -> bytes:
"""
Decrypts data using a given algorithm.
"""
return _awscrt.rsa_decrypt(self._binding, encryption_algorithm, ciphertext)

def sign(self, signature_algorithm: RSASignatureAlgorithm,
digest: Union[bytes, bytearray, memoryview]) -> bytes:
"""
Signs data using a given algorithm.
Note: function expects digest of the message, ex sha256
"""
return _awscrt.rsa_sign(self._binding, signature_algorithm, digest)

def verify(self, signature_algorithm: RSASignatureAlgorithm,
digest: Union[bytes, bytearray, memoryview],
signature: Union[bytes, bytearray, memoryview]) -> bool:
"""
Verifies signature against digest.
Returns True if signature matches and False if not.
"""
return _awscrt.rsa_verify(self._binding, signature_algorithm, digest, signature)
5 changes: 5 additions & 0 deletions docsrc/source/api/crypto.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
awscrt.crypto
=============

.. automodule:: awscrt.crypto
:members:
1 change: 1 addition & 0 deletions docsrc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ API Reference

api/auth
api/common
api/crypto
api/exceptions
api/eventstream
api/http
Expand Down
233 changes: 233 additions & 0 deletions source/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

#include "aws/cal/hash.h"
#include "aws/cal/hmac.h"
#include "aws/cal/rsa.h"
#include "aws/io/pem.h"

const char *s_capsule_name_hash = "aws_hash";
const char *s_capsule_name_hmac = "aws_hmac";
const char *s_capsule_name_rsa = "aws_rsa";

static void s_hash_destructor(PyObject *hash_capsule) {
assert(PyCapsule_CheckExact(hash_capsule));
Expand Down Expand Up @@ -238,3 +241,233 @@ PyObject *aws_py_hmac_digest(PyObject *self, PyObject *args) {

return PyBytes_FromStringAndSize((const char *)output, digest_buf.len);
}

static void s_rsa_destructor(PyObject *rsa_capsule) {
struct aws_rsa_key_pair *key_pair = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
assert(key_pair);

aws_rsa_key_pair_release(key_pair);
}

struct aws_pem_object *s_find_pem_object(struct aws_array_list *pem_list, enum aws_pem_object_type pem_type) {
for (size_t i = 0; i < aws_array_list_length(pem_list); ++i) {
struct aws_pem_object *pem_object = NULL;
if (aws_array_list_get_at_ptr(pem_list, (void **)&pem_object, 0)) {
return NULL;
}

if (pem_object->type == pem_type) {
return pem_object;
}
}

return NULL;
}

PyObject *aws_py_rsa_private_key_from_pem_data(PyObject *self, PyObject *args) {
(void)self;

struct aws_byte_cursor pem_data_cur;
if (!PyArg_ParseTuple(args, "s#", &pem_data_cur.ptr, &pem_data_cur.len)) {
return NULL;
}

PyObject *capsule = NULL;
struct aws_allocator *allocator = aws_py_get_allocator();
struct aws_array_list pem_list;
if (aws_pem_objects_init_from_file_contents(&pem_list, allocator, pem_data_cur)) {
return PyErr_AwsLastError();
}

/* From hereon, we need to clean up if errors occur */

struct aws_pem_object *found_pem_object = s_find_pem_object(&pem_list, AWS_PEM_TYPE_PRIVATE_RSA_PKCS1);

if (found_pem_object == NULL) {
PyErr_SetString(PyExc_ValueError, "RSA private key not found in PEM.");
goto on_done;
}

struct aws_rsa_key_pair *key_pair =
aws_rsa_key_pair_new_from_private_key_pkcs1(allocator, aws_byte_cursor_from_buf(&found_pem_object->data));

if (key_pair == NULL) {
PyErr_AwsLastError();
goto on_done;
}

capsule = PyCapsule_New(key_pair, s_capsule_name_rsa, s_rsa_destructor);

if (capsule == NULL) {
aws_rsa_key_pair_release(key_pair);
}

on_done:
aws_pem_objects_clean_up(&pem_list);
return capsule;
}

PyObject *aws_py_rsa_public_key_from_pem_data(PyObject *self, PyObject *args) {
(void)self;

struct aws_byte_cursor pem_data_cur;
if (!PyArg_ParseTuple(args, "s#", &pem_data_cur.ptr, &pem_data_cur.len)) {
return NULL;
}

PyObject *capsule = NULL;
struct aws_allocator *allocator = aws_py_get_allocator();
struct aws_array_list pem_list;
if (aws_pem_objects_init_from_file_contents(&pem_list, allocator, pem_data_cur)) {
return PyErr_AwsLastError();
}

/* From hereon, we need to clean up if errors occur */

struct aws_pem_object *found_pem_object = s_find_pem_object(&pem_list, AWS_PEM_TYPE_PUBLIC_RSA_PKCS1);

if (found_pem_object == NULL) {
PyErr_SetString(PyExc_ValueError, "RSA public key not found in PEM.");
goto on_done;
}

struct aws_rsa_key_pair *key_pair =
aws_rsa_key_pair_new_from_public_key_pkcs1(allocator, aws_byte_cursor_from_buf(&found_pem_object->data));

if (key_pair == NULL) {
PyErr_AwsLastError();
goto on_done;
}

capsule = PyCapsule_New(key_pair, s_capsule_name_rsa, s_rsa_destructor);

if (capsule == NULL) {
aws_rsa_key_pair_release(key_pair);
}

on_done:
aws_pem_objects_clean_up(&pem_list);
return capsule;
}

PyObject *aws_py_rsa_encrypt(PyObject *self, PyObject *args) {
(void)self;

struct aws_allocator *allocator = aws_py_get_allocator();
PyObject *rsa_capsule = NULL;
int encrypt_algo = 0;
struct aws_byte_cursor plaintext_cur;
if (!PyArg_ParseTuple(args, "Ois#", &rsa_capsule, &encrypt_algo, &plaintext_cur.ptr, &plaintext_cur.len)) {
return NULL;
}

struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
if (rsa == NULL) {
return NULL;
}

struct aws_byte_buf result_buf;
aws_byte_buf_init(&result_buf, allocator, aws_rsa_key_pair_block_length(rsa));

if (aws_rsa_key_pair_encrypt(rsa, encrypt_algo, plaintext_cur, &result_buf)) {
aws_byte_buf_clean_up_secure(&result_buf);
return PyErr_AwsLastError();
}

PyObject *ret = PyBytes_FromStringAndSize((const char *)result_buf.buffer, result_buf.len);
aws_byte_buf_clean_up_secure(&result_buf);
return ret;
}

PyObject *aws_py_rsa_decrypt(PyObject *self, PyObject *args) {
(void)self;

struct aws_allocator *allocator = aws_py_get_allocator();
PyObject *rsa_capsule = NULL;
int encrypt_algo = 0;
struct aws_byte_cursor ciphertext_cur;
if (!PyArg_ParseTuple(args, "Oiy#", &rsa_capsule, &encrypt_algo, &ciphertext_cur.ptr, &ciphertext_cur.len)) {
return NULL;
}

struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
if (rsa == NULL) {
return NULL;
}

struct aws_byte_buf result_buf;
aws_byte_buf_init(&result_buf, allocator, aws_rsa_key_pair_block_length(rsa));

if (aws_rsa_key_pair_decrypt(rsa, encrypt_algo, ciphertext_cur, &result_buf)) {
aws_byte_buf_clean_up_secure(&result_buf);
return PyErr_AwsLastError();
}

PyObject *ret = PyBytes_FromStringAndSize((const char *)result_buf.buffer, result_buf.len);
aws_byte_buf_clean_up_secure(&result_buf);
return ret;
}

PyObject *aws_py_rsa_sign(PyObject *self, PyObject *args) {
(void)self;

struct aws_allocator *allocator = aws_py_get_allocator();
PyObject *rsa_capsule = NULL;
int sign_algo = 0;
struct aws_byte_cursor digest_cur;
if (!PyArg_ParseTuple(args, "Oiy#", &rsa_capsule, &sign_algo, &digest_cur.ptr, &digest_cur.len)) {
return NULL;
}

struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
if (rsa == NULL) {
return NULL;
}

struct aws_byte_buf result_buf;
aws_byte_buf_init(&result_buf, allocator, aws_rsa_key_pair_signature_length(rsa));

if (aws_rsa_key_pair_sign_message(rsa, sign_algo, digest_cur, &result_buf)) {
aws_byte_buf_clean_up_secure(&result_buf);
return PyErr_AwsLastError();
}

PyObject *ret = PyBytes_FromStringAndSize((const char *)result_buf.buffer, result_buf.len);
aws_byte_buf_clean_up_secure(&result_buf);
return ret;
}

PyObject *aws_py_rsa_verify(PyObject *self, PyObject *args) {
(void)self;

PyObject *rsa_capsule = NULL;
int sign_algo = 0;
struct aws_byte_cursor digest_cur;
struct aws_byte_cursor signature_cur;
if (!PyArg_ParseTuple(
args,
"Oiy#y#",
&rsa_capsule,
&sign_algo,
&digest_cur.ptr,
&digest_cur.len,
&signature_cur.ptr,
&signature_cur.len)) {
return NULL;
}

struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
if (rsa == NULL) {
return NULL;
}

if (aws_rsa_key_pair_verify_signature(rsa, sign_algo, digest_cur, signature_cur)) {
if (aws_last_error() == AWS_ERROR_CAL_SIGNATURE_VALIDATION_FAILED) {
aws_reset_error();
Py_RETURN_FALSE;
}
return PyErr_AwsLastError();
}

Py_RETURN_TRUE;
}
10 changes: 10 additions & 0 deletions source/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
extern const char *s_capsule_name_hash;
/** Name string for hmac capsule. */
extern const char *s_capsule_name_hmac;
/** Name string for rsa capsule. */
extern const char *s_capsule_name_rsa;

PyObject *aws_py_sha1_new(PyObject *self, PyObject *args);
PyObject *aws_py_sha256_new(PyObject *self, PyObject *args);
Expand All @@ -27,4 +29,12 @@ PyObject *aws_py_sha256_compute(PyObject *self, PyObject *args);
PyObject *aws_py_md5_compute(PyObject *self, PyObject *args);
PyObject *aws_py_sha256_hmac_compute(PyObject *self, PyObject *args);

PyObject *aws_py_rsa_private_key_from_pem_data(PyObject *self, PyObject *args);
PyObject *aws_py_rsa_public_key_from_pem_data(PyObject *self, PyObject *args);

PyObject *aws_py_rsa_encrypt(PyObject *self, PyObject *args);
PyObject *aws_py_rsa_decrypt(PyObject *self, PyObject *args);
PyObject *aws_py_rsa_sign(PyObject *self, PyObject *args);
PyObject *aws_py_rsa_verify(PyObject *self, PyObject *args);

#endif /* AWS_CRT_PYTHON_CRYPTO_H */
2 changes: 1 addition & 1 deletion source/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ PyObject *aws_py_init_logging(PyObject *self, PyObject *args);
PyObject *aws_py_is_alpn_available(PyObject *self, PyObject *args);

/**
* Returns True if the input TLS Cipher Preference Enum is suupported on the current platform. False otherwise.
* Returns True if the input TLS Cipher Preference Enum is supported on the current platform. False otherwise.
*/
PyObject *aws_py_is_tls_cipher_supported(PyObject *self, PyObject *args);

Expand Down
Loading

0 comments on commit 00e826a

Please sign in to comment.