From ee65ea9fb77470ac64f86487c7e475f851bf82a7 Mon Sep 17 00:00:00 2001 From: Vikram Dattu Date: Mon, 25 Sep 2023 15:51:26 +0530 Subject: [PATCH] refactor(esp_srp): Expose esp_srp.h and add API docs - Also added missing `protocomm_security2` reference in `protocomm.rst` --- components/protocomm/CMakeLists.txt | 5 +- .../protocomm/include/crypto/srp6a/esp_srp.h | 204 ++++++++++++++++++ .../protocomm/src/crypto/srp6a/esp_srp.c | 58 ++++- .../crypto/srp6a/{include => }/esp_srp_mpi.h | 0 .../src/crypto/srp6a/include/esp_srp.h | 103 --------- components/protocomm/src/security/security2.c | 24 +-- docs/doxygen/Doxyfile | 2 + .../api-reference/provisioning/protocomm.rst | 4 +- .../api-reference/provisioning/protocomm.rst | 10 +- 9 files changed, 274 insertions(+), 136 deletions(-) create mode 100644 components/protocomm/include/crypto/srp6a/esp_srp.h rename components/protocomm/src/crypto/srp6a/{include => }/esp_srp_mpi.h (100%) delete mode 100644 components/protocomm/src/crypto/srp6a/include/esp_srp.h diff --git a/components/protocomm/CMakeLists.txt b/components/protocomm/CMakeLists.txt index e74a13cf30..ab026e55c8 100644 --- a/components/protocomm/CMakeLists.txt +++ b/components/protocomm/CMakeLists.txt @@ -6,8 +6,9 @@ endif() set(include_dirs include/common include/security - include/transports) -set(priv_include_dirs proto-c src/common src/crypto/srp6a/include) + include/transports + include/crypto/srp6a) +set(priv_include_dirs proto-c src/common) set(srcs "src/common/protocomm.c" "proto-c/constants.pb-c.c" diff --git a/components/protocomm/include/crypto/srp6a/esp_srp.h b/components/protocomm/include/crypto/srp6a/esp_srp.h new file mode 100644 index 0000000000..c9e672af1f --- /dev/null +++ b/components/protocomm/include/crypto/srp6a/esp_srp.h @@ -0,0 +1,204 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief SRP-6a protocol implementation + * + * More information on protocol can be found: https://datatracker.ietf.org/doc/html/rfc5054 + * + * This implementation is used by security2 of wifi_provisioning and local control features. + * Details on how these protocols use this feature can be found here: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/provisioning/provisioning.html#security-2-scheme + * + * Below is the example usage of the srp6a protocol in protocomm component, + * which can help understand the APIs better. + * + * Variables used: + * + * N, g: group parameters (prime and generator) + * s: salt + * B, b: server's public and private values + * A, a: client's public and private values + * I: user name (aka "identity") + * P: password + * v: verifier + * k: SRP-6 multiplier + * + * salt (s) is random of given length, 16 in our case, which along with username and password + * is used to generate verifier. + * + * x = SHA1(s | SHA1(I | ":" | P)) + * v = g^x % N + * + * Steps involved (From protocomm component usage): + * Step1. Client Hello (PhoneApp): + * a. Generate Key pair: + * a (cli_privkey) = 256 bit random value, + * A (cli_pubkey) = g^a. + * g - generator, N - large safe prime, All arithmetic operations are performed in ring of integers modulo N, + * thus all occurrences like y^z should be read as y^z modulo N. + * b. SessionCmd0 (cli_pubkey, username I) + * + * Step2. Device(ESP): + * a. Obtain Salt and verifier stored on ESP + * Salt s = 256 bit random value, + * Verifier v = g^× where x = H(s | I | P) + * b. Generate Key Pair + * b (dev_privkey) = 256 bit random value + * B(dev_pubkey) = k*v + g^b where k = H(N, g) + * c. Shared Key K = H(S) where, + * S = (A * v^u) ^ b + * u = H(A, B) + * d. SessionResp0(dev_pubkey B, dev_random) + * + * Step3. Client (PhoneApp) + * a. shared_key(K) = H(S) where, + * S = (B - k*v) ^ (a + ux), + * u = H(A, B), + * k = H(N, g), + * V = g^x, + * x = H(s | I | P). + * b. Verification token + * client proof M = H[H(N) XOR H(g) | H(I) | S | A | B | K] + * c. SessionCmd1(Client proof M1) + * + * Step4. Device (ESP): + * a. Verify client: + * device generates M1 = H[H(N) XOR H(g) | H(I) | S | A | B | K] + * device verifies this M1 with the M1 obtained from Client + * b. Verification token: Device generate device proof M2 = H(A, M, K) + * c. Initialization Vector(IV): + * dev_rand = gen_16byte_random) This random number is to be used for AES-GCM operation + * for encryption and decryption of the data using the shared secret + * d. SessionResp1 (DeviceProofM2, dev_rand) + * + * Step5. Client (PhoneApp) + * a. Verify Device + * Client calculates device proof M2 as M2 = H(A, M, K) + * verifies this M2 with M2 obtained from device + */ + +/** + * @brief Large prime+generator to be used for the algorithm + */ +typedef enum { + /* SRP specific: + * N = 3072 bit large safe prime, + * g = generator */ + ESP_NG_3072 = 0, +} esp_ng_type_t; + +typedef struct esp_srp_handle esp_srp_handle_t; + +/** + * @brief Initialize srp context for given NG type + * + * @param ng NG type given by `esp_ng_type_t` + * @return esp_srp_handle_t* srp handle + * + * @note the handle gets freed with `esp_srp_free` + */ +esp_srp_handle_t *esp_srp_init(esp_ng_type_t ng); + +/** + * @brief free esp_srp_context + * + * @param hd handle to be free + */ +void esp_srp_free(esp_srp_handle_t *hd); + +/** + * @brief Returns B (pub key) and salt. [Step2.b] + * + * @param hd esp_srp handle + * @param username Username not expected NULL terminated + * @param username_len Username length + * @param pass Password not expected to be NULL terminated + * @param pass_len Pasword length + * @param salt_len Salt length + * @param bytes_B Public Key returned + * @param len_B Length of the public key + * @param bytes_salt Salt bytes generated + * @return esp_err_t ESP_OK on success, appropriate error otherwise + * + * @note *bytes_B MUST NOT BE FREED BY THE CALLER + * @note *bytes_salt MUST NOT BE FREE BY THE CALLER + */ +esp_err_t esp_srp_srv_pubkey(esp_srp_handle_t *hd, const char *username, int username_len, + const char *pass, int pass_len, int salt_len, + char **bytes_B, int *len_B, char **bytes_salt); + +/** + * @brief Set the Salt and Verifier pre-generated for a given password. + * + * This should be used only if the actual password is not available. + * The public key can then be generated using esp_srp_srv_pubkey_from_salt_verifier() + * and not esp_srp_srv_pubkey() + * + * @param hd esp_srp_handle + * @param salt pre-generated salt bytes + * @param salt_len length of the salt bytes + * @param verifier pre-generated verifier + * @param verifier_len length of the verifier bytes + * @return esp_err_t ESP_OK on success, appropriate error otherwise + */ +esp_err_t esp_srp_set_salt_verifier(esp_srp_handle_t *hd, const char *salt, int salt_len, + const char *verifier, int verifier_len); + +/** + * @brief Returns B (pub key)[Step2.b] when the salt and verifier are set using esp_srp_set_salt_verifier() + * + * @param hd esp_srp handle + * @param bytes_B Key returned to the called + * @param len_B Length of the key returned + * @return esp_err_t ESP_OK on success, appropriate error otherwise + * + * @note *bytes_B MUST NOT BE FREED BY THE CALLER + */ +esp_err_t esp_srp_srv_pubkey_from_salt_verifier(esp_srp_handle_t *hd, char **bytes_B, int *len_B); + +/** + * @brief Get session key in `*bytes_key` given by len in `*len_key`. [Step2.c]. + * + * This calculated session key is used for further communication given the proofs are + * exchanged/authenticated with `esp_srp_exchange_proofs` + * + * @param hd esp_srp handle + * @param bytes_A Private Key + * @param len_A Private Key length + * @param bytes_key Key returned to the caller + * @param len_key length of the key in *bytes_key + * @return esp_err_t ESP_OK on success, appropriate error otherwise + * + * @note *bytes_key MUST NOT BE FREED BY THE CALLER + */ +esp_err_t esp_srp_get_session_key(esp_srp_handle_t *hd, char *bytes_A, int len_A, char **bytes_key, uint16_t *len_key); + +/** + * @brief Complete the authentication. If this step fails, the session_key exchanged should not be used + * + * This is the final authentication step in SRP algorithm [Step4.1, Step4.b, Step4.c] + * + * @param hd esp_srp handle + * @param username Username not expected NULL terminated + * @param username_len Username length + * @param bytes_user_proof param in + * @param bytes_host_proof parameter out (should be SHA512_DIGEST_LENGTH) bytes in size + * @return esp_err_t ESP_OK if user's proof is ok and subsequently bytes_host_proof is populated with our own proof. + */ +esp_err_t esp_srp_exchange_proofs(esp_srp_handle_t *hd, char *username, uint16_t username_len, + char *bytes_user_proof, char *bytes_host_proof); + +#ifdef __cplusplus +} +#endif diff --git a/components/protocomm/src/crypto/srp6a/esp_srp.c b/components/protocomm/src/crypto/srp6a/esp_srp.c index 36fbc08342..55a42e5b06 100644 --- a/components/protocomm/src/crypto/srp6a/esp_srp.c +++ b/components/protocomm/src/crypto/srp6a/esp_srp.c @@ -17,6 +17,45 @@ static const char *TAG = "srp6a"; +typedef struct esp_srp_handle { + int allocated; + esp_ng_type_t type; + esp_mpi_ctx_t *ctx; + + /* N + * the bytes_n simply points to the static array + */ + esp_mpi_t *n; + const char *bytes_n; + int len_n; + + /* g + * the bytes_g simply points to the static array + */ + esp_mpi_t *g; + const char *bytes_g; + int len_g; + + /* Salt */ + esp_mpi_t *s; + char *bytes_s; + int len_s; + /* Verifier */ + esp_mpi_t *v; + /* B */ + esp_mpi_t *B; + char *bytes_B; + int len_B; + /* b */ + esp_mpi_t *b; + /* A */ + esp_mpi_t *A; + char *bytes_A; + int len_A; + /* K - session key*/ + char *session_key; +} esp_srp_handle; + static void hexdump_mpi(const char *name, esp_mpi_t *bn) { int len = 0; @@ -59,15 +98,13 @@ static const char N_3072[] = { static const char g_3072[] = { 5 }; -esp_err_t esp_srp_init(esp_srp_handle_t *hd, esp_ng_type_t ng) +esp_srp_handle_t *esp_srp_init(esp_ng_type_t ng) { - if (hd->allocated) { - esp_srp_free(hd); + esp_srp_handle_t *hd = calloc(1, sizeof(esp_srp_handle)); + if (!hd) { + return NULL; } - memset(hd, 0, sizeof(*hd)); - hd->allocated = 1; - hd->ctx = esp_mpi_ctx_new(); if (! hd->ctx) { goto error; @@ -90,18 +127,17 @@ esp_err_t esp_srp_init(esp_srp_handle_t *hd, esp_ng_type_t ng) goto error; } hd->type = ng; - return ESP_OK; + return hd; error: esp_srp_free(hd); - return ESP_FAIL; + return NULL; } void esp_srp_free(esp_srp_handle_t *hd) { - if (hd->allocated != 1) { + if (!hd) { return; } - if (hd->ctx) { esp_mpi_ctx_free(hd->ctx); } @@ -138,7 +174,7 @@ void esp_srp_free(esp_srp_handle_t *hd) if (hd->session_key) { free(hd->session_key); } - memset(hd, 0, sizeof(*hd)); + free(hd); } static esp_mpi_t *calculate_x(char *bytes_salt, int salt_len, const char *username, int username_len, const char *pass, int pass_len) diff --git a/components/protocomm/src/crypto/srp6a/include/esp_srp_mpi.h b/components/protocomm/src/crypto/srp6a/esp_srp_mpi.h similarity index 100% rename from components/protocomm/src/crypto/srp6a/include/esp_srp_mpi.h rename to components/protocomm/src/crypto/srp6a/esp_srp_mpi.h diff --git a/components/protocomm/src/crypto/srp6a/include/esp_srp.h b/components/protocomm/src/crypto/srp6a/include/esp_srp.h deleted file mode 100644 index 972704f038..0000000000 --- a/components/protocomm/src/crypto/srp6a/include/esp_srp.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "esp_srp_mpi.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - /* SRP specific: - * N = 3072 bit large safe prime, - * g = generator */ - ESP_NG_3072 = 0, -} esp_ng_type_t; - -typedef struct esp_srp_handle { - int allocated; - esp_ng_type_t type; - esp_mpi_ctx_t *ctx; - - /* N - * the bytes_n simply points to the static array - */ - esp_mpi_t *n; - const char *bytes_n; - int len_n; - - /* g - * the bytes_g simply points to the static array - */ - esp_mpi_t *g; - const char *bytes_g; - int len_g; - - /* Salt */ - esp_mpi_t *s; - char *bytes_s; - int len_s; - /* Verifier */ - esp_mpi_t *v; - /* B */ - esp_mpi_t *B; - char *bytes_B; - int len_B; - /* b */ - esp_mpi_t *b; - /* A */ - esp_mpi_t *A; - char *bytes_A; - int len_A; - /* K - session key*/ - char *session_key; -} esp_srp_handle_t; - -int esp_srp_init(esp_srp_handle_t *hd, esp_ng_type_t ng); - -void esp_srp_free(esp_srp_handle_t *hd); - -/* Returns B (pub key) and salt - * - * *bytes_B MUST NOT BE FREED BY THE CALLER - * *bytes_salt MUST NOT BE FREE BY THE CALLER - * - */ -esp_err_t esp_srp_srv_pubkey(esp_srp_handle_t *hd, const char *username, int username_len, const char *pass, int pass_len, int salt_len, - char **bytes_B, int *len_B, char **bytes_salt); - -/* Set the Salt and Verifier pre-generated for a given password. - * This should be used only if the actual password is not available. - * The public key can then be generated using esp_srp_srv_pubkey_from_salt_verifier() - * and not esp_srp_srv_pubkey() - */ -esp_err_t esp_srp_set_salt_verifier(esp_srp_handle_t *hd, const char *salt, int salt_len, - const char *verifier, int verifier_len); - -/* Returns B (pub key) when the salt and verifier are set using esp_srp_set_salt_verifier() - * - * *bytes_B MUST NOT BE FREED BY THE CALLER - */ -esp_err_t esp_srp_srv_pubkey_from_salt_verifier(esp_srp_handle_t *hd, char **bytes_B, int *len_B); - -/* Returns bytes_key - * *bytes_key MUST NOT BE FREED BY THE CALLER - */ -esp_err_t esp_srp_get_session_key(esp_srp_handle_t *hd, char *bytes_A, int len_A, char **bytes_key, uint16_t *len_key); - -/* Exchange proofs - * Returns 1 if user's proof is ok. Also 1 when is returned, bytes_host_proof contains our proof. - * - * bytes_user_proof is parameter in - * bytes_host_proof is parameter out (should be SHA512_DIGEST_LENGTH) bytes in size - */ -esp_err_t esp_srp_exchange_proofs(esp_srp_handle_t *hd, char *username, uint16_t username_len, char *bytes_user_proof, char *bytes_host_proof); - -#ifdef __cplusplus -} -#endif diff --git a/components/protocomm/src/security/security2.c b/components/protocomm/src/security/security2.c index 8256114ad7..73d104d47c 100644 --- a/components/protocomm/src/security/security2.c +++ b/components/protocomm/src/security/security2.c @@ -109,15 +109,9 @@ static esp_err_t handle_session_command0(session_t *cur_session, hexdump("Client Public Key", (char *) in->sc0->client_pubkey.data, PUBLIC_KEY_LEN); /* Initialize mu srp context */ - cur_session->srp_hd = calloc(1, sizeof(esp_srp_handle_t)); - if (!cur_session->srp_hd) { - ESP_LOGE(TAG, "Failed to allocate security context!"); - return ESP_ERR_NO_MEM; - } - - if (esp_srp_init(cur_session->srp_hd, ESP_NG_3072) != ESP_OK) { + cur_session->srp_hd = esp_srp_init(ESP_NG_3072); + if (cur_session->srp_hd == NULL) { ESP_LOGE(TAG, "Failed to initialise security context!"); - free(cur_session->srp_hd); return ESP_FAIL; } @@ -131,14 +125,15 @@ static esp_err_t handle_session_command0(session_t *cur_session, ESP_LOGI(TAG, "Using salt and verifier to generate public key..."); if (sv->salt != NULL && sv->salt_len != 0 && sv->verifier != NULL && sv->verifier_len != 0) { - if (esp_srp_set_salt_verifier(cur_session->srp_hd, cur_session->salt, cur_session->salt_len, cur_session->verifier, cur_session->verifier_len) != ESP_OK) { + if (esp_srp_set_salt_verifier(cur_session->srp_hd, cur_session->salt, + cur_session->salt_len, cur_session->verifier, cur_session->verifier_len) != ESP_OK) { ESP_LOGE(TAG, "Failed to set salt and verifier!"); - free(cur_session->srp_hd); + esp_srp_free(cur_session->srp_hd); return ESP_FAIL; } if (esp_srp_srv_pubkey_from_salt_verifier(cur_session->srp_hd, &device_pubkey, &device_pubkey_len) != ESP_OK) { ESP_LOGE(TAG, "Failed to device public key!"); - free(cur_session->srp_hd); + esp_srp_free(cur_session->srp_hd); return ESP_FAIL; } } @@ -147,7 +142,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, if (esp_srp_get_session_key(cur_session->srp_hd, (char *) in->sc0->client_pubkey.data, PUBLIC_KEY_LEN, &cur_session->session_key, &cur_session->session_key_len) != ESP_OK) { ESP_LOGE(TAG, "Failed to generate device session key!"); - free(cur_session->srp_hd); + esp_srp_free(cur_session->srp_hd); return ESP_FAIL; } hexdump("Session Key", cur_session->session_key, cur_session->session_key_len); @@ -156,7 +151,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, S2SessionResp0 *out_resp = (S2SessionResp0 *) malloc(sizeof(S2SessionResp0)); if (!out || !out_resp) { ESP_LOGE(TAG, "Error allocating memory for response0"); - free(cur_session->srp_hd); + esp_srp_free(cur_session->srp_hd); free(out); free(out_resp); return ESP_ERR_NO_MEM; @@ -185,7 +180,7 @@ static esp_err_t handle_session_command0(session_t *cur_session, cur_session->username = malloc(cur_session->username_len); if (!cur_session->username) { ESP_LOGE(TAG, "Failed to allocate memory!"); - free(cur_session->srp_hd); + esp_srp_free(cur_session->srp_hd); return ESP_ERR_NO_MEM; } memcpy(cur_session->username, in->sc0->client_username.data, in->sc0->client_username.len); @@ -365,7 +360,6 @@ static esp_err_t sec2_close_session(protocomm_security_handle_t handle, uint32_t if (cur_session->srp_hd) { esp_srp_free(cur_session->srp_hd); - free(cur_session->srp_hd); } memset(cur_session, 0, sizeof(session_t)); diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 2321f7424e..72546a6483 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -274,9 +274,11 @@ INPUT = \ $(PROJECT_PATH)/components/protocomm/include/security/protocomm_security.h \ $(PROJECT_PATH)/components/protocomm/include/security/protocomm_security0.h \ $(PROJECT_PATH)/components/protocomm/include/security/protocomm_security1.h \ + $(PROJECT_PATH)/components/protocomm/include/security/protocomm_security2.h \ $(PROJECT_PATH)/components/protocomm/include/transports/protocomm_ble.h \ $(PROJECT_PATH)/components/protocomm/include/transports/protocomm_console.h \ $(PROJECT_PATH)/components/protocomm/include/transports/protocomm_httpd.h \ + $(PROJECT_PATH)/components/protocomm/include/crypto/srp6a/esp_srp.h \ $(PROJECT_PATH)/components/pthread/include/esp_pthread.h \ $(PROJECT_PATH)/components/sdmmc/include/sdmmc_cmd.h \ $(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/adc_channel.h \ diff --git a/docs/en/api-reference/provisioning/protocomm.rst b/docs/en/api-reference/provisioning/protocomm.rst index 04cfe3f9ce..a196bd4375 100644 --- a/docs/en/api-reference/provisioning/protocomm.rst +++ b/docs/en/api-reference/provisioning/protocomm.rst @@ -43,7 +43,7 @@ The protocomm component provides a project configuration menu to enable/disable * Support ``protocomm_security2`` with SRP6a-based key exchange + AES-GCM encryption/decryption: :ref:`CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2`. .. note:: - + Enabling multiple security versions at once offers the ability to control them dynamically but also increases the firmware size. .. only:: SOC_WIFI_SUPPORTED @@ -299,5 +299,7 @@ API Reference .. include-build-file:: inc/protocomm_security.inc .. include-build-file:: inc/protocomm_security0.inc .. include-build-file:: inc/protocomm_security1.inc +.. include-build-file:: inc/protocomm_security2.inc +.. include-build-file:: inc/esp_srp.inc .. include-build-file:: inc/protocomm_httpd.inc .. include-build-file:: inc/protocomm_ble.inc diff --git a/docs/zh_CN/api-reference/provisioning/protocomm.rst b/docs/zh_CN/api-reference/provisioning/protocomm.rst index 4293496fa5..bc4dcd5dac 100644 --- a/docs/zh_CN/api-reference/provisioning/protocomm.rst +++ b/docs/zh_CN/api-reference/provisioning/protocomm.rst @@ -40,10 +40,10 @@ Protocomm 为以下各种传输提供框架: * 支持 ``protocomm_security0``,该版本无安全功能::ref:`CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0`,该选项默认启用。 * 支持 ``protocomm_security1``,使用 Curve25519 密钥交换和 AES-CTR 加密/解密::ref:`CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1`,该选项默认启用。 - * 支持 ``protocomm_security2``,使用基于 SRP6a 的密钥交换和 AES-GCM 加密/解密::ref:`CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2`。 + * 支持 ``protocomm_security2``,使用基于 SRP6a 的密钥交换和 AES-GCM 加密/解密::ref:`CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2`。 + +.. note:: -.. note:: - 启用多个安全版本后可以动态控制安全版本,但也会增加固件大小。 .. only:: SOC_WIFI_SUPPORTED @@ -241,7 +241,7 @@ Protocomm 为以下各种传输提供框架: 使用 Security 0 的低功耗蓝牙传输方案示例 ------------------------------------------- - + 示例用法请参阅 :component_file:`wifi_provisioning/src/scheme_ble.c`。 .. highlight:: c @@ -299,5 +299,7 @@ API 参考 .. include-build-file:: inc/protocomm_security.inc .. include-build-file:: inc/protocomm_security0.inc .. include-build-file:: inc/protocomm_security1.inc +.. include-build-file:: inc/protocomm_security2.inc +.. include-build-file:: inc/esp_srp.inc .. include-build-file:: inc/protocomm_httpd.inc .. include-build-file:: inc/protocomm_ble.inc