diff --git a/proxy/cproxy.c b/proxy/cproxy.c index fab5f63c..29eafdfc 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -16,6 +16,7 @@ #include "marshal.h" const bool socks5_chap_available = true; +const bool http_digest_available = true; strbuf *chap_response(ptrlen challenge, ptrlen password) { @@ -24,3 +25,158 @@ strbuf *chap_response(ptrlen challenge, ptrlen password) mac_simple(alg, password, challenge, strbuf_append(sb, alg->len)); return sb; } + +void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, size_t len) +{ + const unsigned char *p = (const unsigned char *)vptr; + const char *hexdigits = "0123456789abcdef"; + while (len-- > 0) { + unsigned c = *p++; + put_byte(bs, hexdigits[0xF & (c >> 4)]); + put_byte(bs, hexdigits[0xF & (c )]); + } +} + +#define put_hex_data(bs, p, len) \ + BinarySink_put_hex_data(BinarySink_UPCAST(bs), p, len) + +const char *const httphashnames[] = { + #define DECL_ARRAY(id, str, alg, bits) str, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +}; + +static const ssh_hashalg *const httphashalgs[] = { + #define DECL_ARRAY(id, str, alg, bits) alg, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +}; +static const size_t httphashlengths[] = { + #define DECL_ARRAY(id, str, alg, bits) bits/8, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +}; + +void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, + ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, + ptrlen nonce, ptrlen opaque, uint32_t nonce_count, + HttpDigestHash hash, bool hash_username) +{ + unsigned char a1hash[MAX_HASH_LEN]; + unsigned char a2hash[MAX_HASH_LEN]; + unsigned char rsphash[MAX_HASH_LEN]; + const ssh_hashalg *alg = httphashalgs[hash]; + size_t hashlen = httphashlengths[hash]; + + unsigned char ncbuf[4]; + PUT_32BIT_MSB_FIRST(ncbuf, nonce_count); + + unsigned char client_nonce_raw[33]; + random_read(client_nonce_raw, lenof(client_nonce_raw)); + char client_nonce_base64[lenof(client_nonce_raw) / 3 * 4]; + for (unsigned i = 0; i < lenof(client_nonce_raw)/3; i++) + base64_encode_atom(client_nonce_raw + 3*i, 3, + client_nonce_base64 + 4*i); + + /* + * RFC 7616 section 3.4.2: the hash "A1" is a hash of + * username:realm:password (in the absence of hash names like + * "MD5-sess" which as far as I know don't sensibly apply to + * proxies and HTTP CONNECT). + */ + ssh_hash *h = ssh_hash_new(alg); + put_datapl(h, username); + put_byte(h, ':'); + put_datapl(h, realm); + put_byte(h, ':'); + put_datapl(h, password); + ssh_hash_digest_nondestructive(h, a1hash); + + /* + * RFC 7616 section 3.4.3: the hash "A2" is a hash of method:uri + * (in the absence of more interesting quality-of-protection + * schemes than plain "auth" - e.g. "auth-int" hashes the entire + * document as well - which again I don't think make sense in the + * context of proxies and CONNECT). + */ + ssh_hash_reset(h); + put_datapl(h, method); + put_byte(h, ':'); + put_datapl(h, uri); + ssh_hash_digest_nondestructive(h, a2hash); + + /* + * RFC 7616 section 3.4.1: the overall output hash in the + * "response" parameter of the authorization header is a hash of + * A1:nonce:nonce-count:client-nonce:qop:A2, where A1 and A2 are + * the hashes computed above. + */ + ssh_hash_reset(h); + put_hex_data(h, a1hash, hashlen); + put_byte(h, ':'); + put_datapl(h, nonce); + put_byte(h, ':'); + put_hex_data(h, ncbuf, 4); + put_byte(h, ':'); + put_data(h, client_nonce_base64, lenof(client_nonce_base64)); + put_byte(h, ':'); + put_datapl(h, qop); + put_byte(h, ':'); + put_hex_data(h, a2hash, hashlen); + ssh_hash_final(h, rsphash); + + /* + * Now construct the output header (everything after the initial + * "Proxy-Authorization: Digest ") and write it to the provided + * BinarySink. + */ + put_datalit(bs, "username=\""); + if (hash_username) { + /* + * RFC 7616 section 3.4.4: if we're hashing the username, we + * actually hash username:realm (like a truncated version of + * A1 above). + */ + ssh_hash *h = ssh_hash_new(alg); + put_datapl(h, username); + put_byte(h, ':'); + put_datapl(h, realm); + ssh_hash_final(h, a1hash); + put_hex_data(bs, a1hash, hashlen); + } else { + put_datapl(bs, username); + } + put_datalit(bs, "\", realm=\""); + put_datapl(bs, realm); + put_datalit(bs, "\", uri=\""); + put_datapl(bs, uri); + put_datalit(bs, "\", algorithm="); + put_dataz(bs, httphashnames[hash]); + put_datalit(bs, ", nonce=\""); + put_datapl(bs, nonce); + put_datalit(bs, "\", nc="); + put_hex_data(bs, ncbuf, 4); + put_datalit(bs, ", cnonce=\""); + put_data(bs, client_nonce_base64, lenof(client_nonce_base64)); + put_datalit(bs, "\", qop="); + put_datapl(bs, qop); + put_datalit(bs, ", response=\""); + put_hex_data(bs, rsphash, hashlen); + put_datalit(bs, "\""); + + if (opaque.ptr) { + put_datalit(bs, ", opaque=\""); + put_datapl(bs, opaque); + put_datalit(bs, "\""); + } + + if (hash_username) { + put_datalit(bs, ", userhash=true"); + } + + smemclr(a1hash, lenof(a1hash)); + smemclr(a2hash, lenof(a2hash)); + smemclr(rsphash, lenof(rsphash)); + smemclr(client_nonce_raw, lenof(client_nonce_raw)); + smemclr(client_nonce_base64, lenof(client_nonce_base64)); +} diff --git a/proxy/cproxy.h b/proxy/cproxy.h new file mode 100644 index 00000000..c3e7ba15 --- /dev/null +++ b/proxy/cproxy.h @@ -0,0 +1,25 @@ +/* + * Header for the interaction between proxy.c and cproxy.c. Separated + * from proxy.h proper so that testcrypt can include it conveniently. + */ + +extern const bool socks5_chap_available; +strbuf *chap_response(ptrlen challenge, ptrlen password); +extern const bool http_digest_available; + +#define HTTP_DIGEST_HASHES(X) \ + X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128) \ + X(HTTP_DIGEST_SHA256, "SHA-256", &ssh_sha256, 256) \ + X(HTTP_DIGEST_SHA512_256, "SHA-512-256", &ssh_sha512, 256) \ + /* end of list */ +typedef enum HttpDigestHash { + #define DECL_ENUM(id, str, alg, bits) id, + HTTP_DIGEST_HASHES(DECL_ENUM) + #undef DECL_ENUM + N_HTTP_DIGEST_HASHES +} HttpDigestHash; +extern const char *const httphashnames[]; +void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, + ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, + ptrlen nonce, ptrlen opaque, uint32_t nonce_count, + HttpDigestHash hash, bool hash_username); diff --git a/proxy/http.c b/proxy/http.c index 4f184dbe..5176027c 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -41,6 +41,8 @@ static bool read_line(bufchain *input, strbuf *output, bool is_header) return true; } +typedef enum HttpAuthType { AUTH_NONE, AUTH_BASIC, AUTH_DIGEST } HttpAuthType; + typedef struct HttpProxyNegotiator { int crLine; strbuf *response, *header, *token; @@ -49,7 +51,14 @@ typedef struct HttpProxyNegotiator { strbuf *username, *password; int http_status; bool connection_close; - bool tried_no_auth, try_auth_from_conf; + HttpAuthType next_auth_type; + bool try_auth_from_conf; + strbuf *realm, *nonce, *opaque, *uri; + bool got_opaque; + uint32_t nonce_count; + bool digest_nonce_was_stale; + HttpDigestHash digest_hash; + bool hash_username; prompts_t *prompts; int username_prompt_index, password_prompt_index; size_t content_length; @@ -66,6 +75,17 @@ static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) s->token = strbuf_new(); s->username = strbuf_new(); s->password = strbuf_new_nm(); + s->realm = strbuf_new(); + s->nonce = strbuf_new(); + s->opaque = strbuf_new(); + s->uri = strbuf_new(); + s->nonce_count = 0; + /* + * Always start with a CONNECT request containing no auth. If the + * proxy rejects that, it will tell us what kind of auth it would + * prefer. + */ + s->next_auth_type = AUTH_NONE; return &s->pn; } @@ -77,6 +97,10 @@ static void proxy_http_free(ProxyNegotiator *pn) strbuf_free(s->token); strbuf_free(s->username); strbuf_free(s->password); + strbuf_free(s->realm); + strbuf_free(s->nonce); + strbuf_free(s->opaque); + strbuf_free(s->uri); if (s->prompts) free_prompts(s->prompts); sfree(s); @@ -110,6 +134,21 @@ static inline bool is_separator(char c) #define HTTP_SEPARATORS +static bool get_end_of_header(HttpProxyNegotiator *s) +{ + size_t pos = s->header_pos; + + while (pos < s->header->len && is_whitespace(s->header->s[pos])) + pos++; + + if (pos == s->header->len) { + s->header_pos = pos; + return true; + } + + return false; +} + static bool get_token(HttpProxyNegotiator *s) { size_t pos = s->header_pos; @@ -150,6 +189,39 @@ static bool get_separator(HttpProxyNegotiator *s, char sep) return true; } +static bool get_quoted_string(HttpProxyNegotiator *s) +{ + size_t pos = s->header_pos; + + while (pos < s->header->len && is_whitespace(s->header->s[pos])) + pos++; + + if (pos == s->header->len) + return false; /* end of string */ + + if (s->header->s[pos] != '"') + return false; + pos++; + + strbuf_clear(s->token); + while (pos < s->header->len && s->header->s[pos] != '"') { + if (s->header->s[pos] == '\\') { + /* Backslash makes the next char literal, even if it's " or \ */ + pos++; + if (pos == s->header->len) + return false; /* unexpected end of string */ + } + put_byte(s->token, s->header->s[pos++]); + } + + if (pos == s->header->len) + return false; /* no closing quote */ + pos++; + + s->header_pos = pos; + return true; +} + static void proxy_http_process_queue(ProxyNegotiator *pn) { HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); @@ -164,46 +236,60 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) if (s->username->len || s->password->len) s->try_auth_from_conf = true; + /* + * Set up the host:port string we're trying to connect to, also + * used as the URI string in HTTP Digest auth. + */ + { + char dest[512]; + sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); + put_fmt(s->uri, "%s:%d", dest, pn->ps->remote_port); + } + while (true) { /* * Standard prefix for the HTTP CONNECT request. */ - { - char dest[512]; - sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); - put_fmt(pn->output, - "CONNECT %s:%d HTTP/1.1\r\n" - "Host: %s:%d\r\n", - dest, pn->ps->remote_port, dest, pn->ps->remote_port); - } + put_fmt(pn->output, + "CONNECT %s HTTP/1.1\r\n" + "Host: %s\r\n", s->uri->s, s->uri->s); /* - * Optionally send an HTTP Basic auth header with the username - * and password. We do this only after we've first tried no - * authentication at all (even if we have a password to start - * with). + * Add an auth header, if we're planning to this time round. */ - if (s->tried_no_auth) { - if (s->username->len || s->password->len) { - put_datalit(pn->output, "Proxy-Authorization: Basic "); + if (s->next_auth_type == AUTH_BASIC) { + put_datalit(pn->output, "Proxy-Authorization: Basic "); - strbuf *base64_input = strbuf_new_nm(); - put_datapl(base64_input, ptrlen_from_strbuf(s->username)); - put_byte(base64_input, ':'); - put_datapl(base64_input, ptrlen_from_strbuf(s->password)); + strbuf *base64_input = strbuf_new_nm(); + put_datapl(base64_input, ptrlen_from_strbuf(s->username)); + put_byte(base64_input, ':'); + put_datapl(base64_input, ptrlen_from_strbuf(s->password)); - char base64_output[4]; - for (size_t i = 0, e = base64_input->len; i < e; i += 3) { - base64_encode_atom(base64_input->u + i, - e-i > 3 ? 3 : e-i, base64_output); - put_data(pn->output, base64_output, 4); - } - strbuf_free(base64_input); - smemclr(base64_output, sizeof(base64_output)); - put_datalit(pn->output, "\r\n"); + char base64_output[4]; + for (size_t i = 0, e = base64_input->len; i < e; i += 3) { + base64_encode_atom(base64_input->u + i, + e-i > 3 ? 3 : e-i, base64_output); + put_data(pn->output, base64_output, 4); } - } else { - s->tried_no_auth = true; + strbuf_free(base64_input); + smemclr(base64_output, sizeof(base64_output)); + put_datalit(pn->output, "\r\n"); + } else if (s->next_auth_type == AUTH_DIGEST) { + put_datalit(pn->output, "Proxy-Authorization: Digest "); + http_digest_response(BinarySink_UPCAST(pn->output), + ptrlen_from_strbuf(s->username), + ptrlen_from_strbuf(s->password), + ptrlen_from_strbuf(s->realm), + PTRLEN_LITERAL("CONNECT"), + ptrlen_from_strbuf(s->uri), + PTRLEN_LITERAL("auth"), + ptrlen_from_strbuf(s->nonce), + (s->got_opaque ? + ptrlen_from_strbuf(s->opaque) : + make_ptrlen(NULL, 0)), + ++s->nonce_count, s->digest_hash, + s->hash_username); + put_datalit(pn->output, "\r\n"); } /* @@ -281,7 +367,114 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) continue; if (!stricmp(s->token->s, "Basic")) { - /* fine, we know how to do Basic auth */ + s->next_auth_type = AUTH_BASIC; + } else if (!stricmp(s->token->s, "Digest")) { + if (!http_digest_available) { + pn->error = dupprintf( + "HTTP proxy requested Digest authentication " + "which we do not support"); + crStopV; + } + + /* Parse the rest of the Digest header */ + s->digest_nonce_was_stale = false; + s->digest_hash = HTTP_DIGEST_MD5; + strbuf_clear(s->realm); + strbuf_clear(s->nonce); + strbuf_clear(s->opaque); + s->got_opaque = false; + s->hash_username = false; + + while (true) { + if (!get_token(s)) + goto bad_digest; + + if (!stricmp(s->token->s, "realm")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + put_datapl(s->realm, ptrlen_from_strbuf(s->token)); + } else if (!stricmp(s->token->s, "nonce")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + + /* If we have a fresh nonce, reset the + * nonce count. Otherwise, keep incrementing it. */ + if (!ptrlen_eq_ptrlen( + ptrlen_from_strbuf(s->token), + ptrlen_from_strbuf(s->nonce))) + s->nonce_count = 0; + + put_datapl(s->nonce, ptrlen_from_strbuf(s->token)); + } else if (!stricmp(s->token->s, "opaque")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + put_datapl(s->opaque, + ptrlen_from_strbuf(s->token)); + s->got_opaque = true; + } else if (!stricmp(s->token->s, "stale")) { + if (!get_separator(s, '=') || + !get_token(s)) + goto bad_digest; + s->digest_nonce_was_stale = !stricmp( + s->token->s, "true"); + } else if (!stricmp(s->token->s, "userhash")) { + if (!get_separator(s, '=') || + !get_token(s)) + goto bad_digest; + s->hash_username = !stricmp(s->token->s, "true"); + } else if (!stricmp(s->token->s, "algorithm")) { + if (!get_separator(s, '=') || + !get_token(s)) + goto bad_digest; + bool found = false; + + for (size_t i = 0; i < N_HTTP_DIGEST_HASHES; i++) { + if (!stricmp(s->token->s, httphashnames[i])) { + s->digest_hash = i; + found = true; + break; + } + } + + if (!found) { + pn->error = dupprintf( + "HTTP proxy requested Digest hash " + "algorithm '%s' which we do not support", + s->token->s); + crStopV; + } + } else if (!stricmp(s->token->s, "qop")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + if (stricmp(s->token->s, "auth")) { + pn->error = dupprintf( + "HTTP proxy requested Digest quality-of-" + "protection type '%s' which we do not " + "support", s->token->s); + crStopV; + } + } else { + /* Ignore any other auth-param */ + if (!get_separator(s, '=') || + (!get_quoted_string(s) && !get_token(s))) + goto bad_digest; + } + + if (get_end_of_header(s)) + break; + if (!get_separator(s, ',')) + goto bad_digest; + } + s->next_auth_type = AUTH_DIGEST; + continue; + bad_digest: + pn->error = dupprintf("HTTP proxy sent Digest auth " + "request we could not parse"); + crStopV; } else { pn->error = dupprintf("HTTP proxy asked for unsupported " "authentication type '%s'", @@ -314,6 +507,13 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) continue; } + /* If the server sent us stale="true" in a Digest auth + * header, that means we _don't_ need to request a new + * password yet; just try again with the existing details + * and the fresh nonce it sent us. */ + if (s->digest_nonce_was_stale) + continue; + /* Either we never had a password in the first place, or * the one we already presented was rejected. We can only * proceed from here if we have a way to ask the user diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c index 85beec36..38d416d5 100644 --- a/proxy/nocproxy.c +++ b/proxy/nocproxy.c @@ -13,8 +13,19 @@ #include "proxy.h" const bool socks5_chap_available = false; +const bool http_digest_available = false; strbuf *chap_response(ptrlen challenge, ptrlen password) { unreachable("CHAP is not built into this binary"); } + +const char *const httphashnames[] = { NULL }; /* dummy to prevent link error */ + +void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, + ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, + ptrlen nonce, ptrlen opaque, uint32_t nonce_count, + HttpDigestHash hash, bool hash_username) +{ + unreachable("HTTP DIGEST is not built into this binary"); +} diff --git a/proxy/proxy.h b/proxy/proxy.h index 9268fcb4..f44a0c55 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -105,11 +105,6 @@ prompts_t *proxy_new_prompts(ProxySocket *ps); char *format_telnet_command(SockAddr *addr, int port, Conf *conf, unsigned *flags_out); -/* - * These are implemented in cproxy.c or nocproxy.c, depending on - * whether encrypted proxy authentication is available. - */ -extern const bool socks5_chap_available; -strbuf *chap_response(ptrlen challenge, ptrlen password); +#include "cproxy.h" #endif diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 2ab95481..29bc464e 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -3171,6 +3171,69 @@ class standard_test_vectors(MyTestBase): self.assertEqual(crc32_rfc1662(vec[:-4]), expected) self.assertEqual(crc32_rfc1662(vec), 0x2144DF1C) + def testHttpDigest(self): + # RFC 7616 section 3.9.1 + params = ["Mufasa", "Circle of Life", "http-auth@example.org", + "GET", "/dir/index.html", "auth", + "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", + "FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", 1, + "MD5", False] + cnonce = base64.decodebytes( + b'f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ') + with queued_specific_random_data(cnonce): + self.assertEqual(http_digest_response(*params), + b'username="Mufasa", ' + b'realm="http-auth@example.org", ' + b'uri="/dir/index.html", ' + b'algorithm=MD5, ' + b'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' + b'nc=00000001, ' + b'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", ' + b'qop=auth, ' + b'response="8ca523f5e9506fed4657c9700eebdbec", ' + b'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"') + + # And again with all the same details except the hash + params[9] = "SHA-256" + with queued_specific_random_data(cnonce): + self.assertEqual(http_digest_response(*params), + b'username="Mufasa", ' + b'realm="http-auth@example.org", ' + b'uri="/dir/index.html", ' + b'algorithm=SHA-256, ' + b'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' + b'nc=00000001, ' + b'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", ' + b'qop=auth, ' + b'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1", ' + b'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"') + + # RFC 7616 section 3.9.2, using SHA-512-256 (demonstrating + # that they think it's just a 256-bit truncation of SHA-512, + # and not the version defined in FIPS 180-4 which also uses + # a different initial hash state), and username hashing. + params = ["J\u00E4s\u00F8n Doe".encode("UTF-8"), + "Secret, or not?", "api@example.org", + "GET", "/doe.json", "auth", + "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", + "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", 1, + "SHA-512-256", True] + cnonce = base64.decodebytes( + b'NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v') + with queued_specific_random_data(cnonce): + self.assertEqual(http_digest_response(*params), + b'username="488869477bf257147b804c45308cd62ac4e25eb717b12b298c79e62dcea254ec", ' + b'realm="api@example.org", ' + b'uri="/doe.json", ' + b'algorithm=SHA-512-256, ' + b'nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", ' + b'nc=00000001, ' + b'cnonce="NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", ' + b'qop=auth, ' + b'response="ae66e67d6b427bd3f120414a82e4acff38e8ecd9101d6c861229025f607a79dd", ' + b'opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", ' + b'userhash=true') + if __name__ == "__main__": # Run the tests, suppressing automatic sys.exit and collecting the # unittest.TestProgram instance returned by unittest.main instead. diff --git a/test/testcrypt.py b/test/testcrypt.py index 66611ed3..28a00897 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -178,7 +178,7 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve): if typename in { "hashalg", "macalg", "keyalg", "cipheralg", "dh_group", "ecdh_alg", "rsaorder", "primegenpolicy", - "argon2flavour", "fptype"}: + "argon2flavour", "fptype", "httpdigesthash"}: arg = coerce_to_bytes(arg) if isinstance(arg, bytes) and b" " not in arg: return arg diff --git a/testcrypt.c b/testcrypt.c index 24ec2a5b..3bdaa518 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -35,6 +35,7 @@ #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" +#include "proxy/cproxy.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { @@ -463,6 +464,25 @@ static FingerprintType get_fptype(BinarySource *in) fatal_error("fingerprint type '%.*s': not found", PTRLEN_PRINTF(name)); } +static HttpDigestHash get_httpdigesthash(BinarySource *in) +{ + static const struct { + const char *key; + HttpDigestHash value; + } hashes[] = { + #define DECL_ARRAY(id, str, alg, bits) {str, id}, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(hashes); i++) + if (ptrlen_eq_string(name, hashes[i].key)) + return hashes[i].value; + + fatal_error("httpdigesthash '%.*s': not found", PTRLEN_PRINTF(name)); +} + static uintmax_t get_uint(BinarySource *in) { ptrlen word = get_word(in); @@ -1384,6 +1404,7 @@ typedef PockleStatus TD_pocklestatus; typedef struct mr_result TD_mr_result; typedef Argon2Flavour TD_argon2flavour; typedef FingerprintType TD_fptype; +typedef HttpDigestHash TD_httpdigesthash; #define FUNC0(rettype, function) \ static void handle_##function(BinarySource *in, strbuf *out) { \ diff --git a/testcrypt.h b/testcrypt.h index 7e0ef3cf..d6ac0a2a 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -318,6 +318,7 @@ FUNC1(uint, crc32_ssh1, val_string_ptrlen) FUNC2(uint, crc32_update, uint, val_string_ptrlen) FUNC2(boolean, crcda_detect, val_string_ptrlen, val_string_ptrlen) FUNC1(val_string, get_implementations_commasep, val_string_ptrlen) +FUNC12(void, http_digest_response, out_val_string_binarysink, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, uint, httpdigesthash, boolean) /* * These functions aren't part of PuTTY's own API, but are additions