diff --git a/firmware/src/hal/sgx/test/common/common.mk b/firmware/src/hal/sgx/test/common/common.mk index 71522536..eb2f23f7 100644 --- a/firmware/src/hal/sgx/test/common/common.mk +++ b/firmware/src/hal/sgx/test/common/common.mk @@ -27,7 +27,7 @@ TESTCOMMONDIR = ../common CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-function CFLAGS += -iquote $(SRCDIR) -iquote $(HALINCDIR) CFLAGS += -iquote $(TESTCOMMONDIR) -CFLAGS += -iquote $(MOCKDIR) +CFLAGS += -I$(MOCKDIR) CFLAGS += -DHSM_PLATFORM_SGX VPATH += $(MOCKDIR):$(SRCDIR) diff --git a/firmware/src/hal/sgx/test/mock/hsm_t.h b/firmware/src/hal/sgx/test/mock/hsm_t.h new file mode 100644 index 00000000..a9e577a0 --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/hsm_t.h @@ -0,0 +1,6 @@ +#ifndef __MOCK_HSM_T_H +#define __MOCK_HSM_T_H + +#include "mock_ocall.h" + +#endif // __MOCK_HSM_T_H \ No newline at end of file diff --git a/firmware/src/hal/sgx/test/mock/mock.h b/firmware/src/hal/sgx/test/mock/mock.h index c263ce1b..1cd47890 100644 --- a/firmware/src/hal/sgx/test/mock/mock.h +++ b/firmware/src/hal/sgx/test/mock/mock.h @@ -26,5 +26,7 @@ #define __MOCK_H #include "mock_secret_store.h" +#include "mock_seal.h" +#include "mock_ocall.h" #endif // #ifndef __MOCK_H diff --git a/firmware/src/hal/sgx/test/mock/mock_ocall.c b/firmware/src/hal/sgx/test/mock/mock_ocall.c new file mode 100644 index 00000000..8c555810 --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/mock_ocall.c @@ -0,0 +1,95 @@ +#include +#include "openenclave/common.h" +#include "mock_ocall.h" + +// Maximum size of a sealed blob, as defined in secret_store.c +#define MAX_BLOB_SIZE (1024 * 1024) + +// Trivial key-value store implementation for testing purposes +// This key-value store is only capable of storing a single key-value pair +static char G_kvstore_key[256]; +static uint8_t G_kvstore_data[256]; +static size_t G_kvstore_data_size; +// The type of failure to simulate on the next call to the mock implementation +static mock_kvstore_failure_type_t G_next_failure; + +void mock_ocall_init() { + memset(G_kvstore_key, 0, sizeof(G_kvstore_key)); + memset(G_kvstore_data, 0, sizeof(G_kvstore_data)); + G_kvstore_data_size = 0; + G_next_failure = KVSTORE_FAILURE_NONE; +} + +oe_result_t ocall_kvstore_save(bool* _retval, + char* key, + uint8_t* data, + size_t data_size) { + if (G_next_failure == KVSTORE_FAILURE_SAVE) { + G_next_failure = KVSTORE_FAILURE_NONE; + *_retval = false; + return OE_OK; + } else if (G_next_failure == KVSTORE_FAILURE_OE_FAILURE) { + G_next_failure = KVSTORE_FAILURE_NONE; + return OE_FAILURE; + } + + strcpy(G_kvstore_key, key); + memcpy(G_kvstore_data, data, data_size); + G_kvstore_data_size = data_size; + *_retval = true; + return OE_OK; +} + +oe_result_t ocall_kvstore_exists(bool* _retval, char* key) { + if (G_next_failure == KVSTORE_FAILURE_OE_FAILURE) { + G_next_failure = KVSTORE_FAILURE_NONE; + return OE_FAILURE; + } + + *_retval = (strcmp(key, G_kvstore_key) == 0); + return OE_OK; +} + +oe_result_t ocall_kvstore_get(size_t* _retval, + char* key, + uint8_t* data_buf, + size_t buffer_size) { + if (G_next_failure == KVSTORE_FAILURE_OE_FAILURE) { + G_next_failure = KVSTORE_FAILURE_NONE; + return OE_FAILURE; + } + + if (strcmp(key, G_kvstore_key) == 0) { + if (G_next_failure == KVSTORE_FAILURE_GET_SEALED_BLOB_TOO_LARGE) { + // Return a blob size that exceeds the limit allowed by the caller + G_next_failure = KVSTORE_FAILURE_NONE; + *_retval = MAX_BLOB_SIZE + 1; + } else { + *_retval = G_kvstore_data_size; + } + memcpy(data_buf, G_kvstore_data, G_kvstore_data_size); + } else { + *_retval = 0; + } + return OE_OK; +} + +oe_result_t ocall_kvstore_remove(bool* _retval, char* key) { + if (G_next_failure == KVSTORE_FAILURE_OE_FAILURE) { + G_next_failure = KVSTORE_FAILURE_NONE; + return OE_FAILURE; + } + if (strcmp(key, G_kvstore_key) == 0) { + memset(G_kvstore_key, 0, sizeof(G_kvstore_key)); + memset(G_kvstore_data, 0, sizeof(G_kvstore_data)); + G_kvstore_data_size = 0; + *_retval = true; + } else { + *_retval = false; + } + return OE_OK; +} + +void mock_ocall_kvstore_fail_next(mock_kvstore_failure_type_t failure) { + G_next_failure = failure; +} diff --git a/firmware/src/hal/sgx/test/mock/mock_ocall.h b/firmware/src/hal/sgx/test/mock/mock_ocall.h new file mode 100644 index 00000000..6a905f9e --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/mock_ocall.h @@ -0,0 +1,79 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __MOCK_OCALL_H +#define __MOCK_OCALL_H + +#include +#include +#include +#include "openenclave/common.h" + +// Types of failures that can be simulated in this mock implementation +typedef enum mock_kvstore_failure_type { + KVSTORE_FAILURE_NONE, + KVSTORE_FAILURE_SAVE, + KVSTORE_FAILURE_GET_SEALED_BLOB_TOO_LARGE, + KVSTORE_FAILURE_OE_FAILURE, +} mock_kvstore_failure_type_t; + +/** + * @brief Initializes the mock ocall implementation + */ +void mock_ocall_init(); + +/** + * @brief Mock implementation of ocall_kvstore_save + */ +oe_result_t ocall_kvstore_save(bool* _retval, + char* key, + uint8_t* data, + size_t data_size); + +/** + * @brief Mock implementation of ocall_kvstore_exists + */ +oe_result_t ocall_kvstore_exists(bool* _retval, char* key); + +/** + * @brief Mock implementation of ocall_kvstore_get + */ +oe_result_t ocall_kvstore_get(size_t* _retval, + char* key, + uint8_t* data_buf, + size_t buffer_size); + +/** + * @brief Mock implementation of ocall_kvstore_remove + */ +oe_result_t ocall_kvstore_remove(bool* _retval, char* key); + +/** + * @brief Simulates a failure on the next call to this mock implementation + * + * @param failure the type of failure to simulate + */ +void mock_ocall_kvstore_fail_next(mock_kvstore_failure_type_t failure); + +#endif // __MOCK_OCALL_H \ No newline at end of file diff --git a/firmware/src/hal/sgx/test/mock/mock_seal.c b/firmware/src/hal/sgx/test/mock/mock_seal.c new file mode 100644 index 00000000..3629f555 --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/mock_seal.c @@ -0,0 +1,159 @@ +#include +#include +#include "mock_seal.h" +#include "assert_utils.h" + +// The maximum allowed blob size, as defined in secret_store.c +#define MAX_BLOB_SIZE (1024 * 1024) + +// A prefix added to sealed blobs in this mock implementation. +// This is just to keep things simple and easily distinguishable. +#define SEALED_PREFIX "SEALED - " + +// Captures the arguments passed to oe_seal +typedef struct oe_seal_args { + const void* plugin_id; + oe_seal_setting_t settings; + size_t settings_count; + const uint8_t* plaintext; + size_t plaintext_size; + const uint8_t* additional_data; + size_t additional_data_size; +} oe_seal_args_t; + +// Captures the arguments passed to oe_unseal +typedef struct oe_unseal_args { + uint8_t blob[MAX_BLOB_SIZE]; + size_t blob_size; + const uint8_t* additional_data; + size_t additional_data_size; +} oe_unseal_args_t; + +// Global variables to capture the arguments passed to oe_seal and oe_unseal +static oe_seal_args_t G_oe_seal_args; +static oe_unseal_args_t G_oe_unseal_args; +// The next failure type to simulate +mock_seal_failure_type_t G_next_failure = SEAL_FAILURE_NONE; + +void mock_seal_init() { + memset(&G_oe_seal_args, 0, sizeof(G_oe_seal_args)); + memset(&G_oe_unseal_args, 0, sizeof(G_oe_unseal_args)); + G_next_failure = SEAL_FAILURE_NONE; +} + +oe_result_t oe_seal(const void* plugin_id, + const oe_seal_setting_t* settings, + size_t settings_count, + const uint8_t* plaintext, + size_t plaintext_size, + const uint8_t* additional_data, + size_t additional_data_size, + uint8_t** blob, + size_t* blob_size) { + G_oe_seal_args.plugin_id = plugin_id; + memcpy(&G_oe_seal_args.settings, settings, sizeof(oe_seal_setting_t)); + G_oe_seal_args.settings_count = settings_count; + G_oe_seal_args.plaintext = plaintext; + G_oe_seal_args.plaintext_size = plaintext_size; + G_oe_seal_args.additional_data = additional_data; + G_oe_seal_args.additional_data_size = additional_data_size; + + if (G_next_failure == SEAL_FAILURE_OE_FAILURE) { + G_next_failure = SEAL_FAILURE_NONE; + return OE_FAILURE; + } + + size_t prefix_length = strlen(SEALED_PREFIX); + *blob_size = plaintext_size + prefix_length; + *blob = malloc(*blob_size); + assert(*blob != NULL); + memcpy(*blob, SEALED_PREFIX, prefix_length); + memcpy(*blob + prefix_length, plaintext, plaintext_size); + + return OE_OK; +} + +oe_result_t oe_unseal(const uint8_t* blob, + size_t blob_size, + const uint8_t* additional_data, + size_t additional_data_size, + uint8_t** plaintext, + size_t* plaintext_size) { + memcpy(G_oe_unseal_args.blob, blob, blob_size); + G_oe_unseal_args.blob_size = blob_size; + G_oe_unseal_args.additional_data = additional_data; + G_oe_unseal_args.additional_data_size = additional_data_size; + + switch (G_next_failure) { + case SEAL_FAILURE_OE_FAILURE: + G_next_failure = SEAL_FAILURE_NONE; + return OE_FAILURE; + case SEAL_FAILURE_OE_UNSEAL_PLAINTEXT_TOO_LARGE: + *plaintext_size = MAX_BLOB_SIZE + 1; + G_next_failure = SEAL_FAILURE_NONE; + break; + case SEAL_FAILURE_NONE: + *plaintext_size = blob_size - strlen(SEALED_PREFIX); + break; + default: + assert(false); + break; + } + + *plaintext = malloc(*plaintext_size); + assert(*plaintext != NULL); + memcpy(*plaintext, blob + strlen(SEALED_PREFIX), *plaintext_size); + + return OE_OK; +} + +void assert_oe_seal_called_with(const void* plugin_id, + const oe_seal_setting_t* settings, + size_t settings_count, + const uint8_t* plaintext, + size_t plaintext_size, + const uint8_t* additional_data, + size_t additional_data_size) { + assert(G_oe_seal_args.plugin_id == plugin_id && + memcmp(&G_oe_seal_args.settings, settings, sizeof(*settings)) == 0 && + G_oe_seal_args.settings_count == settings_count && + G_oe_seal_args.plaintext == plaintext && + G_oe_seal_args.plaintext_size == plaintext_size && + G_oe_seal_args.additional_data == additional_data && + G_oe_seal_args.additional_data_size == additional_data_size); +} + +void assert_oe_unseal_called_with(const uint8_t* blob, + size_t blob_size, + const uint8_t* additional_data, + size_t additional_data_size) { + assert((memcmp(blob, G_oe_unseal_args.blob, blob_size) == 0) && + G_oe_unseal_args.blob_size == blob_size && + G_oe_unseal_args.additional_data == additional_data && + G_oe_unseal_args.additional_data_size == additional_data_size); +} + +void assert_oe_unseal_not_called() { + ASSERT_ARRAY_CLEARED(G_oe_unseal_args.blob); + assert(G_oe_unseal_args.blob_size == 0); + assert(G_oe_unseal_args.additional_data == NULL); + assert(G_oe_unseal_args.additional_data_size == 0); +} + +void assert_oe_seal_not_called() { + assert(G_oe_seal_args.plugin_id == NULL); + assert(G_oe_seal_args.settings.policy == 0); + assert(G_oe_seal_args.settings_count == 0); + assert(G_oe_seal_args.plaintext == NULL); + assert(G_oe_seal_args.plaintext_size == 0); + assert(G_oe_seal_args.additional_data == NULL); + assert(G_oe_seal_args.additional_data_size == 0); +} + +void mock_seal_fail_next(mock_seal_failure_type_t failure) { + G_next_failure = failure; +} + +size_t mock_seal_get_max_plaintext_size() { + return MAX_BLOB_SIZE - strlen(SEALED_PREFIX); +} diff --git a/firmware/src/hal/sgx/test/mock/mock_seal.h b/firmware/src/hal/sgx/test/mock/mock_seal.h new file mode 100644 index 00000000..dd6af69f --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/mock_seal.h @@ -0,0 +1,121 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __MOCK_SEAL_H +#define __MOCK_SEAL_H + +#include +#include +#include +#include "openenclave/common.h" + +// Types of failures that can be simulated in this mock implementation +typedef enum mock_seal_failure_type { + SEAL_FAILURE_NONE, + SEAL_FAILURE_OE_UNSEAL_PLAINTEXT_TOO_LARGE, + SEAL_FAILURE_OE_FAILURE, +} mock_seal_failure_type_t; + +// Simplified version of the seal settings type. This is only used to ensure +// that the API was called with the expected parameters. +typedef struct { + int policy; +} oe_seal_setting_t; + +#define OE_SEAL_SET_POLICY(policy) \ + { (int)(policy) } + +/** + * @brief Initializes the mock seal implementation + */ +void mock_seal_init(); + +/** + * @brief Mock implementation of oe_seal API function + */ +oe_result_t oe_seal(const void* plugin_id, + const oe_seal_setting_t* settings, + size_t settings_count, + const uint8_t* plaintext, + size_t plaintext_size, + const uint8_t* additional_data, + size_t additional_data_size, + uint8_t** blob, + size_t* blob_size); + +/** + * @brief Mock implementation of oe_unseal API function + */ +oe_result_t oe_unseal(const uint8_t* blob, + size_t blob_size, + const uint8_t* additional_data, + size_t additional_data_size, + uint8_t** plaintext, + size_t* plaintext_size); + +/** + * @brief Asserts that the last call to oe_seal was made with the expected + * parameters + */ +void assert_oe_seal_called_with(const void* plugin_id, + const oe_seal_setting_t* settings, + size_t settings_count, + const uint8_t* plaintext, + size_t plaintext_size, + const uint8_t* additional_data, + size_t additional_data_size); + +/** + * @brief Asserts that the last call to oe_unseal was made with the expected + * parameters + */ +void assert_oe_unseal_called_with(const uint8_t* blob, + size_t blob_size, + const uint8_t* additional_data, + size_t additional_data_size); + +/** + * @brief Asserts that oe_unseal was not called since the mock was initialized + */ +void assert_oe_unseal_not_called(); + +/** + * @brief Asserts that oe_seal was not called since the mock was initialized + */ +void assert_oe_seal_not_called(); + +/** + * @brief Simulates a failure on the next call to this mock implementation + * + * @param failure the type of failure to simulate + */ +void mock_seal_fail_next(mock_seal_failure_type_t failure); + +/** + * @brief Returns the maximum plaintext size allowed, based on the maximum blob + * size. + */ +size_t mock_seal_get_max_plaintext_size(); + +#endif // #ifndef __MOCK_SEAL_H diff --git a/firmware/src/hal/sgx/test/mock/openenclave/common.h b/firmware/src/hal/sgx/test/mock/openenclave/common.h new file mode 100644 index 00000000..54a3fc35 --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/openenclave/common.h @@ -0,0 +1,35 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __MOCK_OE_COMMON_H +#define __MOCK_OE_COMMON_H + +typedef enum oe_result { + OE_OK, + OE_FAILURE, +} oe_result_t; + +#define oe_result_str(result) ((result) == OE_OK ? "OE_OK" : "OE_FAILURE") + +#endif // #ifndef __MOCK_OE_COMMON_H \ No newline at end of file diff --git a/firmware/src/hal/sgx/test/mock/openenclave/corelibc/stdlib.h b/firmware/src/hal/sgx/test/mock/openenclave/corelibc/stdlib.h new file mode 100644 index 00000000..482a7661 --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/openenclave/corelibc/stdlib.h @@ -0,0 +1,30 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __MOCK_OE_STDLIB_H +#define __MOCK_OE_STDLIB_H + +#define oe_free(ptr) free(ptr) + +#endif // #ifndef __MOCK_OE_STDLIB_H \ No newline at end of file diff --git a/firmware/src/hal/sgx/test/mock/openenclave/seal.h b/firmware/src/hal/sgx/test/mock/openenclave/seal.h new file mode 100644 index 00000000..9a0d367d --- /dev/null +++ b/firmware/src/hal/sgx/test/mock/openenclave/seal.h @@ -0,0 +1,30 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __MOCK_OE_SEAL_H +#define __MOCK_OE_SEAL_H + +#include "mock_seal.h" + +#endif // #ifndef __MOCK_OE_SEAL_H \ No newline at end of file diff --git a/firmware/src/hal/sgx/test/run-all.sh b/firmware/src/hal/sgx/test/run-all.sh index 2a4217fe..8a7c782d 100755 --- a/firmware/src/hal/sgx/test/run-all.sh +++ b/firmware/src/hal/sgx/test/run-all.sh @@ -1,6 +1,6 @@ #!/bin/bash BASEDIR=$(dirname $0) -TESTDIRS="nvmem" +TESTDIRS="nvmem secret_store" TESTDIRS=${1:-"$TESTDIRS"} for d in $TESTDIRS; do diff --git a/firmware/src/hal/sgx/test/secret_store/Makefile b/firmware/src/hal/sgx/test/secret_store/Makefile new file mode 100644 index 00000000..4ed0fb95 --- /dev/null +++ b/firmware/src/hal/sgx/test/secret_store/Makefile @@ -0,0 +1,38 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +include ../common/common.mk + +PROG = test.out +OBJS = secret_store.o test_secret_store.o platform.o mock_seal.o mock_ocall.o + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) $(COVFLAGS) -o $@ $^ + +.PHONY: clean test +clean: + rm -f $(PROG) *.o $(COVFILES) + +test: all + ./$(PROG) diff --git a/firmware/src/hal/sgx/test/secret_store/test_secret_store.c b/firmware/src/hal/sgx/test/secret_store/test_secret_store.c new file mode 100644 index 00000000..896dd275 --- /dev/null +++ b/firmware/src/hal/sgx/test/secret_store/test_secret_store.c @@ -0,0 +1,385 @@ +#include +#include +#include +#include + +#include "assert_utils.h" +#include "secret_store.h" +#include "mock_seal.h" +#include "mock_ocall.h" + +// Error code for the sest API as defined in secret_store.c +#define SEST_ERROR (0) +// The maximum value that can be returned by sest_read +#define MAX_SEST_READ_SIZE (255) + +void setup() { + mock_seal_init(); + mock_ocall_init(); + assert(sest_init()); +} + +void test_secret_exists_after_write() { + setup(); + printf("Test secret exists after write...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + // Ensure the secret doesn't exist before the write + assert(!sest_exists(key)); + // Write the secret and ensure it now exists + assert(sest_write("key", secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(sest_exists("key")); +} + +void test_write_and_retrieve_secret() { + setup(); + printf("Test write and retrieve secret...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + // Write the secret and make sure the seal API is called with the correct + // arguments + assert(sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + // Retrieve the secret and make sure the unseal API is called with the + // correct arguments + uint8_t retrieved[MAX_SEST_READ_SIZE]; + uint8_t retrieved_length = sest_read(key, retrieved, sizeof(retrieved)); + uint8_t expected_sealed_blob[] = "SEALED - secret"; + assert_oe_unseal_called_with( + expected_sealed_blob, sizeof(expected_sealed_blob), NULL, 0); + assert(retrieved_length == sizeof(secret)); + ASSERT_MEMCMP(retrieved, secret, retrieved_length); +} + +void test_write_and_remove_secret() { + setup(); + printf("Test write and remove secret...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + assert(sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(sest_exists(key)); + assert(sest_remove(key)); + assert(!sest_exists(key)); +} + +void test_exists_fails_when_kvstore_exists_fails() { + setup(); + printf("Test sest_exists fails when ocall_kvstore_exists fails...\n"); + + // Write a valid secret and ensure it exists + char* key = "key"; + uint8_t secret[] = "secret"; + assert(!sest_exists(key)); + assert(sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(sest_exists(key)); + + // Force the next call to ocall_kvstore_exists to fail + mock_ocall_kvstore_fail_next(KVSTORE_FAILURE_OE_FAILURE); + assert(!sest_exists(key)); +} + +void test_read_fails_when_oe_unseal_fails() { + setup(); + printf("Test read fails when oe_unseal fails (OE_FAILURE)...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + assert(sest_write(key, secret, sizeof(secret))); + assert(sest_exists(key)); + // Force the next call to oe_unseal to fail with OE_FAILURE + mock_seal_fail_next(SEAL_FAILURE_OE_FAILURE); + uint8_t retrieved[MAX_SEST_READ_SIZE]; + memset(retrieved, 0, sizeof(retrieved)); + uint8_t retrieved_length = sest_read(key, retrieved, sizeof(retrieved)); + uint8_t expected_sealed_blob[] = "SEALED - secret"; + assert_oe_unseal_called_with( + expected_sealed_blob, sizeof(expected_sealed_blob), NULL, 0); + assert(retrieved_length == SEST_ERROR); + ASSERT_ARRAY_CLEARED(retrieved); +} + +void test_read_fails_when_plaintext_is_too_large() { + setup(); + printf("Test read fails when unsealed secret is too large...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + assert(sest_write(key, secret, sizeof(secret))); + assert(sest_exists(key)); + // Force the next call to oe_unseal to fail by returning a plaintext that is + // too large + mock_seal_fail_next(SEAL_FAILURE_OE_UNSEAL_PLAINTEXT_TOO_LARGE); + uint8_t retrieved[MAX_SEST_READ_SIZE]; + memset(retrieved, 0, sizeof(retrieved)); + uint8_t retrieved_length = sest_read(key, retrieved, sizeof(retrieved)); + uint8_t expected_sealed_blob[] = "SEALED - secret"; + assert_oe_unseal_called_with( + expected_sealed_blob, sizeof(expected_sealed_blob), NULL, 0); + assert(retrieved_length == SEST_ERROR); + ASSERT_ARRAY_CLEARED(retrieved); +} + +void test_write_zero_length_secret_fails() { + setup(); + printf("Test write zero length secret fails...\n"); + + char* key = "key"; + // Ensure the secret doesn't exist before the write + assert(!sest_exists(key)); + // Write the secret and ensure it fails + assert(!sest_write(key, NULL, 0)); + // Make sure the seal API was never reached + assert_oe_seal_not_called(); + assert(!sest_exists(key)); +} + +void test_write_fails_when_oe_seal_fails() { + setup(); + printf("Test write fails when oe_seal fails (OE_FAILURE)...\n"); + + // Force the next call to oe_seal to fail + mock_seal_fail_next(SEAL_FAILURE_OE_FAILURE); + char* key = "key"; + uint8_t secret[] = "secret"; + assert(!sest_exists(key)); + assert(!sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(!sest_exists(key)); +} + +void test_write_fails_when_kvstore_save_fails() { + setup(); + printf("Test write fails when ocall_kvstore_save fails...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + assert(!sest_exists(key)); + // Force the next call to ocall_kvstore_save to fail + mock_ocall_kvstore_fail_next(KVSTORE_FAILURE_SAVE); + assert(!sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(!sest_exists(key)); +} + +void test_write_fails_when_kvstore_save_fails_oe_failure() { + setup(); + printf("Test write fails when ocall_kvstore_save fails (OE_FAILURE)...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + assert(!sest_exists(key)); + // Force the next call to ocall_kvstore_save to fail with OE_FAILURE + mock_ocall_kvstore_fail_next(KVSTORE_FAILURE_OE_FAILURE); + assert(!sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(!sest_exists(key)); +} + +void test_write_fails_when_secret_too_large() { + setup(); + printf("Test write fails when secret is too large...\n"); + + // Attempt to write a secret that is too large + char* key = "key"; + size_t secret_size = mock_seal_get_max_plaintext_size() + 1; + uint8_t secret[secret_size]; + assert(!sest_exists(key)); + assert(!sest_write(key, secret, secret_size)); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + secret_size, + NULL, + 0); + assert(!sest_exists(key)); +} + +void test_read_with_invalid_key_fails() { + setup(); + printf("Test read with invalid key fails...\n"); + + char* valid_key = "valid key"; + uint8_t secret[] = "secret"; + assert(sest_write(valid_key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(sest_exists(valid_key)); + + char* invalid_key = "invalid key"; + uint8_t retrieved[MAX_SEST_READ_SIZE]; + uint8_t retrieved_length = + sest_read(invalid_key, retrieved, sizeof(retrieved)); + assert_oe_unseal_not_called(); + assert(retrieved_length == SEST_ERROR); +} + +void test_read_fails_when_kvstore_get_fails() { + setup(); + printf("Test read fails when ocall_kvstore_get fails (OE_FAILURE)...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + // Write the secret + assert(sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(sest_exists(key)); + // Force OE_FAILURE on the next call to ocall_kvstore_get + mock_ocall_kvstore_fail_next(KVSTORE_FAILURE_OE_FAILURE); + uint8_t retrieved[MAX_SEST_READ_SIZE]; + memset(retrieved, 0, sizeof(retrieved)); + uint8_t retrieved_length = sest_read(key, retrieved, sizeof(retrieved)); + assert_oe_unseal_not_called(); + assert(retrieved_length == SEST_ERROR); + ASSERT_ARRAY_CLEARED(retrieved); +} + +void test_read_fails_when_blob_is_too_large() { + setup(); + printf("Test read fails sealed blob is too large...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + // Write the secret + assert(sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(sest_exists(key)); + // Force the next call to ocall_kvstore_get to return a blob that is too + // large + mock_ocall_kvstore_fail_next(KVSTORE_FAILURE_GET_SEALED_BLOB_TOO_LARGE); + uint8_t retrieved[MAX_SEST_READ_SIZE]; + memset(retrieved, 0, sizeof(retrieved)); + uint8_t retrieved_length = sest_read(key, retrieved, sizeof(retrieved)); + assert_oe_unseal_not_called(); + assert(retrieved_length == SEST_ERROR); + ASSERT_ARRAY_CLEARED(retrieved); +} + +void test_remove_with_invalid_key_fails() { + setup(); + printf("Test write and remove invalid key fails...\n"); + + char* valid_key = "valid key"; + uint8_t secret[] = "secret"; + assert(sest_write(valid_key, secret, sizeof(secret))); + assert(sest_exists(valid_key)); + char* invalid_key = "invalid key"; + assert(!sest_remove(invalid_key)); +} + +void test_remove_fails_when_kvstore_remove_fails() { + setup(); + printf("Test remove fails when ocall_kvstore_remove fails...\n"); + + char* key = "key"; + uint8_t secret[] = "secret"; + assert(sest_write(key, secret, sizeof(secret))); + assert_oe_seal_called_with( + NULL, + (const oe_seal_setting_t[]){OE_SEAL_SET_POLICY(1)}, + 1, + secret, + sizeof(secret), + NULL, + 0); + assert(sest_exists(key)); + // Force the next call to ocall_kvstore_remove to fail + mock_ocall_kvstore_fail_next(KVSTORE_FAILURE_OE_FAILURE); + assert(!sest_remove(key)); + assert(sest_exists(key)); +} + +int main() { + test_secret_exists_after_write(); + test_write_and_retrieve_secret(); + test_write_and_remove_secret(); + test_write_zero_length_secret_fails(); + test_write_fails_when_oe_seal_fails(); + test_write_fails_when_secret_too_large(); + test_write_fails_when_kvstore_save_fails(); + test_write_fails_when_kvstore_save_fails_oe_failure(); + test_read_with_invalid_key_fails(); + test_read_fails_when_plaintext_is_too_large(); + test_read_fails_when_kvstore_get_fails(); + test_read_fails_when_blob_is_too_large(); + test_read_fails_when_oe_unseal_fails(); + test_exists_fails_when_kvstore_exists_fails(); + test_remove_with_invalid_key_fails(); + test_remove_fails_when_kvstore_remove_fails(); +}