/* * Copyright (c) 2018 ARM Limited. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * 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. */ // ----------------------------------------------------------- Includes ----------------------------------------------------------- #include "securestore/SecureStore.h" #if SECURESTORE_ENABLED #include "aes.h" #include "cmac.h" #include "mbedtls/platform.h" #include "entropy.h" #include "DeviceKey.h" #include "mbed_assert.h" #include "mbed_wait_api.h" #include "mbed_error.h" #include <algorithm> #include <string.h> #include <stdio.h> using namespace mbed; // --------------------------------------------------------- Definitions ---------------------------------------------------------- static const uint32_t securestore_revision = 1; static const uint32_t enc_block_size = 16; static const uint32_t cmac_size = 16; static const uint32_t iv_size = 8; static const uint32_t scratch_buf_size = 256; static const uint32_t derived_key_size = 16; static const char *const enc_prefix = "ENC"; static const char *const auth_prefix = "AUTH"; static const uint32_t security_flags = KVStore::REQUIRE_CONFIDENTIALITY_FLAG | KVStore::REQUIRE_REPLAY_PROTECTION_FLAG; namespace { typedef struct { uint16_t metadata_size = 0u; uint16_t revision = 0u; uint32_t data_size = 0u; uint32_t create_flags = 0u; uint8_t iv[iv_size] = { 0u }; } record_metadata_t; // iterator handle typedef struct { KVStore::iterator_t underlying_it; } key_iterator_handle_t; } // incremental set handle struct SecureStore::inc_set_handle_t { record_metadata_t metadata; char *key = nullptr; uint32_t offset_in_data = 0u; uint8_t ctr_buf[enc_block_size] = { 0u }; mbedtls_aes_context enc_ctx; mbedtls_cipher_context_t auth_ctx; KVStore::set_handle_t underlying_handle; }; // -------------------------------------------------- Local Functions Declaration ---------------------------------------------------- // -------------------------------------------------- Functions Implementation ---------------------------------------------------- int encrypt_decrypt_start(mbedtls_aes_context &enc_aes_ctx, uint8_t *iv, const char *key, uint8_t *ctr_buf, uint8_t *salt_buf, int salt_buf_size) { DeviceKey &devkey = DeviceKey::get_instance(); char *salt = reinterpret_cast<char *>(salt_buf); uint8_t encrypt_key[derived_key_size]; strcpy(salt, enc_prefix); int pos = strlen(enc_prefix); strncpy(salt + pos, key, salt_buf_size - pos - 1); salt_buf[salt_buf_size - 1] = 0; int os_ret = devkey.generate_derived_key(salt_buf, strlen(salt), encrypt_key, DEVICE_KEY_16BYTE); if (os_ret) { return os_ret; } mbedtls_aes_init(&enc_aes_ctx); mbedtls_aes_setkey_enc(&enc_aes_ctx, encrypt_key, enc_block_size * 8); memcpy(ctr_buf, iv, iv_size); memset(ctr_buf + iv_size, 0, iv_size); return 0; } int encrypt_decrypt_data(mbedtls_aes_context &enc_aes_ctx, const uint8_t *in_buf, uint8_t *out_buf, uint32_t chunk_size, uint8_t *ctr_buf, size_t &aes_offs) { uint8_t stream_block[enc_block_size] = { 0 }; return mbedtls_aes_crypt_ctr(&enc_aes_ctx, chunk_size, &aes_offs, ctr_buf, stream_block, in_buf, out_buf); } int cmac_calc_start(mbedtls_cipher_context_t &auth_ctx, const char *key, uint8_t *salt_buf, int salt_buf_size) { DeviceKey &devkey = DeviceKey::get_instance(); char *salt = reinterpret_cast<char *>(salt_buf); uint8_t auth_key[derived_key_size]; strcpy(salt, auth_prefix); int pos = strlen(auth_prefix); strncpy(salt + pos, key, salt_buf_size - pos - 1); salt_buf[salt_buf_size - 1] = 0; int os_ret = devkey.generate_derived_key(salt_buf, strlen(salt), auth_key, DEVICE_KEY_16BYTE); if (os_ret) { return os_ret; } const mbedtls_cipher_info_t *cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB); mbedtls_cipher_init(&auth_ctx); if ((os_ret = mbedtls_cipher_setup(&auth_ctx, cipher_info)) != 0) { return os_ret; } os_ret = mbedtls_cipher_cmac_starts(&auth_ctx, auth_key, cmac_size * 8); if (os_ret != 0) { return os_ret; } return 0; } int cmac_calc_data(mbedtls_cipher_context_t &auth_ctx, const void *input, size_t ilen) { int os_ret; os_ret = mbedtls_cipher_cmac_update(&auth_ctx, static_cast<const uint8_t *>(input), ilen); return os_ret; } int cmac_calc_finish(mbedtls_cipher_context_t &auth_ctx, uint8_t *output) { int os_ret; os_ret = mbedtls_cipher_cmac_finish(&auth_ctx, output); return os_ret; } // Class member functions SecureStore::SecureStore(KVStore *underlying_kv, KVStore *rbp_kv) : _is_initialized(false), _underlying_kv(underlying_kv), _rbp_kv(rbp_kv), _entropy(0), _ih(0), _scratch_buf(0) { } SecureStore::~SecureStore() { deinit(); } int SecureStore::set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags) { int ret, os_ret; info_t info; bool enc_started = false, auth_started = false; if (!_is_initialized) { return MBED_ERROR_NOT_READY; } if (!is_valid_key(key)) { return MBED_ERROR_INVALID_ARGUMENT; } _mutex.lock(); *handle = reinterpret_cast<set_handle_t>(_ih); // Validate internal RBP data if (_rbp_kv) { ret = _rbp_kv->get_info(key, &info); if (ret == MBED_SUCCESS) { if (info.flags & WRITE_ONCE_FLAG) { // Trying to re-write a key that is write protected ret = MBED_ERROR_WRITE_PROTECTED; goto fail; } if (!(create_flags & REQUIRE_REPLAY_PROTECTION_FLAG)) { // Trying to re-write a key that that has REPLAY_PROTECTION // with a new key that has this flag not set. ret = MBED_ERROR_INVALID_ARGUMENT; goto fail; } } else if (ret != MBED_ERROR_ITEM_NOT_FOUND) { ret = MBED_ERROR_READ_FAILED; goto fail; } } else { // Only trust external flags, if internal RBP is not in use ret = _underlying_kv->get(key, &_ih->metadata, sizeof(record_metadata_t)); if (ret == MBED_SUCCESS) { // Must not remove RP flag, even though internal RBP KV is not in use. if (!(create_flags & REQUIRE_REPLAY_PROTECTION_FLAG) && (_ih->metadata.create_flags & REQUIRE_REPLAY_PROTECTION_FLAG)) { ret = MBED_ERROR_INVALID_ARGUMENT; goto fail; } // Existing key is write protected if (_ih->metadata.create_flags & WRITE_ONCE_FLAG) { ret = MBED_ERROR_WRITE_PROTECTED; goto fail; } } } // Fill metadata _ih->metadata.create_flags = create_flags; _ih->metadata.data_size = final_data_size; _ih->metadata.metadata_size = sizeof(record_metadata_t); _ih->metadata.revision = securestore_revision; if (create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { // generate a new random iv os_ret = mbedtls_entropy_func(_entropy, _ih->metadata.iv, iv_size); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto fail; } os_ret = encrypt_decrypt_start(_ih->enc_ctx, _ih->metadata.iv, key, _ih->ctr_buf, _scratch_buf, scratch_buf_size); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto fail; } enc_started = true; } else { memset(_ih->metadata.iv, 0, iv_size); } os_ret = cmac_calc_start(_ih->auth_ctx, key, _scratch_buf, scratch_buf_size); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto fail; } auth_started = true; // Although name is not part of the data, we calculate CMAC on it as well os_ret = cmac_calc_data(_ih->auth_ctx, key, strlen(key)); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto fail; } os_ret = cmac_calc_data(_ih->auth_ctx, &_ih->metadata, sizeof(record_metadata_t)); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto fail; } _ih->offset_in_data = 0; _ih->key = 0; // Should strip security flags from underlying storage ret = _underlying_kv->set_start(&_ih->underlying_handle, key, sizeof(record_metadata_t) + final_data_size + cmac_size, create_flags & ~security_flags); if (ret) { goto fail; } ret = _underlying_kv->set_add_data(_ih->underlying_handle, &_ih->metadata, sizeof(record_metadata_t)); if (ret) { goto fail; } if (create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG)) { _ih->key = new char[strlen(key) + 1]; strcpy(_ih->key, key); } goto end; fail: if (enc_started) { mbedtls_aes_free(&_ih->enc_ctx); } if (auth_started) { mbedtls_cipher_free(&_ih->auth_ctx); } // mark handle as invalid by clearing metadata size field in header _ih->metadata.metadata_size = 0; _mutex.unlock(); end: return ret; } int SecureStore::set_add_data(set_handle_t handle, const void *value_data, size_t data_size) { size_t aes_offs = 0; int os_ret, ret = MBED_SUCCESS; const uint8_t *src_ptr; if (reinterpret_cast<inc_set_handle_t *>(handle) != _ih) { return MBED_ERROR_INVALID_ARGUMENT; } if (!value_data && data_size) { return MBED_ERROR_INVALID_ARGUMENT; } if (!_ih->metadata.metadata_size) { return MBED_ERROR_INVALID_ARGUMENT; } if (_ih->offset_in_data + data_size > _ih->metadata.data_size) { ret = MBED_ERROR_INVALID_SIZE; goto end; } src_ptr = static_cast<const uint8_t *>(value_data); while (data_size) { uint32_t chunk_size; const uint8_t *dst_ptr; if (_ih->metadata.create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { // In encrypt mode we don't want to allocate a buffer in the size given by the user - // Encrypt the data chunk by chunk chunk_size = std::min((uint32_t) data_size, scratch_buf_size); dst_ptr = _scratch_buf; os_ret = encrypt_decrypt_data(_ih->enc_ctx, src_ptr, _scratch_buf, chunk_size, _ih->ctr_buf, aes_offs); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto fail; } } else { chunk_size = data_size; dst_ptr = static_cast <const uint8_t *>(value_data); } os_ret = cmac_calc_data(_ih->auth_ctx, dst_ptr, chunk_size); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto fail; } ret = _underlying_kv->set_add_data(_ih->underlying_handle, dst_ptr, chunk_size); if (ret) { goto fail; } data_size -= chunk_size; src_ptr += chunk_size; _ih->offset_in_data += chunk_size; } goto end; fail: if (_ih->key) { delete[] _ih->key; } if (_ih->metadata.create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { mbedtls_aes_free(&_ih->enc_ctx); } mbedtls_cipher_free(&_ih->auth_ctx); // mark handle as invalid by clearing metadata size field in header _ih->metadata.metadata_size = 0; _mutex.unlock(); end: return ret; } int SecureStore::set_finalize(set_handle_t handle) { int os_ret, ret = MBED_SUCCESS; uint8_t cmac[cmac_size] = {0}; if (reinterpret_cast<inc_set_handle_t *>(handle) != _ih) { return MBED_ERROR_INVALID_ARGUMENT; } if (!_ih->metadata.metadata_size) { return MBED_ERROR_INVALID_ARGUMENT; } if (_ih->offset_in_data != _ih->metadata.data_size) { ret = MBED_ERROR_INVALID_SIZE; goto end; } os_ret = cmac_calc_finish(_ih->auth_ctx, cmac); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } ret = _underlying_kv->set_add_data(_ih->underlying_handle, cmac, cmac_size); if (ret) { goto end; } ret = _underlying_kv->set_finalize(_ih->underlying_handle); if (ret) { goto end; } if (_rbp_kv && (_ih->metadata.create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG))) { // In rollback protect case, we need to store CMAC in RBP store. // If it's also write once case, set write once flag in the RBP key as well. // Use RBP storage also in write once case only - in order to prevent attacks removing // a written once value from underlying KV. ret = _rbp_kv->set(_ih->key, cmac, cmac_size, _ih->metadata.create_flags & WRITE_ONCE_FLAG); delete[] _ih->key; if (ret) { goto end; } } end: // mark handle as invalid by clearing metadata size field in header _ih->metadata.metadata_size = 0; if (_ih->metadata.create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { mbedtls_aes_free(&_ih->enc_ctx); } mbedtls_cipher_free(&_ih->auth_ctx); _mutex.unlock(); return ret; } int SecureStore::set(const char *key, const void *buffer, size_t size, uint32_t create_flags) { int ret; set_handle_t handle; // Don't wait till we get to set_add_data to catch this if (!buffer && size) { return MBED_ERROR_INVALID_ARGUMENT; } ret = set_start(&handle, key, size, create_flags); if (ret) { return ret; } ret = set_add_data(handle, buffer, size); if (ret) { return ret; } ret = set_finalize(handle); return ret; } int SecureStore::remove(const char *key) { info_t info; _mutex.lock(); int ret = do_get(key, 0, 0, 0, 0, &info); // Allow deleting key if read error is of our own errors if ((ret != MBED_SUCCESS) && (ret != MBED_ERROR_AUTHENTICATION_FAILED) && (ret != MBED_ERROR_RBP_AUTHENTICATION_FAILED)) { goto end; } if (ret == 0 && info.flags & WRITE_ONCE_FLAG) { ret = MBED_ERROR_WRITE_PROTECTED; goto end; } ret = _underlying_kv->remove(key); if (ret) { goto end; } if (_rbp_kv && (info.flags & REQUIRE_REPLAY_PROTECTION_FLAG)) { ret = _rbp_kv->remove(key); if ((ret != MBED_SUCCESS) && (ret != MBED_ERROR_ITEM_NOT_FOUND)) { goto end; } } ret = MBED_SUCCESS; end: _mutex.unlock(); return ret; } int SecureStore::do_get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, size_t offset, info_t *info) { int os_ret, ret; bool rbp_key_exists = false; uint8_t rbp_cmac[cmac_size]; size_t aes_offs = 0; uint32_t data_size; uint32_t actual_data_size; uint32_t current_offset; uint32_t chunk_size; uint32_t enc_lead_size; uint8_t *dest_buf; bool enc_started = false, auth_started = false; uint32_t create_flags; size_t read_len; info_t rbp_info; if (!is_valid_key(key)) { return MBED_ERROR_INVALID_ARGUMENT; } if (_rbp_kv) { ret = _rbp_kv->get_info(key, &rbp_info); if (ret == MBED_SUCCESS) { rbp_key_exists = true; ret = _rbp_kv->get(key, rbp_cmac, cmac_size, &read_len); if (ret) { goto end; } if ((read_len != cmac_size) || (rbp_info.size != cmac_size)) { ret = MBED_ERROR_RBP_AUTHENTICATION_FAILED; } } else if (ret != MBED_ERROR_ITEM_NOT_FOUND) { goto end; } } ret = _underlying_kv->get(key, &_ih->metadata, sizeof(record_metadata_t), &read_len); if (ret) { // In case we have the key in the RBP KV, then even if the key wasn't found in // the underlying KV, we may have been exposed to an attack. Return an RBP authentication error. if (rbp_key_exists) { ret = MBED_ERROR_RBP_AUTHENTICATION_FAILED; } goto end; } // Validate header size if ((read_len != sizeof(record_metadata_t)) || (_ih->metadata.metadata_size != sizeof(record_metadata_t))) { ret = MBED_ERROR_RBP_AUTHENTICATION_FAILED; goto end; } create_flags = _ih->metadata.create_flags; if (!_rbp_kv) { create_flags &= ~REQUIRE_REPLAY_PROTECTION_FLAG; } // Another potential attack case - key hasn't got the RP flag set, but exists in the RBP KV if (rbp_key_exists && !(create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG))) { ret = MBED_ERROR_RBP_AUTHENTICATION_FAILED; goto end; } os_ret = cmac_calc_start(_ih->auth_ctx, key, _scratch_buf, scratch_buf_size); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } auth_started = true; // Although name is not part of the data, we calculate CMAC on it as well os_ret = cmac_calc_data(_ih->auth_ctx, key, strlen(key)); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } os_ret = cmac_calc_data(_ih->auth_ctx, &_ih->metadata, sizeof(record_metadata_t)); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } if (create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { os_ret = encrypt_decrypt_start(_ih->enc_ctx, _ih->metadata.iv, key, _ih->ctr_buf, _scratch_buf, scratch_buf_size); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } enc_started = true; } data_size = _ih->metadata.data_size; actual_data_size = std::min((uint32_t) buffer_size, data_size - offset); current_offset = 0; enc_lead_size = 0; while (data_size) { // Make sure we read to the user buffer only between offset and offset + actual_data_size if ((current_offset >= offset) && (current_offset < offset + actual_data_size)) { dest_buf = (static_cast <uint8_t *>(buffer)) + enc_lead_size; chunk_size = actual_data_size - enc_lead_size; enc_lead_size = 0; } else { dest_buf = _scratch_buf; if (current_offset < offset) { chunk_size = std::min(scratch_buf_size, offset - current_offset); // A special case: encrypted user data starts at a middle of an encryption block. // In this case, we need to read entire block into our scratch buffer, and copy // the encrypted lead size to the user buffer start if ((create_flags & REQUIRE_CONFIDENTIALITY_FLAG) && (chunk_size % enc_block_size)) { enc_lead_size = std::min(enc_block_size - chunk_size % enc_block_size, actual_data_size); chunk_size += enc_lead_size; } } else { chunk_size = std::min(scratch_buf_size, data_size); enc_lead_size = 0; } } ret = _underlying_kv->get(key, dest_buf, chunk_size, 0, _ih->metadata.metadata_size + current_offset); if (ret != MBED_SUCCESS) { goto end; } os_ret = cmac_calc_data(_ih->auth_ctx, dest_buf, chunk_size); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } if (create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { // Decrypt data in place os_ret = encrypt_decrypt_data(_ih->enc_ctx, dest_buf, dest_buf, chunk_size, _ih->ctr_buf, aes_offs); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } if (enc_lead_size) { // Now copy decrypted lead size to user buffer start memcpy(buffer, dest_buf + chunk_size - enc_lead_size, enc_lead_size); } } current_offset += chunk_size; data_size -= chunk_size; } if (actual_size) { *actual_size = actual_data_size; } uint8_t calc_cmac[cmac_size], read_cmac[cmac_size]; os_ret = cmac_calc_finish(_ih->auth_ctx, calc_cmac); if (os_ret) { ret = MBED_ERROR_FAILED_OPERATION; goto end; } // Check with record CMAC ret = _underlying_kv->get(key, read_cmac, cmac_size, 0, _ih->metadata.metadata_size + _ih->metadata.data_size); if (ret) { goto end; } if (memcmp(calc_cmac, read_cmac, cmac_size) != 0) { ret = MBED_ERROR_AUTHENTICATION_FAILED; goto end; } // If rollback protect, check also CMAC stored in RBP store if (_rbp_kv && (create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG))) { if (!rbp_key_exists) { ret = MBED_ERROR_RBP_AUTHENTICATION_FAILED; goto end; } if (memcmp(calc_cmac, rbp_cmac, cmac_size) != 0) { ret = MBED_ERROR_RBP_AUTHENTICATION_FAILED; goto end; } } if (info) { info->flags = _ih->metadata.create_flags; info->size = _ih->metadata.data_size; } end: _ih->metadata.metadata_size = 0; if (enc_started) { mbedtls_aes_free(&_ih->enc_ctx); } if (auth_started) { mbedtls_cipher_free(&_ih->auth_ctx); } return ret; } int SecureStore::get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, size_t offset) { _mutex.lock(); int ret = do_get(key, buffer, buffer_size, actual_size, offset); _mutex.unlock(); return ret; } int SecureStore::get_info(const char *key, info_t *info) { _mutex.lock(); int ret = do_get(key, 0, 0, 0, 0, info); _mutex.unlock(); return ret; } int SecureStore::init() { int ret = MBED_SUCCESS; MBED_ASSERT(!(scratch_buf_size % enc_block_size)); if (scratch_buf_size % enc_block_size) { return MBED_SYSTEM_ERROR_BASE; } _mutex.lock(); #if defined(MBEDTLS_PLATFORM_C) ret = mbedtls_platform_setup(NULL); if (ret) { goto fail; } #endif /* MBEDTLS_PLATFORM_C */ _entropy = new mbedtls_entropy_context; mbedtls_entropy_init(_entropy); _scratch_buf = new uint8_t[scratch_buf_size]; _ih = new inc_set_handle_t; ret = _underlying_kv->init(); if (ret) { goto fail; } if (_rbp_kv) { ret = _rbp_kv->init(); if (ret) { goto fail; } } _is_initialized = true; fail: _mutex.unlock(); return ret; } int SecureStore::deinit() { _mutex.lock(); int ret; if (_is_initialized) { if (_entropy) { mbedtls_entropy_free(_entropy); delete _entropy; delete _ih; delete[] _scratch_buf; _entropy = nullptr; } ret = _underlying_kv->deinit(); if (ret) { goto END; } if (_rbp_kv) { ret = _rbp_kv->deinit(); if (ret) { goto END; } } } _is_initialized = false; #if defined(MBEDTLS_PLATFORM_C) mbedtls_platform_teardown(NULL); #endif /* MBEDTLS_PLATFORM_C */ ret = MBED_SUCCESS; END: _mutex.unlock(); return ret; } int SecureStore::reset() { int ret; if (!_is_initialized) { return MBED_ERROR_NOT_READY; } _mutex.lock(); ret = _underlying_kv->reset(); if (ret) { goto end; } if (_rbp_kv) { ret = _rbp_kv->reset(); if (ret) { goto end; } } end: _mutex.unlock(); return ret; } int SecureStore::iterator_open(iterator_t *it, const char *prefix) { key_iterator_handle_t *handle; if (!_is_initialized) { return MBED_ERROR_NOT_READY; } if (!it) { return MBED_ERROR_INVALID_ARGUMENT; } handle = new key_iterator_handle_t; *it = reinterpret_cast<iterator_t>(handle); return _underlying_kv->iterator_open(&handle->underlying_it, prefix); } int SecureStore::iterator_next(iterator_t it, char *key, size_t key_size) { key_iterator_handle_t *handle; if (!_is_initialized) { return MBED_ERROR_NOT_READY; } handle = reinterpret_cast<key_iterator_handle_t *>(it); return _underlying_kv->iterator_next(handle->underlying_it, key, key_size); } int SecureStore::iterator_close(iterator_t it) { key_iterator_handle_t *handle; int ret; if (!_is_initialized) { return MBED_ERROR_NOT_READY; } handle = reinterpret_cast<key_iterator_handle_t *>(it); ret = _underlying_kv->iterator_close(handle->underlying_it); delete handle; return ret; } #endif