1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

HTTP proxy: implement Digest authentication.

In http.c, this drops in reasonably neatly alongside the existing
support for Basic, now that we're waiting for an initial 407 response
from the proxy to tell us which auth mechanism it would prefer to use.

The rest of this patch is mostly contriving to add testcrypt support
for the function in cproxy.c that generates the complicated output
header to go in the HTTP request: you need about a dozen assorted
parameters, the actual response hash has two more hashes in its
preimage, and there's even an option to hash the username as well if
necessary. Much more complicated than CHAP (which is just plain
HMAC-MD5), so it needs testing!

Happily, RFC 7616 comes with some reasonably useful test cases, and
I've managed to transcribe them directly into cryptsuite.py and
demonstrate that my response-generator agrees with them.

End-to-end testing of the whole system was done against Squid 4.13
(specifically, the squid package in Debian bullseye, version 4.13-10).
This commit is contained in:
Simon Tatham 2021-11-20 14:56:32 +00:00
parent 52ee636b09
commit 3c21fa54c5
9 changed files with 511 additions and 39 deletions

View File

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

25
proxy/cproxy.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

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

View File

@ -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) { \

View File

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