diff --git a/README.md b/README.md index 5164809..b59eac0 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Additionaly, thanks to the Arduino library [HomeSpan](https://github.com/HomeSpa ## Overview +Only the PN532 is supported as an NFC module. + - Only the FAST and STANDARD flows are implementated right now, the ATTESTATION flow will be implemented at a later time since STANDARD can be used in most cases - Lock State can be received and controlled via MQTT through user-defined topics - Any NFC Target that's not identified as homekey will skip the flow and at least at the moment it only just publishes the UID, ATQA and SAK to the same MQTT topic as homekey with the property `homekey` set to false @@ -17,13 +19,29 @@ The code still needs some working so it's very much a work in progress, but the Goal of the project is to make it easy to add the homekey functionality to locks that don't support it or to anything for that matter :) . +## Wiring + +The current implementation is using SPI for communication. + +Pins are the default Arduino pins for SPI which should be as follows: + +GPIO18 - SCK + +GPIO19 - MISO + +GPIO23 - MOSI + +GPIO5 - SS + ## Configuration Currently the WiFi can only be configured from the terminal, though the library [HomeSpan](https://github.com/HomeSpan/HomeSpan) that this is based on also has the option of a hotspot but requires a button so haven't bothered with it. ### WIFI -To connect it to WiFi, open the serial terminal, press W + Return, and now it should start searching for networks and then proceed accordingly. +To connect it to WiFi there are two options +- Open the serial terminal, press W + Return, and now it should start searching for networks from which to select. +- Open the serial terminal, press A to start a temporary Access Point then connect to the Wifi network "HomeSpan-Setup" with the password `homespan` and if you are on a phone it should automatically open up the page where you can configure the Wifi credentials, alternatively you can access the page manually on `http://192.168.4.1/hotspot-detect.html` ### HomeKit diff --git a/platformio.ini b/platformio.ini index 854fa1e..f6f3871 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,6 +18,7 @@ framework = arduino, espidf monitor_speed = 115200 monitor_echo = yes monitor_filters = + direct esp32_exception_decoder log2file lib_ldf_mode = deep @@ -34,7 +35,7 @@ build_flags = -DCONFIG_LOG_COLORS -std=gnu++17 build_unflags = - -Os + -Osfas -std=gnu++11 [env:debug] diff --git a/sdkconfig.debug b/sdkconfig.debug index d3d8558..3acd904 100644 --- a/sdkconfig.debug +++ b/sdkconfig.debug @@ -841,7 +841,7 @@ CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # CONFIG_LWIP_L2_TO_L3_COPY is not set CONFIG_LWIP_IRAM_OPTIMIZATION=y CONFIG_LWIP_TIMERS_ONDEMAND=y -CONFIG_LWIP_MAX_SOCKETS=14 +CONFIG_LWIP_MAX_SOCKETS=16 # CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set # CONFIG_LWIP_SO_LINGER is not set CONFIG_LWIP_SO_REUSE=y diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 37fb63d..e4b881f 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -841,7 +841,7 @@ CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # CONFIG_LWIP_L2_TO_L3_COPY is not set CONFIG_LWIP_IRAM_OPTIMIZATION=y CONFIG_LWIP_TIMERS_ONDEMAND=y -CONFIG_LWIP_MAX_SOCKETS=14 +CONFIG_LWIP_MAX_SOCKETS=16 # CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set # CONFIG_LWIP_SO_LINGER is not set CONFIG_LWIP_SO_REUSE=y diff --git a/sdkconfig.ota b/sdkconfig.ota index a19d496..42906b1 100644 --- a/sdkconfig.ota +++ b/sdkconfig.ota @@ -842,7 +842,7 @@ CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # CONFIG_LWIP_L2_TO_L3_COPY is not set CONFIG_LWIP_IRAM_OPTIMIZATION=y CONFIG_LWIP_TIMERS_ONDEMAND=y -CONFIG_LWIP_MAX_SOCKETS=14 +CONFIG_LWIP_MAX_SOCKETS=16 # CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set # CONFIG_LWIP_SO_LINGER is not set CONFIG_LWIP_SO_REUSE=y diff --git a/sdkconfig.release b/sdkconfig.release index 1718dee..09da94b 100644 --- a/sdkconfig.release +++ b/sdkconfig.release @@ -842,7 +842,7 @@ CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # CONFIG_LWIP_L2_TO_L3_COPY is not set CONFIG_LWIP_IRAM_OPTIMIZATION=y CONFIG_LWIP_TIMERS_ONDEMAND=y -CONFIG_LWIP_MAX_SOCKETS=14 +CONFIG_LWIP_MAX_SOCKETS=16 # CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set # CONFIG_LWIP_SO_LINGER is not set CONFIG_LWIP_SO_REUSE=y diff --git a/src/auth/authContext.cpp b/src/auth/authContext.cpp index cfa1307..92ecf05 100644 --- a/src/auth/authContext.cpp +++ b/src/auth/authContext.cpp @@ -2,70 +2,13 @@ #define LOG(x, format, ...) ESP_LOG##x(TAG, "%s > " format , __FUNCTION__ __VA_OPT__(,) __VA_ARGS__) /** - * The function performs an elliptic curve Diffie-Hellman key exchange to compute a shared key between - * the reader and the endpoint. - * - * @param outBuf The `outBuf` parameter is a pointer to a buffer where the computed shared key will be - * stored. It should be allocated with enough space to hold `oLen` bytes. - * @param oLen oLen is the length of the output buffer (outBuf) where the shared key will be written. + * The HKAuthenticationContext constructor initializes various member variables and generates an + * ephemeral key for the reader. + * + * @param nfcInDataExchange nfcInDataExchange is a function pointer that points to a function with the + * following signature: bool (*)(uint8_t *data, size_t lenData, uint8_t *res, uint8_t *resLen) + * @param readerData readerData is a reference to an object of type homeKeyReader::readerData_t. */ -void HKAuthenticationContext::get_shared_key(uint8_t *outBuf, size_t oLen) -{ - mbedtls_ecp_group grp; - mbedtls_mpi reader_ephemeral_private_key, shared_key; - mbedtls_ecp_point endpoint_ephemeral_public_key; - - mbedtls_ecp_group_init(&grp); - mbedtls_mpi_init(&reader_ephemeral_private_key); - mbedtls_mpi_init(&shared_key); - mbedtls_ecp_point_init(&endpoint_ephemeral_public_key); - - // Initialize the elliptic curve group (e.g., SECP256R1) - mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); - - // Set the reader's ephemeral private key - int mpi_read = mbedtls_mpi_read_binary(&reader_ephemeral_private_key, readerEphPrivKey.data(), readerEphPrivKey.size()); - - if(mpi_read != 0){ - LOG(E, "get_shared_key > mpi_read - %s: %s", mbedtls_high_level_strerr(mpi_read), mbedtls_low_level_strerr(mpi_read)); - return; - } - - // Set the endpoint's ephemeral public key - int ecp_read = mbedtls_ecp_point_read_binary(&grp, &endpoint_ephemeral_public_key, endpointEphPubKey.data(), endpointEphPubKey.size()); - if(ecp_read != 0){ - LOG(E, "get_shared_key > ecp_read - %s: %s", mbedtls_high_level_strerr(ecp_read), mbedtls_low_level_strerr(ecp_read)); - return; - } - - // Perform key exchange - int ecdh_compute_shared = mbedtls_ecdh_compute_shared(&grp, &shared_key, &endpoint_ephemeral_public_key, &reader_ephemeral_private_key, - esp_rng, NULL); - if(ecdh_compute_shared != 0){ - LOG(E, "get_shared_key > ecdh_compute_shared - %s: %s", mbedtls_high_level_strerr(ecdh_compute_shared), mbedtls_low_level_strerr(ecdh_compute_shared)); - return; - } - - int mpi_write = mbedtls_mpi_write_binary(&shared_key, outBuf, oLen); - if(mpi_write != 0){ - LOG(E, "get_shared_key > mpi_write - %s: %s", mbedtls_high_level_strerr(mpi_write), mbedtls_low_level_strerr(mpi_write)); - return; - } - - mbedtls_ecp_group_free(&grp); - mbedtls_mpi_free(&reader_ephemeral_private_key); - mbedtls_mpi_free(&shared_key); - mbedtls_ecp_point_free(&endpoint_ephemeral_public_key); -} - -/** - * The function `HKAuthenticationContext::HKAuthenticationContext` initializes the HKAuthenticationContext - * object by generating an ephemeral key, filling the transaction identifier with random data, and - * copying reader identifiers. - * - * @param nfc A pointer to an instance of the PN532 class, which is used for NFC communication. - * @param readerData readerData is a pointer to a structure of type readerData_t. - */ HKAuthenticationContext::HKAuthenticationContext(bool (*nfcInDataExchange)(uint8_t *data, size_t lenData, uint8_t *res, uint8_t *resLen), homeKeyReader::readerData_t &readerData) : readerData(readerData), nfcInDataExchange(nfcInDataExchange) { auto startTime = std::chrono::high_resolution_clock::now(); @@ -80,41 +23,80 @@ HKAuthenticationContext::HKAuthenticationContext(bool (*nfcInDataExchange)(uint8 readerIdentifier.insert(readerIdentifier.end(), readerData.identifier, readerData.identifier + sizeof(readerData.identifier)); readerEphX = std::move(get_x(readerEphPubKey)); auto stopTime = std::chrono::high_resolution_clock::now(); + endpointEphX = std::vector(); + endpointEphPubKey = std::vector(); LOG(I, "Initialization Time: %lli ms", std::chrono::duration_cast(stopTime - startTime).count()); } +/** + * The function `HKAuthenticationContext::authenticate` performs authentication using various + * parameters and returns the result along with the authentication flow type. + * + * @param defaultToStd The parameter "defaultToStd" is a boolean flag that determines whether the + * authentication process should default to the standard flow. + * @param savedData The parameter `savedData` is a reference to an `nvs_handle` object. It is used to + * store and retrieve data in the Non-Volatile Storage (NVS) of the device. The NVS is a key-value + * storage system that allows persistent storage of data even when the device is + * + * @return a tuple containing three elements: + * 1. A pointer to a uint8_t array. + * 2. A pointer to a uint8_t array. + * 3. An enum value of type `homeKeyReader::KeyFlow`. + */ std::tuple HKAuthenticationContext::authenticate(bool defaultToStd, nvs_handle &savedData){ auto startTime = std::chrono::high_resolution_clock::now(); - auto fastAuth = fast_auth(defaultToStd); + uint8_t prot_v_data[2] = {0x02, 0x0}; + + std::vector fastTlv(sizeof(prot_v_data) + readerEphPubKey.size() + transactionIdentifier.size() + readerIdentifier.size() + 8); + size_t len = 0; + utils::simple_tlv(0x5C, prot_v_data, sizeof(prot_v_data), fastTlv.data(), &len); + + utils::simple_tlv(0x87, readerEphPubKey.data(), readerEphPubKey.size(), fastTlv.data() + len, &len); + + utils::simple_tlv(0x4C, transactionIdentifier.data(), transactionIdentifier.size(), fastTlv.data() + len, &len); + + utils::simple_tlv(0x4D, readerIdentifier.data(), readerIdentifier.size(), fastTlv.data() + len, &len); + std::vector apdu{0x80, 0x80, 0x01, 0x01, (uint8_t)len}; + apdu.insert(apdu.begin() + 5, fastTlv.begin(), fastTlv.end()); + uint8_t response[128]; + uint8_t responseLength = 128; + LOG(D, "Auth0 APDU Length: %d, DATA: %s", apdu.size(), utils::bufToHexString(apdu.data(), apdu.size()).c_str()); + nfcInDataExchange(apdu.data(), apdu.size(), response, &responseLength); + LOG(D, "Auth0 Response Length: %d, DATA: %s", responseLength, utils::bufToHexString(response, responseLength).c_str()); homeKeyIssuer::issuer_t *foundIssuer = nullptr; homeKeyEndpoint::endpoint_t *foundEndpoint = nullptr; - if (std::get<1>(fastAuth) != nullptr && std::get<2>(fastAuth) != homeKeyReader::kFlowFailed) - { - foundIssuer = std::get<0>(fastAuth); - foundEndpoint = std::get<1>(fastAuth); - auto stopTime = std::chrono::high_resolution_clock::now(); - LOG(I, "Home Key authenticated, transaction took %lli ms", std::chrono::duration_cast(stopTime - startTime).count()); - return std::make_tuple(foundIssuer->issuerId, foundEndpoint->endpointId, homeKeyReader::kFlowFAST); - } - else if (std::get<2>(fastAuth) == homeKeyReader::kFlowSTANDARD) - { - auto stdAuth = std_auth(); - foundIssuer = std::get<0>(stdAuth); - foundEndpoint = std::get<1>(stdAuth); - if (foundEndpoint != nullptr && std::get<4>(stdAuth) == homeKeyReader::kFlowSTANDARD) + if(!defaultToStd){ + auto fastAuth = fast_auth(response, responseLength); + if (std::get<1>(fastAuth) != nullptr && std::get<2>(fastAuth) != homeKeyReader::kFlowFailed) { + foundIssuer = std::get<0>(fastAuth); + foundEndpoint = std::get<1>(fastAuth); auto stopTime = std::chrono::high_resolution_clock::now(); LOG(I, "Home Key authenticated, transaction took %lli ms", std::chrono::duration_cast(stopTime - startTime).count()); - std::vector persistentKey = std::get<3>(stdAuth); - memcpy(foundEndpoint->persistent_key, persistentKey.data(), 32); - json serializedData = readerData; - auto msgpack = json::to_msgpack(serializedData); - esp_err_t set_nvs = nvs_set_blob(savedData, "READERDATA", msgpack.data(), msgpack.size()); - esp_err_t commit_nvs = nvs_commit(savedData); - LOG(V, "NVS SET STATUS: %s", esp_err_to_name(set_nvs)); - LOG(V, "NVS COMMIT STATUS: %s", esp_err_to_name(commit_nvs)); - return std::make_tuple(foundIssuer->issuerId, foundEndpoint->endpointId, homeKeyReader::kFlowSTANDARD); + return std::make_tuple(foundIssuer->issuerId, foundEndpoint->endpointId, homeKeyReader::kFlowFAST); } + } else { + auto Auth0Res = BERTLV::unpack_array(response, responseLength); + auto endpointPubKey = BERTLV::findTag(kEndpoint_Public_Key, Auth0Res); + endpointEphPubKey = std::move(endpointPubKey.value); + endpointEphX = std::move(get_x(endpointEphPubKey)); + } + auto stdAuth = std_auth(); + foundIssuer = std::get<0>(stdAuth); + foundEndpoint = std::get<1>(stdAuth); + if (foundEndpoint != nullptr && std::get<4>(stdAuth) == homeKeyReader::kFlowSTANDARD) + { + auto stopTime = std::chrono::high_resolution_clock::now(); + LOG(I, "Home Key authenticated, transaction took %lli ms", std::chrono::duration_cast(stopTime - startTime).count()); + std::vector persistentKey = std::get<3>(stdAuth); + memcpy(foundEndpoint->persistent_key, persistentKey.data(), 32); + json serializedData = readerData; + auto msgpack = json::to_msgpack(serializedData); + esp_err_t set_nvs = nvs_set_blob(savedData, "READERDATA", msgpack.data(), msgpack.size()); + esp_err_t commit_nvs = nvs_commit(savedData); + LOG(V, "NVS SET STATUS: %s", esp_err_to_name(set_nvs)); + LOG(V, "NVS COMMIT STATUS: %s", esp_err_to_name(commit_nvs)); + return std::make_tuple(foundIssuer->issuerId, foundEndpoint->endpointId, homeKeyReader::kFlowSTANDARD); } return std::make_tuple(foundIssuer->issuerId, foundEndpoint->endpointId, homeKeyReader::kFlowFailed); } @@ -191,6 +173,14 @@ void HKAuthenticationContext::Auth1_keying_material(uint8_t *keyingMaterial, con mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, keyingMaterial, 32, dataMaterial, olen, out, outLen); } +/** + * The function `HKAuthenticationContext::commandFlow` sends the command flow status APDU command + * and returns the response. + * + * @param status The parameter "status" is of type "homeKeyReader::CommandFlowStatus" + * + * @return a std::vector object, which contains the received response + */ std::vector HKAuthenticationContext::commandFlow(homeKeyReader::CommandFlowStatus status) { uint8_t apdu[4] = {0x80, 0x3c, status, status == homeKeyReader::kCmdFlowAttestation ? (uint8_t)0xa0 : (uint8_t)0x0}; @@ -219,10 +209,10 @@ std::tuple HKAuthentic for (auto &&endpoint : issuer.endpoints) { LOG(V, "Endpoint: %s, Persistent Key: %s", utils::bufToHexString(endpoint.endpointId, sizeof(endpoint.endpointId)).c_str(), utils::bufToHexString(endpoint.persistent_key, sizeof(endpoint.persistent_key)).c_str()); - uint8_t hkdf[58]; - Auth0_keying_material("VolatileFast", endpoint.endpoint_key_x, endpoint.persistent_key, hkdf, sizeof(hkdf)); - LOG(V, "HKDF Derived Key: %s", utils::bufToHexString(hkdf, sizeof(hkdf)).c_str()); - if (!memcmp(hkdf, cryptogram.data(), 16)) + std::vector hkdf(58, 0); + Auth0_keying_material("VolatileFast", endpoint.endpoint_key_x, endpoint.persistent_key, hkdf.data(), hkdf.size()); + LOG(V, "HKDF Derived Key: %s", utils::bufToHexString(hkdf.data(), hkdf.size()).c_str()); + if (!memcmp(hkdf.data(), cryptogram.data(), 16)) { LOG(D, "Endpoint %s matches cryptogram", utils::bufToHexString(endpoint.endpointId, sizeof(endpoint.endpointId)).c_str()); foundEndpoint = &endpoint; @@ -251,7 +241,7 @@ void HKAuthenticationContext::Auth1_keys_generator(uint8_t *persistentKey, uint8 { uint8_t sharedKey[32]; - get_shared_key(sharedKey, sizeof(sharedKey)); + get_shared_key(readerEphPrivKey, endpointEphPubKey, sharedKey, sizeof(sharedKey)); LOG(D, "Shared Key: %s", utils::bufToHexString(sharedKey, 32).c_str()); X963KDF kdf(MBEDTLS_MD_SHA256, 32, transactionIdentifier.data(), 16); @@ -267,70 +257,45 @@ void HKAuthenticationContext::Auth1_keys_generator(uint8_t *persistentKey, uint8 } /** - * The function `fast_auth` performs a fast authentication process and returns the endpoint and - * authentication flow type. - * - * @param fallbackToStd The `fallbackToStd` parameter is a boolean flag that indicates whether to - * fallback to the standard authentication flow if the fast authentication flow fails. If - * `fallbackToStd` is `true`, the function will return a tuple with `endpoint` set to `nullptr` and the - * second value set to ` - * - * @return a std::tuple containing a pointer to an endpoint_t object and an integer value. + * Performs a fast authentication process using the given data and returns the + * issuer, endpoint, and key flow status. + * + * @param data A pointer to an array of uint8_t (unsigned 8-bit integers) representing a TLV object + * that should contain the endpoint's public key and a cryptogram + * @param dataLen Length of the `data` array + * + * @return a tuple containing three elements: a pointer to the issuer, a pointer to the endpoint, and a + * value of the enum type `homeKeyReader::KeyFlow` */ -std::tuple HKAuthenticationContext::fast_auth(bool fallbackToStd) +std::tuple HKAuthenticationContext::fast_auth(uint8_t *data, size_t dataLen) { - uint8_t prot_v_data[2] = {0x02, 0x0}; - - std::vector fastTlv(sizeof(prot_v_data) + readerEphPubKey.size() + transactionIdentifier.size() + readerIdentifier.size() + 8); - size_t len = 0; - utils::simple_tlv(0x5C, prot_v_data, sizeof(prot_v_data), fastTlv.data(), &len); - - utils::simple_tlv(0x87, readerEphPubKey.data(), readerEphPubKey.size(), fastTlv.data() + len, &len); - - utils::simple_tlv(0x4C, transactionIdentifier.data(), transactionIdentifier.size(), fastTlv.data() + len, &len); - - utils::simple_tlv(0x4D, readerIdentifier.data(), readerIdentifier.size(), fastTlv.data() + len, &len); - std::vector apdu{0x80, 0x80, 0x01, 0x01, (uint8_t)len}; - apdu.insert(apdu.begin() + 5, fastTlv.begin(), fastTlv.end()); - uint8_t response[128]; - uint8_t responseLength = 128; - LOG(D, "fast_auth: Auth0 APDU Length: %d, DATA: %s", apdu.size(), utils::bufToHexString(apdu.data(), apdu.size()).c_str()); - nfcInDataExchange(apdu.data(), apdu.size(), response, &responseLength); - LOG(D, "fast_auth: Auth0 Response Length: %d, DATA: %s", responseLength, utils::bufToHexString(response, responseLength).c_str()); homeKeyIssuer::issuer_t *issuer = nullptr; homeKeyEndpoint::endpoint_t *endpoint = nullptr; - if (response[responseLength - 2] == 0x90 && response[0] == 0x86) + if (data[dataLen - 2] == 0x90 && data[0] == 0x86) { - auto Auth0Res = BERTLV::unpack_array(response, responseLength); + auto Auth0Res = BERTLV::unpack_array(data, dataLen); auto endpointPubKey = BERTLV::findTag(kEndpoint_Public_Key, Auth0Res); - endpointEphPubKey = std::move(endpointPubKey.value); + endpointEphPubKey = endpointPubKey.value; auto encryptedMessage = BERTLV::findTag(kAuth0_Cryptogram, Auth0Res); endpointEphX = std::move(get_x(endpointEphPubKey)); - if (fallbackToStd) + auto foundData = find_endpoint_by_cryptogram(encryptedMessage.value); + endpoint = std::get<1>(foundData); + issuer = std::get<0>(foundData); + if (endpoint != nullptr) { - return std::make_tuple(issuer, endpoint, homeKeyReader::kFlowSTANDARD); + LOG(D, "Endpoint %s Authenticated via FAST Flow", utils::bufToHexString(endpoint->endpointId, sizeof(endpoint->endpointId), true).c_str()); + std::vector cmdFlowStatus = commandFlow(homeKeyReader::kCmdFlowSuccess); + LOG(D, "RESPONSE: %s, Length: %d", utils::bufToHexString(cmdFlowStatus.data(), cmdFlowStatus.size()).c_str(), cmdFlowStatus.size()); + if (cmdFlowStatus.data()[0] == 0x90) + { + LOG(D, "Command Status 0x90, FAST Flow Complete"); + return std::make_tuple(issuer, endpoint, homeKeyReader::kFlowFAST); + } } else { - auto foundData = find_endpoint_by_cryptogram(encryptedMessage.value); - endpoint = std::get<1>(foundData); - issuer = std::get<0>(foundData); - if (endpoint != nullptr) - { - LOG(D, "Endpoint %s Authenticated via FAST Flow", utils::bufToHexString(endpoint->endpointId, sizeof(endpoint->endpointId), true).c_str()); - std::vector cmdFlowStatus = commandFlow(homeKeyReader::kCmdFlowSuccess); - LOG(D, "RESPONSE: %s, Length: %d", utils::bufToHexString(cmdFlowStatus.data(), cmdFlowStatus.size()).c_str(), cmdFlowStatus.size()); - if (cmdFlowStatus.data()[0] == 0x90) - { - LOG(D, "Command Status 0x90, FAST Flow Complete"); - return std::make_tuple(issuer, endpoint, homeKeyReader::kFlowFAST); - } - } - else - { - LOG(W, "FAST Flow failed!"); - return std::make_tuple(issuer, endpoint, homeKeyReader::kFlowSTANDARD); - } + LOG(W, "FAST Flow failed!"); + return std::make_tuple(issuer, endpoint, homeKeyReader::kFlowSTANDARD); } } LOG(E, "Response not valid, something went wrong!"); @@ -339,14 +304,14 @@ std::tuple` - * 4. An object of type `homeKeyReader::KeyFlow` + * Performs authentication using the STANDARD flow. + * + * @return a tuple containing the following elements: + * 1. A pointer to the issuer object (`homeKeyIssuer::issuer_t*`) + * 2. A pointer to the endpoint object (`homeKeyEndpoint::endpoint_t*`) + * 3. An object of type `DigitalKeySecureContext` + * 4. A vector of `uint8_t` elements + * 5. An enum value of type `homeKeyReader:: */ std::tuple, homeKeyReader::KeyFlow> HKAuthenticationContext::std_auth() { @@ -362,7 +327,8 @@ std::tuple sigPoint = signSharedInfo(stdTlv.data(), len, readerData.reader_private_key, sizeof(readerData.reader_private_key)); + std::vector sigTlv = utils::simple_tlv(0x9E, sigPoint.data(), sigPoint.size()); std::vector apdu{0x80, 0x81, 0x0, 0x0, (uint8_t)sigTlv.size()}; apdu.resize(apdu.size() + sigTlv.size()); std::move(sigTlv.begin(), sigTlv.end(), apdu.begin() + 5); @@ -380,10 +346,10 @@ std::tuple(response_result).size(), utils::bufToHexString(std::get<0>(response_result).data(), std::get<0>(response_result).size()).c_str()); - if (std::get<1>(response_result) > 0) + LOG(D, "Decrypted Length: %d, Data: %s", response_result.size(), utils::bufToHexString(response_result.data(), response_result.size()).c_str()); + if (response_result.size() > 0) { - std::vector decryptedTlv = BERTLV::unpack_array(std::vector{std::get<0>(response_result).data(), std::get<0>(response_result).data() + std::get<1>(response_result)}); + std::vector decryptedTlv = BERTLV::unpack_array(std::vector{response_result.data(), response_result.data() + response_result.size()}); BERTLV *signature = nullptr; BERTLV *device_identifier = nullptr; for (auto &data : decryptedTlv) @@ -462,7 +428,7 @@ std::tupletag.size() > 0) { return std::make_tuple(foundIssuer, foundEndpoint, context, persistentKey, homeKeyReader::kFlowATTESTATION); } diff --git a/src/auth/authContext.h b/src/auth/authContext.h index ada192a..4cd207a 100644 --- a/src/auth/authContext.h +++ b/src/auth/authContext.h @@ -34,11 +34,10 @@ class HKAuthenticationContext : CommonCryptoUtils std::vector readerIdentifier; void Auth0_keying_material(const char *context, const uint8_t *ePub_X, const uint8_t *keyingMaterial, uint8_t *out, size_t outLen); void Auth1_keys_generator(uint8_t *persistentKey, uint8_t *volatileKey); - void get_shared_key(uint8_t *outBuf, size_t oLen); std::tuple find_endpoint_by_cryptogram(std::vector& cryptogram); void Auth1_keying_material(uint8_t *keyingMaterial, const char *context, uint8_t *out, size_t outLen); std::vector commandFlow(homeKeyReader::CommandFlowStatus status); - std::tuple fast_auth(bool fallbackToStd); + std::tuple fast_auth(uint8_t *data, size_t dataLen); std::tuple, homeKeyReader::KeyFlow> std_auth(); public: diff --git a/src/main.cpp b/src/main.cpp index 19506e8..d74eeab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,7 +69,6 @@ struct LockManagement : Service::LockManagement { Serial.print("Configuring LockManagement\n"); // initialization message - new Characteristic::Name("Lock Management"); lockControlPoint = new Characteristic::LockControlPoint(); version = new Characteristic::Version(); @@ -110,7 +109,6 @@ struct LockMechanism : Service::LockMechanism LockMechanism() : Service::LockMechanism() { LOG(I, "Configuring LockMechanism"); // initialization message - new Characteristic::Name("NFC Lock"); lockCurrentState = new Characteristic::LockCurrentState(1, true); lockTargetState = new Characteristic::LockTargetState(1, true); mqtt.subscribe( @@ -202,10 +200,10 @@ struct LockMechanism : Service::LockMechanism mqtt.publish(MQTT_AUTH_TOPIC, payload.dump().c_str()); } } + nfc.inRelease(); bool deviceStillInField = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLen); while (deviceStillInField) { - nfc.inRelease(); deviceStillInField = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLen); vTaskDelay(100 / portTICK_PERIOD_MS); } @@ -233,7 +231,6 @@ struct NFCAccess : Service::NFCAccess, CommonCryptoUtils NFCAccess() : Service::NFCAccess() { LOG(I, "Configuring NFCAccess"); // initialization message - new Characteristic::Name("NFC Access"); configurationState = new Characteristic::ConfigurationState(); nfcControlPoint = new Characteristic::NFCAccessControlPoint(); nfcSupportedConfiguration = new Characteristic::NFCAccessSupportedConfiguration(); @@ -714,7 +711,7 @@ void setup() LOG(D, "Issuer ID: %s, Public Key: %s", utils::bufToHexString(issuer.issuerId, sizeof(issuer.issuerId)).c_str(), utils::bufToHexString(issuer.publicKey, sizeof(issuer.publicKey)).c_str()); } homeSpan.enableOTA(); - homeSpan.begin(Category::Locks, "Test NFC Lock"); + homeSpan.begin(Category::Locks, "HK Lock"); new SpanUserCommand('D', "Delete Home Key Data", deleteReaderData); new SpanUserCommand('L', "Set Log Level", setLogLevel); @@ -741,11 +738,11 @@ void setup() new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), no arguments needed new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); - new Characteristic::Manufacturer(); - new Characteristic::Model(); + new Characteristic::Manufacturer("Kodeative"); + new Characteristic::Model("HomeKey-ESP32"); new Characteristic::Name("NFC Lock"); - new Characteristic::SerialNumber(); - new Characteristic::FirmwareRevision(); + new Characteristic::SerialNumber("HK-360"); + new Characteristic::FirmwareRevision("0.1"); new Characteristic::HardwareFinish(); new LockManagement(); diff --git a/src/util/CommonCryptoUtils.cpp b/src/util/CommonCryptoUtils.cpp index b2e9fbe..db2e135 100644 --- a/src/util/CommonCryptoUtils.cpp +++ b/src/util/CommonCryptoUtils.cpp @@ -1,5 +1,5 @@ #include -#include "CommonCryptoUtils.h" +#define LOG(x, format, ...) ESP_LOG##x(TAG, "%s > " format , __FUNCTION__ __VA_OPT__(,) __VA_ARGS__) CommonCryptoUtils::CommonCryptoUtils() { @@ -11,6 +11,63 @@ int CommonCryptoUtils::esp_rng(void *, uint8_t *buf, size_t len) return 0; } +/** + * The function performs an elliptic curve Diffie-Hellman key exchange to compute a shared key between + * the reader and the endpoint. + * + * @param outBuf The `outBuf` parameter is a pointer to a buffer where the computed shared key will be + * stored. It should be allocated with enough space to hold `oLen` bytes. + * @param oLen oLen is the length of the output buffer (outBuf) where the shared key will be written. + */ +void CommonCryptoUtils::get_shared_key(const std::vector &key1, const std::vector &key2, uint8_t *outBuf, size_t oLen) +{ + mbedtls_ecp_group grp; + mbedtls_mpi reader_ephemeral_private_key, shared_key; + mbedtls_ecp_point endpoint_ephemeral_public_key; + + mbedtls_ecp_group_init(&grp); + mbedtls_mpi_init(&reader_ephemeral_private_key); + mbedtls_mpi_init(&shared_key); + mbedtls_ecp_point_init(&endpoint_ephemeral_public_key); + + // Initialize the elliptic curve group (e.g., SECP256R1) + mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); + + // Set the reader's ephemeral private key + int mpi_read = mbedtls_mpi_read_binary(&reader_ephemeral_private_key, key1.data(), key1.size()); + + if(mpi_read != 0){ + LOG(E, "mpi_read - %s", mbedtls_high_level_strerr(mpi_read)); + return; + } + + // Set the endpoint's ephemeral public key + int ecp_read = mbedtls_ecp_point_read_binary(&grp, &endpoint_ephemeral_public_key, key2.data(), key2.size()); + if(ecp_read != 0){ + LOG(E, "ecp_read - %s", mbedtls_high_level_strerr(ecp_read)); + return; + } + + // Perform key exchange + int ecdh_compute_shared = mbedtls_ecdh_compute_shared(&grp, &shared_key, &endpoint_ephemeral_public_key, &reader_ephemeral_private_key, + esp_rng, NULL); + if(ecdh_compute_shared != 0){ + LOG(E, "ecdh_compute_shared - %s", mbedtls_high_level_strerr(ecdh_compute_shared)); + return; + } + + int mpi_write = mbedtls_mpi_write_binary(&shared_key, outBuf, oLen); + if(mpi_write != 0){ + LOG(E, "mpi_write - %s", mbedtls_high_level_strerr(mpi_write)); + return; + } + + mbedtls_ecp_group_free(&grp); + mbedtls_mpi_free(&reader_ephemeral_private_key); + mbedtls_mpi_free(&shared_key); + mbedtls_ecp_point_free(&endpoint_ephemeral_public_key); +} + /** * The function generates an ephemeral key pair using the secp256r1 elliptic curve and returns the * private and public keys as vectors of uint8_t. @@ -25,22 +82,22 @@ std::tuple, std::vector> CommonCryptoUtils::genera mbedtls_ecp_keypair_init(&ephemeral); int gen_key = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, &ephemeral, esp_rng, NULL); if(gen_key != 0){ - ESP_LOGE(TAG, "generateEphemeralKey > gen_key - %s: %s", mbedtls_high_level_strerr(gen_key), mbedtls_low_level_strerr(gen_key)); + LOG(E, "gen_key - %s", mbedtls_high_level_strerr(gen_key)); return std::make_tuple(std::vector(), std::vector()); } std::vector bufPriv(mbedtls_mpi_size(&ephemeral.d)); int mpi_write = mbedtls_mpi_write_binary(&ephemeral.d, bufPriv.data(), bufPriv.capacity()); if(mpi_write != 0){ - ESP_LOGE(TAG, "generateEphemeralKey > mpi_write - %s: %s", mbedtls_high_level_strerr(mpi_write), mbedtls_low_level_strerr(mpi_write)); + LOG(E, "mpi_write - %s", mbedtls_high_level_strerr(mpi_write)); return std::make_tuple(std::vector(), std::vector()); } std::vector bufPub(MBEDTLS_ECP_MAX_BYTES); size_t olen = 0; int ecp_write = mbedtls_ecp_point_write_binary(&ephemeral.grp, &ephemeral.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, bufPub.data(), bufPub.capacity()); if(!ecp_write){ - ESP_LOGD(TAG, "Ephemeral Key generated -- private: %s, public: %s", utils::bufToHexString(bufPriv.data(), bufPriv.size()).c_str(), utils::bufToHexString(bufPub.data(), bufPub.size()).c_str()); + LOG(D, "Ephemeral Key generated -- private: %s, public: %s", utils::bufToHexString(bufPriv.data(), bufPriv.size()).c_str(), utils::bufToHexString(bufPub.data(), bufPub.size()).c_str()); } else{ - ESP_LOGE(TAG, "generateEphemeralKey > ecp_write - %s : %s", mbedtls_high_level_strerr(ecp_write), mbedtls_low_level_strerr(ecp_write)); + LOG(E, "ecp_write - %s", mbedtls_high_level_strerr(ecp_write)); return std::make_tuple(std::vector(), std::vector()); } bufPub.resize(olen); @@ -67,13 +124,13 @@ std::vector CommonCryptoUtils::get_x(std::vector &pubKey) mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); int ecp_read = mbedtls_ecp_point_read_binary(&grp, &point, pubKey.data(), pubKey.size()); if(ecp_read != 0) - ESP_LOGE(TAG, "get_x > ecp_read - %s: %s", mbedtls_high_level_strerr(ecp_read), mbedtls_low_level_strerr(ecp_read)); + LOG(E, "ecp_read - %s", mbedtls_high_level_strerr(ecp_read)); size_t buffer_size_x = mbedtls_mpi_size(&point.X); std::vector X(buffer_size_x); int ecp_write = mbedtls_mpi_write_binary(&point.X, X.data(), buffer_size_x); if(ecp_write != 0) - ESP_LOGE(TAG, "get_x > ecp_write - %s: %s", mbedtls_high_level_strerr(ecp_write), mbedtls_low_level_strerr(ecp_write)); - ESP_LOGV(TAG, "PublicKey: %s, X Coordinate: %s", utils::bufToHexString(pubKey.data(), pubKey.size()).c_str(), utils::bufToHexString(X.data(), X.size()).c_str()); + LOG(E, "ecp_write - %s", mbedtls_high_level_strerr(ecp_write)); + LOG(V, "PublicKey: %s, X Coordinate: %s", utils::bufToHexString(pubKey.data(), pubKey.size()).c_str(), utils::bufToHexString(X.data(), X.size()).c_str()); mbedtls_ecp_group_free(&grp); mbedtls_ecp_point_free(&point); return X; @@ -98,34 +155,37 @@ std::vector CommonCryptoUtils::get_x(uint8_t *pubKey, size_t len) mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); int ecp_read = mbedtls_ecp_point_read_binary(&grp, &point, pubKey, len); if(ecp_read != 0){ - ESP_LOGE(TAG, "get_x > ecp_read - %s: %s", mbedtls_high_level_strerr(ecp_read), mbedtls_low_level_strerr(ecp_read)); + LOG(E, "ecp_read - %s", mbedtls_high_level_strerr(ecp_read)); return std::vector(); } size_t buffer_size_x = mbedtls_mpi_size(&point.X); std::vector X(buffer_size_x); int ecp_write = mbedtls_mpi_write_binary(&point.X, X.data(), buffer_size_x); if(ecp_write != 0){ - ESP_LOGE(TAG, "get_x > ecp_write - %s: %s", mbedtls_high_level_strerr(ecp_write), mbedtls_low_level_strerr(ecp_write)); + LOG(E, "ecp_write - %s", mbedtls_high_level_strerr(ecp_write)); return std::vector(); } - ESP_LOGV(TAG, "PublicKey: %s, X Coordinate: %s", utils::bufToHexString(pubKey, len).c_str(), utils::bufToHexString(X.data(), X.size()).c_str()); + LOG(V, "PublicKey: %s, X Coordinate: %s", utils::bufToHexString(pubKey, len).c_str(), utils::bufToHexString(X.data(), X.size()).c_str()); mbedtls_ecp_group_free(&grp); mbedtls_ecp_point_free(&point); return X; } /** - * The function signSharedInfo signs the given data using the private key and returns the signature in - * TLV format. - * - * @param stdTlv A pointer to an array of uint8_t values representing a TLV (Tag-Length-Value) - * structure. - * @param len The `len` parameter represents the length of the `stdTlv` array. - * - * @return a std::vector containing a TLV (Tag-Length-Value) structure. The TLV structure - * consists of a tag (0x9E) and the signature point (sigPoint) as the value. + * The function `signSharedInfo` takes in data and a key, performs a SHA256 hash on the data, and then + * signs the hash with the key using the ECDSA algorithm. + * + * @param data A pointer to the data that needs to be signed. + * @param dataLen The parameter `dataLen` represents the length of the `data` array, which is the input + * data that needs to be signed. + * @param key The "key" parameter is a pointer to an array of uint8_t values that represents the key + * used for signing the data. The "keyLen" parameter specifies the length of the key array. + * @param keyLen The parameter `keyLen` represents the length of the key in bytes. + * + * @return a std::vector object, which contains the signature point generated by signing the + * shared information using the provided data and key. */ -std::vector CommonCryptoUtils::signSharedInfo(uint8_t *stdTlv, size_t len, uint8_t *privateKey, size_t keyLen) +std::vector CommonCryptoUtils::signSharedInfo(const uint8_t *data, const size_t dataLen, const uint8_t *key, const size_t keyLen) { mbedtls_ecp_keypair keypair; mbedtls_ecp_keypair_init(&keypair); @@ -137,33 +197,33 @@ std::vector CommonCryptoUtils::signSharedInfo(uint8_t *stdTlv, size_t l uint8_t hash[32]; - mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), stdTlv, len, hash); + mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data, dataLen, hash); - int ecp_read = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &keypair, privateKey, keyLen); + int ecp_read = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &keypair, key, keyLen); if(ecp_read != 0){ - ESP_LOGE(TAG, "signSharedInfo > ecp_read - %s: %s", mbedtls_high_level_strerr(ecp_read), mbedtls_low_level_strerr(ecp_read)); + LOG(E, "ecp_read - %s", mbedtls_high_level_strerr(ecp_read)); return std::vector(); } int ecdsa_sign = mbedtls_ecdsa_sign_det_ext(&keypair.grp, &sigMpi1, &sigMpi2, &keypair.d, hash, keyLen, MBEDTLS_MD_SHA256, esp_rng, NULL); if(ecdsa_sign != 0){ - ESP_LOGE(TAG, "signSharedInfo > ecdsa_sign - %s: %s", mbedtls_high_level_strerr(ecdsa_sign), mbedtls_low_level_strerr(ecdsa_sign)); + LOG(E, "ecdsa_sign - %s", mbedtls_high_level_strerr(ecdsa_sign)); return std::vector(); } - uint8_t sigPoint[mbedtls_mpi_size(&sigMpi1) + mbedtls_mpi_size(&sigMpi2)]; - int ecp_write_1 = mbedtls_mpi_write_binary(&sigMpi1, sigPoint, mbedtls_mpi_size(&sigMpi1)); + std::vector sigPoint(mbedtls_mpi_size(&sigMpi1) + mbedtls_mpi_size(&sigMpi2)); + int ecp_write_1 = mbedtls_mpi_write_binary(&sigMpi1, sigPoint.data(), mbedtls_mpi_size(&sigMpi1)); if(ecp_write_1 != 0){ - ESP_LOGE(TAG, "signSharedInfo > ecp_write_1 - %s: %s", mbedtls_high_level_strerr(ecp_write_1), mbedtls_low_level_strerr(ecp_write_1)); + LOG(E, "ecp_write_1 - %s", mbedtls_high_level_strerr(ecp_write_1)); return std::vector(); } - int ecp_write_2 = mbedtls_mpi_write_binary(&sigMpi2, sigPoint + mbedtls_mpi_size(&sigMpi1), mbedtls_mpi_size(&sigMpi2)); + int ecp_write_2 = mbedtls_mpi_write_binary(&sigMpi2, sigPoint.data() + mbedtls_mpi_size(&sigMpi1), mbedtls_mpi_size(&sigMpi2)); if(ecp_write_2 != 0){ - ESP_LOGE(TAG, "signSharedInfo > ecp_write_2 - %s: %s", mbedtls_high_level_strerr(ecp_write_2), mbedtls_low_level_strerr(ecp_write_2)); + LOG(E, "ecp_write_2 - %s", mbedtls_high_level_strerr(ecp_write_2)); return std::vector(); } mbedtls_ecp_keypair_free(&keypair); mbedtls_mpi_free(&sigMpi1); mbedtls_mpi_free(&sigMpi2); - return utils::simple_tlv(0x9E, sigPoint, sizeof(sigPoint)); + return sigPoint; } std::vector CommonCryptoUtils::getPublicKey(uint8_t *privKey, size_t len) diff --git a/src/util/CommonCryptoUtils.h b/src/util/CommonCryptoUtils.h index 3b30603..cc18261 100644 --- a/src/util/CommonCryptoUtils.h +++ b/src/util/CommonCryptoUtils.h @@ -20,8 +20,9 @@ class CommonCryptoUtils public: CommonCryptoUtils(); + void get_shared_key(const std::vector &key1, const std::vector &key2, uint8_t *outBuf, size_t oLen); std::tuple, std::vector> generateEphemeralKey(); - std::vector signSharedInfo(uint8_t *stdTlv, size_t len, uint8_t *privateKey, size_t keyLen); + std::vector signSharedInfo(const uint8_t *data, const size_t len, const uint8_t *privateKey, const size_t keyLen); std::vector get_x(std::vector &pubKey); std::vector get_x(uint8_t *pubKey, size_t len); std::vector getPublicKey(uint8_t *privKey, size_t len); diff --git a/src/util/DigitalKeySecureContext.cpp b/src/util/DigitalKeySecureContext.cpp index f7d73e6..ceacb2b 100644 --- a/src/util/DigitalKeySecureContext.cpp +++ b/src/util/DigitalKeySecureContext.cpp @@ -3,9 +3,22 @@ */ #include "util/DigitalKeySecureContext.h" - -const unsigned char RESPONSE_PCB[] = {0x80}; - +#define LOG(x, format, ...) ESP_LOG##x(TAG, "%s > " format , __FUNCTION__ __VA_OPT__(,) __VA_ARGS__) + +/** + * The function `pad_mode_3` pads a given message with a specified pad byte and block size, and returns + * the padded message along with its total length. + * + * @param message A pointer to the start of the message data. + * @param message_size The parameter `message_size` represents the size of the `message` array, which + * is the length of the input message in bytes. + * @param pad_byte The pad_byte parameter is the byte value used for padding the message. In this case, + * it is set to 0x80, which is the hexadecimal representation of the byte value 128. + * @param block_size The block_size parameter in the pad_mode_3 function is the size of each block in + * bytes. In this case, it is set to 8 bytes. + * + * @return a std::tuple containing a std::vector and a size_t. + */ std::tuple, size_t> DigitalKeySecureContext::pad_mode_3(unsigned char* message, size_t message_size, unsigned char pad_byte = 0x80, size_t block_size = 8) { size_t totalLen = message_size; size_t padding_length = block_size - (totalLen + 1) % block_size; @@ -16,63 +29,130 @@ std::tuple, size_t> DigitalKeySecureContext::pad_mode_3(uns std::fill(&buf.front() + message_size + 1, &buf.back(), 0); return std::make_tuple(buf, totalLen); } - -void DigitalKeySecureContext::unpad_mode_3(unsigned char* message, size_t message_size, unsigned char *unpadded, size_t *outLen, unsigned char pad_flag_byte = 0x80, size_t block_size = 16) { - size_t result_size = 0; - bool padding = true; - // printf("\n"); +/** + * The function `unpad_mode_3` searches for a padding flag byte in a given message and returns the + * index of the last occurrence of the flag byte. + * + * @param message The `message` parameter is a pointer to an array of unsigned characters, which + * represents the message that needs to be unpadded. + * @param message_size The parameter `message_size` represents the size of the `message` array, which + * is the length of the message in bytes. + * @param pad_flag_byte The pad_flag_byte parameter is a byte value that is used to indicate the + * padding in the message. In this case, the value is set to 0x80. + * @param block_size The `block_size` parameter in the `unpad_mode_3` function represents the size of + * each block in the message. In this case, the block size is set to 16 bytes. + * + * @return the index of the padding flag byte in the message array. + */ +int DigitalKeySecureContext::unpad_mode_3(unsigned char* message, size_t message_size, unsigned char pad_flag_byte = 0x80, size_t block_size = 16) { for (int i = message_size - 1; i >= 0; --i) { - if (!padding) + if (message[i] == pad_flag_byte) { - // printf("%x ", message[i]); - unpadded[result_size++] = message[i]; + LOG(V, "Padding found at index: %d", i); + return i; } else if (message[i] == 0x00) { continue; } - else if (message[i] == pad_flag_byte) - { - // printf("found the padding!"); - padding = false; - } } - // printf("\n"); - - if (result_size == 0) { - return; - } - - std::reverse(unpadded, unpadded + result_size); - memcpy(outLen, &result_size, sizeof(result_size)); + LOG(W, "Padding %x could not be found", pad_flag_byte); + return message_size - 1; } -void DigitalKeySecureContext::encrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* plaintext, size_t length, unsigned char* ciphertext) { - size_t olen; +/** + * The function `encrypt_aes_cbc` encrypts a plaintext using AES-CBC encryption with a given key and + * initialization vector (IV). + * + * @param key The "key" parameter is a pointer to an array of unsigned characters that represents the + * encryption key. It is used to set the encryption key for the AES algorithm. + * @param iv The "iv" parameter stands for "initialization vector". It is a fixed-size random or + * pseudorandom value that is used along with the encryption key to initialize the encryption + * algorithm. The IV is typically used to ensure that the same plaintext does not encrypt to the same + * ciphertext when encrypted multiple times with + * @param plaintext The plaintext parameter is a pointer to the input data that you want to encrypt + * using AES-CBC mode. It is of type unsigned char* and points to a buffer containing the plaintext + * data. + * @param length The "length" parameter represents the length of the plaintext in bytes. + * @param ciphertext The `ciphertext` parameter is a pointer to an unsigned char array where the + * encrypted data will be stored. + * + * @return an integer value. If the value is 0, it means the encryption was successful. If the value is + * non-zero, it indicates an error occurred during the encryption process. + */ +int DigitalKeySecureContext::encrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* plaintext, size_t length, unsigned char* ciphertext) { mbedtls_aes_context aes_ctx; mbedtls_aes_init(&aes_ctx); mbedtls_aes_setkey_enc(&aes_ctx, key, 128); - int ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, length, iv, plaintext, ciphertext); - // printf("%d\n", strlen((const char *)ciphertext)); + int crypt_cbc = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, length, iv, plaintext, ciphertext); mbedtls_aes_free(&aes_ctx); + if(crypt_cbc != 0){ + LOG(E, "crypt_cbc - %s", mbedtls_high_level_strerr(crypt_cbc)); + return crypt_cbc; + } + return 0; } -void DigitalKeySecureContext::decrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* ciphertext, size_t length, unsigned char* plaintext) { +/** + * The function `decrypt_aes_cbc` decrypts a given ciphertext using AES-CBC mode with a specified key + * and initialization vector (IV), and stores the resulting plaintext in a buffer. + * + * @param key The "key" parameter is a pointer to the unsigned char array that contains the AES + * encryption key. It is used to set the decryption key for the AES context. + * @param iv The "iv" parameter stands for "initialization vector". It is a fixed-size random or + * pseudorandom value that is used along with the encryption key to initialize the encryption + * algorithm. The IV is used to ensure that even if the same plaintext is encrypted multiple times, the + * resulting ciphertext will be different + * @param ciphertext The `ciphertext` parameter is a pointer to the encrypted data that needs to be + * decrypted. It is of type `const unsigned char*`, which means it is a pointer to an array of unsigned + * characters (bytes) that represent the encrypted data. + * @param length The "length" parameter represents the length of the ciphertext in bytes. + * @param plaintext The `plaintext` parameter is a pointer to a buffer where the decrypted data will be + * stored. + * + * @return an integer value. If the value is 0, it means the decryption was successful. If the value is + * non-zero, it indicates an error occurred during the decryption process. + */ +int DigitalKeySecureContext::decrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* ciphertext, size_t length, unsigned char* plaintext) { mbedtls_aes_context aes_ctx; mbedtls_aes_init(&aes_ctx); mbedtls_aes_setkey_dec(&aes_ctx, key, 128); - mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, length, iv, ciphertext, plaintext); + int crypt_cbc = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, length, iv, ciphertext, plaintext); mbedtls_aes_free(&aes_ctx); - // for (uint8_t i = 0; i < length; i++) { - // Serial.printf(" %02X", plaintext[i]); - // } - // Serial.printf("\n"); + if(crypt_cbc != 0){ + LOG(E, "crypt_cbc - %s", mbedtls_high_level_strerr(crypt_cbc)); + return crypt_cbc; + } + return 0; } -void DigitalKeySecureContext::aes_cmac(const unsigned char* key, const unsigned char* data, size_t data_size, unsigned char* mac) { - int ret = mbedtls_cipher_cmac(mbedtls_cipher_info_from_values(MBEDTLS_CIPHER_ID_AES, 128, MBEDTLS_MODE_ECB), key, 128, data, data_size, mac); - // std::cout << "result of cmac: " << ret << std::endl; +/** + * The function `aes_cmac` calculates the CMAC (Cipher-based Message Authentication Code) using AES + * encryption algorithm. + * + * @param key The "key" parameter is a pointer to an array of unsigned characters, which represents the + * encryption key used for AES-CMAC algorithm. It is assumed to be 128 bits (16 bytes) in length. + * @param data The "data" parameter is a pointer to the input data that you want to generate a CMAC + * (Cipher-based Message Authentication Code) for. It is of type "unsigned char*" which means it is a + * pointer to an array of unsigned characters (bytes). + * @param data_size The parameter `data_size` represents the size (in bytes) of the `data` array. It + * indicates the length of the data that needs to be processed for generating the CMAC (Cipher-based + * Message Authentication Code). + * @param mac The "mac" parameter is a pointer to an unsigned char array where the resulting CMAC + * (Cipher-based Message Authentication Code) will be stored. The CMAC is a fixed-size authentication + * tag that is generated using the AES-CMAC algorithm. + * + * @return an integer value. If the value is 0, it means the CMAC calculation was successful. If the + * value is non-zero, it indicates an error occurred during the CMAC calculation. + */ +int DigitalKeySecureContext::aes_cmac(const unsigned char* key, const unsigned char* data, size_t data_size, unsigned char* mac) { + int cmac = mbedtls_cipher_cmac(mbedtls_cipher_info_from_values(MBEDTLS_CIPHER_ID_AES, 128, MBEDTLS_MODE_ECB), key, 128, data, data_size, mac); + if(cmac != 0){ + LOG(E, "cmac - %s", mbedtls_high_level_strerr(cmac)); + return cmac; + } + return 0; } DigitalKeySecureContext::DigitalKeySecureContext(){} @@ -82,122 +162,177 @@ DigitalKeySecureContext::DigitalKeySecureContext(const unsigned char* volatileKe memcpy(this->kenc, volatileKey, 16); // Assuming a 128-bit key size memcpy(this->kmac, volatileKey + 16, 16); // Assuming a 128-bit key size memcpy(this->krmac, volatileKey + 32, 16); // Assuming a 128-bit key size - memcpy(response_pcb, RESPONSE_PCB, 1); - std::fill(mac_chaining_value, mac_chaining_value + 16, 0); - std::fill(response_pcb + 1, response_pcb + 15, 0); - std::fill(command_pcb, command_pcb + 15, 0); } -std::tuple, size_t, std::vector> DigitalKeySecureContext::encrypt_command(unsigned char* data, size_t dataSize) { - std::vector ciphertext(dataSize + 16); - // printf("\n%d\n", dataSize); - size_t olen = 0; - encrypt(data, dataSize, command_pcb, kenc, counter, ciphertext.data(), &olen); - - // printf("\n"); - // for (uint8_t i = 0; i < olen; i++) { - // printf(" %02X", ciphertext[i]); - // } - // printf("\n"); - +/** + * The function encrypts a command using a digital key secure context and returns the encrypted data + * along with the calculated message authentication code (MAC). + * + * @param data The `data` parameter is a pointer to an array of unsigned characters (bytes) that + * represents the data to be encrypted. + * @param dataSize The parameter `dataSize` represents the size of the `data` array, which is of type + * `unsigned char*`. It indicates the number of elements in the `data` array. + * + * @return The function `encrypt_command` returns a tuple containing two vectors of type `uint8_t`. The + * first element of the tuple is `result`, which is a vector containing the encrypted data concatenated + * with the calculated rmac. The second element of the tuple is `calculated_rmac`, which is a vector + * containing the calculated rmac. + */ +std::tuple, std::vector> DigitalKeySecureContext::encrypt_command(unsigned char* data, size_t dataSize) { + std::vector ciphertext = encrypt(data, dataSize, command_pcb, kenc, counter);; std::vector calculated_rmac(16); - size_t input_dataSize = 16 + olen; - std::vector input_data = concatenate_arrays(mac_chaining_value, ciphertext.data(), 16, olen); - aes_cmac(kmac, input_data.data(), input_dataSize, calculated_rmac.data()); - // for (uint8_t i = 0; i < 16; i++) { - // printf(" %02X", calculated_rmac[i]); - // } - // printf("\n"); + size_t input_dataSize = 16 + ciphertext.size(); + std::vector input_data = concatenate_arrays(mac_chaining_value, ciphertext.data(), 16, ciphertext.size()); + int cmac_status = aes_cmac(kmac, input_data.data(), input_dataSize, calculated_rmac.data()); + if(cmac_status) return std::make_tuple(std::vector(), std::vector()); - std::vector result = concatenate_arrays(ciphertext.data(), calculated_rmac.data(), olen, 8); + std::vector result = concatenate_arrays(ciphertext.data(), calculated_rmac.data(), ciphertext.size(), calculated_rmac.size()); - return std::make_tuple(result, olen + 8, calculated_rmac); + return std::make_tuple(result, calculated_rmac); } -std::tuple, size_t> DigitalKeySecureContext::decrypt_response(const unsigned char* data, size_t dataSize) { - std::vector ciphertext(dataSize - 8); - memcpy(ciphertext.data(), data, dataSize - 8); - - std::vector rmac(8); - memcpy(rmac.data(), data + dataSize - 8, 8); - +/** + * The function decrypts a response by verifying the integrity of the data and then decrypting it using + * a specified key. + * + * @param data The `data` parameter is a pointer to the encrypted data that needs to be decrypted. It + * is of type `const unsigned char*`. + * @param dataSize The parameter `dataSize` represents the size of the `data` array, which is of type + * `unsigned char*`. It is used to determine the length of the data that needs to be decrypted. + * + * @return a vector of uint8_t, which represents the decrypted plaintext. + */ +std::vector DigitalKeySecureContext::decrypt_response(const unsigned char* data, size_t dataSize) { + LOG(V, "encrypted_data: %s", utils::bufToHexString(data, dataSize).c_str()); std::vector calculated_rmac(16); size_t input_dataSize = 16 + (dataSize - 8); - std::vector input_data = concatenate_arrays(mac_chaining_value, ciphertext.data(), 16, dataSize - 8); - aes_cmac(krmac, input_data.data(), input_dataSize, calculated_rmac.data()); - // for (uint8_t i = 0; i < 8; i++) { - // Serial.printf(" %02X", calculated_rmac[i]); - // } - // Serial.printf("\n"); - // for (uint8_t i = 0; i < 8; i++) { - // Serial.printf(" %02X", rmac[i]); - // } - // Serial.printf("\n"); - - // assert(memcmp(rmac, calculated_rmac, 8) == 0); - - if(memcmp(rmac.data(), calculated_rmac.data(), 8)){ - return std::make_tuple(std::vector{}, 0); + std::vector input_data = concatenate_arrays(mac_chaining_value, data, 16, dataSize - 8); + int cmac_status = aes_cmac(krmac, input_data.data(), input_dataSize, calculated_rmac.data()); + LOG(V, "recv_rmac: %s", utils::bufToHexString(data + (dataSize - 8), 8).c_str()); + LOG(V, "calculated_rmac: %s", utils::bufToHexString(calculated_rmac.data(), 8).c_str()); + if(cmac_status) return std::vector(); + + if(memcmp(data + (dataSize - 8), calculated_rmac.data(), 8)){ + LOG(E, "calculated_rmac != recv_rmac"); + return std::vector(); } - std::vector plaintext(dataSize - 8); - size_t olen = 0; - decrypt(ciphertext.data(), dataSize - 8, response_pcb, kenc, counter, plaintext.data(), &olen); + std::vector plaintext = decrypt(data, dataSize - 8, response_pcb, kenc, counter); counter++; - return std::make_tuple(plaintext, olen); + return plaintext; } -void DigitalKeySecureContext::encrypt(unsigned char* plaintext, size_t data_size, const unsigned char* pcb, const unsigned char* key, int counter, unsigned char* ciphertext, size_t *olen) { +/** + * The function encrypts a given plaintext using AES-CBC encryption with a specified key and + * initialization vector (IV), and returns the encrypted data. + * + * @param plaintext A pointer to the plaintext data that needs to be encrypted. + * @param data_size The `data_size` parameter represents the size of the plaintext data in bytes. + * @param pcb The parameter `pcb` is a pointer to an array of unsigned characters. It is used as input + * data for encryption. + * @param key The "key" parameter is a pointer to an array of unsigned characters, which represents the + * encryption key used in the AES-CBC encryption algorithm. + * @param counter The "counter" parameter is an integer value that is used as part of the encryption + * process. It is used to generate a counter byte, which is then concatenated with other data to form + * the input data for the encryption algorithm. The counter value is used to ensure that each + * encryption operation is unique and prevents + * + * @return a std::vector object, which contains the encrypted data. + */ +std::vector DigitalKeySecureContext::encrypt(unsigned char* plaintext, size_t data_size, const unsigned char* pcb, const unsigned char* key, int counter) { if (data_size == 0) { - return; + return std::vector(); } // Pad plaintext auto padded = pad_mode_3(plaintext, data_size, 0x80, 16); - // printf("\n"); - // for (uint8_t i = 0; i < std::get<1>(padded); i++) { - // printf(" %02X", std::get<0>(padded)[i]); - // } - // printf("\n"); - std::vector icv(16); std::vector iv(16); std::fill(iv.data(), iv.data() + 16, 0); unsigned char counter_byte[] = {static_cast(counter % 256)}; size_t input_data_size = 15 + 1; std::vector input_data = concatenate_arrays(pcb, counter_byte, 15, 1); - encrypt_aes_cbc(key, iv.data(), input_data.data(), input_data_size, icv.data()); + int encrypt_status1 = encrypt_aes_cbc(key, iv.data(), input_data.data(), input_data_size, icv.data()); + if(encrypt_status1) return std::vector(); + + LOG(V, "ICV: %s", utils::bufToHexString(icv.data(), icv.size()).c_str()); + + std::vector enc(std::get<1>(padded)); // Encrypt using AES-CBC - encrypt_aes_cbc(key, icv.data(), std::get<0>(padded).data(), std::get<1>(padded), ciphertext); + int encrypt_status = encrypt_aes_cbc(key, icv.data(), std::get<0>(padded).data(), std::get<1>(padded), enc.data()); + + if(encrypt_status) return std::vector(); - memcpy(olen, &std::get<1>(padded), 1); + LOG(V, "ENCRYPTED: %s", utils::bufToHexString(enc.data(), enc.size()).c_str()); + + return enc; } -void DigitalKeySecureContext::decrypt(const unsigned char* ciphertext, size_t cipherTextLen, const unsigned char* pcb, const unsigned char* key, int counter, unsigned char* plaintext, size_t* olen) { +/** + * The function decrypts a given ciphertext using AES-CBC encryption with a specified key and + * initialization vector (IV), and returns the decrypted plaintext after unpadding it. + * + * @param ciphertext A pointer to the ciphertext data, which is an array of unsigned characters. + * @param cipherTextLen The parameter `cipherTextLen` represents the length of the ciphertext, which is + * the length of the `ciphertext` array. + * @param pcb The parameter "pcb" stands for "Protocol Control Block". It is a data structure used in + * network protocols to store information about the current state of a communication session. In this + * context, it is used as input data for the encryption process. + * @param key The "key" parameter is a pointer to an array of unsigned characters, which represents the + * encryption key used for AES-CBC encryption and decryption. + * @param counter The "counter" parameter is an integer value used for encryption. It is used to + * generate a counter byte, which is then concatenated with other data to create the input for the + * encryption algorithm. + * + * @return a std::vector object. + */ +std::vector DigitalKeySecureContext::decrypt(const unsigned char* ciphertext, size_t cipherTextLen, const unsigned char* pcb, const unsigned char* key, int counter) { if (cipherTextLen == 0) { - return; + return std::vector(); } - std::vector icv(16); - std::vector iv(16); - std::fill(iv.data(), iv.data() + 16, 0); + std::vector icv(16, 0); + std::vector iv(16, 0); unsigned char counter_byte[] = {static_cast(counter % 256)}; size_t input_data_size = 15 + 1; std::vector input_data = concatenate_arrays(pcb, counter_byte, 15, 1); - encrypt_aes_cbc(key, iv.data(), input_data.data(), input_data_size, icv.data()); + int encrypt_status = encrypt_aes_cbc(key, iv.data(), input_data.data(), input_data_size, icv.data()); + if(encrypt_status) return std::vector(); - uint8_t dec[cipherTextLen]; + LOG(V, "ICV: %s", utils::bufToHexString(icv.data(), icv.size()).c_str()); + + std::vector dec(cipherTextLen); // Decrypt using AES-CBC - decrypt_aes_cbc(key, icv.data(), ciphertext, cipherTextLen, dec); + int decrypt_status = decrypt_aes_cbc(key, icv.data(), ciphertext, cipherTextLen, dec.data()); + + if(decrypt_status) return std::vector(); + + LOG(V, "decryted: %s", utils::bufToHexString(dec.data(), dec.size()).c_str()); // Unpad plaintext - unpad_mode_3(dec, cipherTextLen, plaintext, olen); + int padding_index = unpad_mode_3(dec.data(), cipherTextLen); + + dec.resize(padding_index); + + return dec; } +/** + * The function `concatenate_arrays` takes two arrays of unsigned characters, along with their + * respective sizes, and concatenates them into a single vector of uint8_t. + * + * @param a The parameter "a" is a pointer to an array of unsigned characters (bytes). + * @param b The parameter "b" in the given code is a pointer to an array of unsigned characters + * (unsigned char). + * @param size_a The parameter `size_a` represents the size (in bytes) of the array `a`. + * @param size_b The parameter "size_b" represents the size (number of elements) of the array "b". + * + * @return a std::vector object. + */ std::vector DigitalKeySecureContext::concatenate_arrays(const unsigned char* a, const unsigned char* b, size_t size_a, size_t size_b) { std::vector result(size_a + size_b); memcpy(result.data(), a, size_a); diff --git a/src/util/DigitalKeySecureContext.h b/src/util/DigitalKeySecureContext.h index f5a988a..87e62c6 100644 --- a/src/util/DigitalKeySecureContext.h +++ b/src/util/DigitalKeySecureContext.h @@ -12,31 +12,33 @@ #include #include #include +#include class DigitalKeySecureContext { public: DigitalKeySecureContext(); DigitalKeySecureContext(const unsigned char *volatileKey); - std::tuple, size_t, std::vector> encrypt_command(unsigned char* data, size_t dataSize); - std::tuple, size_t> decrypt_response(const unsigned char* data, size_t dataSize); + std::tuple, std::vector> encrypt_command(unsigned char* data, size_t dataSize); + std::vector decrypt_response(const unsigned char* data, size_t dataSize); private: + const char *TAG = "DigitalKeySC"; int counter; - unsigned char mac_chaining_value[16]; - unsigned char command_pcb[15]; - unsigned char response_pcb[15]; + unsigned char mac_chaining_value[16] = {0x0, 0x0, 0x0, 0x0, 0x0 ,0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + unsigned char command_pcb[15] = {0x0, 0x0, 0x0, 0x0, 0x0 ,0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + unsigned char response_pcb[15] = {0x80, 0x0, 0x0, 0x0, 0x0 ,0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; unsigned char kenc[16]; unsigned char kmac[16]; unsigned char krmac[16]; - void encrypt(unsigned char* plaintext, size_t data_size, const unsigned char* pcb, const unsigned char* key, int counter, unsigned char *ciphertext, size_t *olen); - void decrypt(const unsigned char* ciphertext, size_t data_size, const unsigned char* pcb, const unsigned char* key, int counter, unsigned char *plaintext, size_t *olen); + std::vector encrypt(unsigned char* plaintext, size_t data_size, const unsigned char* pcb, const unsigned char* key, int counter); + std::vector decrypt(const unsigned char* ciphertext, size_t data_size, const unsigned char* pcb, const unsigned char* key, int counter); std::tuple, size_t> pad_mode_3(unsigned char* message, size_t message_size, unsigned char pad_byte, size_t block_size); - void unpad_mode_3(unsigned char* message, size_t message_size, unsigned char *unpadded, size_t *outLen, unsigned char pad_flag_byte, size_t block_size); - void encrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* plaintext, size_t length, unsigned char* ciphertext); - void decrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* ciphertext, size_t length, unsigned char* plaintext); - void aes_cmac(const unsigned char* key, const unsigned char* data, size_t data_size, unsigned char* mac); + int unpad_mode_3(unsigned char* message, size_t message_size, unsigned char pad_flag_byte, size_t block_size); + int encrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* plaintext, size_t length, unsigned char* ciphertext); + int decrypt_aes_cbc(const unsigned char* key, unsigned char* iv, const unsigned char* ciphertext, size_t length, unsigned char* plaintext); + int aes_cmac(const unsigned char* key, const unsigned char* data, size_t data_size, unsigned char* mac); std::vector concatenate_arrays(const unsigned char* a, const unsigned char* b, size_t size_a, size_t size_b); };