Skip to content

Commit 00e826a

Browse files
rsa bindings (#511)
Co-authored-by: Michael Graeb <graebm@amazon.com>
1 parent 2ea0145 commit 00e826a

File tree

10 files changed

+639
-34
lines changed

10 files changed

+639
-34
lines changed

.gitignore

Lines changed: 193 additions & 30 deletions
Large diffs are not rendered by default.

awscrt/crypto.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# SPDX-License-Identifier: Apache-2.0.
33

44
import _awscrt
5+
from awscrt import NativeResource
6+
from typing import Union
7+
from enum import IntEnum
58

69

710
class Hash:
@@ -59,3 +62,89 @@ def update(self, to_hmac):
5962

6063
def digest(self, truncate_to=0):
6164
return _awscrt.hmac_digest(self._hmac, truncate_to)
65+
66+
67+
class RSAEncryptionAlgorithm(IntEnum):
68+
"""RSA Encryption Algorithm"""
69+
70+
PKCS1_5 = 0
71+
"""
72+
PKCSv1.5 padding
73+
"""
74+
75+
OAEP_SHA256 = 1
76+
"""
77+
OAEP padding with sha256 hash function
78+
"""
79+
80+
OAEP_SHA512 = 2
81+
"""
82+
OAEP padding with sha512 hash function
83+
"""
84+
85+
86+
class RSASignatureAlgorithm(IntEnum):
87+
"""RSA Encryption Algorithm"""
88+
89+
PKCS1_5_SHA256 = 0
90+
"""
91+
PKCSv1.5 padding with sha256 hash function
92+
"""
93+
94+
PSS_SHA256 = 1
95+
"""
96+
PSS padding with sha256 hash function
97+
"""
98+
99+
100+
class RSA(NativeResource):
101+
def __init__(self, binding):
102+
super().__init__()
103+
self._binding = binding
104+
105+
@staticmethod
106+
def new_private_key_from_pem_data(pem_data: Union[str, bytes, bytearray, memoryview]) -> 'RSA':
107+
"""
108+
Creates a new instance of private RSA key pair from pem data.
109+
Raises ValueError if pem does not have private key object.
110+
"""
111+
return RSA(binding=_awscrt.rsa_private_key_from_pem_data(pem_data))
112+
113+
@staticmethod
114+
def new_public_key_from_pem_data(pem_data: Union[str, bytes, bytearray, memoryview]) -> 'RSA':
115+
"""
116+
Creates a new instance of public RSA key pair from pem data.
117+
Raises ValueError if pem does not have public key object.
118+
"""
119+
return RSA(binding=_awscrt.rsa_public_key_from_pem_data(pem_data))
120+
121+
def encrypt(self, encryption_algorithm: RSAEncryptionAlgorithm,
122+
plaintext: Union[bytes, bytearray, memoryview]) -> bytes:
123+
"""
124+
Encrypts data using a given algorithm.
125+
"""
126+
return _awscrt.rsa_encrypt(self._binding, encryption_algorithm, plaintext)
127+
128+
def decrypt(self, encryption_algorithm: RSAEncryptionAlgorithm,
129+
ciphertext: Union[bytes, bytearray, memoryview]) -> bytes:
130+
"""
131+
Decrypts data using a given algorithm.
132+
"""
133+
return _awscrt.rsa_decrypt(self._binding, encryption_algorithm, ciphertext)
134+
135+
def sign(self, signature_algorithm: RSASignatureAlgorithm,
136+
digest: Union[bytes, bytearray, memoryview]) -> bytes:
137+
"""
138+
Signs data using a given algorithm.
139+
Note: function expects digest of the message, ex sha256
140+
"""
141+
return _awscrt.rsa_sign(self._binding, signature_algorithm, digest)
142+
143+
def verify(self, signature_algorithm: RSASignatureAlgorithm,
144+
digest: Union[bytes, bytearray, memoryview],
145+
signature: Union[bytes, bytearray, memoryview]) -> bool:
146+
"""
147+
Verifies signature against digest.
148+
Returns True if signature matches and False if not.
149+
"""
150+
return _awscrt.rsa_verify(self._binding, signature_algorithm, digest, signature)

docsrc/source/api/crypto.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
awscrt.crypto
2+
=============
3+
4+
.. automodule:: awscrt.crypto
5+
:members:

docsrc/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ API Reference
1313

1414
api/auth
1515
api/common
16+
api/crypto
1617
api/exceptions
1718
api/eventstream
1819
api/http

source/crypto.c

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77

88
#include "aws/cal/hash.h"
99
#include "aws/cal/hmac.h"
10+
#include "aws/cal/rsa.h"
11+
#include "aws/io/pem.h"
1012

1113
const char *s_capsule_name_hash = "aws_hash";
1214
const char *s_capsule_name_hmac = "aws_hmac";
15+
const char *s_capsule_name_rsa = "aws_rsa";
1316

1417
static void s_hash_destructor(PyObject *hash_capsule) {
1518
assert(PyCapsule_CheckExact(hash_capsule));
@@ -238,3 +241,233 @@ PyObject *aws_py_hmac_digest(PyObject *self, PyObject *args) {
238241

239242
return PyBytes_FromStringAndSize((const char *)output, digest_buf.len);
240243
}
244+
245+
static void s_rsa_destructor(PyObject *rsa_capsule) {
246+
struct aws_rsa_key_pair *key_pair = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
247+
assert(key_pair);
248+
249+
aws_rsa_key_pair_release(key_pair);
250+
}
251+
252+
struct aws_pem_object *s_find_pem_object(struct aws_array_list *pem_list, enum aws_pem_object_type pem_type) {
253+
for (size_t i = 0; i < aws_array_list_length(pem_list); ++i) {
254+
struct aws_pem_object *pem_object = NULL;
255+
if (aws_array_list_get_at_ptr(pem_list, (void **)&pem_object, 0)) {
256+
return NULL;
257+
}
258+
259+
if (pem_object->type == pem_type) {
260+
return pem_object;
261+
}
262+
}
263+
264+
return NULL;
265+
}
266+
267+
PyObject *aws_py_rsa_private_key_from_pem_data(PyObject *self, PyObject *args) {
268+
(void)self;
269+
270+
struct aws_byte_cursor pem_data_cur;
271+
if (!PyArg_ParseTuple(args, "s#", &pem_data_cur.ptr, &pem_data_cur.len)) {
272+
return NULL;
273+
}
274+
275+
PyObject *capsule = NULL;
276+
struct aws_allocator *allocator = aws_py_get_allocator();
277+
struct aws_array_list pem_list;
278+
if (aws_pem_objects_init_from_file_contents(&pem_list, allocator, pem_data_cur)) {
279+
return PyErr_AwsLastError();
280+
}
281+
282+
/* From hereon, we need to clean up if errors occur */
283+
284+
struct aws_pem_object *found_pem_object = s_find_pem_object(&pem_list, AWS_PEM_TYPE_PRIVATE_RSA_PKCS1);
285+
286+
if (found_pem_object == NULL) {
287+
PyErr_SetString(PyExc_ValueError, "RSA private key not found in PEM.");
288+
goto on_done;
289+
}
290+
291+
struct aws_rsa_key_pair *key_pair =
292+
aws_rsa_key_pair_new_from_private_key_pkcs1(allocator, aws_byte_cursor_from_buf(&found_pem_object->data));
293+
294+
if (key_pair == NULL) {
295+
PyErr_AwsLastError();
296+
goto on_done;
297+
}
298+
299+
capsule = PyCapsule_New(key_pair, s_capsule_name_rsa, s_rsa_destructor);
300+
301+
if (capsule == NULL) {
302+
aws_rsa_key_pair_release(key_pair);
303+
}
304+
305+
on_done:
306+
aws_pem_objects_clean_up(&pem_list);
307+
return capsule;
308+
}
309+
310+
PyObject *aws_py_rsa_public_key_from_pem_data(PyObject *self, PyObject *args) {
311+
(void)self;
312+
313+
struct aws_byte_cursor pem_data_cur;
314+
if (!PyArg_ParseTuple(args, "s#", &pem_data_cur.ptr, &pem_data_cur.len)) {
315+
return NULL;
316+
}
317+
318+
PyObject *capsule = NULL;
319+
struct aws_allocator *allocator = aws_py_get_allocator();
320+
struct aws_array_list pem_list;
321+
if (aws_pem_objects_init_from_file_contents(&pem_list, allocator, pem_data_cur)) {
322+
return PyErr_AwsLastError();
323+
}
324+
325+
/* From hereon, we need to clean up if errors occur */
326+
327+
struct aws_pem_object *found_pem_object = s_find_pem_object(&pem_list, AWS_PEM_TYPE_PUBLIC_RSA_PKCS1);
328+
329+
if (found_pem_object == NULL) {
330+
PyErr_SetString(PyExc_ValueError, "RSA public key not found in PEM.");
331+
goto on_done;
332+
}
333+
334+
struct aws_rsa_key_pair *key_pair =
335+
aws_rsa_key_pair_new_from_public_key_pkcs1(allocator, aws_byte_cursor_from_buf(&found_pem_object->data));
336+
337+
if (key_pair == NULL) {
338+
PyErr_AwsLastError();
339+
goto on_done;
340+
}
341+
342+
capsule = PyCapsule_New(key_pair, s_capsule_name_rsa, s_rsa_destructor);
343+
344+
if (capsule == NULL) {
345+
aws_rsa_key_pair_release(key_pair);
346+
}
347+
348+
on_done:
349+
aws_pem_objects_clean_up(&pem_list);
350+
return capsule;
351+
}
352+
353+
PyObject *aws_py_rsa_encrypt(PyObject *self, PyObject *args) {
354+
(void)self;
355+
356+
struct aws_allocator *allocator = aws_py_get_allocator();
357+
PyObject *rsa_capsule = NULL;
358+
int encrypt_algo = 0;
359+
struct aws_byte_cursor plaintext_cur;
360+
if (!PyArg_ParseTuple(args, "Ois#", &rsa_capsule, &encrypt_algo, &plaintext_cur.ptr, &plaintext_cur.len)) {
361+
return NULL;
362+
}
363+
364+
struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
365+
if (rsa == NULL) {
366+
return NULL;
367+
}
368+
369+
struct aws_byte_buf result_buf;
370+
aws_byte_buf_init(&result_buf, allocator, aws_rsa_key_pair_block_length(rsa));
371+
372+
if (aws_rsa_key_pair_encrypt(rsa, encrypt_algo, plaintext_cur, &result_buf)) {
373+
aws_byte_buf_clean_up_secure(&result_buf);
374+
return PyErr_AwsLastError();
375+
}
376+
377+
PyObject *ret = PyBytes_FromStringAndSize((const char *)result_buf.buffer, result_buf.len);
378+
aws_byte_buf_clean_up_secure(&result_buf);
379+
return ret;
380+
}
381+
382+
PyObject *aws_py_rsa_decrypt(PyObject *self, PyObject *args) {
383+
(void)self;
384+
385+
struct aws_allocator *allocator = aws_py_get_allocator();
386+
PyObject *rsa_capsule = NULL;
387+
int encrypt_algo = 0;
388+
struct aws_byte_cursor ciphertext_cur;
389+
if (!PyArg_ParseTuple(args, "Oiy#", &rsa_capsule, &encrypt_algo, &ciphertext_cur.ptr, &ciphertext_cur.len)) {
390+
return NULL;
391+
}
392+
393+
struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
394+
if (rsa == NULL) {
395+
return NULL;
396+
}
397+
398+
struct aws_byte_buf result_buf;
399+
aws_byte_buf_init(&result_buf, allocator, aws_rsa_key_pair_block_length(rsa));
400+
401+
if (aws_rsa_key_pair_decrypt(rsa, encrypt_algo, ciphertext_cur, &result_buf)) {
402+
aws_byte_buf_clean_up_secure(&result_buf);
403+
return PyErr_AwsLastError();
404+
}
405+
406+
PyObject *ret = PyBytes_FromStringAndSize((const char *)result_buf.buffer, result_buf.len);
407+
aws_byte_buf_clean_up_secure(&result_buf);
408+
return ret;
409+
}
410+
411+
PyObject *aws_py_rsa_sign(PyObject *self, PyObject *args) {
412+
(void)self;
413+
414+
struct aws_allocator *allocator = aws_py_get_allocator();
415+
PyObject *rsa_capsule = NULL;
416+
int sign_algo = 0;
417+
struct aws_byte_cursor digest_cur;
418+
if (!PyArg_ParseTuple(args, "Oiy#", &rsa_capsule, &sign_algo, &digest_cur.ptr, &digest_cur.len)) {
419+
return NULL;
420+
}
421+
422+
struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
423+
if (rsa == NULL) {
424+
return NULL;
425+
}
426+
427+
struct aws_byte_buf result_buf;
428+
aws_byte_buf_init(&result_buf, allocator, aws_rsa_key_pair_signature_length(rsa));
429+
430+
if (aws_rsa_key_pair_sign_message(rsa, sign_algo, digest_cur, &result_buf)) {
431+
aws_byte_buf_clean_up_secure(&result_buf);
432+
return PyErr_AwsLastError();
433+
}
434+
435+
PyObject *ret = PyBytes_FromStringAndSize((const char *)result_buf.buffer, result_buf.len);
436+
aws_byte_buf_clean_up_secure(&result_buf);
437+
return ret;
438+
}
439+
440+
PyObject *aws_py_rsa_verify(PyObject *self, PyObject *args) {
441+
(void)self;
442+
443+
PyObject *rsa_capsule = NULL;
444+
int sign_algo = 0;
445+
struct aws_byte_cursor digest_cur;
446+
struct aws_byte_cursor signature_cur;
447+
if (!PyArg_ParseTuple(
448+
args,
449+
"Oiy#y#",
450+
&rsa_capsule,
451+
&sign_algo,
452+
&digest_cur.ptr,
453+
&digest_cur.len,
454+
&signature_cur.ptr,
455+
&signature_cur.len)) {
456+
return NULL;
457+
}
458+
459+
struct aws_rsa_key_pair *rsa = PyCapsule_GetPointer(rsa_capsule, s_capsule_name_rsa);
460+
if (rsa == NULL) {
461+
return NULL;
462+
}
463+
464+
if (aws_rsa_key_pair_verify_signature(rsa, sign_algo, digest_cur, signature_cur)) {
465+
if (aws_last_error() == AWS_ERROR_CAL_SIGNATURE_VALIDATION_FAILED) {
466+
aws_reset_error();
467+
Py_RETURN_FALSE;
468+
}
469+
return PyErr_AwsLastError();
470+
}
471+
472+
Py_RETURN_TRUE;
473+
}

source/crypto.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
extern const char *s_capsule_name_hash;
1111
/** Name string for hmac capsule. */
1212
extern const char *s_capsule_name_hmac;
13+
/** Name string for rsa capsule. */
14+
extern const char *s_capsule_name_rsa;
1315

1416
PyObject *aws_py_sha1_new(PyObject *self, PyObject *args);
1517
PyObject *aws_py_sha256_new(PyObject *self, PyObject *args);
@@ -27,4 +29,12 @@ PyObject *aws_py_sha256_compute(PyObject *self, PyObject *args);
2729
PyObject *aws_py_md5_compute(PyObject *self, PyObject *args);
2830
PyObject *aws_py_sha256_hmac_compute(PyObject *self, PyObject *args);
2931

32+
PyObject *aws_py_rsa_private_key_from_pem_data(PyObject *self, PyObject *args);
33+
PyObject *aws_py_rsa_public_key_from_pem_data(PyObject *self, PyObject *args);
34+
35+
PyObject *aws_py_rsa_encrypt(PyObject *self, PyObject *args);
36+
PyObject *aws_py_rsa_decrypt(PyObject *self, PyObject *args);
37+
PyObject *aws_py_rsa_sign(PyObject *self, PyObject *args);
38+
PyObject *aws_py_rsa_verify(PyObject *self, PyObject *args);
39+
3040
#endif /* AWS_CRT_PYTHON_CRYPTO_H */

source/io.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ PyObject *aws_py_init_logging(PyObject *self, PyObject *args);
2929
PyObject *aws_py_is_alpn_available(PyObject *self, PyObject *args);
3030

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

0 commit comments

Comments
 (0)