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

Support for AES GCM mode #6389

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
36 changes: 36 additions & 0 deletions docs/library/ucryptolib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,39 @@ Classes
.. method:: decrypt(in_buf, [out_buf])

Like `encrypt()`, but for decryption.

.. class:: aesgcm

.. classmethod:: __init__(key)

Initialize a cipher object for Authenticated Encryption with
Additional Data (AEAD) using AES in Galois Counter Mode,
suitable for either encryption or decryption. AEAD protects
the confidentiality of the plaintext and also protects the
integrity of both plaintext and additional data. Note: This
object can be reused for both encryption and decryption
operations in any order.

Parameters are:

* *key* is an encryption/decryption key (bytes-like).

.. method:: encrypt(nonce, in_buf, out_buf=None, adata=None)

Encrypt the data in the buffer *in_buf* using the one-time
initialisation value *nonce* and append an authentication tag
based on the data in *in_buf* and optionally the additional
data in the buffer *adata*. If no *out_buf* is given the
result is returned as a newly allocated `bytes`
object. Otherwise, the result is written into mutable buffer
*out_buf*.

.. method:: decrypt(nonce, in_buf, out_buf=None, adata=None)

Decrypt and authenticate the contents of the input buffer
*in_buf* using the initialisation value *nonce* and optionally
authenticating the additional data in *adata*. If no *out_buf*
is given the result is returned as a newly allocated `bytes`
object. Otherwise, the result is written into mutable buffer
*out_buf*.

186 changes: 186 additions & 0 deletions extmod/moducryptolib.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <string.h>

#include "py/runtime.h"
#include "py/objstr.h"

// This module implements crypto ciphers API, roughly following
// https://www.python.org/dev/peps/pep-0272/ . Exact implementation
Expand Down Expand Up @@ -63,6 +64,10 @@ struct ctr_params {
#if MICROPY_SSL_MBEDTLS
#include <mbedtls/aes.h>

#if MICROPY_PY_UCRYPTOLIB_GCM
#include <mbedtls/gcm.h>
#endif

// we can't run mbedtls AES key schedule until we know whether we're used for encrypt or decrypt.
// therefore, we store the key & keysize and on the first call to encrypt/decrypt we override them
// with the mbedtls_aes_context, as they are not longer required. (this is done to save space)
Expand Down Expand Up @@ -356,9 +361,190 @@ STATIC const mp_obj_type_t ucryptolib_aes_type = {
.locals_dict = (void *)&ucryptolib_aes_locals_dict,
};

#if MICROPY_PY_UCRYPTOLIB_GCM

#if MICROPY_SSL_AXTLS
#warning GCM is not currentl implementing with axTLS
nickovs marked this conversation as resolved.
Show resolved Hide resolved
#endif

#if MICROPY_SSL_MBEDTLS
typedef struct _mp_obj_aesgcm_t {
mp_obj_base_t base;
int key_len;
uint8_t key[32];
} mp_obj_aesgcm_t;

STATIC mp_obj_t ucryptolib_aesgcm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 1, 1, false);

mp_obj_aesgcm_t *o = m_new_obj(mp_obj_aesgcm_t);

o->base.type = type;
mp_buffer_info_t keyinfo;
mp_get_buffer_raise(args[0], &keyinfo, MP_BUFFER_READ);
if (32 != keyinfo.len && 16 != keyinfo.len) {
mp_raise_ValueError(MP_ERROR_TEXT("key length"));
}

o->key_len = keyinfo.len;
memcpy(&o->key, keyinfo.buf, keyinfo.len);

return MP_OBJ_FROM_PTR(o);
}

STATIC mp_obj_t ucryptolib_aesgcm_enc_dec_check_args(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs,
mp_buffer_info_t *nonce, mp_buffer_info_t *input, mp_buffer_info_t *output, mp_buffer_info_t *ad, int in_out_delta) {
mp_obj_t out_obj;
mp_map_elem_t *map_element;

if (n_args != 3) {
mp_raise_ValueError(MP_ERROR_TEXT("2 positional arguments required"));
}

mp_get_buffer_raise(args[1], nonce, MP_BUFFER_READ);
mp_get_buffer_raise(args[2], input, MP_BUFFER_READ);

size_t output_len = input->len + in_out_delta;
nickovs marked this conversation as resolved.
Show resolved Hide resolved

map_element = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_out_buf), MP_MAP_LOOKUP);
if (map_element != NULL) {
out_obj = map_element->value;
mp_get_buffer_raise(out_obj, output, MP_BUFFER_WRITE);
if (output->len != output_len) {
nickovs marked this conversation as resolved.
Show resolved Hide resolved
mp_raise_ValueError(MP_ERROR_TEXT("incorrect output length"));
}
} else {
out_obj = MP_OBJ_NULL;
output->buf = m_new(char, output_len);
output->len = output_len;
}

map_element = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_adata), MP_MAP_LOOKUP);
if (map_element != NULL) {
mp_get_buffer_raise(map_element->value, ad, MP_BUFFER_READ);
} else {
ad->buf = NULL;
ad->len = 0;
}

// Maybe this should check for spurious keyword args...
nickovs marked this conversation as resolved.
Show resolved Hide resolved

return out_obj;
}

#define UCRYPTOLIB_AESGCM_TAG_LEN 16

STATIC mp_obj_t ucryptolib_aesgcm_encrypt(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) {
mp_obj_aesgcm_t *self = MP_OBJ_TO_PTR(args[0]);
mp_buffer_info_t nonce;
mp_buffer_info_t input;
mp_buffer_info_t output;
mp_obj_t out_obj;
mp_buffer_info_t ad;

out_obj = ucryptolib_aesgcm_enc_dec_check_args(n_args, args, kwargs,
&nonce, &input, &output, &ad,
UCRYPTOLIB_AESGCM_TAG_LEN);

mbedtls_gcm_context gcm;
int rc;

mbedtls_gcm_init(&gcm);

rc = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, self->key, self->key_len * 8);
nickovs marked this conversation as resolved.
Show resolved Hide resolved
nickovs marked this conversation as resolved.
Show resolved Hide resolved

int plaintext_len = input.len;
nickovs marked this conversation as resolved.
Show resolved Hide resolved

rc = mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT,
plaintext_len,
nonce.buf, nonce.len,
ad.buf, ad.len,
input.buf, output.buf,
UCRYPTOLIB_AESGCM_TAG_LEN,
((unsigned char *)output.buf) + plaintext_len);

mbedtls_gcm_free(&gcm);

if (rc != 0) {
mp_raise_OSError(rc);
}

if (out_obj == MP_OBJ_NULL) {
out_obj = mp_obj_new_str_copy(&mp_type_bytes, output.buf, output.len);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use mp_obj_new_bytes here.

And anyway - it makes no sense you have to do the allocation twice. Before you run encrypt / decrypt - create the final output buffer object, instead of allocating the output buffer twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually the problem with doing that is that since strings and bytes are immutable the creation functions compute the hash when the object is created. If I create the object before encrypt/decrypt operation then the empty buffer gets hashed (and then I write over something that is supposed to be immutable). I could find no sign of a function that let me take an existing buffer and hand over ownership to a bytes object, hence doing it this way. If you have a suggestion for a better way to do this which ends up with the correct object hash then Iid be delighted to use it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think mp_obj_new_str_from_vstr can be used for that. It will try to reuse the buffer from the vstr object. But you have to make sure the vstr is properly allocated with exact size (of len+1, + 1 is because all str/bytes in MicroPython are null-terminated)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. I've switched over to using that.

m_del(char, output.buf, output.len);
}

return out_obj;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(ucryptolib_aesgcm_encrypt_obj, 3, ucryptolib_aesgcm_encrypt);

STATIC mp_obj_t ucryptolib_aesgcm_decrypt(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) {
nickovs marked this conversation as resolved.
Show resolved Hide resolved
mp_obj_aesgcm_t *self = MP_OBJ_TO_PTR(args[0]);
mp_buffer_info_t nonce;
mp_buffer_info_t input;
mp_buffer_info_t output;
mp_obj_t out_obj;
mp_buffer_info_t ad;

out_obj = ucryptolib_aesgcm_enc_dec_check_args(n_args, args, kwargs,
&nonce, &input, &output, &ad,
-UCRYPTOLIB_AESGCM_TAG_LEN);

mbedtls_gcm_context gcm;
mbedtls_gcm_init(&gcm);
mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, self->key, self->key_len * 8);

int plaintext_len = input.len - UCRYPTOLIB_AESGCM_TAG_LEN;
nickovs marked this conversation as resolved.
Show resolved Hide resolved

int rc = mbedtls_gcm_auth_decrypt(&gcm, plaintext_len,
nonce.buf, nonce.len,
ad.buf, ad.len,
((const unsigned char *)input.buf) + plaintext_len,
UCRYPTOLIB_AESGCM_TAG_LEN,
input.buf, output.buf);

mbedtls_gcm_free(&gcm);

if (rc == MBEDTLS_ERR_GCM_AUTH_FAILED) {
mp_raise_ValueError(MP_ERROR_TEXT("Authentication failed"));
}

if (rc != 0) {
mp_raise_OSError(rc);
nickovs marked this conversation as resolved.
Show resolved Hide resolved
}

if (out_obj == MP_OBJ_NULL) {
out_obj = mp_obj_new_bytes(output.buf, output.len);
m_del(char, output.buf, output.len);
}

return out_obj;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(ucryptolib_aesgcm_decrypt_obj, 3, ucryptolib_aesgcm_decrypt);

STATIC const mp_rom_map_elem_t ucryptolib_aesgcm_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_encrypt), MP_ROM_PTR(&ucryptolib_aesgcm_encrypt_obj) },
{ MP_ROM_QSTR(MP_QSTR_decrypt), MP_ROM_PTR(&ucryptolib_aesgcm_decrypt_obj) },
};
STATIC MP_DEFINE_CONST_DICT(ucryptolib_aesgcm_locals_dict, ucryptolib_aesgcm_locals_dict_table);

STATIC const mp_obj_type_t ucryptolib_aesgcm_type = {
{ &mp_type_type },
.name = MP_QSTR_aesgcm,
.make_new = ucryptolib_aesgcm_make_new,
.locals_dict = (void *)&ucryptolib_aesgcm_locals_dict,
};

#endif // MICROPY_SSL_MBEDTLS
#endif


STATIC const mp_rom_map_elem_t mp_module_ucryptolib_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ucryptolib) },
{ MP_ROM_QSTR(MP_QSTR_aes), MP_ROM_PTR(&ucryptolib_aes_type) },
#if MICROPY_PY_UCRYPTOLIB_GCM && MICROPY_SSL_MBEDTLS
{ MP_ROM_QSTR(MP_QSTR_aesgcm), MP_ROM_PTR(&ucryptolib_aesgcm_type) },
#endif
#if MICROPY_PY_UCRYPTOLIB_CONSTS
{ MP_ROM_QSTR(MP_QSTR_MODE_ECB), MP_ROM_INT(UCRYPTOLIB_MODE_ECB) },
{ MP_ROM_QSTR(MP_QSTR_MODE_CBC), MP_ROM_INT(UCRYPTOLIB_MODE_CBC) },
Expand Down
1 change: 1 addition & 0 deletions ports/esp32/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
#define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (ets_get_cpu_frequency() * 1000000 / 200) // roughly
#define MICROPY_PY_USSL (1)
#define MICROPY_SSL_MBEDTLS (1)
#define MICROPY_PY_UCRYPTOLIB_GCM (1)
nickovs marked this conversation as resolved.
Show resolved Hide resolved
#define MICROPY_PY_USSL_FINALISER (1)
#define MICROPY_PY_UWEBSOCKET (1)
#define MICROPY_PY_WEBREPL (1)
Expand Down