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

http-client: digest auth support added

This commit is contained in:
Ilya Smelykh 2022-11-22 01:18:51 +07:00
parent 91f0b3bc0f
commit b6a4080947
8 changed files with 444 additions and 5 deletions

View file

@ -234,6 +234,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

@ -307,7 +307,7 @@ lws_json_simple_strcmp(const char *buf, size_t len, const char *name, const char
* string.
*/
LWS_VISIBLE LWS_EXTERN int
lws_hex_to_byte_array(const char *h, uint8_t *dest, int max);
lws_hex_to_byte_array(const char *h, int hlen_or_minus1, uint8_t *dest, int max);
/**
* lws_hex_from_byte_array(): render byte array as hex char string

View file

@ -359,6 +359,8 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)
cisin[CIS_METHOD] = i->method;
cisin[CIS_IFACE] = i->iface;
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

@ -221,6 +221,8 @@ enum {
CIS_METHOD,
CIS_IFACE,
CIS_ALPN,
CIS_USERNAME,
CIS_PASSWORD,
CIS_COUNT

View file

@ -130,11 +130,14 @@ signed char char_to_hex(const char c)
}
int
lws_hex_to_byte_array(const char *h, uint8_t *dest, int max)
lws_hex_to_byte_array(const char *h, int hlen_or_minus1, uint8_t *dest, int max)
{
uint8_t *odest = dest;
while (max-- && *h) {
if (hlen_or_minus1 != -1 && (hlen_or_minus1 & 1))
return -1;
while ((hlen_or_minus1 == -1 || hlen_or_minus1 > 1) && max-- && *h) {
int t = char_to_hex(*h++), t1;
if (!*h || t < 0)
@ -150,7 +153,7 @@ lws_hex_to_byte_array(const char *h, uint8_t *dest, int max)
if (max < 0)
return -1;
return lws_ptr_diff(dest, odest);
return (int)(dest - odest);
}
static char *hexch = "0123456789abcdef";
@ -171,6 +174,25 @@ lws_hex_from_byte_array(const uint8_t *src, size_t slen, char *dest, size_t len)
*dest = '\0';
}
int
lws_byte_array_to_hex(const uint8_t *src, size_t len, char *dest, size_t dlen)
{
char *odest = dest;
if (dlen < (2 * len) + 1)
return -1;
while (len--) {
*dest++ = hexch[(*src) >> 4];
*dest++ = hexch[(*src) & 15];
src++;
}
*dest = '\0';
return (int)(dest - odest);
}
int
lws_hex_random(struct lws_context *context, char *dest, size_t len)
{

View file

@ -579,6 +579,372 @@ 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) {
char b64[512];
int m, ml, fi;
uint8_t nonce[128], response[LWS_GENHASH_LARGEST], qop[32];
int seen = 0, n, pend = -1, skipping = 0, urilen;
struct lws_tokenize ts;
lws_tokenize_elem e;
char realm[64];
time_t t;
char resp_username[32];
/* 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_err("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_err("%s: HTTP auth length bad\n", __func__);
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 = 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_err("wrong alg %.*s\n", 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 */
lwsl_notice("%s: repeated auth type\n", __func__);
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) {
lwsl_notice("%s: b\n", __func__);
/* 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_notice("%s: c: '%.*s'\n", __func__, ts.token_len,
ts.token);
return LCBA_END_TRANSACTION;
}
if (seen & (1 << n) || !(seen & (1 << 15))) {
lwsl_notice("%s: d\n", __func__);
/* 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) {
lwsl_notice("%s: e\n", __func__);
return LCBA_END_TRANSACTION;
}
switch (pend) {
case 1: /* username */
if (ts.token_len >= (int)sizeof(resp_username)) {
lwsl_notice("%s: f\n", __func__);
return LCBA_END_TRANSACTION;
}
strncpy(resp_username, ts.token, ts.token_len);
break;
case 2: /* realm */
if (ts.token_len >= (int)sizeof(realm)) {
lwsl_notice("%s: f1\n", __func__);
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)) {
lwsl_notice("%s: g\n", __func__);
return LCBA_END_TRANSACTION;
}
strncpy(nonce, ts.token, ts.token_len);
nonce[ts.token_len] = 0;
break;
case 4: /* uri */
break;
case 5: /* response */
if (ts.token_len !=
(int)lws_genhash_size(LWS_GENHASH_TYPE_MD5) * 2) {
lwsl_notice("%s: h\n", __func__);
return LCBA_END_TRANSACTION;
}
if (lws_hex_to_byte_array(ts.token, ts.token_len,
response,
sizeof(response)) < 0) {
lwsl_notice("%s: i\n", __func__);
return LCBA_END_TRANSACTION;
}
break;
case 6: /* opaque */
break;
case 7: /* qop */
if (strncmp(ts.token, "auth", ts.token_len)) {
lwsl_notice("%s: j\n", __func__);
return LCBA_END_TRANSACTION;
}
strncpy(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) {
lwsl_notice("%s: k\n", __func__);
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_notice("%s: unexpected token %d\n", __func__, e);
return LCBA_END_TRANSACTION;
}
} while (e > 0);
if (e != LWS_TOKZE_ENDED) {
lwsl_notice("%s: l\n", __func__);
return LCBA_END_TRANSACTION;
}
/* we got all the parts we care about? */
// Realm, nonce
if ((seen & 0xc) != 0xc) {
#if LWS_LIBRARY_VERSION_NUMBER >= 4003000
lwsl_wsi_err(wsi,
"%s: Not all digest auth tokens found! m: 0x%x\nServer sent: %s",
__func__, seen & 0x81ef, b64);
#else
lwsl_err(
"%s: Not all digest auth tokens found! m: 0x%x\nServer sent: %s",
__func__, seen & 0x81ef, b64);
#endif
return LCBA_END_TRANSACTION;
}
lwsl_notice("HTTP digest auth realm %s nonce %s\n", realm, nonce);
if (wsi->stash && wsi->stash->cis[CIS_METHOD] && wsi->stash->cis[CIS_PATH]) {
uint8_t digest[LWS_GENHASH_LARGEST * 2 + 1];
char a1[LWS_GENHASH_LARGEST * 2 + 1];
char a2[LWS_GENHASH_LARGEST * 2 + 1];
char cnonce[128];
char nc[sizeof(int) * 2 + 1];
int ncount = 1;
char response[512];
struct lws_genhash_ctx hc;
char* username = wsi->stash->cis[CIS_USERNAME];
char* password = wsi->stash->cis[CIS_PASSWORD];
char* uri = wsi->stash->cis[CIS_PATH];
char* nbuf[strlen(uri) + 256];
// A1
n = lws_snprintf(nbuf, sizeof(nbuf), "%s:%s:%s", username,
realm, password);
if (lws_genhash_init(&hc, LWS_GENHASH_TYPE_MD5) ||
lws_genhash_update(&hc, nbuf, n) || lws_genhash_destroy(&hc, digest)) {
lws_genhash_destroy(&hc, NULL);
lwsl_err("%s: hash failed\n", __func__);
return -1;
}
lws_byte_array_to_hex(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(nbuf, sizeof(nbuf), "%s:%s",
wsi->stash->cis[CIS_METHOD],
uri);
if (lws_genhash_init(&hc, LWS_GENHASH_TYPE_MD5) ||
lws_genhash_update(&hc, nbuf, n) || lws_genhash_destroy(&hc, digest)) {
lws_genhash_destroy(&hc, NULL);
lwsl_err("%s: hash failed\n", __func__);
return -1;
}
lws_byte_array_to_hex(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);
// cnonce
lws_hex_random(lws_get_context(wsi), cnonce, sizeof(cnonce));
// nc
lws_byte_array_to_hex(&ncount, sizeof(ncount), nc, sizeof(nc));
// response
n = lws_snprintf(nbuf, sizeof(nbuf), "%s:%s:%08x:%s:%s:%s", a1,
nonce, ncount, cnonce, qop, a2);
if (lws_genhash_init(&hc, LWS_GENHASH_TYPE_MD5) ||
lws_genhash_update(&hc, nbuf, n) || lws_genhash_destroy(&hc, digest)) {
lws_genhash_destroy(&hc, NULL);
lwsl_err("%s: hash failed\n", __func__);
return -1;
}
lwsl_debug("digest response: %s\n", nbuf);
lws_byte_array_to_hex(digest, lws_genhash_size(LWS_GENHASH_TYPE_MD5),
response, lws_genhash_size(LWS_GENHASH_TYPE_MD5) * 2 + 1);
// Authorization header
n = lws_snprintf(
nbuf, sizeof(nbuf),
"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(nbuf, n);
wsi->http.pending_digest_auth_hdr = 1;
strncpy(wsi->http.digest_auth_hdr, nbuf, n);
if (lws_hdr_simple_create(wsi, WSI_TOKEN_HTTP_AUTHORIZATION, nbuf)) {
lwsl_err("Failed to add Auth header to WSI for Digest auth");
return -1;
}
struct lws* nwsi = lws_get_network_wsi(wsi);
int ssl = nwsi->tls.use_ssl & LCCSCF_USE_SSL;
void* stash = wsi->stash;
const char *a, *p;
a = wsi->stash->cis[CIS_ADDRESS];
p = &wsi->stash->cis[CIS_PATH][1];
// wsi->stash = NULL;
// This prevents connection pipelining when two HTTP connection use same
// tcp socket.
wsi->keepalive_rejected = 1;
if (!lws_client_reset(&wsi, ssl, a, wsi->c_port, p, a, 1)) {
#if LWS_LIBRARY_VERSION_NUMBER >= 4003000
lwsl_wsi_err(wsi, "Failed to reset WSI for Digest auth");
#else
lwsl_err("Failed to reset WSI for Digest auth");
#endif
return -1;
}
// wsi->stash = stash;
wsi->client_pipeline = 0;
}
return 0;
}
#endif
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
int
@ -605,6 +971,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 +1059,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;
@ -1249,6 +1642,13 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)
}
#endif
#if defined(LWS_WITH_HTTP_DIGEST_AUTH)
if (wsi->http.pending_digest_auth_hdr) {
p += lws_snprintf(p, 1024, "Authorization: %s\x0d\x0a",
wsi->http.digest_auth_hdr);
}
#endif
#if defined(LWS_ROLE_WS)
if (wsi->do_ws) {
const char *conn1 = "";
@ -1635,7 +2035,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,11 @@ 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];
unsigned int pending_digest_auth_hdr:1;
char digest_auth_hdr[4096];
};