Skip to content

Commit b203ce1

Browse files
committed
Merge pull request #689 from fadushin/crypto
Add support for `crypto:hash/2` These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents eb2a5a1 + c5a51b8 commit b203ce1

File tree

13 files changed

+565
-59
lines changed

13 files changed

+565
-59
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5858
functions that default to `?ATOMVM_NVS_NS` are deprecated now).
5959
- Added most format possibilities to `io:format/2` and `io_lib:format/2`
6060
- Added `unicode` module with `characters_to_list/1,2` and `characters_to_binary/1,2,3` functions
61+
- Added support for `crypto:hash/2` (ESP32 and generic_unix with openssl)
6162

6263
### Fixed
6364
- Fixed issue with formatting integers with io:format() on STM32 platform

doc/src/programmers-guide.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -627,11 +627,6 @@ To convert a `datetime()` to convert the number of seconds since midnight Januar
627627
628628
### Miscellaneous
629629

630-
Use the `erlang:md5/1` function to compute the MD5 hash of an input binary. The output is a fixed-length binary ()
631-
632-
%% erlang
633-
Hash = erlang:md5(<<foo>>).
634-
635630
Use `atomvm:random/0` to generate a random unsigned 32-bit integer in the range `0..4294967295`:
636631

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

802797
> Note. If the AtomVM virtual machine is built with floating point arithmetic support disabled, these functions will result in a `badarg` error.
803798
799+
### Cryptographic Operations
800+
801+
You can hash binary date using the `crypto:hash/2` function.
802+
803+
%% erlang
804+
crypto:hash(sha, [<<"Some binary">>, $\s, "data"])
805+
806+
This function takes a hash algorithm, which may be one of:
807+
808+
-type md_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512.
809+
810+
and an IO list. The output type is a binary, who's length (in bytes) is dependent on the algorithm chosen:
811+
812+
| Algorithm | Hash Length (bytes) |
813+
|-----------|-------------|
814+
| `md5` | 16 |
815+
| `sha` | 20 |
816+
| `sha224` | 32 |
817+
| `sha256` | 32 |
818+
| `sha384` | 64 |
819+
| `sha512` | 64 |
820+
821+
> Note. The `crypto:hash/2` function is currently only supported on the ESP32 and generic UNIX platforms.
822+
823+
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)
824+
825+
%% erlang
826+
Hash = erlang:md5(<<foo>>).
827+
804828
## ESP32-specific APIs
805829

806830
Certain APIs are specific to and only supported on the ESP32 platform. This section describes these APIs.

libs/estdlib/src/crypto.erl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2023 Fred Dushin <[email protected]>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License")
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
-module(crypto).
21+
22+
-export([
23+
hash/2
24+
]).
25+
26+
-type hash_algorithm() :: md5 | sha | sha224 | sha256 | sha384 | sha512.
27+
-type digest() :: binary().
28+
29+
%%-----------------------------------------------------------------------------
30+
%% @param Type the hash algorithm
31+
%% @param Data the data to hash
32+
%% @returns Returns the result of hashing the supplied data using the supplied
33+
%% hash algorithm.
34+
%% @doc Hash data using a specified hash algorithm.
35+
%% @end
36+
%%-----------------------------------------------------------------------------
37+
-spec hash(Type :: hash_algorithm(), Data :: iolist()) -> digest().
38+
hash(_Type, _Data) ->
39+
erlang:nif_error(undefined).

libs/estdlib/src/erlang.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ system_flag(_Key, _Value) ->
250250
%%-----------------------------------------------------------------------------
251251
-spec md5(Data :: binary()) -> binary().
252252
md5(Data) when is_binary(Data) ->
253-
erlang:nif_error(undefined).
253+
crypto:hash(md5, Data).
254254

255255
%%-----------------------------------------------------------------------------
256256
%% @param Module Name of module

src/platforms/esp32/components/avm_sys/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ endif()
3939
idf_component_register(
4040
SRCS ${AVM_SYS_COMPONENT_SRCS}
4141
INCLUDE_DIRS "include"
42-
REQUIRES "spi_flash" "soc" "newlib" "pthread" "vfs" ${ADDITIONAL_COMPONENTS}
42+
REQUIRES "spi_flash" "soc" "newlib" "pthread" "vfs" "mbedtls" ${ADDITIONAL_COMPONENTS}
4343
PRIV_REQUIRES "libatomvm" "esp_timer" ${ADDITIONAL_PRIV_REQUIRES}
4444
)
4545

src/platforms/esp32/components/avm_sys/platform_nifs.c

Lines changed: 182 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,12 @@
3636
#include <esp_partition.h>
3737
#include <esp_sleep.h>
3838
#include <esp_system.h>
39+
#include <mbedtls/md5.h>
40+
#include <mbedtls/sha1.h>
41+
#include <mbedtls/sha256.h>
42+
#include <mbedtls/sha512.h>
3943
#include <soc/soc.h>
4044
#include <stdlib.h>
41-
#if defined __has_include
42-
# if __has_include (<esp_idf_version.h>)
43-
# include <esp_idf_version.h>
44-
# endif
45-
#endif
46-
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) && CONFIG_IDF_TARGET_ESP32
47-
#include <esp_rom_md5.h>
48-
#else
49-
#include <rom/md5_hash.h>
50-
#endif
51-
#if CONFIG_IDF_TARGET_ESP32C3
52-
#include "esp32c3/rom/md5_hash.h"
53-
#elif CONFIG_IDF_TARGET_ESP32S2
54-
#include "esp32s2/rom/md5_hash.h"
55-
#elif CONFIG_IDF_TARGET_ESP32S3
56-
#include "esp32s3/rom/md5_hash.h"
57-
#endif
5845

5946
// introduced starting with 4.4
6047
#if ESP_IDF_VERSION_MAJOR >= 5
@@ -66,7 +53,7 @@
6653

6754
#define TAG "atomvm"
6855

69-
#define MD5_DIGEST_LENGTH 16
56+
#define MAX_MD_SIZE 64
7057

7158
static const char *const esp_rst_unknown_atom = "\xF" "esp_rst_unknown";
7259
static const char *const esp_rst_poweron = "\xF" "esp_rst_poweron";
@@ -93,6 +80,33 @@ static const AtomStringIntPair interface_table[] = {
9380
SELECT_INT_DEFAULT(InvalidInterface)
9481
};
9582

83+
enum crypto_algorithm
84+
{
85+
CryptoInvalidAlgorithm = 0,
86+
CryptoMd5,
87+
CryptoSha1,
88+
CryptoSha224,
89+
CryptoSha256,
90+
CryptoSha384,
91+
CryptoSha512
92+
};
93+
94+
static const AtomStringIntPair crypto_algorithm_table[] = {
95+
{ ATOM_STR("\x3", "md5"), CryptoMd5 },
96+
{ ATOM_STR("\x3", "sha"), CryptoSha1 },
97+
{ ATOM_STR("\x6", "sha224"), CryptoSha224 },
98+
{ ATOM_STR("\x6", "sha256"), CryptoSha256 },
99+
{ ATOM_STR("\x6", "sha384"), CryptoSha384 },
100+
{ ATOM_STR("\x6", "sha512"), CryptoSha512 },
101+
SELECT_INT_DEFAULT(CryptoInvalidAlgorithm)
102+
};
103+
104+
#if defined __has_include
105+
#if __has_include(<esp_idf_version.h>)
106+
#include <esp_idf_version.h>
107+
#endif
108+
#endif
109+
96110
//
97111
// NIFs
98112
//
@@ -435,27 +449,156 @@ static term nif_esp_sleep_enable_ext1_wakeup(Context *ctx, int argc, term argv[]
435449

436450
#endif
437451

438-
static term nif_rom_md5(Context *ctx, int argc, term argv[])
452+
#define DEFINE_DO_HASH(ALGORITHM, SUFFIX) \
453+
static InteropFunctionResult ALGORITHM##_hash_fold_fun(term t, void *accum) \
454+
{ \
455+
mbedtls_##ALGORITHM##_context *md_ctx = (mbedtls_##ALGORITHM##_context *) accum; \
456+
if (term_is_integer(t)) { \
457+
uint8_t val = term_to_uint8(t); \
458+
mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1); \
459+
} else if (term_is_binary(t)) { \
460+
mbedtls_##ALGORITHM##_update(md_ctx, (uint8_t *) term_binary_data(t), term_binary_size(t)); \
461+
} \
462+
return InteropOk; \
463+
} \
464+
\
465+
static bool do_##ALGORITHM##_hash(term data, unsigned char *dst) \
466+
{ \
467+
mbedtls_##ALGORITHM##_context md_ctx; \
468+
\
469+
mbedtls_##ALGORITHM##_init(&md_ctx); \
470+
mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx); \
471+
\
472+
InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun, NULL, (void *) &md_ctx); \
473+
if (UNLIKELY(result != InteropOk)) { \
474+
return false; \
475+
} \
476+
\
477+
if (UNLIKELY(mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst) != 0)) { \
478+
return false; \
479+
} \
480+
\
481+
return true; \
482+
}
483+
484+
#define DEFINE_DO_HASH2(ALGORITHM, SUFFIX, IS_OTHER) \
485+
static InteropFunctionResult ALGORITHM##_hash_fold_fun_##IS_OTHER(term t, void *accum) \
486+
{ \
487+
mbedtls_##ALGORITHM##_context *md_ctx = (mbedtls_##ALGORITHM##_context *) accum; \
488+
if (term_is_any_integer(t)) { \
489+
avm_int64_t tmp = term_maybe_unbox_int64(t); \
490+
if (tmp < 0 || tmp > 255) { \
491+
return InteropBadArg; \
492+
} \
493+
uint8_t val = (avm_int64_t) tmp; \
494+
mbedtls_##ALGORITHM##_update##SUFFIX(md_ctx, &val, 1); \
495+
} else if (term_is_binary(t)) { \
496+
mbedtls_##ALGORITHM##_update(md_ctx, (uint8_t *) term_binary_data(t), term_binary_size(t)); \
497+
} \
498+
return InteropOk; \
499+
} \
500+
\
501+
static bool do_##ALGORITHM##_hash_##IS_OTHER(term data, unsigned char *dst) \
502+
{ \
503+
mbedtls_##ALGORITHM##_context md_ctx; \
504+
\
505+
mbedtls_##ALGORITHM##_init(&md_ctx); \
506+
mbedtls_##ALGORITHM##_starts##SUFFIX(&md_ctx, IS_OTHER); \
507+
\
508+
InteropFunctionResult result = interop_chardata_fold(data, ALGORITHM##_hash_fold_fun_##IS_OTHER, NULL, (void *) &md_ctx); \
509+
if (UNLIKELY(result != InteropOk)) { \
510+
return false; \
511+
} \
512+
\
513+
if (UNLIKELY(mbedtls_##ALGORITHM##_finish##SUFFIX(&md_ctx, dst) != 0)) { \
514+
return false; \
515+
} \
516+
\
517+
return true; \
518+
}
519+
520+
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
521+
522+
DEFINE_DO_HASH(md5, _ret)
523+
DEFINE_DO_HASH(sha1, _ret)
524+
DEFINE_DO_HASH2(sha256, _ret, true)
525+
DEFINE_DO_HASH2(sha256, _ret, false)
526+
DEFINE_DO_HASH2(sha512, _ret, true)
527+
DEFINE_DO_HASH2(sha512, _ret, false)
528+
529+
#else
530+
531+
DEFINE_DO_HASH(md5, )
532+
DEFINE_DO_HASH(sha1, )
533+
DEFINE_DO_HASH2(sha256, , true)
534+
DEFINE_DO_HASH2(sha256, , false)
535+
DEFINE_DO_HASH2(sha512, , true)
536+
DEFINE_DO_HASH2(sha512, , false)
537+
538+
#endif
539+
540+
static term nif_crypto_hash(Context *ctx, int argc, term argv[])
439541
{
440542
UNUSED(argc);
441-
term data = argv[0];
442-
VALIDATE_VALUE(data, term_is_binary);
443-
444-
unsigned char digest[MD5_DIGEST_LENGTH];
445-
struct MD5Context md5;
446-
#if __has_include (<esp_idf_version.h>) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) && CONFIG_IDF_TARGET_ESP32
447-
esp_rom_md5_init(&md5);
448-
esp_rom_md5_update(&md5, (const unsigned char *) term_binary_data(data), term_binary_size(data));
449-
esp_rom_md5_final(digest, &md5);
450-
#else
451-
MD5Init(&md5);
452-
MD5Update(&md5, (const unsigned char *) term_binary_data(data), term_binary_size(data));
453-
MD5Final(digest, &md5);
454-
#endif
455-
if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(MD5_DIGEST_LENGTH)) != MEMORY_GC_OK)) {
543+
term type = argv[0];
544+
VALIDATE_VALUE(type, term_is_atom);
545+
term data = argv[1];
546+
547+
unsigned char digest[MAX_MD_SIZE];
548+
size_t digest_len = 0;
549+
550+
enum crypto_algorithm algo = interop_atom_term_select_int(crypto_algorithm_table, type, ctx->global);
551+
switch (algo) {
552+
case CryptoMd5: {
553+
if (UNLIKELY(!do_md5_hash(data, digest))) {
554+
RAISE_ERROR(BADARG_ATOM)
555+
}
556+
digest_len = 16;
557+
break;
558+
}
559+
case CryptoSha1: {
560+
if (UNLIKELY(!do_sha1_hash(data, digest))) {
561+
RAISE_ERROR(BADARG_ATOM)
562+
}
563+
digest_len = 20;
564+
break;
565+
}
566+
case CryptoSha224: {
567+
if (UNLIKELY(!do_sha256_hash_true(data, digest))) {
568+
RAISE_ERROR(BADARG_ATOM)
569+
}
570+
digest_len = 28;
571+
break;
572+
}
573+
case CryptoSha256: {
574+
if (UNLIKELY(!do_sha256_hash_false(data, digest))) {
575+
RAISE_ERROR(BADARG_ATOM)
576+
}
577+
digest_len = 32;
578+
break;
579+
}
580+
case CryptoSha384: {
581+
if (UNLIKELY(!do_sha512_hash_true(data, digest))) {
582+
RAISE_ERROR(BADARG_ATOM)
583+
}
584+
digest_len = 48;
585+
break;
586+
}
587+
case CryptoSha512: {
588+
if (UNLIKELY(!do_sha512_hash_false(data, digest))) {
589+
RAISE_ERROR(BADARG_ATOM)
590+
}
591+
digest_len = 64;
592+
break;
593+
}
594+
default:
595+
RAISE_ERROR(BADARG_ATOM);
596+
}
597+
598+
if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(digest_len)) != MEMORY_GC_OK)) {
456599
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
457600
}
458-
return term_from_literal_binary(digest, MD5_DIGEST_LENGTH, &ctx->heap, ctx->global);
601+
return term_from_literal_binary(digest, digest_len, &ctx->heap, ctx->global);
459602
}
460603

461604
static term nif_atomvm_platform(Context *ctx, int argc, term argv[])
@@ -566,10 +709,10 @@ static const struct Nif esp_sleep_enable_ext1_wakeup_nif =
566709
.nif_ptr = nif_esp_sleep_enable_ext1_wakeup
567710
};
568711
#endif
569-
static const struct Nif rom_md5_nif =
712+
static const struct Nif crypto_hash_nif =
570713
{
571714
.base.type = NIFFunctionType,
572-
.nif_ptr = nif_rom_md5
715+
.nif_ptr = nif_crypto_hash
573716
};
574717
static const struct Nif atomvm_platform_nif =
575718
{
@@ -638,9 +781,9 @@ const struct Nif *platform_nifs_get_nif(const char *nifname)
638781
return &esp_sleep_enable_ext1_wakeup_nif;
639782
}
640783
#endif
641-
if (strcmp("erlang:md5/1", nifname) == 0) {
784+
if (strcmp("crypto:hash/2", nifname) == 0) {
642785
TRACE("Resolved platform nif %s ...\n", nifname);
643-
return &rom_md5_nif;
786+
return &crypto_hash_nif;
644787
}
645788
if (strcmp("atomvm:platform/0", nifname) == 0) {
646789
TRACE("Resolved platform nif %s ...\n", nifname);

0 commit comments

Comments
 (0)