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

190 lines
5.9 KiB
C
Raw Permalink Normal View History

/*
* 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"
Reorganise proxy system into coroutines. Previously, the proxy negotiation functions were written as explicit state machines, with ps->state being manually set to a sequence of positive integer values which would be tested by if statements in the next call to the same negotiation function. That's not how this code base likes to do things! We have a coroutine system to allow those state machines to be implicit rather than explicit, so that we can use ordinary control flow statements like while loops. Reorganised each proxy negotiation function into a coroutine-based system like that. While I'm at it, I've also moved each proxy negotiator out into its own source file, to make proxy.c less overcrowded and monolithic. And _that_ gave me the opportunity to define each negotiator as an implementation of a trait rather than as a single function - which means now each one can define its own local variables and have its own cleanup function, instead of all of them having to share the variables inside the main ProxySocket struct. In the new coroutine system, negotiators don't have to worry about the mechanics of actually sending data down the underlying Socket any more. The negotiator coroutine just appends to a bufchain (via a provided bufchain_sink), and after every call to the coroutine, central code in proxy.c transfers the data to the Socket itself. This avoids a lot of intermediate allocations within the negotiators, which previously kept having to make temporary strbufs or arrays in order to have something to point an sk_write() at; now they can just put formatted data directly into the output bufchain via the marshal.h interface. In this version of the code, I've also moved most of the SOCKS5 CHAP implementation from cproxy.c into socks5.c, so that it can sit in the same coroutine as the rest of the proxy negotiation control flow. That's because calling a sub-coroutine (co-subroutine?) is awkward to set up (though it is _possible_ - we do SSH-2 kex that way), and there's no real need to bother in this case, since the only thing that really needs to go in cproxy.c is the actual cryptography plus a flag to tell socks5.c whether to offer CHAP authentication in the first place.
2021-11-19 10:26:41 +00:00
const bool socks5_chap_available = true;
const bool http_digest_available = true;
Reorganise proxy system into coroutines. Previously, the proxy negotiation functions were written as explicit state machines, with ps->state being manually set to a sequence of positive integer values which would be tested by if statements in the next call to the same negotiation function. That's not how this code base likes to do things! We have a coroutine system to allow those state machines to be implicit rather than explicit, so that we can use ordinary control flow statements like while loops. Reorganised each proxy negotiation function into a coroutine-based system like that. While I'm at it, I've also moved each proxy negotiator out into its own source file, to make proxy.c less overcrowded and monolithic. And _that_ gave me the opportunity to define each negotiator as an implementation of a trait rather than as a single function - which means now each one can define its own local variables and have its own cleanup function, instead of all of them having to share the variables inside the main ProxySocket struct. In the new coroutine system, negotiators don't have to worry about the mechanics of actually sending data down the underlying Socket any more. The negotiator coroutine just appends to a bufchain (via a provided bufchain_sink), and after every call to the coroutine, central code in proxy.c transfers the data to the Socket itself. This avoids a lot of intermediate allocations within the negotiators, which previously kept having to make temporary strbufs or arrays in order to have something to point an sk_write() at; now they can just put formatted data directly into the output bufchain via the marshal.h interface. In this version of the code, I've also moved most of the SOCKS5 CHAP implementation from cproxy.c into socks5.c, so that it can sit in the same coroutine as the rest of the proxy negotiation control flow. That's because calling a sub-coroutine (co-subroutine?) is awkward to set up (though it is _possible_ - we do SSH-2 kex that way), and there's no real need to bother in this case, since the only thing that really needs to go in cproxy.c is the actual cryptography plus a flag to tell socks5.c whether to offer CHAP authentication in the first place.
2021-11-19 10:26:41 +00:00
strbuf *chap_response(ptrlen challenge, ptrlen password)
{
Reorganise proxy system into coroutines. Previously, the proxy negotiation functions were written as explicit state machines, with ps->state being manually set to a sequence of positive integer values which would be tested by if statements in the next call to the same negotiation function. That's not how this code base likes to do things! We have a coroutine system to allow those state machines to be implicit rather than explicit, so that we can use ordinary control flow statements like while loops. Reorganised each proxy negotiation function into a coroutine-based system like that. While I'm at it, I've also moved each proxy negotiator out into its own source file, to make proxy.c less overcrowded and monolithic. And _that_ gave me the opportunity to define each negotiator as an implementation of a trait rather than as a single function - which means now each one can define its own local variables and have its own cleanup function, instead of all of them having to share the variables inside the main ProxySocket struct. In the new coroutine system, negotiators don't have to worry about the mechanics of actually sending data down the underlying Socket any more. The negotiator coroutine just appends to a bufchain (via a provided bufchain_sink), and after every call to the coroutine, central code in proxy.c transfers the data to the Socket itself. This avoids a lot of intermediate allocations within the negotiators, which previously kept having to make temporary strbufs or arrays in order to have something to point an sk_write() at; now they can just put formatted data directly into the output bufchain via the marshal.h interface. In this version of the code, I've also moved most of the SOCKS5 CHAP implementation from cproxy.c into socks5.c, so that it can sit in the same coroutine as the rest of the proxy negotiation control flow. That's because calling a sub-coroutine (co-subroutine?) is awkward to set up (though it is _possible_ - we do SSH-2 kex that way), and there's no real need to bother in this case, since the only thing that really needs to go in cproxy.c is the actual cryptography plus a flag to tell socks5.c whether to offer CHAP authentication in the first place.
2021-11-19 10:26:41 +00:00
strbuf *sb = strbuf_new_nm();
const ssh2_macalg *alg = &ssh_hmac_md5;
mac_simple(alg, password, challenge, strbuf_append(sb, alg->len));
return sb;
}
2022-09-03 11:02:48 +00:00
static 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, accepted) str,
HTTP_DIGEST_HASHES(DECL_ARRAY)
#undef DECL_ARRAY
};
const bool httphashaccepted[] = {
#define DECL_ARRAY(id, str, alg, bits, accepted) accepted,
HTTP_DIGEST_HASHES(DECL_ARRAY)
#undef DECL_ARRAY
};
static const ssh_hashalg *const httphashalgs[] = {
#define DECL_ARRAY(id, str, alg, bits, accepted) alg,
HTTP_DIGEST_HASHES(DECL_ARRAY)
#undef DECL_ARRAY
};
static const size_t httphashlengths[] = {
#define DECL_ARRAY(id, str, alg, bits, accepted) 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));
}