diff --git a/.github/workflows/build-and-test-other.yaml b/.github/workflows/build-and-test-other.yaml index c927e9f073..1857e0e97f 100644 --- a/.github/workflows/build-and-test-other.yaml +++ b/.github/workflows/build-and-test-other.yaml @@ -148,7 +148,7 @@ jobs: ${{ matrix.install_deps }} else apt update && - apt install -y file gcc g++ binutils cmake make doxygen gperf zlib1g-dev libssl-dev + apt install -y file gcc g++ binutils cmake make doxygen gperf zlib1g-dev libssl-dev libmbedtls-dev fi && file /bin/bash && uname -a && diff --git a/src/libAtomVM/otp_crypto.c b/src/libAtomVM/otp_crypto.c new file mode 100644 index 0000000000..1f55ca9535 --- /dev/null +++ b/src/libAtomVM/otp_crypto.c @@ -0,0 +1,593 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// #define ENABLE_TRACE +#include "trace.h" + +#define MAX_MD_SIZE 64 + +enum crypto_algorithm +{ + CryptoInvalidAlgorithm = 0, + CryptoMd5, + CryptoSha1, + CryptoSha224, + CryptoSha256, + CryptoSha384, + CryptoSha512 +}; + +static const AtomStringIntPair crypto_algorithm_table[] = { + { ATOM_STR("\x3", "md5"), CryptoMd5 }, + { ATOM_STR("\x3", "sha"), CryptoSha1 }, + { ATOM_STR("\x6", "sha224"), CryptoSha224 }, + { ATOM_STR("\x6", "sha256"), CryptoSha256 }, + { ATOM_STR("\x6", "sha384"), CryptoSha384 }, + { ATOM_STR("\x6", "sha512"), CryptoSha512 }, + SELECT_INT_DEFAULT(CryptoInvalidAlgorithm) +}; + +#define DEFINE_HASH_FOLD(ALGORITHM, SUFFIX) \ + static InteropFunctionResult ALGORITHM##_hash_fold_fun(term t, void *accum) \ + { \ + mbedtls_##ALGORITHM##_context *md_ctx = (mbedtls_##ALGORITHM##_context *) accum; \ + if (term_is_integer(t)) { \ + avm_int64_t tmp = term_maybe_unbox_int64(t); \ + if (tmp < 0 || tmp > 255) { \ + return InteropBadArg; \ + } \ + uint8_t val = (uint8_t) tmp; \ + if (UNLIKELY(mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1) != 0)) { \ + return InteropBadArg; \ + } \ + } else /* term_is_binary(t) */ { \ + if (UNLIKELY(mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, (uint8_t *) term_binary_data(t), term_binary_size(t)) != 0)) { \ + return InteropBadArg; \ + } \ + } \ + return InteropOk; \ + } + +#define DEFINE_HASH_FOLD_NORET(ALGORITHM, SUFFIX) \ + static InteropFunctionResult ALGORITHM##_hash_fold_fun(term t, void *accum) \ + { \ + mbedtls_##ALGORITHM##_context *md_ctx = (mbedtls_##ALGORITHM##_context *) accum; \ + if (term_is_integer(t)) { \ + avm_int64_t tmp = term_maybe_unbox_int64(t); \ + if (tmp < 0 || tmp > 255) { \ + return InteropBadArg; \ + } \ + uint8_t val = (uint8_t) tmp; \ + mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1); \ + } else /* term_is_binary(t) */ { \ + mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, (uint8_t *) term_binary_data(t), term_binary_size(t)); \ + } \ + return InteropOk; \ + } + +#define DEFINE_DO_HASH(ALGORITHM, SUFFIX) \ + static bool do_##ALGORITHM##_hash(term data, unsigned char *dst) \ + { \ + mbedtls_##ALGORITHM##_context md_ctx; \ + \ + mbedtls_##ALGORITHM##_init(&md_ctx); \ + mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx); \ + \ + InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun, NULL, (void *) &md_ctx); \ + if (UNLIKELY(result != InteropOk)) { \ + return false; \ + } \ + \ + if (UNLIKELY(mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst) != 0)) { \ + return false; \ + } \ + \ + return true; \ + } + +#define DEFINE_DO_HASH_IS_OTHER(ALGORITHM, SUFFIX, IS_OTHER) \ + static bool do_##ALGORITHM##_hash_##IS_OTHER(term data, unsigned char *dst) \ + { \ + mbedtls_##ALGORITHM##_context md_ctx; \ + \ + mbedtls_##ALGORITHM##_init(&md_ctx); \ + mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx, IS_OTHER); \ + \ + InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun, NULL, (void *) &md_ctx); \ + if (UNLIKELY(result != InteropOk)) { \ + return false; \ + } \ + \ + if (UNLIKELY(mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst) != 0)) { \ + return false; \ + } \ + \ + return true; \ + } + +#define DEFINE_DO_HASH_NORET(ALGORITHM, SUFFIX) \ + static bool do_##ALGORITHM##_hash(term data, unsigned char *dst) \ + { \ + mbedtls_##ALGORITHM##_context md_ctx; \ + \ + mbedtls_##ALGORITHM##_init(&md_ctx); \ + mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx); \ + \ + InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun, NULL, (void *) &md_ctx); \ + if (UNLIKELY(result != InteropOk)) { \ + return false; \ + } \ + \ + mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst); \ + \ + return true; \ + } + +#define DEFINE_DO_HASH_NORET_IS_OTHER(ALGORITHM, SUFFIX, IS_OTHER) \ + static bool do_##ALGORITHM##_hash_##IS_OTHER(term data, unsigned char *dst) \ + { \ + mbedtls_##ALGORITHM##_context md_ctx; \ + \ + mbedtls_##ALGORITHM##_init(&md_ctx); \ + mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx, IS_OTHER); \ + \ + InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun, NULL, (void *) &md_ctx); \ + if (UNLIKELY(result != InteropOk)) { \ + return false; \ + } \ + \ + mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst); \ + \ + return true; \ + } + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + +// 3.x API: functions return an int that represents errors + +DEFINE_HASH_FOLD(md5, ) +DEFINE_DO_HASH(md5, ) +DEFINE_HASH_FOLD(sha1, ) +DEFINE_DO_HASH(sha1, ) +DEFINE_HASH_FOLD(sha256, ) +DEFINE_DO_HASH_IS_OTHER(sha256, , true) +DEFINE_DO_HASH_IS_OTHER(sha256, , false) +DEFINE_HASH_FOLD(sha512, ) +DEFINE_DO_HASH_IS_OTHER(sha512, , true) +DEFINE_DO_HASH_IS_OTHER(sha512, , false) + +#elif MBEDTLS_VERSION_NUMBER >= 0x02070000 + +// 2.x API: functions are suffixed with _ret and return an int that represents errors + +DEFINE_HASH_FOLD(md5, _ret) +DEFINE_DO_HASH(md5, _ret) +DEFINE_HASH_FOLD(sha1, _ret) +DEFINE_DO_HASH(sha1, _ret) +DEFINE_HASH_FOLD(sha256, _ret) +DEFINE_DO_HASH_IS_OTHER(sha256, _ret, true) +DEFINE_DO_HASH_IS_OTHER(sha256, _ret, false) +DEFINE_HASH_FOLD(sha512, _ret) +DEFINE_DO_HASH_IS_OTHER(sha512, _ret, true) +DEFINE_DO_HASH_IS_OTHER(sha512, _ret, false) + +#else + +// 1.x API: functions do not return anything + +DEFINE_HASH_FOLD_NORET(md5, ) +DEFINE_DO_HASH_NORET(md5, ) +DEFINE_HASH_FOLD_NORET(sha1, ) +DEFINE_DO_HASH_NORET(sha1, ) +DEFINE_HASH_FOLD_NORET(sha256, ) +DEFINE_DO_HASH_NORET_IS_OTHER(sha256, , true) +DEFINE_DO_HASH_NORET_IS_OTHER(sha256, , false) +DEFINE_HASH_FOLD_NORET(sha512, ) +DEFINE_DO_HASH_NORET_IS_OTHER(sha512, , true) +DEFINE_DO_HASH_NORET_IS_OTHER(sha512, , false) + +#endif + +static term nif_crypto_hash(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + term type = argv[0]; + VALIDATE_VALUE(type, term_is_atom); + term data = argv[1]; + + unsigned char digest[MAX_MD_SIZE]; + size_t digest_len = 0; + + enum crypto_algorithm algo = interop_atom_term_select_int(crypto_algorithm_table, type, ctx->global); + switch (algo) { + case CryptoMd5: { + if (UNLIKELY(!do_md5_hash(data, digest))) { + RAISE_ERROR(BADARG_ATOM) + } + digest_len = 16; + break; + } + case CryptoSha1: { + if (UNLIKELY(!do_sha1_hash(data, digest))) { + RAISE_ERROR(BADARG_ATOM) + } + digest_len = 20; + break; + } + case CryptoSha224: { + if (UNLIKELY(!do_sha256_hash_true(data, digest))) { + RAISE_ERROR(BADARG_ATOM) + } + digest_len = 28; + break; + } + case CryptoSha256: { + if (UNLIKELY(!do_sha256_hash_false(data, digest))) { + RAISE_ERROR(BADARG_ATOM) + } + digest_len = 32; + break; + } + case CryptoSha384: { + if (UNLIKELY(!do_sha512_hash_true(data, digest))) { + RAISE_ERROR(BADARG_ATOM) + } + digest_len = 48; + break; + } + case CryptoSha512: { + if (UNLIKELY(!do_sha512_hash_false(data, digest))) { + RAISE_ERROR(BADARG_ATOM) + } + digest_len = 64; + break; + } + default: + RAISE_ERROR(BADARG_ATOM); + } + + if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(digest_len)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + return term_from_literal_binary(digest, digest_len, &ctx->heap, ctx->global); +} + +static const AtomStringIntPair cipher_table[] = { + { ATOM_STR("\xB", "aes_128_ecb"), MBEDTLS_CIPHER_AES_128_ECB }, + { ATOM_STR("\xB", "aes_192_ecb"), MBEDTLS_CIPHER_AES_192_ECB }, + { ATOM_STR("\xB", "aes_256_ecb"), MBEDTLS_CIPHER_AES_256_ECB }, + { ATOM_STR("\xB", "aes_128_cbc"), MBEDTLS_CIPHER_AES_128_CBC }, + { ATOM_STR("\xB", "aes_192_cbc"), MBEDTLS_CIPHER_AES_192_CBC }, + { ATOM_STR("\xB", "aes_256_cbc"), MBEDTLS_CIPHER_AES_256_CBC }, + { ATOM_STR("\xE", "aes_128_cfb128"), MBEDTLS_CIPHER_AES_128_CFB128 }, + { ATOM_STR("\xE", "aes_192_cfb128"), MBEDTLS_CIPHER_AES_192_CFB128 }, + { ATOM_STR("\xE", "aes_256_cfb128"), MBEDTLS_CIPHER_AES_256_CFB128 }, + { ATOM_STR("\xB", "aes_128_ctr"), MBEDTLS_CIPHER_AES_128_CTR }, + { ATOM_STR("\xB", "aes_192_ctr"), MBEDTLS_CIPHER_AES_192_CTR }, + { ATOM_STR("\xB", "aes_256_ctr"), MBEDTLS_CIPHER_AES_256_CTR }, + SELECT_INT_DEFAULT(MBEDTLS_CIPHER_NONE) +}; + +static const AtomStringIntPair padding_table[] = { + { ATOM_STR("\x4", "none"), MBEDTLS_PADDING_NONE }, + { ATOM_STR("\xC", "pkcs_padding"), MBEDTLS_PADDING_PKCS7 }, + SELECT_INT_DEFAULT(-1) +}; + +static term handle_iodata(term iodata, const void **data, size_t *len, void **allocated_ptr) +{ + *allocated_ptr = NULL; + + if (term_is_binary(iodata)) { + *data = term_binary_data(iodata); + *len = term_binary_size(iodata); + return OK_ATOM; + } else if (term_is_list(iodata)) { + InteropFunctionResult result = interop_iolist_size(iodata, len); + switch (result) { + case InteropOk: + break; + case InteropMemoryAllocFail: + return OUT_OF_MEMORY_ATOM; + case InteropBadArg: + return BADARG_ATOM; + } + void *allocated_buf = malloc(*len); + if (IS_NULL_PTR(allocated_buf)) { + return OUT_OF_MEMORY_ATOM; + } + result = interop_write_iolist(iodata, allocated_buf); + switch (result) { + case InteropOk: + break; + case InteropMemoryAllocFail: + free(allocated_buf); + return OUT_OF_MEMORY_ATOM; + case InteropBadArg: + free(allocated_buf); + return BADARG_ATOM; + } + *data = allocated_buf; + *allocated_ptr = allocated_buf; + return OK_ATOM; + } else { + return BADARG_ATOM; + } +} + +static bool bool_to_mbedtls_operation(term encrypt_flag, mbedtls_operation_t *operation) +{ + switch (encrypt_flag) { + case TRUE_ATOM: + *operation = MBEDTLS_ENCRYPT; + return true; + case FALSE_ATOM: + *operation = MBEDTLS_DECRYPT; + return true; + default: + return false; + } +} + +static term make_crypto_error(const char *file, int line, const char *message, Context *ctx) +{ + int err_needed_mem = (strlen(file) * CONS_SIZE) + TUPLE_SIZE(2) + (strlen(message) * CONS_SIZE) + + TUPLE_SIZE(3); + + if (UNLIKELY(memory_ensure_free(ctx, err_needed_mem) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + + term file_t = interop_bytes_to_list(file, strlen(file), &ctx->heap); + term file_line_t = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(file_line_t, 0, file_t); + term_put_tuple_element(file_line_t, 1, term_from_int(line)); + + term message_t = interop_bytes_to_list(message, strlen(message), &ctx->heap); + + term err_t = term_alloc_tuple(3, &ctx->heap); + term_put_tuple_element(err_t, 0, BADARG_ATOM); + term_put_tuple_element(err_t, 1, file_line_t); + term_put_tuple_element(err_t, 2, message_t); + + return err_t; +} + +static term nif_crypto_crypto_one_time(Context *ctx, int argc, term argv[]) +{ + bool has_iv = argc == 5; + term key; + term iv; + term data; + term flag_or_options; + if (has_iv) { + key = argv[1]; + iv = argv[2]; + data = argv[3]; + flag_or_options = argv[4]; + } else { + key = argv[1]; + data = argv[2]; + flag_or_options = argv[3]; + } + + term cipher_term = argv[0]; + mbedtls_cipher_type_t cipher + = interop_atom_term_select_int(cipher_table, cipher_term, ctx->global); + if (UNLIKELY(cipher == MBEDTLS_CIPHER_NONE)) { + RAISE_ERROR(make_crypto_error(__FILE__, __LINE__, "Unknown cipher", ctx)); + } + + // from this point onward use `goto raise_error` in order to raise and free all buffers + term error_atom = UNDEFINED_ATOM; + + void *allocated_key_data = NULL; + void *allocated_iv_data = NULL; + void *allocated_data_data = NULL; + + const void *key_data; + size_t key_len; + term result_t = handle_iodata(key, &key_data, &key_len, &allocated_key_data); + if (UNLIKELY(result_t != OK_ATOM)) { + error_atom = result_t; + goto raise_error; + } + + const void *iv_data = NULL; + size_t iv_len = 0; + if (has_iv) { + result_t = handle_iodata(iv, &iv_data, &iv_len, &allocated_iv_data); + if (UNLIKELY(result_t != OK_ATOM)) { + error_atom = result_t; + goto raise_error; + } + } + + const void *data_data; + size_t data_size; + result_t = handle_iodata(data, &data_data, &data_size, &allocated_data_data); + if (UNLIKELY(result_t != OK_ATOM)) { + error_atom = result_t; + goto raise_error; + } + + mbedtls_operation_t operation; + mbedtls_cipher_padding_t padding = MBEDTLS_PADDING_NONE; + bool padding_has_been_set = false; + + if (term_is_list(flag_or_options)) { + term encrypt_flag = interop_kv_get_value_default( + flag_or_options, ATOM_STR("\x7", "encrypt"), UNDEFINED_ATOM, ctx->global); + if (UNLIKELY(!bool_to_mbedtls_operation(encrypt_flag, &operation))) { + error_atom = BADARG_ATOM; + goto raise_error; + } + + term padding_term = interop_kv_get_value_default( + flag_or_options, ATOM_STR("\x7", "padding"), UNDEFINED_ATOM, ctx->global); + + if (padding_term != UNDEFINED_ATOM) { + padding_has_been_set = true; + + int padding_int = interop_atom_term_select_int(padding_table, padding_term, ctx->global); + if (UNLIKELY(padding_int < 0)) { + error_atom = BADARG_ATOM; + goto raise_error; + } + padding = (mbedtls_cipher_padding_t) padding_int; + } + + } else { + if (UNLIKELY(!bool_to_mbedtls_operation(flag_or_options, &operation))) { + error_atom = BADARG_ATOM; + goto raise_error; + } + } + + const mbedtls_cipher_info_t *cipher_info = mbedtls_cipher_info_from_type(cipher); + + mbedtls_cipher_context_t cipher_ctx; + + void *temp_buf = NULL; + + int source_line; + int result = mbedtls_cipher_setup(&cipher_ctx, cipher_info); + if (UNLIKELY(result != 0)) { + source_line = __LINE__; + goto mbed_error; + } + + result = mbedtls_cipher_setkey(&cipher_ctx, key_data, key_len * 8, operation); + if (UNLIKELY(result != 0)) { + source_line = __LINE__; + goto mbed_error; + } + + // we know that mbedtls supports padding just for CBC, so it makes sense to change to OTP + // default (none) just for it. However in case a padding is set for other modes let mbedtls + // decide which error should be raised. + if (mbedtls_cipher_get_cipher_mode(&cipher_ctx) == MBEDTLS_MODE_CBC || padding_has_been_set) { + result = mbedtls_cipher_set_padding_mode(&cipher_ctx, padding); + if (UNLIKELY(result != 0)) { + source_line = __LINE__; + goto mbed_error; + } + } + + unsigned int block_size = mbedtls_cipher_get_block_size(&cipher_ctx); + + size_t temp_buf_size = data_size + block_size; + temp_buf = malloc(temp_buf_size); + if (IS_NULL_PTR(temp_buf)) { + error_atom = OUT_OF_MEMORY_ATOM; + goto raise_error; + } + + // from this point onward use `mbed_error` in order to raise and free all buffers + + result = mbedtls_cipher_crypt( + &cipher_ctx, iv_data, iv_len, data_data, data_size, temp_buf, &temp_buf_size); + if (result != 0 && result != MBEDTLS_ERR_CIPHER_FULL_BLOCK_EXPECTED) { + source_line = __LINE__; + goto mbed_error; + } + mbedtls_cipher_free(&cipher_ctx); + + free(allocated_key_data); + free(allocated_iv_data); + free(allocated_data_data); + + if (UNLIKELY(memory_ensure_free(ctx, temp_buf_size) != MEMORY_GC_OK)) { + free(temp_buf); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + + term out = term_from_literal_binary(temp_buf, temp_buf_size, &ctx->heap, ctx->global); + + free(temp_buf); + + return out; + +raise_error: + free(allocated_key_data); + free(allocated_iv_data); + free(allocated_data_data); + RAISE_ERROR(error_atom); + +mbed_error: + free(temp_buf); + free(allocated_key_data); + free(allocated_iv_data); + free(allocated_data_data); + + char err_msg[24]; + snprintf(err_msg, sizeof(err_msg), "Error %x", -result); + RAISE_ERROR(make_crypto_error(__FILE__, source_line, err_msg, ctx)); +} + +static const struct Nif crypto_hash_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_crypto_hash +}; +static const struct Nif crypto_crypto_one_time_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_crypto_crypto_one_time +}; + +// +// Entrypoints +// + +const struct Nif *otp_crypto_nif_get_nif(const char *nifname) +{ + if (strncmp("crypto:", nifname, 7) == 0) { + const char *rest = nifname + 7; + if (strcmp("hash/2", rest) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &crypto_hash_nif; + } + if (strcmp("crypto_one_time/4", rest) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &crypto_crypto_one_time_nif; + } + if (strcmp("crypto_one_time/5", rest) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &crypto_crypto_one_time_nif; + } + } + return NULL; +} diff --git a/src/libAtomVM/otp_crypto.h b/src/libAtomVM/otp_crypto.h new file mode 100644 index 0000000000..5b7a24c815 --- /dev/null +++ b/src/libAtomVM/otp_crypto.h @@ -0,0 +1,36 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifndef _OTP_CRYPTO_H_ +#define _OTP_CRYPTO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +const struct Nif *otp_crypto_nif_get_nif(const char *nifname); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/platforms/esp32/components/avm_builtins/CMakeLists.txt b/src/platforms/esp32/components/avm_builtins/CMakeLists.txt index 72b5dd7faf..ff163591aa 100644 --- a/src/platforms/esp32/components/avm_builtins/CMakeLists.txt +++ b/src/platforms/esp32/components/avm_builtins/CMakeLists.txt @@ -28,6 +28,7 @@ set(AVM_BUILTIN_COMPONENT_SRCS "socket_driver.c" "spi_driver.c" "uart_driver.c" + "otp_crypto_platform.c" "otp_net_platform.c" "otp_socket_platform.c" "otp_ssl_platform.c" @@ -39,7 +40,7 @@ else() set(ADDITIONAL_PRIV_REQUIRES "") endif() -if(CONFIG_AVM_ENABLE_OTP_SSL_NIFS) +if(CONFIG_AVM_ENABLE_OTP_SSL_NIFS OR CONFIG_AVM_ENABLE_OTP_CRYPTO_NIFS) set(ADDITIONAL_PRIV_REQUIRES ${ADDITIONAL_PRIV_REQUIRES} "mbedtls") endif() diff --git a/src/platforms/esp32/components/avm_builtins/Kconfig b/src/platforms/esp32/components/avm_builtins/Kconfig index 61c1640905..04531f8831 100644 --- a/src/platforms/esp32/components/avm_builtins/Kconfig +++ b/src/platforms/esp32/components/avm_builtins/Kconfig @@ -66,6 +66,10 @@ config AVM_ENABLE_UART_PORT_DRIVER bool "Enable UART port driver" default y +config AVM_ENABLE_OTP_CRYPTO_NIFS + bool "Enable OTP Crypto NIFs" + default y + config AVM_ENABLE_OTP_SOCKET_NIFS bool "Enable OTP Socket NIFs" default y diff --git a/src/platforms/esp32/components/avm_builtins/otp_crypto_platform.c b/src/platforms/esp32/components/avm_builtins/otp_crypto_platform.c new file mode 100644 index 0000000000..ee671cdb1f --- /dev/null +++ b/src/platforms/esp32/components/avm_builtins/otp_crypto_platform.c @@ -0,0 +1,30 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 by Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#include +#include +#include +#include + +#ifdef CONFIG_AVM_ENABLE_OTP_CRYPTO_NIFS + +REGISTER_NIF_COLLECTION(otp_crypto, NULL, NULL, otp_crypto_nif_get_nif) + +#endif diff --git a/src/platforms/esp32/components/avm_sys/CMakeLists.txt b/src/platforms/esp32/components/avm_sys/CMakeLists.txt index b9030be346..a44a0cd410 100644 --- a/src/platforms/esp32/components/avm_sys/CMakeLists.txt +++ b/src/platforms/esp32/components/avm_sys/CMakeLists.txt @@ -26,6 +26,7 @@ set(AVM_SYS_COMPONENT_SRCS "platform_nifs.c" "platform_defaultatoms.c" "../../../../libAtomVM/inet.c" + "../../../../libAtomVM/otp_crypto.c" "../../../../libAtomVM/otp_net.c" "../../../../libAtomVM/otp_socket.c" "../../../../libAtomVM/otp_ssl.c" diff --git a/src/platforms/esp32/components/avm_sys/platform_nifs.c b/src/platforms/esp32/components/avm_sys/platform_nifs.c index 4e93f16be8..2d0b1248fb 100644 --- a/src/platforms/esp32/components/avm_sys/platform_nifs.c +++ b/src/platforms/esp32/components/avm_sys/platform_nifs.c @@ -54,8 +54,6 @@ #define TAG "atomvm" -#define MAX_MD_SIZE 64 - static const char *const esp_rst_unknown_atom = "\xF" "esp_rst_unknown"; static const char *const esp_rst_poweron = "\xF" "esp_rst_poweron"; static const char *const esp_rst_ext = "\xB" "esp_rst_ext"; @@ -81,27 +79,6 @@ static const AtomStringIntPair interface_table[] = { SELECT_INT_DEFAULT(InvalidInterface) }; -enum crypto_algorithm -{ - CryptoInvalidAlgorithm = 0, - CryptoMd5, - CryptoSha1, - CryptoSha224, - CryptoSha256, - CryptoSha384, - CryptoSha512 -}; - -static const AtomStringIntPair crypto_algorithm_table[] = { - { ATOM_STR("\x3", "md5"), CryptoMd5 }, - { ATOM_STR("\x3", "sha"), CryptoSha1 }, - { ATOM_STR("\x6", "sha224"), CryptoSha224 }, - { ATOM_STR("\x6", "sha256"), CryptoSha256 }, - { ATOM_STR("\x6", "sha384"), CryptoSha384 }, - { ATOM_STR("\x6", "sha512"), CryptoSha512 }, - SELECT_INT_DEFAULT(CryptoInvalidAlgorithm) -}; - #if defined __has_include #if __has_include() #include @@ -473,441 +450,6 @@ static term nif_esp_sleep_enable_ulp_wakeup(Context *ctx, int argc, term argv[]) #endif -#define DEFINE_DO_HASH(ALGORITHM, SUFFIX) \ - static InteropFunctionResult ALGORITHM##_hash_fold_fun(term t, void *accum) \ - { \ - mbedtls_##ALGORITHM##_context *md_ctx = (mbedtls_##ALGORITHM##_context *) accum; \ - if (term_is_integer(t)) { \ - avm_int64_t tmp = term_maybe_unbox_int64(t); \ - if (tmp < 0 || tmp > 255) { \ - return InteropBadArg; \ - } \ - uint8_t val = (uint8_t) tmp; \ - mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1); \ - } else /* term_is_binary(t) */ { \ - mbedtls_##ALGORITHM##_update(md_ctx, (uint8_t *) term_binary_data(t), term_binary_size(t)); \ - } \ - return InteropOk; \ - } \ - \ - static bool do_##ALGORITHM##_hash(term data, unsigned char *dst) \ - { \ - mbedtls_##ALGORITHM##_context md_ctx; \ - \ - mbedtls_##ALGORITHM##_init(&md_ctx); \ - mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx); \ - \ - InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun, NULL, (void *) &md_ctx); \ - if (UNLIKELY(result != InteropOk)) { \ - return false; \ - } \ - \ - if (UNLIKELY(mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst) != 0)) { \ - return false; \ - } \ - \ - return true; \ - } - -#define DEFINE_DO_HASH2(ALGORITHM, SUFFIX, IS_OTHER) \ - static InteropFunctionResult ALGORITHM##_hash_fold_fun_##IS_OTHER(term t, void *accum) \ - { \ - mbedtls_##ALGORITHM##_context *md_ctx = (mbedtls_##ALGORITHM##_context *) accum; \ - if (term_is_any_integer(t)) { \ - avm_int64_t tmp = term_maybe_unbox_int64(t); \ - if (tmp < 0 || tmp > 255) { \ - return InteropBadArg; \ - } \ - uint8_t val = (avm_int64_t) tmp; \ - mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1); \ - } else /* term_is_binary(t) */ { \ - mbedtls_##ALGORITHM##_update(md_ctx, (uint8_t *) term_binary_data(t), term_binary_size(t)); \ - } \ - return InteropOk; \ - } \ - \ - static bool do_##ALGORITHM##_hash_##IS_OTHER(term data, unsigned char *dst) \ - { \ - mbedtls_##ALGORITHM##_context md_ctx; \ - \ - mbedtls_##ALGORITHM##_init(&md_ctx); \ - mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx, IS_OTHER); \ - \ - InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun_##IS_OTHER, NULL, (void *) &md_ctx); \ - if (UNLIKELY(result != InteropOk)) { \ - return false; \ - } \ - \ - if (UNLIKELY(mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst) != 0)) { \ - return false; \ - } \ - \ - return true; \ - } - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - -DEFINE_DO_HASH(md5, _ret) -DEFINE_DO_HASH(sha1, _ret) -DEFINE_DO_HASH2(sha256, _ret, true) -DEFINE_DO_HASH2(sha256, _ret, false) -DEFINE_DO_HASH2(sha512, _ret, true) -DEFINE_DO_HASH2(sha512, _ret, false) - -#else - -DEFINE_DO_HASH(md5, ) -DEFINE_DO_HASH(sha1, ) -DEFINE_DO_HASH2(sha256, , true) -DEFINE_DO_HASH2(sha256, , false) -DEFINE_DO_HASH2(sha512, , true) -DEFINE_DO_HASH2(sha512, , false) - -#endif - -static term nif_crypto_hash(Context *ctx, int argc, term argv[]) -{ - UNUSED(argc); - term type = argv[0]; - VALIDATE_VALUE(type, term_is_atom); - term data = argv[1]; - - unsigned char digest[MAX_MD_SIZE]; - size_t digest_len = 0; - - enum crypto_algorithm algo = interop_atom_term_select_int(crypto_algorithm_table, type, ctx->global); - switch (algo) { - case CryptoMd5: { - if (UNLIKELY(!do_md5_hash(data, digest))) { - RAISE_ERROR(BADARG_ATOM) - } - digest_len = 16; - break; - } - case CryptoSha1: { - if (UNLIKELY(!do_sha1_hash(data, digest))) { - RAISE_ERROR(BADARG_ATOM) - } - digest_len = 20; - break; - } - case CryptoSha224: { - if (UNLIKELY(!do_sha256_hash_true(data, digest))) { - RAISE_ERROR(BADARG_ATOM) - } - digest_len = 28; - break; - } - case CryptoSha256: { - if (UNLIKELY(!do_sha256_hash_false(data, digest))) { - RAISE_ERROR(BADARG_ATOM) - } - digest_len = 32; - break; - } - case CryptoSha384: { - if (UNLIKELY(!do_sha512_hash_true(data, digest))) { - RAISE_ERROR(BADARG_ATOM) - } - digest_len = 48; - break; - } - case CryptoSha512: { - if (UNLIKELY(!do_sha512_hash_false(data, digest))) { - RAISE_ERROR(BADARG_ATOM) - } - digest_len = 64; - break; - } - default: - RAISE_ERROR(BADARG_ATOM); - } - - if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(digest_len)) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - return term_from_literal_binary(digest, digest_len, &ctx->heap, ctx->global); -} - -static const AtomStringIntPair cipher_no_iv_table[] = { - { ATOM_STR("\xB", "aes_128_ecb"), MBEDTLS_CIPHER_AES_128_ECB }, - { ATOM_STR("\xB", "aes_192_ecb"), MBEDTLS_CIPHER_AES_192_ECB }, - { ATOM_STR("\xB", "aes_256_ecb"), MBEDTLS_CIPHER_AES_256_ECB }, - SELECT_INT_DEFAULT(MBEDTLS_CIPHER_NONE) -}; - -static const AtomStringIntPair cipher_iv_table[] = { - { ATOM_STR("\xB", "aes_128_cbc"), MBEDTLS_CIPHER_AES_128_CBC }, - { ATOM_STR("\xB", "aes_192_cbc"), MBEDTLS_CIPHER_AES_192_CBC }, - { ATOM_STR("\xB", "aes_256_cbc"), MBEDTLS_CIPHER_AES_256_CBC }, - { ATOM_STR("\xE", "aes_128_cfb128"), MBEDTLS_CIPHER_AES_128_CFB128 }, - { ATOM_STR("\xE", "aes_192_cfb128"), MBEDTLS_CIPHER_AES_192_CFB128 }, - { ATOM_STR("\xE", "aes_256_cfb128"), MBEDTLS_CIPHER_AES_256_CFB128 }, - { ATOM_STR("\xB", "aes_128_ctr"), MBEDTLS_CIPHER_AES_128_CTR }, - { ATOM_STR("\xB", "aes_192_ctr"), MBEDTLS_CIPHER_AES_192_CTR }, - { ATOM_STR("\xB", "aes_256_ctr"), MBEDTLS_CIPHER_AES_256_CTR }, - SELECT_INT_DEFAULT(MBEDTLS_CIPHER_NONE) -}; - -static const AtomStringIntPair padding_table[] = { - { ATOM_STR("\x4", "none"), MBEDTLS_PADDING_NONE }, - { ATOM_STR("\xC", "pkcs_padding"), MBEDTLS_PADDING_PKCS7 }, - SELECT_INT_DEFAULT(-1) -}; - -static term handle_iodata(term iodata, const void **data, size_t *len, void **allocated_ptr) -{ - *allocated_ptr = NULL; - - if (term_is_binary(iodata)) { - *data = term_binary_data(iodata); - *len = term_binary_size(iodata); - return OK_ATOM; - } else if (term_is_list(iodata)) { - InteropFunctionResult result = interop_iolist_size(iodata, len); - switch (result) { - case InteropOk: - break; - case InteropMemoryAllocFail: - return OUT_OF_MEMORY_ATOM; - case InteropBadArg: - return BADARG_ATOM; - } - void *allocated_buf = malloc(*len); - if (IS_NULL_PTR(allocated_buf)) { - return OUT_OF_MEMORY_ATOM; - } - result = interop_write_iolist(iodata, allocated_buf); - switch (result) { - case InteropOk: - break; - case InteropMemoryAllocFail: - free(allocated_buf); - return OUT_OF_MEMORY_ATOM; - case InteropBadArg: - free(allocated_buf); - return BADARG_ATOM; - } - *data = allocated_buf; - *allocated_ptr = allocated_buf; - return OK_ATOM; - } else { - return BADARG_ATOM; - } -} - -static bool bool_to_mbedtls_operation(term encrypt_flag, mbedtls_operation_t *operation) -{ - switch (encrypt_flag) { - case TRUE_ATOM: - *operation = MBEDTLS_ENCRYPT; - return true; - case FALSE_ATOM: - *operation = MBEDTLS_DECRYPT; - return true; - default: - return false; - } -} - -static term make_crypto_error(const char *file, int line, const char *message, Context *ctx) -{ - int err_needed_mem = (strlen(file) * CONS_SIZE) + TUPLE_SIZE(2) + (strlen(message) * CONS_SIZE) - + TUPLE_SIZE(3); - - if (UNLIKELY(memory_ensure_free(ctx, err_needed_mem) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - - term file_t = interop_bytes_to_list(file, strlen(file), &ctx->heap); - term file_line_t = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(file_line_t, 0, file_t); - term_put_tuple_element(file_line_t, 1, term_from_int(line)); - - term message_t = interop_bytes_to_list(message, strlen(message), &ctx->heap); - - term err_t = term_alloc_tuple(3, &ctx->heap); - term_put_tuple_element(err_t, 0, BADARG_ATOM); - term_put_tuple_element(err_t, 1, file_line_t); - term_put_tuple_element(err_t, 2, message_t); - - return err_t; -} - -static term nif_crypto_crypto_one_time(Context *ctx, int argc, term argv[]) -{ - bool has_iv = argc == 5; - const AtomStringIntPair *cipher_table; - term key; - term iv; - term data; - term flag_or_options; - if (has_iv) { - cipher_table = cipher_iv_table; - key = argv[1]; - iv = argv[2]; - data = argv[3]; - flag_or_options = argv[4]; - } else { - cipher_table = cipher_no_iv_table; - key = argv[1]; - data = argv[2]; - flag_or_options = argv[3]; - } - - term cipher_term = argv[0]; - mbedtls_cipher_type_t cipher - = interop_atom_term_select_int(cipher_table, cipher_term, ctx->global); - if (UNLIKELY(cipher == MBEDTLS_CIPHER_NONE)) { - RAISE_ERROR(make_crypto_error(__FILE__, __LINE__, "Unknown cipher", ctx)); - } - - // from this point onward use `goto raise_error` in order to raise and free all buffers - term error_atom = UNDEFINED_ATOM; - - void *allocated_key_data = NULL; - void *allocated_iv_data = NULL; - void *allocated_data_data = NULL; - - const void *key_data; - size_t key_len; - term result_t = handle_iodata(key, &key_data, &key_len, &allocated_key_data); - if (UNLIKELY(result_t != OK_ATOM)) { - error_atom = result_t; - goto raise_error; - } - - const void *iv_data = NULL; - size_t iv_len = 0; - if (has_iv) { - result_t = handle_iodata(iv, &iv_data, &iv_len, &allocated_iv_data); - if (UNLIKELY(result_t != OK_ATOM)) { - error_atom = result_t; - goto raise_error; - } - } - - const void *data_data; - size_t data_size; - result_t = handle_iodata(data, &data_data, &data_size, &allocated_data_data); - if (UNLIKELY(result_t != OK_ATOM)) { - error_atom = result_t; - goto raise_error; - } - - mbedtls_operation_t operation; - mbedtls_cipher_padding_t padding = MBEDTLS_PADDING_NONE; - bool padding_has_been_set = false; - - if (term_is_list(flag_or_options)) { - term encrypt_flag = interop_kv_get_value_default( - flag_or_options, ATOM_STR("\x7", "encrypt"), UNDEFINED_ATOM, ctx->global); - if (UNLIKELY(!bool_to_mbedtls_operation(encrypt_flag, &operation))) { - error_atom = BADARG_ATOM; - goto raise_error; - } - - term padding_term = interop_kv_get_value_default( - flag_or_options, ATOM_STR("\x7", "padding"), UNDEFINED_ATOM, ctx->global); - - if (padding_term != UNDEFINED_ATOM) { - padding_has_been_set = true; - - padding = interop_atom_term_select_int(padding_table, padding_term, ctx->global); - if (UNLIKELY(padding < 0)) { - error_atom = BADARG_ATOM; - goto raise_error; - } - } - - } else { - if (UNLIKELY(!bool_to_mbedtls_operation(flag_or_options, &operation))) { - error_atom = BADARG_ATOM; - goto raise_error; - } - } - - const mbedtls_cipher_info_t *cipher_info = mbedtls_cipher_info_from_type(cipher); - - mbedtls_cipher_context_t cipher_ctx; - - void *temp_buf = NULL; - - int source_line; - int result = mbedtls_cipher_setup(&cipher_ctx, cipher_info); - if (UNLIKELY(result != 0)) { - source_line = __LINE__; - goto mbed_error; - } - - result = mbedtls_cipher_setkey(&cipher_ctx, key_data, key_len * 8, operation); - if (UNLIKELY(result != 0)) { - source_line = __LINE__; - goto mbed_error; - } - - // we know that mbedtls supports padding just for CBC, so it makes sense to change to OTP - // default (none) just for it. However in case a padding is set for other modes let mbedtls - // decide which error should be raised. - if (mbedtls_cipher_get_cipher_mode(&cipher_ctx) == MBEDTLS_MODE_CBC || padding_has_been_set) { - result = mbedtls_cipher_set_padding_mode(&cipher_ctx, padding); - if (UNLIKELY(result != 0)) { - source_line = __LINE__; - goto mbed_error; - } - } - - unsigned int block_size = mbedtls_cipher_get_block_size(&cipher_ctx); - - size_t temp_buf_size = data_size + block_size; - temp_buf = malloc(temp_buf_size); - if (IS_NULL_PTR(temp_buf)) { - error_atom = OUT_OF_MEMORY_ATOM; - goto raise_error; - } - - // from this point onward use `mbed_error` in order to raise and free all buffers - - result = mbedtls_cipher_crypt( - &cipher_ctx, iv_data, iv_len, data_data, data_size, temp_buf, &temp_buf_size); - if (result != 0 && result != MBEDTLS_ERR_CIPHER_FULL_BLOCK_EXPECTED) { - source_line = __LINE__; - goto mbed_error; - } - mbedtls_cipher_free(&cipher_ctx); - - free(allocated_key_data); - free(allocated_iv_data); - free(allocated_data_data); - - if (UNLIKELY(memory_ensure_free(ctx, temp_buf_size) != MEMORY_GC_OK)) { - free(temp_buf); - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - - term out = term_from_literal_binary(temp_buf, temp_buf_size, &ctx->heap, ctx->global); - - free(temp_buf); - - return out; - -raise_error: - free(allocated_key_data); - free(allocated_iv_data); - free(allocated_data_data); - RAISE_ERROR(error_atom); - -mbed_error: - free(temp_buf); - free(allocated_key_data); - free(allocated_iv_data); - free(allocated_data_data); - - char err_msg[24]; - snprintf(err_msg, sizeof(err_msg), "Error %x", -result); - RAISE_ERROR(make_crypto_error(__FILE__, source_line, err_msg, ctx)); -} - static term nif_atomvm_platform(Context *ctx, int argc, term argv[]) { UNUSED(ctx); @@ -1023,16 +565,6 @@ static const struct Nif esp_sleep_ulp_wakeup_nif = .nif_ptr = nif_esp_sleep_enable_ulp_wakeup }; #endif -static const struct Nif crypto_hash_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_crypto_hash -}; -static const struct Nif crypto_crypto_one_time_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_crypto_crypto_one_time -}; static const struct Nif atomvm_platform_nif = { .base.type = NIFFunctionType, @@ -1110,18 +642,6 @@ const struct Nif *platform_nifs_get_nif(const char *nifname) return &esp_sleep_ulp_wakeup_nif; } #endif - if (strcmp("crypto:hash/2", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &crypto_hash_nif; - } - if (strcmp("crypto:crypto_one_time/4", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &crypto_crypto_one_time_nif; - } - if (strcmp("crypto:crypto_one_time/5", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &crypto_crypto_one_time_nif; - } if (strcmp("atomvm:platform/0", nifname) == 0) { TRACE("Resolved platform nif %s ...\n", nifname); return &atomvm_platform_nif; diff --git a/src/platforms/esp32/test/main/test_erl_sources/test_crypto.erl b/src/platforms/esp32/test/main/test_erl_sources/test_crypto.erl index 444f58e873..5084e517f4 100644 --- a/src/platforms/esp32/test/main/test_erl_sources/test_crypto.erl +++ b/src/platforms/esp32/test/main/test_erl_sources/test_crypto.erl @@ -24,6 +24,7 @@ start() -> ok = test_hash(), ok = test_crypto_one_time(), + ok = test_available_ciphers(), ok. test_hash() -> @@ -174,6 +175,42 @@ test_crypto_one_time() -> ok. +test_available_ciphers() -> + <<171, 29, 253, 3, 110, 255, 225, 168, 40, 2, 92, 101, 18, 22, 104, 89>> = + crypto:crypto_one_time(aes_128_cbc, <<1:128>>, <<2:128>>, <<3:128>>, false), + <<172, 173, 71, 170, 66, 92, 132, 117, 22, 33, 191, 18, 17, 207, 171, 238>> = + crypto:crypto_one_time(aes_192_cbc, <<1:192>>, <<2:128>>, <<3:128>>, false), + <<33, 51, 81, 23, 26, 72, 178, 26, 115, 82, 208, 26, 225, 24, 76, 245>> = + crypto:crypto_one_time(aes_256_cbc, <<1:256>>, <<2:128>>, <<3:128>>, false), + <<149, 146, 215, 117, 124, 68, 24, 44, 51, 164, 46, 233, 81, 71, 162, 220>> = + crypto:crypto_one_time(aes_128_ctr, <<1:128>>, <<2:128>>, <<3:128>>, false), + <<220, 113, 165, 81, 21, 142, 16, 189, 39, 210, 3, 12, 128, 110, 174, 43>> = + crypto:crypto_one_time(aes_192_ctr, <<1:192>>, <<2:128>>, <<3:128>>, false), + <<89, 151, 109, 175, 200, 98, 75, 207, 80, 33, 65, 131, 194, 29, 141, 242>> = + crypto:crypto_one_time(aes_256_ctr, <<1:256>>, <<2:128>>, <<3:128>>, false), + <<149, 146, 215, 117, 124, 68, 24, 44, 51, 164, 46, 233, 81, 71, 162, 220>> = + crypto:crypto_one_time(aes_128_cfb128, <<1:128>>, <<2:128>>, <<3:128>>, false), + <<220, 113, 165, 81, 21, 142, 16, 189, 39, 210, 3, 12, 128, 110, 174, 43>> = + crypto:crypto_one_time(aes_192_cfb128, <<1:192>>, <<2:128>>, <<3:128>>, false), + <<89, 151, 109, 175, 200, 98, 75, 207, 80, 33, 65, 131, 194, 29, 141, 242>> = + crypto:crypto_one_time(aes_256_cfb128, <<1:256>>, <<2:128>>, <<3:128>>, false), + <<51, 126, 5, 238, 121, 110, 153, 245, 229, 187, 6, 58, 119, 97, 242, 197>> = + crypto:crypto_one_time(aes_128_ecb, <<1:128>>, <<2:128>>, false), + <<209, 55, 221, 80, 157, 38, 71, 63, 77, 135, 199, 107, 73, 45, 41, 120>> = + crypto:crypto_one_time(aes_192_ecb, <<1:192>>, <<2:128>>, false), + <<209, 55, 221, 80, 157, 38, 71, 63, 77, 135, 199, 107, 73, 45, 41, 120>> = + crypto:crypto_one_time(aes_192_ecb, <<1:192>>, <<2:128>>, false), + <<9, 134, 59, 77, 138, 44, 15, 97, 69, 171, 187, 23, 29, 143, 25, 227>> = + crypto:crypto_one_time(aes_256_ecb, <<1:256>>, <<2:128>>, false), + % Erlang/OTP also allows to call aes_*_ecb with an iv + <<171, 29, 253, 3, 110, 255, 225, 168, 40, 2, 92, 101, 18, 22, 104, 91>> = + crypto:crypto_one_time(aes_128_ecb, <<1:128>>, <<2:128>>, <<3:128>>, false), + <<172, 173, 71, 170, 66, 92, 132, 117, 22, 33, 191, 18, 17, 207, 171, 236>> = + crypto:crypto_one_time(aes_192_ecb, <<1:192>>, <<2:128>>, <<3:128>>, false), + <<33, 51, 81, 23, 26, 72, 178, 26, 115, 82, 208, 26, 225, 24, 76, 247>> = + crypto:crypto_one_time(aes_256_ecb, <<1:256>>, <<2:128>>, <<3:128>>, false), + ok. + get_error(F) -> try F(), diff --git a/src/platforms/generic_unix/lib/CMakeLists.txt b/src/platforms/generic_unix/lib/CMakeLists.txt index 72525665d5..c4d9b8588b 100644 --- a/src/platforms/generic_unix/lib/CMakeLists.txt +++ b/src/platforms/generic_unix/lib/CMakeLists.txt @@ -67,21 +67,23 @@ if (MbedTLS_FOUND) target_compile_definitions(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC ATOMVM_HAS_MBEDTLS) target_sources(libAtomVM${PLATFORM_LIB_SUFFIX} PRIVATE + ../../../libAtomVM/otp_crypto.c + ../../../libAtomVM/otp_crypto.h ../../../libAtomVM/otp_ssl.c ../../../libAtomVM/otp_ssl.h ) else() - message("WARNING: Could NOT find MbedTLS, SSL will not be supported. Install MbedTLS 3.x or try to set MBEDTLS_ROOT_DIR to installation prefix of MbedTLS 2.x") + message("WARNING: Could NOT find MbedTLS, ssl and crypto modules will not be supported. Install MbedTLS 3.x or try to set MBEDTLS_ROOT_DIR to installation prefix of MbedTLS 2.x") endif() -# For now we still use OpenSSL for random and crypto +# For now we still use OpenSSL for random find_package(OpenSSL) if (${OPENSSL_FOUND} STREQUAL TRUE) target_include_directories(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC ${OPENSSL_INCLUDE_DIR}) target_compile_definitions(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC ATOMVM_HAS_OPENSSL) target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC ${OPENSSL_CRYPTO_LIBRARY}) else() - message("WARNING: Some crypto operations will not be supported.") + message("WARNING: atomvm:random/0 and atomvm:rand_bytes/1 will not be supported.") endif() # enable by default dynamic loading on unix diff --git a/src/platforms/generic_unix/lib/platform_nifs.c b/src/platforms/generic_unix/lib/platform_nifs.c index ab92794356..51732076d1 100644 --- a/src/platforms/generic_unix/lib/platform_nifs.c +++ b/src/platforms/generic_unix/lib/platform_nifs.c @@ -25,6 +25,7 @@ #include "interop.h" #include "memory.h" #include "nifs.h" +#include "otp_crypto.h" #include "otp_net.h" #include "otp_socket.h" #include "otp_ssl.h" @@ -33,7 +34,6 @@ #include #if defined ATOMVM_HAS_OPENSSL -#include #include #endif @@ -53,120 +53,6 @@ return term_invalid_term(); #if defined ATOMVM_HAS_OPENSSL - -enum crypto_algorithm -{ - CryptoInvalidAlgorithm = 0, - CryptoMd5, - CryptoSha1, - CryptoSha224, - CryptoSha256, - CryptoSha384, - CryptoSha512 -}; - -static const AtomStringIntPair crypto_algorithm_table[] = { - { ATOM_STR("\x3", "md5"), CryptoMd5 }, - { ATOM_STR("\x3", "sha"), CryptoSha1 }, - { ATOM_STR("\x6", "sha224"), CryptoSha224 }, - { ATOM_STR("\x6", "sha256"), CryptoSha256 }, - { ATOM_STR("\x6", "sha384"), CryptoSha384 }, - { ATOM_STR("\x6", "sha512"), CryptoSha512 }, - SELECT_INT_DEFAULT(CryptoInvalidAlgorithm) -}; - -const char *get_crypto_algorithm(GlobalContext *global, term type) -{ - enum crypto_algorithm algo = interop_atom_term_select_int(crypto_algorithm_table, type, global); - switch (algo) { - case CryptoMd5: - return "MD5"; - case CryptoSha1: - return "SHA1"; - case CryptoSha224: - return "SHA224"; - case CryptoSha256: - return "SHA256"; - case CryptoSha384: - return "SHA384"; - case CryptoSha512: - return "SHA512"; - default: - return NULL; - } -} - -static InteropFunctionResult hash_fold_fun(term t, void *accum) -{ - EVP_MD_CTX *md_ctx = (EVP_MD_CTX *) accum; - if (term_is_any_integer(t)) { - avm_int64_t tmp = term_maybe_unbox_int64(t); - if (tmp < 0 || tmp > 255) { - return InteropBadArg; - } - uint8_t val = (avm_int64_t) tmp; - if (!EVP_DigestUpdate(md_ctx, &val, 1)) { - return InteropBadArg; - } - } else if (term_is_binary(t)) { - if (!EVP_DigestUpdate(md_ctx, (uint8_t *) term_binary_data(t), term_binary_size(t))) { - return InteropBadArg; - } - } - return InteropOk; -} - -static term nif_crypto_hash(Context *ctx, int argc, term argv[]) -{ - UNUSED(argc); - term type = argv[0]; - term data = argv[1]; - VALIDATE_VALUE(type, term_is_atom); - - const char *algorithm = get_crypto_algorithm(ctx->global, type); - if (IS_NULL_PTR(algorithm)) { - RAISE_ERROR(BADARG_ATOM); - } - - const EVP_MD *md = EVP_get_digestbyname(algorithm); - if (IS_NULL_PTR(md)) { - fprintf(stderr, "Could not find digest by name: %s\n", algorithm); - RAISE_ERROR(BADARG_ATOM); - } - EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); - if (IS_NULL_PTR(md_ctx)) { - fprintf(stderr, "Could not allocate MD context\n"); - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - - if (UNLIKELY(!EVP_DigestInit_ex(md_ctx, md, NULL))) { - fprintf(stderr, "Digest init failed for algorithm: %s\n", algorithm); - EVP_MD_CTX_free(md_ctx); - RAISE_ERROR(BADARG_ATOM); - } - - InteropFunctionResult result = interop_chardata_fold(data, hash_fold_fun, NULL, (void *) md_ctx); - if (result != InteropOk) { - fprintf(stderr, "Failed hashing input for algorithm: %s\n", algorithm); - RAISE_ERROR(BADARG_ATOM); - } - - unsigned char digest[EVP_MAX_MD_SIZE]; - unsigned int md_len; - if (!EVP_DigestFinal_ex(md_ctx, digest, &md_len)) { - fprintf(stderr, "Failed finishing hash for algorithm: %s\n", algorithm); - EVP_MD_CTX_free(md_ctx); - RAISE_ERROR(BADARG_ATOM); - } - - EVP_MD_CTX_free(md_ctx); - if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(md_len)) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - - return term_from_literal_binary(digest, md_len, &ctx->heap, ctx->global); -} - static term nif_openssl_rand_bytes(Context *ctx, int argc, term argv[]) { UNUSED(argc); @@ -211,11 +97,6 @@ static term nif_openssl_random(Context *ctx, int argc, term argv[]) return term_make_boxed_int(value, &ctx->heap); } -static const struct Nif crypto_hash_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_crypto_hash -}; static const struct Nif openssl_rand_bytes_nif = { .base.type = NIFFunctionType, @@ -246,10 +127,6 @@ static const struct Nif atomvm_platform_nif = const struct Nif *platform_nifs_get_nif(const char *nifname) { #if defined ATOMVM_HAS_OPENSSL - if (strcmp("crypto:hash/2", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &crypto_hash_nif; - } if (strcmp("atomvm:rand_bytes/1", nifname) == 0) { TRACE("Resolved platform nif %s ...\n", nifname); return &openssl_rand_bytes_nif; @@ -269,6 +146,10 @@ const struct Nif *platform_nifs_get_nif(const char *nifname) } nif = otp_socket_nif_get_nif(nifname); #if defined ATOMVM_HAS_MBEDTLS + if (nif) { + return nif; + } + nif = otp_crypto_nif_get_nif(nifname); if (nif) { return nif; } diff --git a/src/platforms/rp2040/src/lib/CMakeLists.txt b/src/platforms/rp2040/src/lib/CMakeLists.txt index 2ddc275d1f..17bf23c0bb 100644 --- a/src/platforms/rp2040/src/lib/CMakeLists.txt +++ b/src/platforms/rp2040/src/lib/CMakeLists.txt @@ -25,15 +25,18 @@ set(HEADER_FILES platform_defaultatoms.h platform_smp.h rp2040_sys.h + ../../../../libAtomVM/otp_crypto.h ) set(SOURCE_FILES gpiodriver.c networkdriver.c + otp_crypto_platform.c platform_defaultatoms.c platform_nifs.c smp.c sys.c + ../../../../libAtomVM/otp_crypto.c ) set( @@ -55,6 +58,7 @@ target_link_libraries( hardware_rtc hardware_sync pico_float + pico_mbedtls pico_multicore pico_platform pico_runtime @@ -91,8 +95,8 @@ if (PICO_CYW43_SUPPORTED) ../../../../libAtomVM/otp_ssl.h otp_net_lwip_raw.c otp_net_lwip_raw.h) - target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC pico_cyw43_arch_lwip_threadsafe_background pico_lwip_sntp pico_mbedtls INTERFACE pan_lwip_dhserver) + target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC pico_cyw43_arch_lwip_threadsafe_background pico_lwip_sntp INTERFACE pan_lwip_dhserver) target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,networkregister_port_driver -Wl,-u -Wl,otp_socket_nif -Wl,-u -Wl,otp_net_nif -Wl,-u -Wl,otp_ssl_nif") endif() -target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,gpio_nif") +target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,gpio_nif -Wl,-u -Wl,otp_crypto_nif") diff --git a/src/platforms/rp2040/src/lib/mbedtls_config.h b/src/platforms/rp2040/src/lib/mbedtls_config.h index 413dbbd2a6..138cd619f9 100644 --- a/src/platforms/rp2040/src/lib/mbedtls_config.h +++ b/src/platforms/rp2040/src/lib/mbedtls_config.h @@ -26,8 +26,13 @@ // Protocols #define MBEDTLS_SSL_PROTO_TLS1_2 -// Options that enable ciphersuites +// Options for ciphers, required for otp_crypto #define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_CIPHER_MODE_CFB +#define MBEDTLS_CIPHER_MODE_CTR +#define MBEDTLS_CIPHER_PADDING_PKCS7 + +// Options that enable ciphersuites #define MBEDTLS_ECP_DP_SECP192R1_ENABLED #define MBEDTLS_ECP_DP_SECP224R1_ENABLED #define MBEDTLS_ECP_DP_SECP256R1_ENABLED diff --git a/src/platforms/rp2040/src/lib/otp_crypto_platform.c b/src/platforms/rp2040/src/lib/otp_crypto_platform.c new file mode 100644 index 0000000000..80adcd3082 --- /dev/null +++ b/src/platforms/rp2040/src/lib/otp_crypto_platform.c @@ -0,0 +1,25 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 by Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#include +#include +#include + +REGISTER_NIF_COLLECTION(otp_crypto, NULL, NULL, otp_crypto_nif_get_nif) diff --git a/src/platforms/rp2040/tests/test_erl_sources/CMakeLists.txt b/src/platforms/rp2040/tests/test_erl_sources/CMakeLists.txt index da8aaf16fd..17a6384739 100644 --- a/src/platforms/rp2040/tests/test_erl_sources/CMakeLists.txt +++ b/src/platforms/rp2040/tests/test_erl_sources/CMakeLists.txt @@ -24,11 +24,11 @@ ExternalProject_Add(HostAtomVM BUILD_COMMAND cmake --build . --target=atomvmlib --target=PackBEAM ) -function(compile_erlang module_name) +function(compile_erlang module_name module_src_dir) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${module_name}.beam" - COMMAND erlc ${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.erl - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.erl" + COMMAND erlc ${CMAKE_CURRENT_SOURCE_DIR}/${module_src_dir}/${module_name}.erl + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${module_src_dir}/${module_name}.erl" COMMENT "Compiling ${module_name}.erl" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) @@ -36,8 +36,9 @@ function(compile_erlang module_name) set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/${module_name}.beam") endfunction() -compile_erlang(test_clocks) -compile_erlang(test_smp) +compile_erlang(test_clocks "") +compile_erlang(test_smp "") +compile_erlang(test_crypto ../../../esp32/test/main/test_erl_sources/) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rp2040_test_modules.avm" @@ -45,10 +46,12 @@ add_custom_command( HostAtomVM-prefix/src/HostAtomVM-build/libs/atomvmlib.avm test_clocks.beam test_smp.beam + test_crypto.beam DEPENDS HostAtomVM "${CMAKE_CURRENT_BINARY_DIR}/test_clocks.beam" "${CMAKE_CURRENT_BINARY_DIR}/test_smp.beam" + "${CMAKE_CURRENT_BINARY_DIR}/test_crypto.beam" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} VERBATIM ) diff --git a/src/platforms/rp2040/tests/test_main.c b/src/platforms/rp2040/tests/test_main.c index 47be570143..c011dd0315 100644 --- a/src/platforms/rp2040/tests/test_main.c +++ b/src/platforms/rp2040/tests/test_main.c @@ -156,6 +156,12 @@ TEST_CASE(test_clocks) TEST_ASSERT_EQUAL_INT(OK_ATOM, ret_value); } +TEST_CASE(test_crypto) +{ + term ret_value = avm_test_case("test_crypto.beam"); + TEST_ASSERT_EQUAL_INT(OK_ATOM, ret_value); +} + TEST_CASE(test_smp) { term ret_value = avm_test_case("test_smp.beam");