LCOV - code coverage report
Current view: top level - hal/sgx/src/trusted - secret_store.c (source / functions) Hit Total Coverage
Test: powHSM firmware Lines: 105 109 96.3 %
Date: 2025-07-10 13:49:13 Functions: 9 9 100.0 %

          Line data    Source code
       1             : /**
       2             :  * The MIT License (MIT)
       3             :  *
       4             :  * Copyright (c) 2021 RSK Labs Ltd
       5             :  *
       6             :  * Permission is hereby granted, free of charge, to any person obtaining a copy
       7             :  * of this software and associated documentation files (the "Software"), to
       8             :  * deal in the Software without restriction, including without limitation the
       9             :  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
      10             :  * sell copies of the Software, and to permit persons to whom the Software is
      11             :  * furnished to do so, subject to the following conditions:
      12             :  *
      13             :  * The above copyright notice and this permission notice shall be included in
      14             :  * all copies or substantial portions of the Software.
      15             :  *
      16             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      17             :  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      18             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      19             :  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      20             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      21             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
      22             :  * IN THE SOFTWARE.
      23             :  */
      24             : 
      25             : #include <stdio.h>
      26             : #include <string.h>
      27             : #include <stdlib.h>
      28             : 
      29             : #include <openenclave/corelibc/stdlib.h>
      30             : #include <openenclave/seal.h>
      31             : #include <sys/mount.h>
      32             : 
      33             : #include "hal/platform.h"
      34             : #include "hal/log.h"
      35             : #include "hal/constants.h"
      36             : #include "sha256.h"
      37             : #include "secret_store.h"
      38             : #include "hsm_t.h"
      39             : 
      40             : #define SEAL_POLICY_UNIQUE 1
      41             : #define SEAL_POLICY_PRODUCT 2
      42             : 
      43             : #ifdef DEBUG_BUILD
      44             : #define SEAL_POLICY SEAL_POLICY_PRODUCT
      45             : #else
      46             : #define SEAL_POLICY SEAL_POLICY_UNIQUE
      47             : #endif
      48             : 
      49             : #define SEST_ERROR (0)
      50             : 
      51             : #define MAX_BLOB_SIZE (1024 * 1024)
      52             : 
      53             : // To avoid performing dynamic memory allocation whenever we need to manipulate
      54             : // data, we use these static buffers to store the sealed and unsealed data,
      55             : // respectively.
      56             : static uint8_t G_sealed_buffer[MAX_BLOB_SIZE];
      57             : static uint8_t G_unsealed_buffer[MAX_BLOB_SIZE];
      58             : 
      59             : // The sha256 context used for hash operations.
      60             : static SHA256_CTX G_sha256_ctx;
      61             : 
      62             : // The file format of the sealed secrets. This is the format we use to read and
      63             : // write sealed the data to disk.
      64             : typedef struct {
      65             :     size_t blob_size;
      66             :     uint8_t* blob;
      67             : } sealed_secret_t;
      68             : 
      69             : /**
      70             :  * @brief Adds a header to the given input data.
      71             :  * The header is composed of the sha256 hash of the key.
      72             :  * The resulting data is stored in the destination buffer.
      73             :  *
      74             :  * @param key The key to hash and set the header for.
      75             :  * @param src The input data to prepend the key hash to.
      76             :  * @param src_size The size of the input data.
      77             :  * @param dest The destination buffer for generated data.
      78             :  * @param dest_size The size of the destination buffer.
      79             :  *
      80             :  * @returns The number of bytes written to the destination buffer, or SEST_ERROR
      81             :  * upon error.
      82             :  */
      83          11 : static size_t add_header(const char* key,
      84             :                          const uint8_t* src,
      85             :                          size_t src_size,
      86             :                          uint8_t* dest,
      87             :                          size_t dest_size) {
      88          11 :     if (dest_size < HASH_LENGTH + src_size) {
      89             :         LOG("Failed to add header - destination buffer is too small\n");
      90           1 :         return SEST_ERROR;
      91             :     }
      92             : 
      93          10 :     sha256_init(&G_sha256_ctx);
      94          10 :     sha256_update(&G_sha256_ctx, (const uint8_t*)key, strlen(key));
      95          10 :     sha256_final(&G_sha256_ctx, dest);
      96             : 
      97          10 :     if (src_size)
      98           7 :         platform_memmove(dest + HASH_LENGTH, src, src_size);
      99             : 
     100          10 :     return HASH_LENGTH + src_size;
     101             : }
     102             : 
     103             : /**
     104             :  * @brief Checks if the data buffer contains the correct header for the given
     105             :  * key.
     106             :  *
     107             :  * @param key The key to validate the header against.
     108             :  * @param data The buffer containing the data to validate.
     109             :  * @param data_length The length of the data buffer.
     110             :  *
     111             :  * @returns true if the header is valid, false otherwise.
     112             :  */
     113           3 : static bool is_header_valid(const char* key,
     114             :                             const uint8_t* data,
     115             :                             size_t data_length) {
     116           3 :     if (data_length < HASH_LENGTH) {
     117           0 :         return false;
     118             :     }
     119             : 
     120             :     uint8_t expected_header[HASH_LENGTH];
     121           3 :     add_header(key, NULL, 0, expected_header, HASH_LENGTH);
     122             : 
     123           3 :     return memcmp(expected_header, data, HASH_LENGTH) == 0;
     124             : }
     125             : 
     126             : /**
     127             :  * @brief Obtains the plaintext data from a sealed secret.
     128             :  *
     129             :  * @param sealed_secret The sealed secret to unseal.
     130             :  * @param dest The destination buffer for the unsealed data. This must be
     131             :  * pre-allocated and large enough to hold the unsealed data.
     132             :  * @param dest_length The length of the pre-allocated destination buffer.
     133             :  *
     134             :  * @returns the length of the unsealed data, or SEST_ERROR upon error
     135             :  */
     136           4 : static uint8_t unseal_data(const sealed_secret_t* sealed_secret,
     137             :                            uint8_t* dest,
     138             :                            size_t dest_length) {
     139             : #ifndef SIM_BUILD
     140           4 :     uint8_t* plaintext = NULL;
     141           4 :     size_t plaintext_size = 0;
     142           4 :     oe_result_t result = oe_unseal(sealed_secret->blob,
     143           4 :                                    sealed_secret->blob_size,
     144             :                                    NULL,
     145             :                                    0,
     146             :                                    &plaintext,
     147             :                                    &plaintext_size);
     148           4 :     if (result != OE_OK) {
     149             :         LOG("Unsealing failed with result=%u (%s)\n",
     150             :             result,
     151             :             oe_result_str(result));
     152           1 :         goto unseal_data_error;
     153             :     }
     154             : 
     155           3 :     if (plaintext_size > dest_length) {
     156             :         LOG("Unsealed data is too large\n");
     157           0 :         goto unseal_data_error;
     158             :     }
     159             : 
     160           3 :     platform_memmove(dest, plaintext, plaintext_size);
     161           3 :     oe_free(plaintext);
     162           3 :     return plaintext_size;
     163             : 
     164           1 : unseal_data_error:
     165           1 :     if (plaintext)
     166           0 :         oe_free(plaintext);
     167           1 :     return SEST_ERROR;
     168             : #else
     169             :     // *************************************************** //
     170             :     // UNSAFE SIMULATOR-ONLY UNSEAL IMPLEMENTATION         //
     171             :     // NOT FOR PRODUCTION USE                              //
     172             :     if (sealed_secret->blob_size > MAX_BLOB_SIZE) {
     173             :         LOG("Sealed blob size is too large\n");
     174             :         return SEST_ERROR;
     175             :     }
     176             : 
     177             :     if (sealed_secret->blob_size > dest_length) {
     178             :         LOG("Unsealed data is too large\n");
     179             :         return SEST_ERROR;
     180             :     }
     181             : 
     182             :     platform_memmove(dest, sealed_secret->blob, sealed_secret->blob_size);
     183             : 
     184             :     return sealed_secret->blob_size;
     185             :     // *************************************************** //
     186             : #endif
     187             : }
     188             : 
     189             : /**
     190             :  * @brief Seals the given data into a sealed secret.
     191             :  *
     192             :  * @param data The data to seal.
     193             :  * @param data_length The length of the data to seal.
     194             :  * @param sealed_secret The destination for the sealed secret.
     195             :  */
     196           7 : static bool seal_data(uint8_t* data,
     197             :                       size_t data_length,
     198             :                       sealed_secret_t* sealed_secret) {
     199             : #ifndef SIM_BUILD
     200           7 :     uint8_t* blob = NULL;
     201           7 :     size_t blob_size = 0;
     202           7 :     const oe_seal_setting_t settings[] = {OE_SEAL_SET_POLICY(SEAL_POLICY)};
     203           7 :     oe_result_t result = oe_seal(NULL,
     204             :                                  settings,
     205             :                                  sizeof(settings) / sizeof(settings[0]),
     206             :                                  data,
     207             :                                  data_length,
     208             :                                  NULL,
     209             :                                  0,
     210             :                                  &blob,
     211             :                                  &blob_size);
     212           7 :     if (result != OE_OK) {
     213             :         LOG("Sealing failed with result=%u (%s)\n",
     214             :             result,
     215             :             oe_result_str(result));
     216           1 :         oe_free(blob);
     217           1 :         return false;
     218             :     }
     219             : 
     220           6 :     sealed_secret->blob = blob;
     221           6 :     sealed_secret->blob_size = blob_size;
     222           6 :     return true;
     223             : #else
     224             :     // *************************************************** //
     225             :     // UNSAFE SIMULATOR-ONLY SEAL IMPLEMENTATION           //
     226             :     // NOT FOR PRODUCTION USE                              //
     227             :     sealed_secret->blob = oe_malloc(data_length);
     228             :     memcpy(sealed_secret->blob, data, data_length);
     229             :     sealed_secret->blob_size = data_length;
     230             :     return true;
     231             :     // *************************************************** //
     232             : #endif
     233             : }
     234             : 
     235             : // Public API
     236          17 : bool sest_init() {
     237          17 :     explicit_bzero(G_sealed_buffer, sizeof(G_sealed_buffer));
     238          17 :     explicit_bzero(G_unsealed_buffer, sizeof(G_unsealed_buffer));
     239          17 :     return true;
     240             : }
     241             : 
     242          26 : bool sest_exists(char* key) {
     243             :     LOG("Attempting determine secret existence for <%s>...\n", key);
     244             : 
     245             :     bool exists;
     246          26 :     oe_result_t oe_result = ocall_kvstore_exists(&exists, key);
     247             : 
     248          26 :     if (oe_result != OE_OK) {
     249             :         LOG("Key-value store exists query failed with result=%u (%s)\n",
     250             :             oe_result,
     251             :             oe_result_str(oe_result));
     252           1 :         return false;
     253             :     }
     254             : 
     255          25 :     return exists;
     256             : }
     257             : 
     258           7 : uint8_t sest_read(char* key, uint8_t* dest, size_t dest_length) {
     259             :     LOG("Attempting to read secret for <%s>...\n", key);
     260             : 
     261           7 :     size_t blob_size = 0;
     262           7 :     oe_result_t oe_result = ocall_kvstore_get(
     263             :         &blob_size, key, G_sealed_buffer, sizeof(G_sealed_buffer));
     264           7 :     if (oe_result != OE_OK) {
     265             :         LOG("Key-value store read failed with result=%u (%s)\n",
     266             :             oe_result,
     267             :             oe_result_str(oe_result));
     268           1 :         goto sest_read_error;
     269             :     }
     270             : 
     271           6 :     if (!blob_size) {
     272             :         LOG("No secret found for key <%s>\n", key);
     273           1 :         goto sest_read_error;
     274             :     }
     275             : 
     276             :     // This is just an extra sanity check, this can never happen in
     277             :     // practice since the ocall will fail if the blob size
     278             :     // is too large for the buffer.
     279           5 :     if (blob_size > sizeof(G_sealed_buffer)) {
     280             :         LOG("Sealed blob too large\n");
     281           1 :         goto sest_read_error;
     282             :     }
     283             : 
     284           4 :     sealed_secret_t sealed_secret = {
     285             :         .blob_size = blob_size,
     286             :         .blob = G_sealed_buffer,
     287             :     };
     288             : 
     289           4 :     uint8_t unsealed_length = unseal_data(
     290             :         &sealed_secret, G_unsealed_buffer, sizeof(G_unsealed_buffer));
     291           4 :     if (unsealed_length == SEST_ERROR) {
     292             :         LOG("Unable to read secret stored in key <%s>\n", key);
     293           1 :         goto sest_read_error;
     294             :     }
     295             : 
     296           3 :     if (!is_header_valid(key, G_unsealed_buffer, unsealed_length)) {
     297             :         LOG("Secret header validation failed for key <%s>\n", key);
     298           1 :         goto sest_read_error;
     299             :     }
     300             : 
     301             :     // Skip the header and copy the plaintext data to the destination buffer.
     302           2 :     uint8_t* plaintext = G_unsealed_buffer + HASH_LENGTH;
     303           2 :     size_t plaintext_size = unsealed_length - HASH_LENGTH;
     304             : 
     305           2 :     if (plaintext_size > dest_length) {
     306             :         LOG("Unsealed data is too large\n");
     307           1 :         goto sest_read_error;
     308             :     }
     309           1 :     platform_memmove(dest, plaintext, plaintext_size);
     310             : 
     311             :     // Clean up the buffers used to store the sealed and unsealed data.
     312           1 :     explicit_bzero(G_sealed_buffer, sizeof(G_sealed_buffer));
     313           1 :     explicit_bzero(G_unsealed_buffer, sizeof(G_unsealed_buffer));
     314             : 
     315           1 :     return plaintext_size;
     316             : 
     317           6 : sest_read_error:
     318           6 :     explicit_bzero(G_sealed_buffer, sizeof(G_sealed_buffer));
     319           6 :     explicit_bzero(G_unsealed_buffer, sizeof(G_unsealed_buffer));
     320           6 :     return SEST_ERROR;
     321             : }
     322             : 
     323           9 : bool sest_write(char* key, uint8_t* secret, size_t secret_length) {
     324             :     LOG("Attempting to write secret for <%s>...\n", key);
     325           9 :     if (!secret_length) {
     326             :         LOG("Invalid zero-length secret given for key <%s>\n", key);
     327           1 :         return false;
     328             :     }
     329             : 
     330           8 :     sealed_secret_t sealed_secret = {
     331             :         .blob_size = 0,
     332             :         .blob = NULL,
     333             :     };
     334             : 
     335           8 :     size_t unsealed_size = add_header(key,
     336             :                                       secret,
     337             :                                       secret_length,
     338             :                                       G_unsealed_buffer,
     339             :                                       sizeof(G_unsealed_buffer));
     340           8 :     if (unsealed_size == SEST_ERROR) {
     341             :         LOG("Error adding header to secret\n");
     342           1 :         goto sest_write_error;
     343             :     }
     344             : 
     345           7 :     if (!seal_data(G_unsealed_buffer, unsealed_size, &sealed_secret)) {
     346             :         LOG("Error sealing secret for key <%s>\n", key);
     347           1 :         goto sest_write_error;
     348             :     }
     349             : 
     350           6 :     if (sealed_secret.blob_size > MAX_BLOB_SIZE) {
     351             :         LOG("Sealed blob too large\n");
     352           0 :         goto sest_write_error;
     353             :     }
     354             : 
     355           6 :     bool save_success = false;
     356           6 :     oe_result_t oe_result = ocall_kvstore_save(
     357             :         &save_success, key, sealed_secret.blob, sealed_secret.blob_size);
     358           6 :     if (oe_result != OE_OK) {
     359             :         LOG("Key-value store write failed with result=%u (%s)\n",
     360             :             oe_result,
     361             :             oe_result_str(oe_result));
     362           1 :         goto sest_write_error;
     363             :     }
     364             : 
     365           5 :     if (!save_success) {
     366             :         LOG("Error saving secret for key <%s>\n", key);
     367           1 :         goto sest_write_error;
     368             :     }
     369             : 
     370           4 :     oe_free(sealed_secret.blob);
     371           4 :     return true;
     372             : 
     373           4 : sest_write_error:
     374           4 :     explicit_bzero(G_unsealed_buffer, sizeof(G_unsealed_buffer));
     375           4 :     if (sealed_secret.blob)
     376           2 :         oe_free(sealed_secret.blob);
     377           4 :     return false;
     378             : }
     379             : 
     380           3 : bool sest_remove(char* key) {
     381           3 :     bool remove_success = false;
     382           3 :     oe_result_t oe_result = ocall_kvstore_remove(&remove_success, key);
     383           3 :     if (oe_result != OE_OK) {
     384             :         LOG("ocall_oestore_remove_secret() failed with result=%u (%s)\n",
     385             :             oe_result,
     386             :             oe_result_str(oe_result));
     387           1 :         return false;
     388             :     }
     389             : 
     390           2 :     if (!remove_success) {
     391             :         LOG("Error removing secret for key <%s>\n", key);
     392           1 :         return false;
     393             :     }
     394             : 
     395           1 :     return true;
     396             : }

Generated by: LCOV version 1.16