HTTP auth code cleanup

This commit is contained in:
suvendhu 2022-05-09 15:31:23 +05:30
parent 20e8be761b
commit 00d037ce85
7 changed files with 475 additions and 506 deletions

View file

@ -11,7 +11,7 @@ icwmpd_SOURCES = \
./config.c \
./session.c \
./backupSession.c \
./digestauth.c \
./digauth.c \
./event.c \
./http.c \
./netlink.c \

6
cwmp.c
View file

@ -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();

451
digauth.c Normal file
View file

@ -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 <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdint.h>
#include "log.h"
#include "digauth.h"
#include "ssl_utils.h"
#include "common.h"
#ifdef LMBEDTLS
#include <mbedtls/md5.h>
#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 <openssl/md5.h>
#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;
}

View file

@ -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 <oussama.ghorbel@pivasoftware.com>
* Omar Kallel <omar.kallel@pivasoftware.com>
*
*/
#ifdef LMBEDTLS
#include <mbedtls/md5.h>
#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 <openssl/md5.h>
#define MD5_CTX MD5_CTX
#define MD5_INIT MD5_Init
#define MD5_UPDATE MD5_Update
#define MD5_FINAL MD5_Final
#endif
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdint.h>
#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;
}
}

10
http.c
View file

@ -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);

15
inc/digauth.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef DIGAUTH_H_
#define DIGAUTH_H_
#include <stdio.h>
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_ */

View file

@ -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 <oussama.ghorbel@pivasoftware.com>
*
*/
#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_ */