1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

http: auth digest

This commit is contained in:
Ilya Smelykh 2023-12-08 07:30:50 +00:00 committed by Andy Green
parent 42cb5ffbbe
commit fdfde2ce0b
10 changed files with 428 additions and 2 deletions

View file

@ -179,6 +179,10 @@ if (LWS_WITH_SECURE_STREAMS_AUTH_SIGV4)
set(LWS_WITH_GENCRYPTO 1)
endif()
if (LWS_WITH_HTTP_DIGEST_AUTH)
set(LWS_WITH_GENCRYPTO 1)
endif()
if (APPLE)
set(LWS_ROLE_DBUS 0)
endif()

View file

@ -159,6 +159,7 @@ option(LWS_WITH_SYS_ASYNC_DNS "Nonblocking internal IPv4 + IPv6 DNS resolver" OF
option(LWS_WITH_SYS_NTPCLIENT "Build in tiny ntpclient good for tls date validation and run via lws_system" OFF)
option(LWS_WITH_SYS_DHCP_CLIENT "Build in tiny DHCP client" OFF)
option(LWS_WITH_HTTP_BASIC_AUTH "Support Basic Auth" ON)
option(LWS_WITH_HTTP_DIGEST_AUTH "Support Digest Auth (caution deprecated crypto)" ON)
option(LWS_WITH_HTTP_UNCOMMON_HEADERS "Include less common http header support" ON)
option(LWS_WITH_SYS_STATE "lws_system state support" ON)
option(LWS_WITH_SYS_SMD "Lws System Message Distribution" ON)

View file

@ -179,6 +179,7 @@
#cmakedefine LWS_WITH_GZINFLATE
#cmakedefine LWS_WITH_HTTP2
#cmakedefine LWS_WITH_HTTP_BASIC_AUTH
#cmakedefine LWS_WITH_HTTP_DIGEST_AUTH
#cmakedefine LWS_WITH_HTTP_BROTLI
#cmakedefine LWS_HTTP_HEADERS_ALL
#cmakedefine LWS_WITH_HTTP_PROXY

View file

@ -244,6 +244,8 @@ struct lws_client_connect_info {
* context template to take a copy of for this wsi. Used to isolate
* wsi-specific logs into their own stream or file.
*/
const char *auth_username;
const char *auth_password;
/* Add new things just above here ---^
* This is part of the ABI, don't needlessly break compatibility

View file

@ -361,6 +361,8 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)
lws_snprintf(buf_localport, sizeof(buf_localport), "%u", i->local_port);
cisin[CIS_LOCALPORT] = buf_localport;
cisin[CIS_ALPN] = i->alpn;
cisin[CIS_USERNAME] = i->auth_username;
cisin[CIS_PASSWORD] = i->auth_password;
if (lws_client_stash_create(wsi, cisin))
goto bail;

View file

@ -187,6 +187,12 @@ lws_client_connect_2_dnsreq(struct lws *wsi)
goto solo;
}
if (wsi->keepalive_rejected) {
lwsl_notice("defeating pipelining due to no "
"keepalive on server\n");
goto solo;
}
/* only pipeline things we associate with being a stream */
if (meth && strcmp(meth, "RAW") && strcmp(meth, "GET") &&

View file

@ -137,6 +137,13 @@ __lws_reset_wsi(struct lws *wsi)
lws_buflist_destroy_all_segments(&wsi->http.buflist_post_body);
#endif
#if defined(LWS_WITH_HTTP_DIGEST_AUTH)
if (wsi->http.digest_auth_hdr) {
lws_free(wsi->http.digest_auth_hdr);
wsi->http.digest_auth_hdr = NULL;
}
#endif
#if defined(LWS_WITH_SERVER)
lws_dll2_remove(&wsi->listen_list);
#endif

View file

@ -222,7 +222,8 @@ enum {
CIS_IFACE,
CIS_ALPN,
CIS_LOCALPORT,
CIS_USERNAME,
CIS_PASSWORD,
CIS_COUNT
};

View file

@ -579,6 +579,368 @@ lws_http_client_http_response(struct lws *wsi)
}
#endif
#if defined(LWS_WITH_HTTP_DIGEST_AUTH)
static const char *digest_toks[] = {
"Digest", // 1 << 0
"username", // 1 << 1
"realm", // 1 << 2
"nonce", // 1 << 3
"uri", // 1 << 4 optional
"response", // 1 << 5
"opaque", // 1 << 6
"qop", // 1 << 7
"algorithm" // 1 << 8
"nc", // 1 << 9
"cnonce", // 1 << 10
"domain", // 1 << 11
};
#define PEND_NAME_EQ -1
#define PEND_DELIM -2
enum lws_check_basic_auth_results
lws_http_digest_auth(struct lws* wsi)
{
uint8_t nonce[128], response[LWS_GENHASH_LARGEST], qop[32];
int seen = 0, n, pend = -1, skipping = 0;
struct lws_tokenize ts;
char resp_username[32];
lws_tokenize_elem e;
char realm[64];
char b64[512];
int m, ml, fi;
/* Did he send auth? */
ml = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_WWW_AUTHENTICATE);
if (!ml)
return LCBA_FAILED_AUTH;
/* Disallow fragmentation monkey business */
fi = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_WWW_AUTHENTICATE];
if (wsi->http.ah->frags[fi].nfrag) {
lwsl_wsi_err(wsi, "fragmented http auth header not allowed\n");
return LCBA_FAILED_AUTH;
}
m = lws_hdr_copy(wsi, b64, sizeof(b64), WSI_TOKEN_HTTP_WWW_AUTHENTICATE);
if (m < 7) {
lwsl_wsi_err(wsi, "HTTP auth length bad\n");
return LCBA_END_TRANSACTION;
}
/*
* We are expecting AUTHORIZATION to have something like this
*
* Authorization: Digest
* username="Mufasa",
* realm="testrealm@host.com",
* nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
* uri="/dir/index.html",
* response="e966c932a9242554e42c8ee200cec7f6",
* opaque="5ccc069c403ebaf9f0171e9517f40e41"
*
* but the order, whitespace etc is quite open. uri is optional
*/
ts.start = b64;
ts.len = (size_t)m;
ts.flags = LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_NO_INTEGERS |
LWS_TOKENIZE_F_RFC7230_DELIMS;
do {
e = lws_tokenize(&ts);
switch (e) {
case LWS_TOKZE_TOKEN:
if (pend == 8) {
/* algorithm name */
if (strncasecmp(ts.token, "MD5", ts.token_len)) {
lwsl_wsi_err(wsi, "wrong alg %.*s\n",
(int)ts.token_len,
ts.token);
return LCBA_END_TRANSACTION;
}
pend = PEND_DELIM;
break;
}
if (strncasecmp(ts.token, "Digest", ts.token_len)) {
skipping = 1;
seen |= 1 << 0;
break;
}
if (seen) /* we must be first and one time */
return LCBA_END_TRANSACTION;
seen |= 1 << 15;
pend = PEND_NAME_EQ;
break;
case LWS_TOKZE_TOKEN_NAME_EQUALS:
if (skipping)
break;
if (!(seen & (1 << 15)) || pend != -1)
/* no auth type token or disordered */
return LCBA_END_TRANSACTION;
for (n = 0; n < (int)LWS_ARRAY_SIZE(digest_toks); n++)
if (!strncmp(ts.token, digest_toks[n], ts.token_len))
break;
if (n == LWS_ARRAY_SIZE(digest_toks)) {
lwsl_wsi_notice(wsi, "c: '%.*s'\n",
(int)ts.token_len,
ts.token);
return LCBA_END_TRANSACTION;
}
if (seen & (1 << n) || !(seen & (1 << 15)))
/* dup or no auth type token */
return LCBA_END_TRANSACTION;
seen |= 1 << n;
pend = n;
break;
case LWS_TOKZE_QUOTED_STRING:
if (skipping)
break;
if (pend < 0)
return LCBA_END_TRANSACTION;
switch (pend) {
case 1: /* username */
if (ts.token_len >= (int)sizeof(resp_username))
return LCBA_END_TRANSACTION;
strncpy(resp_username, ts.token, ts.token_len);
break;
case 2: /* realm */
if (ts.token_len >= (int)sizeof(realm))
return LCBA_END_TRANSACTION;
strncpy(realm, ts.token, ts.token_len);
realm[ts.token_len] = 0;
break;
case 3: /* nonce */
if (ts.token_len >= (int)sizeof(nonce))
return LCBA_END_TRANSACTION;
strncpy((char *)nonce, ts.token, ts.token_len);
nonce[ts.token_len] = 0;
break;
case 4: /* uri */
break;
case 5: /* response */
if (ts.token_len !=
lws_genhash_size(LWS_GENHASH_TYPE_MD5) * 2)
return LCBA_END_TRANSACTION;
if (lws_hex_len_to_byte_array(ts.token, ts.token_len,
response,
sizeof(response)) < 0)
return LCBA_END_TRANSACTION;
break;
case 6: /* opaque */
break;
case 7: /* qop */
if (strncmp(ts.token, "auth", ts.token_len))
return LCBA_END_TRANSACTION;
strncpy((char *)qop, ts.token, ts.token_len);
qop[ts.token_len] = 0;
break;
}
pend = PEND_DELIM;
break;
case LWS_TOKZE_DELIMITER:
if (*ts.token == ',') {
if (skipping)
break;
if (pend != PEND_DELIM)
return LCBA_END_TRANSACTION;
pend = PEND_NAME_EQ;
break;
}
if (*ts.token == ';') {
if (skipping) {
/* try again with this one */
skipping = 0;
break;
}
/* it's the end */
e = LWS_TOKZE_ENDED;
break;
}
break;
case LWS_TOKZE_ENDED:
break;
default:
lwsl_wsi_notice(wsi, "unexpected token %d\n", e);
return LCBA_END_TRANSACTION;
}
} while (e > 0);
if (e != LWS_TOKZE_ENDED)
return LCBA_END_TRANSACTION;
/* we got all the parts we care about? */
// Realm, nonce
if ((seen & 0xc) != 0xc) {
lwsl_wsi_err(wsi,
"%s: Not all digest auth tokens found! "
"m: 0x%x\nServer sent: %s",
__func__, seen & 0x81ef, b64);
return LCBA_END_TRANSACTION;
}
lwsl_wsi_info(wsi, "HTTP digest auth realm %s nonce %s\n", realm, nonce);
if (wsi->stash && wsi->stash->cis[CIS_METHOD] &&
wsi->stash->cis[CIS_PATH]) {
char *username = wsi->stash->cis[CIS_USERNAME];
char *password = wsi->stash->cis[CIS_PASSWORD];
uint8_t digest[LWS_GENHASH_LARGEST * 2 + 1];
char *uri = wsi->stash->cis[CIS_PATH];
char a1[LWS_GENHASH_LARGEST * 2 + 1];
char a2[LWS_GENHASH_LARGEST * 2 + 1];
char nc[sizeof(int) * 2 + 1];
struct lws_genhash_ctx hc;
int ncount = 1, ssl;
const char *a, *p;
struct lws *nwsi;
char cnonce[128];
size_t l;
if (!wsi->http.digest_auth_hdr) {
l = sizeof(a1) + sizeof(a2) + sizeof(nonce) +
(sizeof(ncount) *2) + sizeof(response) +
sizeof(cnonce) + sizeof(qop) + strlen(uri) +
strlen(username) + strlen(password) +
strlen(realm) + 111;
wsi->http.digest_auth_hdr = lws_malloc(l, __func__);
if (!wsi->http.digest_auth_hdr)
return -1;
}
n = lws_snprintf(wsi->http.digest_auth_hdr, l, "%s:%s:%s",
username, realm, password);
if (lws_genhash_init(&hc, LWS_GENHASH_TYPE_MD5) ||
lws_genhash_update(&hc,
wsi->http.digest_auth_hdr,
(size_t)n) ||
lws_genhash_destroy(&hc, digest)) {
lws_genhash_destroy(&hc, NULL);
goto bail;
}
lws_hex_from_byte_array(digest,
lws_genhash_size(LWS_GENHASH_TYPE_MD5),
a1, sizeof(a1));
lwsl_debug("A1: %s:%s:%s = %s\n", username, realm, password, a1);
n = lws_snprintf(wsi->http.digest_auth_hdr, l, "%s:%s",
wsi->stash->cis[CIS_METHOD], uri);
if (lws_genhash_init(&hc, LWS_GENHASH_TYPE_MD5) ||
lws_genhash_update(&hc,
wsi->http.digest_auth_hdr,
(size_t)n) ||
lws_genhash_destroy(&hc, digest)) {
lws_genhash_destroy(&hc, NULL);
lwsl_err("%s: hash failed\n", __func__);
goto bail;
}
lws_hex_from_byte_array(digest,
lws_genhash_size(LWS_GENHASH_TYPE_MD5),
a2, sizeof(a2));
lwsl_debug("A2: %s:%s = %s\n", wsi->stash->cis[CIS_METHOD],
uri, a2);
lws_hex_random(lws_get_context(wsi), cnonce, sizeof(cnonce));
lws_hex_from_byte_array((const uint8_t *)&ncount,
sizeof(ncount), nc, sizeof(nc));
n = lws_snprintf(wsi->http.digest_auth_hdr, l, "%s:%s:%08x:%s:%s:%s", a1,
nonce, ncount, cnonce, qop, a2);
lwsl_wsi_debug(wsi, "digest response: %s\n", wsi->http.digest_auth_hdr);
if (lws_genhash_init(&hc, LWS_GENHASH_TYPE_MD5) ||
lws_genhash_update(&hc, wsi->http.digest_auth_hdr, (size_t)n) ||
lws_genhash_destroy(&hc, digest)) {
lws_genhash_destroy(&hc, NULL);
lwsl_wsi_err(wsi, "hash failed\n");
goto bail;
}
lws_hex_from_byte_array(digest,
lws_genhash_size(LWS_GENHASH_TYPE_MD5),
(char *)response,
lws_genhash_size(LWS_GENHASH_TYPE_MD5) * 2 + 1);
n = lws_snprintf(wsi->http.digest_auth_hdr, l,
"Digest username=\"%s\", realm=\"%s\", "
"nonce=\"%s\", uri=\"%s\", qop=%s, nc=%08x, "
"cnonce=\"%s\", response=\"%s\", "
"algorithm=\"MD5\"",
username, realm, nonce, uri, qop, ncount,
cnonce, response);
lwsl_hexdump(wsi->http.digest_auth_hdr, l);
if (lws_hdr_simple_create(wsi, WSI_TOKEN_HTTP_AUTHORIZATION,
wsi->http.digest_auth_hdr)) {
lwsl_wsi_err(wsi, "Failed to add Digest auth header");
goto bail;
}
nwsi = lws_get_network_wsi(wsi);
ssl = nwsi->tls.use_ssl & LCCSCF_USE_SSL;
a = wsi->stash->cis[CIS_ADDRESS];
p = &wsi->stash->cis[CIS_PATH][1];
/*
* This prevents connection pipelining when two
* HTTP connection use the same tcp socket.
*/
wsi->keepalive_rejected = 1;
if (!lws_client_reset(&wsi, ssl, a, wsi->c_port, p, a, 1)) {
lwsl_wsi_err(wsi, "Failed to reset WSI for Digest auth");
goto bail;
}
wsi->client_pipeline = 0;
}
return 0;
bail:
lws_free(wsi->http.digest_auth_hdr);
wsi->http.digest_auth_hdr = NULL;
return -1;
}
#endif
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
int
@ -605,6 +967,7 @@ lws_client_interpret_server_handshake(struct lws *wsi)
wsi->conmon.ciu_txn_resp = (lws_conmon_interval_us_t)
(lws_now_usecs() - wsi->conmon_datum);
#endif
// lws_free_set_NULL(wsi->stash);
ah = wsi->http.ah;
if (!wsi->do_ws) {
@ -692,6 +1055,32 @@ lws_client_interpret_server_handshake(struct lws *wsi)
}
#endif
n = atoi(p);
#if defined(LWS_WITH_HTTP_DIGEST_AUTH)
if (n == 401 && lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_WWW_AUTHENTICATE)) {
if (!(wsi->stash && wsi->stash->cis[CIS_USERNAME] &&
wsi->stash->cis[CIS_PASSWORD])) {
lwsl_err(
"Digest auth requested by server but no credentials provided "
"by user\n");
return LCBA_FAILED_AUTH;
}
if (0 != lws_http_digest_auth(wsi)) {
if (wsi)
goto bail3;
return 1;
}
opaque = wsi->a.opaque_user_data;
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "digest_auth_step2");
wsi->a.opaque_user_data = opaque;
return -1;
}
ah = wsi->http.ah;
#endif
if (ah)
ah->http_response = (unsigned int)n;
@ -1250,6 +1639,15 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)
}
#endif
#if defined(LWS_WITH_HTTP_DIGEST_AUTH)
if (wsi->http.digest_auth_hdr) {
p += lws_snprintf(p, 1024, "Authorization: %s\x0d\x0a",
wsi->http.digest_auth_hdr);
lws_free(wsi->http.digest_auth_hdr);
wsi->http.digest_auth_hdr = NULL;
}
#endif
#if defined(LWS_ROLE_WS)
if (wsi->do_ws) {
const char *conn1 = "";
@ -1636,7 +2034,7 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port,
cisin[CIS_ALPN] = wsi->alpn;
#endif
if (lws_client_stash_create(wsi, cisin))
if (!wsi->stash && lws_client_stash_create(wsi, cisin))
return NULL;
if (!port) {

View file

@ -279,6 +279,10 @@ struct _lws_http_mode_related {
unsigned int multipart:1;
unsigned int cgi_transaction_complete:1;
unsigned int multipart_issue_boundary:1;
char auth_username[64];
char auth_password[64];
char *digest_auth_hdr;
};