Skip to content

Commit

Permalink
Added support for crypto:hash/2
Browse files Browse the repository at this point in the history
Signed-off-by: Fred Dushin <[email protected]>
  • Loading branch information
fadushin committed Aug 6, 2023
1 parent a15c922 commit 6f384af
Show file tree
Hide file tree
Showing 13 changed files with 515 additions and 60 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for nodejs with Wasm
- Added support for a subset of the OTP logger interface
- Added `esp:partition_list/0` function
- Added support for `crypto:hash/2` (ESP32 and generic_unix with openssl)

### Fixed
- Fixed issue with formatting integers with io:format() on STM32 platform
Expand Down
34 changes: 29 additions & 5 deletions doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,11 +627,6 @@ To convert a `datetime()` to convert the number of seconds since midnight Januar
### Miscellaneous

Use the `erlang:md5/1` function to compute the MD5 hash of an input binary. The output is a fixed-length binary ()

%% erlang
Hash = erlang:md5(<<foo>>).

Use `atomvm:random/0` to generate a random unsigned 32-bit integer in the range `0..4294967295`:

%% erlang
Expand Down Expand Up @@ -801,6 +796,35 @@ Input values that are out of range for the specific mathematical function or whi

> Note. If the AtomVM virtual machine is built with floating point arithmetic support disabled, these functions will result in a `badarg` error.
### Cryptographic Operations

You can hash binary date using the `crypto:hash/2` function.

%% erlang
crypto:hash(sha, [<<"Some binary">>, $\s, "data"])

This function takes a hash algorithm, which may be one of:

-type md_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512.

and an IO list. The output type is a binary, who's length (in bytes) is dependent on the algorithm chosen:

| Algorithm | Hash Length (bytes) |
|-----------|-------------|
| `md5` | 16 |
| `sha` | 20 |
| `sha224` | 32 |
| `sha256` | 32 |
| `sha384` | 64 |
| `sha512` | 64 |

> Note. The `crypto:hash/2` function is currently only supported on the ESP32 and generic UNIX platforms.
You can also use the legacy `erlang:md5/1` function to compute the MD5 hash of an input binary. The output is a fixed-length binary (16 bytes)

%% erlang
Hash = erlang:md5(<<foo>>).

## ESP32-specific APIs

Certain APIs are specific to and only supported on the ESP32 platform. This section describes these APIs.
Expand Down
39 changes: 39 additions & 0 deletions libs/estdlib/src/crypto.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
%
% This file is part of AtomVM.
%
% Copyright 2023 Fred Dushin <[email protected]>
%
% 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
%
-module(crypto).

-export([
hash/2
]).

-type hash_algorithm() :: md5 | sha | sha224 | sha256 | sha384 | sha512.
-type digest() :: binary().

%%-----------------------------------------------------------------------------
%% @param Type the hash algorithm
%% @param Data the data to hash
%% @returns Returns the result of hashing the supplied data using the supplied
%% hash algorithm.
%% @doc Hash data using a specified hash algorithm.
%% @end
%%-----------------------------------------------------------------------------
-spec hash(Type :: hash_algorithm(), Data :: iolist()) -> digest().
hash(_Type, _Data) ->
error(nif_error).
2 changes: 1 addition & 1 deletion libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ system_flag(_Key, _Value) ->
%%-----------------------------------------------------------------------------
-spec md5(Data :: binary()) -> binary().
md5(Data) when is_binary(Data) ->
throw(nif_error).
crypto:hash(md5, Data).

%%-----------------------------------------------------------------------------
%% @param Module Name of module
Expand Down
2 changes: 1 addition & 1 deletion src/platforms/esp32/components/avm_sys/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ endif()
idf_component_register(
SRCS ${AVM_SYS_COMPONENT_SRCS}
INCLUDE_DIRS "include"
REQUIRES "spi_flash" "soc" "newlib" "pthread" ${ADDITIONAL_COMPONENTS}
REQUIRES "spi_flash" "soc" "newlib" "pthread" "mbedtls" ${ADDITIONAL_COMPONENTS}
PRIV_REQUIRES "libatomvm" "esp_timer" ${ADDITIONAL_PRIV_REQUIRES}
)

Expand Down
197 changes: 157 additions & 40 deletions src/platforms/esp32/components/avm_sys/platform_nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,12 @@
#include <esp_partition.h>
#include <esp_sleep.h>
#include <esp_system.h>
#include <mbedtls/md5.h>
#include <mbedtls/sha1.h>
#include <mbedtls/sha256.h>
#include <mbedtls/sha512.h>
#include <soc/soc.h>
#include <stdlib.h>
#if defined __has_include
# if __has_include (<esp_idf_version.h>)
# include <esp_idf_version.h>
# endif
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) && CONFIG_IDF_TARGET_ESP32
#include <esp_rom_md5.h>
#else
#include <rom/md5_hash.h>
#endif
#if CONFIG_IDF_TARGET_ESP32C3
#include "esp32c3/rom/md5_hash.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/md5_hash.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/rom/md5_hash.h"
#endif

// introduced starting with 4.4
#if ESP_IDF_VERSION_MAJOR >= 5
Expand All @@ -62,7 +49,7 @@
//#define ENABLE_TRACE
#include "trace.h"

#define MD5_DIGEST_LENGTH 16
#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";
Expand All @@ -77,6 +64,29 @@ static const char *const esp_rst_brownout = "\x10" "esp_rst_brownout";
static const char *const esp_rst_sdio = "\xC" "esp_rst_sdio";
// 123456789ABCDEF01

enum crypto_algorithm
{
CryptoInvalidAlgorithm = 0,
CryptoMd5,
CryptoSha1,
CryptoSha256,
CryptoSha512
};

static const AtomStringIntPair crypto_algorithm_table[] = {
{ ATOM_STR("\x3", "md5"), CryptoMd5 },
{ ATOM_STR("\x3", "sha"), CryptoSha1 },
{ ATOM_STR("\x6", "sha256"), CryptoSha256 },
{ ATOM_STR("\x6", "sha512"), CryptoSha512 },
SELECT_INT_DEFAULT(CryptoInvalidAlgorithm)
};

#if defined __has_include
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#endif
#endif

//
// NIFs
//
Expand Down Expand Up @@ -419,27 +429,134 @@ static term nif_esp_sleep_enable_ext1_wakeup(Context *ctx, int argc, term argv[]

#endif

static term nif_rom_md5(Context *ctx, int argc, term argv[])
#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)) { \
uint8_t val = term_to_uint8(t); \
mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1); \
} else if (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_iolist_fold(data, ALGORITHM##_hash_fold_fun, (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(term t, void *accum) \
{ \
mbedtls_##ALGORITHM##_context *md_ctx = (mbedtls_##ALGORITHM##_context *) accum; \
if (term_is_integer(t)) { \
uint8_t val = term_to_uint8(t); \
mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1); \
} else if (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, IS_OTHER); \
\
InteropFunctionResult result = interop_iolist_fold(data, ALGORITHM##_hash_fold_fun, (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, false)
DEFINE_DO_HASH2(sha512, _ret, false)

#else

DEFINE_DO_HASH(md5, )
DEFINE_DO_HASH(sha1, )
DEFINE_DO_HASH2(sha256, , false)
DEFINE_DO_HASH2(sha512, , false)

#endif

static term nif_crypto_hash(Context *ctx, int argc, term argv[])
{
UNUSED(argc);
term data = argv[0];
VALIDATE_VALUE(data, term_is_binary);

unsigned char digest[MD5_DIGEST_LENGTH];
struct MD5Context md5;
#if __has_include (<esp_idf_version.h>) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) && CONFIG_IDF_TARGET_ESP32
esp_rom_md5_init(&md5);
esp_rom_md5_update(&md5, (const unsigned char *) term_binary_data(data), term_binary_size(data));
esp_rom_md5_final(digest, &md5);
#else
MD5Init(&md5);
MD5Update(&md5, (const unsigned char *) term_binary_data(data), term_binary_size(data));
MD5Final(digest, &md5);
#endif
if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(MD5_DIGEST_LENGTH)) != MEMORY_GC_OK)) {
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 CryptoSha256: {
if (UNLIKELY(!do_sha256_hash(data, digest))) {
RAISE_ERROR(BADARG_ATOM)
}
digest_len = 32;
break;
}
case CryptoSha512: {
if (UNLIKELY(!do_sha512_hash(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, MD5_DIGEST_LENGTH, &ctx->heap, ctx->global);
return term_from_literal_binary(digest, digest_len, &ctx->heap, ctx->global);
}

static term nif_atomvm_platform(Context *ctx, int argc, term argv[])
Expand All @@ -451,7 +568,7 @@ static term nif_atomvm_platform(Context *ctx, int argc, term argv[])
}

//
// NIF structures and distpatch
// NIF structures and dispatch
//

static const struct Nif esp_random_nif =
Expand Down Expand Up @@ -516,10 +633,10 @@ static const struct Nif esp_sleep_enable_ext1_wakeup_nif =
.nif_ptr = nif_esp_sleep_enable_ext1_wakeup
};
#endif
static const struct Nif rom_md5_nif =
static const struct Nif crypto_hash_nif =
{
.base.type = NIFFunctionType,
.nif_ptr = nif_rom_md5
.nif_ptr = nif_crypto_hash
};
static const struct Nif atomvm_platform_nif =
{
Expand Down Expand Up @@ -583,9 +700,9 @@ const struct Nif *platform_nifs_get_nif(const char *nifname)
return &esp_sleep_enable_ext1_wakeup_nif;
}
#endif
if (strcmp("erlang:md5/1", nifname) == 0) {
if (strcmp("crypto:hash/2", nifname) == 0) {
TRACE("Resolved platform nif %s ...\n", nifname);
return &rom_md5_nif;
return &crypto_hash_nif;
}
if (strcmp("atomvm:platform/0", nifname) == 0) {
TRACE("Resolved platform nif %s ...\n", nifname);
Expand Down
3 changes: 3 additions & 0 deletions src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ compile_erlang(test_esp_partition)
compile_erlang(test_file)
compile_erlang(test_list_to_binary)
compile_erlang(test_md5)
compile_erlang(test_crypto)
compile_erlang(test_monotonic_time)
compile_erlang(test_rtc_slow)
compile_erlang(test_socket)
Expand All @@ -54,6 +55,7 @@ add_custom_command(
test_file.beam
test_list_to_binary.beam
test_md5.beam
test_crypto.beam
test_monotonic_time.beam
test_rtc_slow.beam
test_socket.beam
Expand All @@ -65,6 +67,7 @@ add_custom_command(
"${CMAKE_CURRENT_BINARY_DIR}/test_file.beam"
"${CMAKE_CURRENT_BINARY_DIR}/test_list_to_binary.beam"
"${CMAKE_CURRENT_BINARY_DIR}/test_md5.beam"
"${CMAKE_CURRENT_BINARY_DIR}/test_crypto.beam"
"${CMAKE_CURRENT_BINARY_DIR}/test_monotonic_time.beam"
"${CMAKE_CURRENT_BINARY_DIR}/test_rtc_slow.beam"
"${CMAKE_CURRENT_BINARY_DIR}/test_socket.beam"
Expand Down
Loading

0 comments on commit 6f384af

Please sign in to comment.