diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c index d480a0e2a7..1b120532c5 100644 --- a/components/esp_http_client/esp_http_client.c +++ b/components/esp_http_client/esp_http_client.c @@ -579,6 +579,7 @@ static esp_err_t _clear_auth_data(esp_http_client_handle_t client) free(client->auth_data->qop); free(client->auth_data->nonce); free(client->auth_data->opaque); + free(client->auth_data->uri); memset(client->auth_data, 0, sizeof(esp_http_auth_data_t)); return ESP_OK; } @@ -610,7 +611,12 @@ static esp_err_t esp_http_client_prepare(esp_http_client_handle_t client) auth_response = http_auth_basic(client->connection_info.username, client->connection_info.password); #ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH } else if (client->connection_info.auth_type == HTTP_AUTH_TYPE_DIGEST && client->auth_data) { - client->auth_data->uri = client->connection_info.path; + client->auth_data->uri = NULL; + http_utils_assign_string(&client->auth_data->uri,client->connection_info.path,-1); + if (client->connection_info.query){ + http_utils_append_string(&client->auth_data->uri,"?",-1); + http_utils_append_string(&client->auth_data->uri,client->connection_info.query,-1); + } client->auth_data->cnonce = ((uint64_t)esp_random() << 32) + esp_random(); auth_response = http_auth_digest(client->connection_info.username, client->connection_info.password, client->auth_data); client->auth_data->nc ++; @@ -1754,6 +1760,9 @@ void esp_http_client_add_auth(esp_http_client_handle_t client) client->auth_data->nc = 1; client->auth_data->realm = http_utils_get_string_between(auth_header, "realm=\"", "\""); client->auth_data->algorithm = http_utils_get_string_between(auth_header, "algorithm=", ","); + if (client->auth_data->algorithm == NULL) { + client->auth_data->algorithm = http_utils_get_string_after(auth_header, "algorithm="); + } if (client->auth_data->algorithm == NULL) { client->auth_data->algorithm = strdup("MD5"); } diff --git a/components/esp_http_client/lib/http_auth.c b/components/esp_http_client/lib/http_auth.c index 6b8e092402..a478660b20 100644 --- a/components/esp_http_client/lib/http_auth.c +++ b/components/esp_http_client/lib/http_auth.c @@ -12,6 +12,7 @@ #include "sys/socket.h" #include "esp_rom_md5.h" #include "esp_tls_crypto.h" +#include "mbedtls/sha256.h" #include "esp_log.h" #include "esp_check.h" @@ -20,6 +21,8 @@ #include "http_auth.h" #define MD5_MAX_LEN (33) +#define SHA256_LEN (32) +#define SHA256_HEX_LEN (65) #define HTTP_AUTH_BUF_LEN (1024) static const char *TAG = "HTTP_AUTH"; @@ -59,6 +62,54 @@ static int md5_printf(char *md, const char *fmt, ...) return MD5_MAX_LEN; } +/** + * @brief This function hash a formatted string with SHA256 and format the result as ascii characters + * + * @param sha The buffer will hold the ascii result + * @param[in] fmt The format + * + * @return Length of the result + */ +static int sha256_sprintf(char *sha, const char *fmt, ...) +{ + + unsigned char *buf; + unsigned char digest[SHA256_LEN]; + int len, i; + va_list ap; + va_start(ap, fmt); + len = vasprintf((char **)&buf, fmt, ap); + if (buf == NULL) { + va_end(ap); + return ESP_FAIL; + } + + int ret = 0; + mbedtls_sha256_context sha256; + mbedtls_sha256_init(&sha256); + if (mbedtls_sha256_starts(&sha256, 0) != 0) { + goto exit; + } + if (mbedtls_sha256_update(&sha256, buf, len) != 0) { + goto exit; + } + if (mbedtls_sha256_finish(&sha256, digest) != 0) { + goto exit; + } + + for (i = 0; i < 32; ++i) { + sprintf(&sha[i * 2], "%02x", (unsigned int)digest[i]); + } + sha[SHA256_HEX_LEN - 1] = '\0'; + ret = SHA256_HEX_LEN; + +exit: + free(buf); + mbedtls_sha256_free(&sha256); + va_end(ap); + return ret; +} + char *http_auth_digest(const char *username, const char *password, esp_http_auth_data_t *auth_data) { char *ha1, *ha2 = NULL; @@ -68,57 +119,67 @@ char *http_auth_digest(const char *username, const char *password, esp_http_auth esp_err_t ret = ESP_OK; if (username == NULL || - password == NULL || - auth_data->nonce == NULL || - auth_data->uri == NULL || - auth_data->realm == NULL) { + password == NULL || + auth_data->nonce == NULL || + auth_data->uri == NULL || + auth_data->realm == NULL) { return NULL; } - ha1 = calloc(1, MD5_MAX_LEN); + int digest_size = MD5_MAX_LEN; + int (*digest_func)(char *digest, const char *fmt, ...) = md5_printf; + if (!memcmp(auth_data->algorithm, "SHA256", strlen("SHA256")) || + !memcmp(auth_data->algorithm, "SHA-256", strlen("SHA-256"))) { + digest_size = SHA256_HEX_LEN; + digest_func = sha256_sprintf; + } + + ha1 = calloc(1, digest_size); ESP_GOTO_ON_FALSE(ha1, ESP_FAIL, _digest_exit, TAG, "Memory exhausted"); - ha2 = calloc(1, MD5_MAX_LEN); + ha2 = calloc(1, digest_size); ESP_GOTO_ON_FALSE(ha2, ESP_FAIL, _digest_exit, TAG, "Memory exhausted"); - digest = calloc(1, MD5_MAX_LEN); + digest = calloc(1, digest_size); ESP_GOTO_ON_FALSE(digest, ESP_FAIL, _digest_exit, TAG, "Memory exhausted"); - if (md5_printf(ha1, "%s:%s:%s", username, auth_data->realm, password) <= 0) { + if (digest_func(ha1, "%s:%s:%s", username, auth_data->realm, password) <= 0) { goto _digest_exit; } ESP_LOGD(TAG, "%s %s %s %s", "Digest", username, auth_data->realm, password); - if (strcasecmp(auth_data->algorithm, "md5-sess") == 0) { - if (md5_printf(ha1, "%s:%s:%016llx", ha1, auth_data->nonce, auth_data->cnonce) <= 0) { + if ((strcasecmp(auth_data->algorithm, "md5-sess") == 0) || + (strcasecmp(auth_data->algorithm, "SHA256") == 0) || + (strcasecmp(auth_data->algorithm, "md5-sess") == 0)) { + if (digest_func(ha1, "%s:%s:%016llx", ha1, auth_data->nonce, auth_data->cnonce) <= 0) { goto _digest_exit; } } - if (md5_printf(ha2, "%s:%s", auth_data->method, auth_data->uri) <= 0) { + if (digest_func(ha2, "%s:%s", auth_data->method, auth_data->uri) <= 0) { goto _digest_exit; } //support qop = auth if (auth_data->qop && strcasecmp(auth_data->qop, "auth-int") == 0) { - if (md5_printf(ha2, "%s:%s", ha2, "entity") <= 0) { + if (digest_func(ha2, "%s:%s", ha2, "entity") <= 0) { goto _digest_exit; } } if (auth_data->qop) { - // response=MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) - if (md5_printf(digest, "%s:%s:%08x:%016llx:%s:%s", ha1, auth_data->nonce, auth_data->nc, auth_data->cnonce, auth_data->qop, ha2) <= 0) { + // response=digest_func(HA1:nonce:nonceCount:cnonce:qop:HA2) + if (digest_func(digest, "%s:%s:%08x:%016llx:%s:%s", ha1, auth_data->nonce, auth_data->nc, auth_data->cnonce, auth_data->qop, ha2) <= 0) { goto _digest_exit; } } else { - // response=MD5(HA1:nonce:HA2) - if (md5_printf(digest, "%s:%s:%s", ha1, auth_data->nonce, ha2) <= 0) { + // response=digest_func(HA1:nonce:HA2) + if (digest_func(digest, "%s:%s:%s", ha1, auth_data->nonce, ha2) <= 0) { goto _digest_exit; } } - int rc = asprintf(&auth_str, "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", algorithm=\"MD5\", " - "response=\"%s\", qop=%s, nc=%08x, cnonce=%016"PRIx64, - username, auth_data->realm, auth_data->nonce, auth_data->uri, digest, auth_data->qop, auth_data->nc, auth_data->cnonce); + int rc = asprintf(&auth_str, "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", algorithm=%s, " + "response=\"%s\", qop=%s, nc=%08x, cnonce=\"%016"PRIx64"\"", + username, auth_data->realm, auth_data->nonce, auth_data->uri, auth_data->algorithm, digest, auth_data->qop, auth_data->nc, auth_data->cnonce); if (rc < 0) { ESP_LOGE(TAG, "asprintf() returned: %d", rc); ret = ESP_FAIL; diff --git a/components/esp_http_client/lib/http_utils.c b/components/esp_http_client/lib/http_utils.c index 495b949a6e..a47796c187 100644 --- a/components/esp_http_client/lib/http_utils.c +++ b/components/esp_http_client/lib/http_utils.c @@ -123,6 +123,23 @@ char *http_utils_get_string_between(const char *str, const char *begin, const ch return NULL; } +char *http_utils_get_string_after(const char *str, const char *begin) +{ + char *found = strcasestr(str, begin); + char *ret = NULL; + if (found) { + found += strlen(begin); + char *found_end = (char *)str + strlen(str); + if (found_end) { + ret = calloc(1, found_end - found + 1); + mem_check(ret); + memcpy(ret, found, found_end - found); + return ret; + } + } + return NULL; +} + int http_utils_str_starts_with(const char *str, const char *start) { int i; diff --git a/components/esp_http_client/lib/include/http_utils.h b/components/esp_http_client/lib/include/http_utils.h index 5d09a5c6ac..e3758e3f3d 100644 --- a/components/esp_http_client/lib/include/http_utils.h +++ b/components/esp_http_client/lib/include/http_utils.h @@ -55,6 +55,17 @@ void http_utils_trim_whitespace(char **str); */ char *http_utils_get_string_between(const char *str, const char *begin, const char *end); +/** + * @brief Returns a string that contains the part after the search string till the end of the source string. + * It will allocate a new memory space for this string, so you need to free it when no longer used + * + * @param[in] str The source string + * @param[in] begin The search string + * + * @return The string between begin and the end of str + */ +char *http_utils_get_string_after(const char *str, const char *begin); + /** * @brief Join 2 strings to one * It will allocate a new memory space for this string, so you need to free it when no longer use diff --git a/docs/en/api-reference/protocols/esp_http_client.rst b/docs/en/api-reference/protocols/esp_http_client.rst index 9ec6fde049..8a487c69a1 100644 --- a/docs/en/api-reference/protocols/esp_http_client.rst +++ b/docs/en/api-reference/protocols/esp_http_client.rst @@ -83,6 +83,7 @@ ESP HTTP client supports both **Basic** and **Digest** Authentication. * Users can provide the username and password in the ``url`` or the ``username`` and ``password`` members of the ``esp_http_client_config_t`` configuration. For ``auth_type = HTTP_AUTH_TYPE_BASIC``, the HTTP client takes only one perform operation to pass the authentication process. * If ``auth_type = HTTP_AUTH_TYPE_NONE``, but the ``username`` and ``password`` fields are present in the configuration, the HTTP client takes two perform operations. The client will receive the ``401 Unauthorized`` header in its first attempt to connect to the server. Based on this information, it decides which authentication method to choose and performs it in the second operation. * Check out the example functions ``http_auth_basic``, ``http_auth_basic_redirect`` (for Basic authentication) and ``http_auth_digest`` (for Digest authentication) in the application example for implementation details. + * Currently, only MD5 and SHA-256 algorithms are supported with Digest authentication. Examples of Authentication Configuration