mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 09:58:01 +00:00
3c21fa54c5
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).
183 lines
5.7 KiB
C
183 lines
5.7 KiB
C
/*
|
|
* Routines to do cryptographic interaction with proxies in PuTTY.
|
|
* This is in a separate module from proxy.c, so that it can be
|
|
* conveniently removed in PuTTYtel by replacing this module with
|
|
* the stub version nocproxy.c.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#include "putty.h"
|
|
#include "ssh.h" /* For MD5 support */
|
|
#include "network.h"
|
|
#include "proxy.h"
|
|
#include "marshal.h"
|
|
|
|
const bool socks5_chap_available = true;
|
|
const bool http_digest_available = true;
|
|
|
|
strbuf *chap_response(ptrlen challenge, ptrlen password)
|
|
{
|
|
strbuf *sb = strbuf_new_nm();
|
|
const ssh2_macalg *alg = &ssh_hmac_md5;
|
|
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));
|
|
}
|