From 00d037ce851495aca6365f2189849c6030f305df Mon Sep 17 00:00:00 2001 From: suvendhu Date: Mon, 9 May 2022 15:31:23 +0530 Subject: [PATCH] HTTP auth code cleanup --- Makefile.am | 2 +- cwmp.c | 6 +- digauth.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++ digestauth.c | 437 --------------------------------------------- http.c | 10 +- inc/digauth.h | 15 ++ inc/digestauth.h | 60 ------- 7 files changed, 475 insertions(+), 506 deletions(-) create mode 100644 digauth.c delete mode 100644 digestauth.c create mode 100644 inc/digauth.h delete mode 100644 inc/digestauth.h diff --git a/Makefile.am b/Makefile.am index 027c62d..2edd492 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,7 +11,7 @@ icwmpd_SOURCES = \ ./config.c \ ./session.c \ ./backupSession.c \ - ./digestauth.c \ + ./digauth.c \ ./event.c \ ./http.c \ ./netlink.c \ diff --git a/cwmp.c b/cwmp.c index 2a75e12..d4e4a77 100644 --- a/cwmp.c +++ b/cwmp.c @@ -31,7 +31,7 @@ #include "config.h" #include "backupSession.h" #include "ubus_utils.h" -#include "digestauth.h" +#include "digauth.h" #include "upload.h" #include "download.h" #include "sched_inform.h" @@ -763,7 +763,7 @@ static int cwmp_init(int argc, char **argv, struct cwmp *cwmp) load_custom_notify_json(cwmp); init_list_param_notify(); cwmp_uci_exit(); - generate_nonce_priv_key(); + get_nonce_key(); return CWMP_OK; } @@ -789,7 +789,7 @@ static void cwmp_free(struct cwmp *cwmp) FREE(cwmp->conf.forced_inform_json_file); FREE(cwmp->conf.custom_notify_json); FREE(cwmp->conf.boot_inform_json_file); - FREE(nonce_privacy_key); + FREE(nonce_key); clean_list_param_notify(); bkp_tree_clean(); diff --git a/digauth.c b/digauth.c new file mode 100644 index 0000000..0cc99ea --- /dev/null +++ b/digauth.c @@ -0,0 +1,451 @@ +/* + * digauth.c - HTTP digest authentication utility + * + * Copyright (C) 2022, IOPSYS Software Solutions AB. + * + * Author: suvendhu.hansa@iopsys.eu + * + * See LICENSE file for license related information. + * + */ + +#include +#include +#include +#include +#include + +#include "log.h" +#include "digauth.h" +#include "ssl_utils.h" +#include "common.h" + +#ifdef LMBEDTLS +#include +#define MD5_CTX mbedtls_md5_context +#define MD5_INIT(X) { mbedtls_md5_init(X); mbedtls_md5_starts_ret(X); } +#define MD5_UPDATE(X, Y, Z) mbedtls_md5_update_ret(X, (unsigned char *)Y, Z) +#define MD5_FINAL(X, Y) mbedtls_md5_finish_ret(Y, X) +#else +#include +#define MD5_CTX MD5_CTX +#define MD5_INIT MD5_Init +#define MD5_UPDATE MD5_Update +#define MD5_FINAL MD5_Final +#endif + +#ifndef MD5_DIGEST_SIZE +#define MD5_DIGEST_SIZE 16 +#endif + +#define MD5_HASH_HEX_LEN (2 * MD5_DIGEST_SIZE) + +char *nonce_key = NULL; + +struct parameters { + char *key; + char value[2049]; +}; + +enum param_index { + E_USERNAME, + E_REALM, + E_NONCE, + E_URI, + E_QOP, + E_NC, + E_CNONCE, + E_RESPONSE, + __E_MAX +}; + +struct parameters param[__E_MAX] = { + { "username", {'\0'} }, + { "realm", {'\0'} }, + { "nonce", {'\0'} }, + { "uri", {'\0'} }, + { "qop", {'\0'} }, + { "nc", {'\0'} }, + { "cnonce", {'\0'} }, + { "response", {'\0'} } +}; + +static void clear_param_values(void) +{ + unsigned int i; + + for (i = 0; i < (sizeof(param)/sizeof(param[0])); i++) { + memset(param[i].value, 0, sizeof(param[i].value)); + } +} + +static int get_param_index(char *key) +{ + unsigned int i; + + for (i = 0; i < (sizeof(param)/sizeof(param[0])); i++) { + if (strncmp(key, param[i].key, strlen(param[i].key)) == 0) + return i; + } + + return -1; +} + +static void strip_lead_trail_char(char *str, char ch) +{ + /* First remove leading strip-char */ + const char* first_valid = str; + + while(*first_valid != '\0' && *first_valid == ch) { + ++first_valid; + } + + size_t len = strlen(first_valid) + 1; + + memmove(str, first_valid, len); + + /* Now remove trailing strip-char */ + char* end_str = str + strlen(str) - 1; + + while(str < end_str && *end_str == ch) { + *end_str = '\0'; + --end_str ; + } +} + +static void get_hexstring(unsigned char *hash, int len, char *hexstr, int buflen) +{ + int i, j; + + if (hash == NULL || hexstr == NULL) + return; + + if (buflen <= len * 2) + return; + + memset(hexstr, 0, buflen); + + for (i = 0, j = 0; i < len; i++) { + sprintf(hexstr + j, "%02X", hash[i]); + j = j + 2; + } +} + +static void get_value_from_header(const char *data) +{ + if (data == NULL) + return; + + int header_len = strlen(data) + 1; + char header[header_len]; + memset(header, 0, header_len); + strncpy(header, data, header_len); + + clear_param_values(); + + char *start = strtok(header, ","); + while (start) { + char *eq = strchr(start, '='); + if (eq == NULL) + return; + + int len = eq - start + 1; + char key[len]; + snprintf(key, len, "%s", start); + strip_lead_trail_char(key, ' '); + strip_lead_trail_char(key, '\"'); + + eq = eq + 1; + char *end = eq + strlen(eq) - 1; + len = end - eq + 2; + char val[len]; + snprintf(val, len, "%s", eq); + strip_lead_trail_char(val, ' '); + strip_lead_trail_char(val, '\"'); + + int ind = get_param_index(key); + if (ind >= 0) { + snprintf(param[ind].value, sizeof(param[ind].value), "%s", val); + } + + start = strtok(NULL, ","); + } +} + +static void get_digest_ha1(const char *algo, const char *uname, const char *rlm, + const char *psw, const char *nonce, const char *cnonce, + char *skey, int skey_len) +{ + unsigned char digest[MD5_DIGEST_SIZE]; + MD5_CTX context; + + if (algo == NULL || uname == NULL || rlm == NULL || + psw == NULL || nonce == NULL || cnonce == NULL || skey == NULL) + return; + + int len = strlen(uname) + strlen(rlm) + strlen(psw) + 3; + char *a = (char *)calloc(sizeof(char), len); + if (a == NULL) + return; + + snprintf(a, len, "%s:%s:%s", uname, rlm, psw); + + MD5_INIT(&context); + MD5_UPDATE(&context, a, strlen(a)); + MD5_FINAL(digest, &context); + + free(a); + a = NULL; + + if (0 == strcasecmp(algo, "md5-sess")) { + len = strlen(nonce) + strlen(cnonce) + 3; + a = (char *)calloc(sizeof(char), len); + if (a == NULL) + return; + + snprintf(a, len, ":%s:%s", nonce, cnonce); + + MD5_INIT(&context); + MD5_UPDATE(&context, digest, sizeof(digest)); + MD5_UPDATE(&context, a, strlen(a)); + MD5_FINAL(digest, &context); + + free(a); + } + + get_hexstring(digest, sizeof(digest), skey, skey_len); +} + +static void get_digest_ha2(const char *method, const char *uri, char *ha2, int ha2_len) +{ + unsigned char digest[MD5_DIGEST_SIZE]; + MD5_CTX context; + + if (method == NULL || uri == NULL || ha2 == NULL) + return; + + int len = strlen(method) + strlen(uri) + 2; + char *a = (char *)calloc(sizeof(char), len); + if (a == NULL) + return; + + snprintf(a, len, "%s:%s", method, uri); + + MD5_INIT(&context); + MD5_UPDATE(&context, a, strlen(a)); + MD5_FINAL(digest, &context); + + free(a); + + get_hexstring(digest, sizeof(digest), ha2, ha2_len); +} + +static void get_digest_response(const char *ha1, const char *nonce, const char *nonce_cnt, + const char *cnonce, const char *qop, const char *ha2, + char *resp, int resp_len) +{ + MD5_CTX context; + unsigned char digest[MD5_DIGEST_SIZE]; + + if (ha1 == NULL || nonce == NULL || nonce_cnt == NULL || cnonce == NULL || + qop == NULL || ha2 == NULL || resp == NULL) + return; + + int len = strlen(nonce) + 3; + char *a = (char *)calloc(sizeof(char), len); + if (a == NULL) + return; + + snprintf(a, len, ":%s:", nonce); + + if (qop[0] != '\0') { + len = len + strlen(nonce_cnt) + strlen(cnonce) + strlen(qop) + 3; + char *b = (char *)calloc(sizeof(char), len); + if (b == NULL) { + free(a); + return; + } + + snprintf(b, len, "%s%s:%s:%s:", a, nonce_cnt, cnonce, qop); + + free(a); + a = b; + } + + MD5_INIT(&context); + MD5_UPDATE(&context, ha1, MD5_HASH_HEX_LEN); + MD5_UPDATE(&context, a, strlen(a)); + MD5_UPDATE(&context, ha2, MD5_HASH_HEX_LEN); + MD5_FINAL(digest, &context); + + free(a); + get_hexstring(digest, sizeof(digest), resp, resp_len); +} + +static void get_nonce(uint32_t time, const char* method, const char *rand, + unsigned int rand_size, const char *uri, const char *rlm, + char *nonce, unsigned int nonce_size) +{ + unsigned char ts[4]; + + if (method == NULL || uri == NULL || rlm == NULL || nonce == NULL) + return; + + int i; + for (i = 3; i >= 0; i--) { + ts[i] = (time >> (8 * (3 - i))) & 0xff; + } + + char tshex[sizeof(ts) * 2 + 1]; + get_hexstring(ts, sizeof(ts), tshex, sizeof(tshex)); + + unsigned int len = strlen(method) + 3; + char *meth = (char *)calloc(sizeof(char), len); + if (meth == NULL) + return; + + snprintf(meth, len, ":%s:", method); + + len = strlen(uri) + strlen(rlm) + 3; + char *uri_realm = (char *)calloc(sizeof(char), len); + if (uri_realm == NULL) { + free(meth); + return; + } + + snprintf(uri_realm, len, ":%s:%s", uri, rlm); + + MD5_CTX context; + unsigned char digest[MD5_DIGEST_SIZE]; + + MD5_INIT(&context); + MD5_UPDATE(&context, ts, 4); + MD5_UPDATE(&context, meth, strlen(meth)); + if (rand != NULL && rand_size > 0) + MD5_UPDATE(&context, rand, rand_size); + MD5_UPDATE(&context, uri_realm, strlen(uri_realm)); + MD5_FINAL(digest, &context); + + free(meth); + free(uri_realm); + memset(nonce, 0, nonce_size); + get_hexstring(digest, sizeof(digest), nonce, nonce_size); + len = nonce_size - strlen(nonce) - 1; + strncat(nonce, tshex, len); +} + +int http_authentication_failure_resp(FILE *fp, const char *http_meth, const char *uri, + const char *rlm, const char *opq) +{ + if (fp == NULL || http_meth == NULL || uri == NULL || rlm == NULL || opq == NULL) + return 0; + + int len; + char nonce[MD5_HASH_HEX_LEN + 9]; + uint32_t tm; + + tm = (uint32_t)time(NULL); + + len = nonce_key ? strlen(nonce_key) : 0; + get_nonce(tm, http_meth, nonce_key, len, uri, rlm, nonce, sizeof(nonce)); + + if (fprintf(fp, "WWW-Authenticate: Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"", rlm, nonce, opq) < 0) + return 0; + + return 1; +} + +int validate_http_digest_auth(const char *http_meth, const char *uri, const char *hdr, + const char *rlm, const char *usr, const char *psw, + unsigned int timeout) +{ + get_value_from_header(hdr); + + if (strcmp(param[E_USERNAME].value, usr) != 0) + return 0; + + if (strlen(param[E_REALM].value) == 0) + return 0; + + if (strcmp(param[E_REALM].value, rlm) != 0) + return 0; + + if (strlen(param[E_CNONCE].value) == 0) + return 0; + + if (strlen(param[E_QOP].value) == 0) + return 0; + + if (strlen(param[E_NC].value) == 0) + return 0; + + if (strlen(param[E_RESPONSE].value) == 0) + return 0; + + int len = strlen(param[E_NONCE].value); + if (len == 0) + return 0; + + char *tms = param[E_NONCE].value + len - 8; + uint32_t tm = strtoul(tms, NULL, 16); + uint32_t cur_tm = (uint32_t)time(NULL); + + if (cur_tm > tm + timeout) { + CWMP_LOG(ERROR, "Time exceeded the timeout"); + return 0; + } + + if (nonce_key ==NULL) { + if (get_nonce_key() != CWMP_OK) + return -1; + } + + char nonce[MD5_HASH_HEX_LEN + 9]; + get_nonce(tm, http_meth, nonce_key, strlen(nonce_key), uri, rlm, nonce, sizeof(nonce)); + + if (strcmp(param[E_NONCE].value, nonce) != 0) { + CWMP_LOG(ERROR, "Nonce value is probably fabricated"); + return 0; + } + + if (strlen(param[E_URI].value) == 0) + return 0; + + if (strncmp(param[E_URI].value, uri, strlen(uri)) != 0) { + CWMP_LOG(ERROR, "Authentication failed, URI is not matched"); + return 0; + } + + if ((strcmp(param[E_QOP].value, "auth") != 0) && (strcmp(param[E_QOP].value, "") != 0)) + return 0; + + char *tmp; + unsigned long int nc_int = strtoul(param[E_NC].value, &tmp, 16); + if ((*tmp != '\0') || (nc_int == LONG_MAX && errno == ERANGE)) { + CWMP_LOG(ERROR, "Authentication failed due to invalid format"); + return 0; + } + + char ha1[MD5_HASH_HEX_LEN + 1]; + char ha2[MD5_HASH_HEX_LEN + 1]; + char resp[MD5_HASH_HEX_LEN + 1]; + + get_digest_ha1("md5", usr, rlm, psw, param[E_NONCE].value, param[E_CNONCE].value, ha1, sizeof(ha1)); + get_digest_ha2(http_meth, uri, ha2, sizeof(ha2)); + get_digest_response(ha1, param[E_NONCE].value, param[E_NC].value, param[E_CNONCE].value, + param[E_QOP].value, ha2, resp, sizeof(resp)); + + if (strcmp(resp, param[E_RESPONSE].value) != 0) + return 0; + + return 1; +} + +int get_nonce_key(void) +{ + nonce_key = generate_random_string(28); + if (nonce_key == NULL) + return CWMP_GEN_ERR; + + return CWMP_OK; +} diff --git a/digestauth.c b/digestauth.c deleted file mode 100644 index 2fcca3e..0000000 --- a/digestauth.c +++ /dev/null @@ -1,437 +0,0 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * HTTP digest auth functions: originally imported from libmicrohttpd - * - * Copyright (C) 2013 Oussama Ghorbel - * Omar Kallel - * - */ - -#ifdef LMBEDTLS -#include -#define MD5_CTX mbedtls_md5_context -#define MD5_INIT(X) { mbedtls_md5_init(X); mbedtls_md5_starts_ret(X); } -#define MD5_UPDATE(X, Y, Z) mbedtls_md5_update_ret(X, (unsigned char *)Y, Z) -#define MD5_FINAL(X, Y) mbedtls_md5_finish_ret(Y, X) -#else -#include -#define MD5_CTX MD5_CTX -#define MD5_INIT MD5_Init -#define MD5_UPDATE MD5_Update -#define MD5_FINAL MD5_Final -#endif - -#include -#include -#include -#include -#include - -#include "log.h" -#include "common.h" -#include "digestauth.h" -#include "ssl_utils.h" - -#ifndef MD5_DIGEST_SIZE -#define MD5_DIGEST_SIZE 16 -#endif - -#define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE) - -/** - * Maximum length of a username for digest authentication. - */ -#define MAX_USERNAME_LENGTH 1024 - -/** - * Maximum length of a realm for digest authentication. - */ -#define MAX_REALM_LENGTH 1024 - -/** - * Maximum length of the response in digest authentication. - */ -#define MAX_AUTH_RESPONSE_LENGTH 1024 - -/** - * Maximum length of the nonce in digest authentication. - */ -#define MAX_NONCE_LENGTH 1024 - -char *nonce_privacy_key = NULL; - -int generate_nonce_priv_key(void) -{ - nonce_privacy_key = generate_random_string(28); - if (nonce_privacy_key == NULL) - return CWMP_GEN_ERR; - return CWMP_OK; -} - -static time_t mhd_monotonic_time(void) -{ - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - return ts.tv_sec; - return time(NULL); -} - -/** - * convert bin to hex - * - * @param bin binary data - * @param len number of bytes in bin - * @param hex pointer to len*2+1 bytes - */ -static void cvthex(const unsigned char *bin, size_t len, char *hex) -{ - size_t i; - - for (i = 0; i < len; ++i) { - unsigned int j; - j = (bin[i] >> 4) & 0x0f; - hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10); - j = bin[i] & 0x0f; - hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10); - } - hex[len * 2] = '\0'; -} - -/** - * Calculate the server nonce so that it mitigates replay attacks - * The current format of the nonce is ... - * H(timestamp ":" method ":" random ":" uri ":" realm) + Hex(timestamp) - * - * @param nonce_time The amount of time in seconds for a nonce to be invalid - * @param method HTTP method - * @param rnd A pointer to a character array for the random seed - * @param rnd_size The size of the random seed array - * @param uri HTTP URI (in MHD, without the arguments ("?k=v") - * @param realm A string of characters that describes the realm of auth. - * @param nonce A pointer to a character array for the nonce to put in - */ -static void calculate_nonce(uint32_t nonce_time, const char *method, const char *rnd, unsigned int rnd_size, const char *uri, const char *realm, char *nonce, size_t size) -{ - MD5_CTX md5; - unsigned char timestamp[4]; - unsigned char tmpnonce[MD5_DIGEST_SIZE]; - char timestamphex[sizeof(timestamp) * 2 + 1]; - - if (nonce == NULL) - return; - - memset(nonce, 0, size); - - MD5_INIT(&md5); - timestamp[0] = (nonce_time & 0xff000000) >> 0x18; - timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10; - timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08; - timestamp[3] = (nonce_time & 0x000000ff); - MD5_UPDATE(&md5, timestamp, 4); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, method, strlen(method)); - MD5_UPDATE(&md5, ":", 1); - if (rnd_size > 0) - MD5_UPDATE(&md5, rnd, rnd_size); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, uri, strlen(uri)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, realm, strlen(realm)); - MD5_FINAL(tmpnonce, &md5); - cvthex(tmpnonce, sizeof(tmpnonce), nonce); - cvthex(timestamp, 4, timestamphex); - size_t len = size - strlen(nonce) - 1; - strncat(nonce, timestamphex, len); -} - -/** - * Lookup subvalue off of the HTTP Authorization header. - * - * A description of the input format for 'data' is at - * http://en.wikipedia.org/wiki/Digest_access_authentication - * - * - * @param dest where to store the result (possibly truncated if - * the buffer is not big enough). - * @param size size of dest - * @param data pointer to the Authorization header - * @param key key to look up in data - * @return size of the located value, 0 if otherwise - */ -static int lookup_sub_value(char *dest, size_t size, const char *data, const char *key) -{ - size_t keylen; - size_t len; - const char *ptr; - const char *eq; - const char *q2; - const char *qn; - - unsigned int diff; - if (0 == size) - return 0; - keylen = strlen(key); - ptr = data; - while ('\0' != *ptr) { - const char *q1; - if (NULL == (eq = strchr(ptr, '='))) - return 0; - q1 = eq + 1; - while (' ' == *q1) - q1++; - if ('\"' != *q1) { - q2 = strchr(q1, ','); - qn = q2; - } else { - q1++; - q2 = strchr(q1, '\"'); - if (NULL == q2) - return 0; /* end quote not found */ - qn = q2 + 1; - } - if ((0 == strncasecmp(ptr, key, keylen)) && (eq == &ptr[keylen])) { - if (NULL == q2) { - len = strlen(q1); - snprintf(dest, size, "%s", q1); - return len; - } else { - diff = (q2 - q1) + 1; - if (size > diff) - size = diff; - snprintf(dest, size, "%s", q1); - return size; - } - } - if (NULL == qn) - return 0; - ptr = strchr(qn, ','); - if (NULL == ptr) - return 0; - ptr++; - while (' ' == *ptr) - ptr++; - } - return 0; -} - -/** - * calculate H(A1) as per RFC2617 spec and store the - * result in 'sessionkey'. - * - * @param alg The hash algorithm used, can be "md5" or "md5-sess" - * @param username A `char *' pointer to the username value - * @param realm A `char *' pointer to the realm value - * @param password A `char *' pointer to the password value - * @param nonce A `char *' pointer to the nonce value - * @param cnonce A `char *' pointer to the cnonce value - * @param sessionkey pointer to buffer of HASH_MD5_HEX_LEN+1 bytes - */ -static void digest_calc_ha1(const char *alg, const char *username, const char *realm, const char *password, const char *nonce, const char *cnonce, char *sessionkey) -{ - MD5_CTX md5; - unsigned char ha1[MD5_DIGEST_SIZE]; - - MD5_INIT(&md5); - MD5_UPDATE(&md5, username, strlen(username)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, realm, strlen(realm)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, password, strlen(password)); - MD5_FINAL(ha1, &md5); - if (0 == strcasecmp(alg, "md5-sess")) { - MD5_INIT(&md5); - MD5_UPDATE(&md5, ha1, sizeof(ha1)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, nonce, strlen(nonce)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, cnonce, strlen(cnonce)); - MD5_FINAL(ha1, &md5); - } - cvthex(ha1, sizeof(ha1), sessionkey); -} - -/** - * Calculate request-digest/response-digest as per RFC2617 spec - * - * @param ha1 H(A1) - * @param nonce nonce from server - * @param noncecount 8 hex digits - * @param cnonce client nonce - * @param qop qop-value: "", "auth" or "auth-int" - * @param method method from request - * @param uri requested URL - * @param hentity H(entity body) if qop="auth-int" - * @param response request-digest or response-digest - */ -static void digest_calc_response(const char *ha1, const char *nonce, const char *noncecount, const char *cnonce, const char *qop, const char *method, const char *uri, char *response) -{ - MD5_CTX md5; - unsigned char ha2[MD5_DIGEST_SIZE]; - unsigned char resphash[MD5_DIGEST_SIZE]; - char ha2hex[HASH_MD5_HEX_LEN + 1]; - - MD5_INIT(&md5); - MD5_UPDATE(&md5, method, strlen(method)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, uri, strlen(uri)); - MD5_FINAL(ha2, &md5); - cvthex(ha2, MD5_DIGEST_SIZE, ha2hex); - MD5_INIT(&md5); - /* calculate response */ - MD5_UPDATE(&md5, ha1, HASH_MD5_HEX_LEN); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, nonce, strlen(nonce)); - MD5_UPDATE(&md5, ":", 1); - if ('\0' != *qop) { - MD5_UPDATE(&md5, noncecount, strlen(noncecount)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, cnonce, strlen(cnonce)); - MD5_UPDATE(&md5, ":", 1); - MD5_UPDATE(&md5, qop, strlen(qop)); - MD5_UPDATE(&md5, ":", 1); - } - MD5_UPDATE(&md5, ha2hex, HASH_MD5_HEX_LEN); - MD5_FINAL(resphash, &md5); - cvthex(resphash, sizeof(resphash), response); -} - -int http_digest_auth_fail_response(FILE *fp, const char *http_method, const char *url, const char *realm, const char *opaque) -{ - size_t hlen, nonce_key_len = 0; - char nonce[HASH_MD5_HEX_LEN + 9]; - - /* Generating the server nonce */ - nonce_key_len = nonce_privacy_key ? strlen(nonce_privacy_key) : 0; - calculate_nonce((uint32_t)mhd_monotonic_time(), http_method, nonce_privacy_key, nonce_key_len, url, realm, nonce, sizeof(nonce)); - - /* Building the authentication header */ - hlen = snprintf(NULL, 0, "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"", realm, nonce, opaque); - { - char header[hlen + 1]; - - snprintf(header, sizeof(header), "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"", realm, nonce, opaque); - - fputs("WWW-Authenticate: ", fp); - fputs(header, fp); - return MHD_YES; - } - return MHD_NO; -} - -int http_digest_auth_check(const char *http_method, const char *url, const char *header, const char *realm, const char *username, const char *password, unsigned int nonce_timeout) -{ - size_t len; - char *end; - char nonce[MAX_NONCE_LENGTH]; - size_t left; /* number of characters left in 'header' for 'uri' */ - - left = strlen(header); - - { - char un[MAX_USERNAME_LENGTH]; - - len = lookup_sub_value(un, sizeof(un), header, "username"); - if (0 != strcmp(username, un)) - return MHD_NO; - left -= strlen("username") + len; - } - - { - char r[MAX_REALM_LENGTH]; - - len = lookup_sub_value(r, sizeof(r), header, "realm"); - if ((0 == len) || (0 != strcmp(realm, r))) - return MHD_NO; - left -= strlen("realm") + len; - } - - if (0 == (len = lookup_sub_value(nonce, sizeof(nonce), header, "nonce"))) - return MHD_NO; - left -= strlen("nonce") + len; - - { - char uri[left]; - char cnonce[MAX_NONCE_LENGTH]; - char qop[15]; /* auth,auth-int */ - char nc[20]; - char response[MAX_AUTH_RESPONSE_LENGTH]; - char ha1[HASH_MD5_HEX_LEN + 1]; - char respexp[HASH_MD5_HEX_LEN + 1]; - char noncehashexp[HASH_MD5_HEX_LEN + 9]; - uint32_t nonce_time; - unsigned long int nci; - uint32_t t; - size_t nonce_key_len = 0; - - - if (0 == lookup_sub_value(uri, sizeof(uri), header, "uri")) - return MHD_NO; - - /* 8 = 4 hexadecimal numbers for the timestamp */ - nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16); - t = (uint32_t)mhd_monotonic_time(); - /* - * First level vetting for the nonce validity if the timestamp - * attached to the nonce exceeds `nonce_timeout' then the nonce is - * invalid. - */ - if ((t > nonce_time + nonce_timeout) || (nonce_time + nonce_timeout < nonce_time)) { - CWMP_LOG(ERROR, "Timestamp attached to the nonce exceeds"); - return MHD_NO; - } - - if (0 != strncmp(uri, url, strlen(url))) { - CWMP_LOG(ERROR, "Authentication failed: URI does not match."); - return MHD_NO; - } - - if (nonce_privacy_key == NULL) { - if (generate_nonce_priv_key() != CWMP_OK) - return MHD_INVALID_NONCE; - } - - nonce_key_len = strlen(nonce_privacy_key); - calculate_nonce(nonce_time, http_method, nonce_privacy_key, nonce_key_len, url, realm, noncehashexp, sizeof(noncehashexp)); - - /* - * Second level vetting for the nonce validity - * if the timestamp attached to the nonce is valid - * and possibly fabricated (in case of an attack) - * the attacker must also know the random seed to be - * able to generate a "sane" nonce, which if he does - * not, the nonce fabrication process going to be - * very hard to achieve. - */ - - if (0 != strcmp(nonce, noncehashexp)) { - CWMP_LOG(ERROR, "Nonce value is valid and possibly fabricated"); - return MHD_NO; - } - - if ((0 == lookup_sub_value(cnonce, sizeof(cnonce), header, "cnonce")) || (0 == lookup_sub_value(qop, sizeof(qop), header, "qop")) || ((0 != strcmp(qop, "auth")) && (0 != strcmp(qop, ""))) || (0 == lookup_sub_value(nc, sizeof(nc), header, "nc")) || - (0 == lookup_sub_value(response, sizeof(response), header, "response"))) { - CWMP_LOG(ERROR, "Authentication failed, invalid format."); - return MHD_NO; - } - - nci = strtoul(nc, &end, 16); - if (('\0' != *end) || ((LONG_MAX == nci) && (ERANGE == errno))) { - CWMP_LOG(ERROR, "Authentication failed, invalid format."); - return MHD_NO; /* invalid nonce format */ - } - - /* - * Checking if that combination of nonce and nc is sound - * and not a replay attack attempt. Also adds the nonce - * to the nonce-nc map if it does not exist there. - */ - - digest_calc_ha1("md5", username, realm, password, nonce, cnonce, ha1); - digest_calc_response(ha1, nonce, nc, cnonce, qop, http_method, uri, respexp); - - return (0 == strcmp(response, respexp)) ? MHD_YES : MHD_NO; - } -} diff --git a/http.c b/http.c index a71f2b8..2e900cc 100644 --- a/http.c +++ b/http.c @@ -21,7 +21,7 @@ #include "event.h" #include "ubus_utils.h" #include "config.h" -#include "digestauth.h" +#include "digauth.h" #define REALM "authenticate@cwmp" #define OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" @@ -326,12 +326,12 @@ static void http_cr_new_client(int client, bool service_available) if (!service_available || !method_is_get) { goto http_end; } - int auth_check = http_digest_auth_check("GET", "/", auth_digest_buffer + strlen("Authorization: Digest "), REALM, username, password, 300); - if (auth_check == MHD_INVALID_NONCE) { + int auth_check = validate_http_digest_auth("GET", "/", auth_digest_buffer + strlen("Authorization: Digest "), REALM, username, password, 300); + if (auth_check == -1) { /* invalid nonce */ internal_error = true; goto http_end; } - if (auth_digest_checked && auth_check == MHD_YES) + if (auth_digest_checked && auth_check == 0) auth_status = 1; else auth_status = 0; @@ -357,7 +357,7 @@ http_end: CWMP_LOG(INFO, "Receive Connection Request: Return 401 Unauthorized"); fputs("HTTP/1.1 401 Unauthorized\r\n", fp); fputs("Connection: close\r\n", fp); - http_digest_auth_fail_response(fp, "GET", "/", REALM, OPAQUE); + http_authentication_failure_resp(fp, "GET", "/", REALM, OPAQUE); fputs("\r\n", fp); } fputs("\r\n", fp); diff --git a/inc/digauth.h b/inc/digauth.h new file mode 100644 index 0000000..6846a27 --- /dev/null +++ b/inc/digauth.h @@ -0,0 +1,15 @@ +#ifndef DIGAUTH_H_ +#define DIGAUTH_H_ + +#include + +extern char *nonce_key; + +int get_nonce_key(void); +int validate_http_digest_auth(const char *http_meth, const char *uri, const char *hdr, + const char *rlm, const char *usr, const char *psw, + unsigned int timeout); +int http_authentication_failure_resp(FILE *fp, const char *http_meth, const char *uri, + const char *rlm, const char *opq); + +#endif /* DIGAUTH_H_ */ diff --git a/inc/digestauth.h b/inc/digestauth.h deleted file mode 100644 index 420871b..0000000 --- a/inc/digestauth.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * HTTP digest auth functions: originally imported from libmicrohttpd - * - * Copyright (C) 2013 Oussama Ghorbel - * - */ - -#ifndef DIGESTAUTH_H_ -#define DIGESTAUTH_H_ - -/** - * MHD-internal return code for "YES". - */ -#define MHD_YES 1 - -/** - * MHD-internal return code for "NO". - */ -#define MHD_NO 0 - -/** - * MHD digest auth internal code for an invalid nonce. - */ -#define MHD_INVALID_NONCE -1 - -extern char *nonce_privacy_key; - -/** - * make response to request authentication from the client - * - * @param fp - * @param http_method - * @param url - * @param realm the realm presented to the client - * @param opaque string to user for opaque value - * @return 'MHD_YES' on success, otherwise 'MHD_NO' - */ -int http_digest_auth_fail_response(FILE *fp, const char *http_method, const char *url, const char *realm, const char *opaque); - -/** - * Authenticates the authorization header sent by the client - * - * @param http_method - * @param url - * @param header: pointer to the position just after the string "Authorization: Digest " - * @param realm The realm presented to the client - * @param username The username needs to be authenticated - * @param password The password used in the authentication - * @param nonce_timeout The amount of time for a nonce to be invalid in seconds - * @return 'MHD_YES' if authenticated, 'MHD_NO' if not, 'MHD_INVALID_NONCE' if nonce is invalid - */ -int http_digest_auth_check(const char *http_method, const char *url, const char *header, const char *realm, const char *username, const char *password, unsigned int nonce_timeout); - -int generate_nonce_priv_key(void); - -#endif /* DIGESTAUTH_H_ */