1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-03-16 03:53:01 -05:00
putty-source/ssh1login-server.c
Simon Tatham a6f1709c2f Adopt C99 <stdbool.h>'s true/false.
This commit includes <stdbool.h> from defs.h and deletes my
traditional definitions of TRUE and FALSE, but other than that, it's a
100% mechanical search-and-replace transforming all uses of TRUE and
FALSE into the C99-standardised lowercase spellings.

No actual types are changed in this commit; that will come next. This
is just getting the noise out of the way, so that subsequent commits
can have a higher proportion of signal.
2018-11-03 13:45:00 +00:00

421 lines
14 KiB
C

/*
* Packet protocol layer for the SSH-1 login phase, from the server side.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshcr.h"
#include "sshserver.h"
struct ssh1_login_server_state {
int crState;
PacketProtocolLayer *successor_layer;
int remote_protoflags;
int local_protoflags;
unsigned long supported_ciphers_mask, supported_auths_mask;
unsigned cipher_type;
unsigned char cookie[8];
unsigned char session_key[32];
unsigned char session_id[16];
char *username_str;
ptrlen username;
struct RSAKey *servkey, *hostkey;
int servkey_generated_here;
Bignum sesskey;
AuthPolicy *authpolicy;
unsigned ap_methods, current_method;
unsigned char auth_rsa_expected_response[16];
struct RSAKey *authkey;
int auth_successful;
PacketProtocolLayer ppl;
};
static void ssh1_login_server_free(PacketProtocolLayer *);
static void ssh1_login_server_process_queue(PacketProtocolLayer *);
static int ssh1_login_server_get_specials(
PacketProtocolLayer *ppl, add_special_fn_t add_special,
void *ctx) { return false; }
static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl,
SessionSpecialCode code, int arg) {}
static int ssh1_login_server_want_user_input(
PacketProtocolLayer *ppl) { return false; }
static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {}
static void ssh1_login_server_reconfigure(
PacketProtocolLayer *ppl, Conf *conf) {}
static const struct PacketProtocolLayerVtable ssh1_login_server_vtable = {
ssh1_login_server_free,
ssh1_login_server_process_queue,
ssh1_login_server_get_specials,
ssh1_login_server_special_cmd,
ssh1_login_server_want_user_input,
ssh1_login_server_got_user_input,
ssh1_login_server_reconfigure,
NULL /* no layer names in SSH-1 */,
};
static void no_progress(void *param, int action, int phase, int iprogress) {}
PacketProtocolLayer *ssh1_login_server_new(
PacketProtocolLayer *successor_layer, struct RSAKey *hostkey,
AuthPolicy *authpolicy)
{
struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh1_login_server_vtable;
s->hostkey = hostkey;
s->authpolicy = authpolicy;
s->successor_layer = successor_layer;
return &s->ppl;
}
static void ssh1_login_server_free(PacketProtocolLayer *ppl)
{
struct ssh1_login_server_state *s =
container_of(ppl, struct ssh1_login_server_state, ppl);
if (s->successor_layer)
ssh_ppl_free(s->successor_layer);
if (s->servkey_generated_here && s->servkey) {
freersakey(s->servkey);
sfree(s->servkey);
}
smemclr(s->session_key, sizeof(s->session_key));
sfree(s->username_str);
sfree(s);
}
static int ssh1_login_server_filter_queue(struct ssh1_login_server_state *s)
{
return ssh1_common_filter_queue(&s->ppl);
}
static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s)
{
if (ssh1_login_server_filter_queue(s))
return NULL;
return pq_pop(s->ppl.in_pq);
}
static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
{
struct ssh1_login_server_state *s =
container_of(ppl, struct ssh1_login_server_state, ppl);
PktIn *pktin;
PktOut *pktout;
int i;
/* Filter centrally handled messages off the front of the queue on
* every entry to this coroutine, no matter where we're resuming
* from, even if we're _not_ looping on pq_pop. That way we can
* still proactively handle those messages even if we're waiting
* for a user response. */
if (ssh1_login_server_filter_queue(s))
return;
crBegin(s->crState);
if (!s->servkey) {
int server_key_bits = s->hostkey->bytes - 256;
if (server_key_bits < 512)
server_key_bits = s->hostkey->bytes + 256;
s->servkey = snew(struct RSAKey);
rsa_generate(s->servkey, server_key_bits, no_progress, NULL);
s->servkey->comment = NULL;
s->servkey_generated_here = true;
}
s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED;
/* FIXME: ability to configure this to a subset */
s->supported_ciphers_mask = ((1U << SSH_CIPHER_3DES) |
(1U << SSH_CIPHER_BLOWFISH) |
(1U << SSH_CIPHER_DES));
s->supported_auths_mask = 0;
s->ap_methods = auth_methods(s->authpolicy);
if (s->ap_methods & AUTHMETHOD_PASSWORD)
s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD);
if (s->ap_methods & AUTHMETHOD_PUBLICKEY)
s->supported_auths_mask |= (1U << SSH1_AUTH_RSA);
if (s->ap_methods & AUTHMETHOD_TIS)
s->supported_auths_mask |= (1U << SSH1_AUTH_TIS);
if (s->ap_methods & AUTHMETHOD_CRYPTOCARD)
s->supported_auths_mask |= (1U << SSH1_AUTH_CCARD);
for (i = 0; i < 8; i++)
s->cookie[i] = random_byte();
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY);
put_data(pktout, s->cookie, 8);
rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
s->servkey, RSA_SSH1_EXPONENT_FIRST);
rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
s->hostkey, RSA_SSH1_EXPONENT_FIRST);
put_uint32(pktout, s->local_protoflags);
put_uint32(pktout, s->supported_ciphers_mask);
put_uint32(pktout, s->supported_auths_mask);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_SESSION_KEY) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response"
" to initial public key packet, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
{
ptrlen client_cookie;
s->cipher_type = get_byte(pktin);
client_cookie = get_data(pktin, 8);
s->sesskey = get_mp_ssh1(pktin);
s->remote_protoflags = get_uint32(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet");
return;
}
if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) {
ssh_proto_error(s->ppl.ssh,
"Client sent incorrect anti-spoofing cookie");
return;
}
}
if (s->cipher_type >= 32 ||
!((s->supported_ciphers_mask >> s->cipher_type) & 1)) {
ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher");
return;
}
{
struct RSAKey *smaller, *larger;
strbuf *data = strbuf_new();
if (bignum_bitcount(s->hostkey->modulus) >
bignum_bitcount(s->servkey->modulus)) {
larger = s->hostkey;
smaller = s->servkey;
} else {
smaller = s->hostkey;
larger = s->servkey;
}
if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) {
freebn(s->sesskey);
s->sesskey = bignum_from_bytes(data->u, data->len);
data->len = 0;
if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) &&
data->len == sizeof(s->session_key)) {
memcpy(s->session_key, data->u, sizeof(s->session_key));
freebn(s->sesskey);
s->sesskey = NULL; /* indicates success */
}
}
strbuf_free(data);
}
if (s->sesskey) {
ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key");
return;
}
ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey);
for (i = 0; i < 16; i++)
s->session_key[i] ^= s->session_id[i];
{
const struct ssh1_cipheralg *cipher =
(s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish :
s->cipher_type == SSH_CIPHER_DES ? &ssh1_des : &ssh1_3des);
ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_USER) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet while "
"expecting username, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
s->username = get_string(pktin);
s->username.ptr = s->username_str = mkstr(s->username);
ppl_logevent(("Received username '%.*s'", PTRLEN_PRINTF(s->username)));
s->auth_successful = auth_none(s->authpolicy, s->username);
while (1) {
/* Signal failed authentication */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) {
s->current_method = AUTHMETHOD_PASSWORD;
if (!(s->ap_methods & s->current_method))
continue;
ptrlen password = get_string(pktin);
/* Tolerate historic traffic-analysis defence of NUL +
* garbage on the end of the binary password string */
char *nul = memchr(password.ptr, '\0', password.len);
if (nul)
password.len = (const char *)nul - (const char *)password.ptr;
if (auth_password(s->authpolicy, s->username, password, NULL))
goto auth_success;
} else if (pktin->type == SSH1_CMSG_AUTH_RSA) {
s->current_method = AUTHMETHOD_PUBLICKEY;
if (!(s->ap_methods & s->current_method))
continue;
{
Bignum modulus = get_mp_ssh1(pktin);
s->authkey = auth_publickey_ssh1(
s->authpolicy, s->username, modulus);
freebn(modulus);
}
if (!s->authkey)
continue;
if (s->authkey->bytes < 32) {
ppl_logevent(("Auth key far too small"));
continue;
}
{
unsigned char *rsabuf =
snewn(s->authkey->bytes, unsigned char);
struct MD5Context md5c;
for (i = 0; i < 32; i++)
rsabuf[i] = random_byte();
MD5Init(&md5c);
put_data(&md5c, rsabuf, 32);
put_data(&md5c, s->session_id, 16);
MD5Final(s->auth_rsa_expected_response, &md5c);
if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) {
sfree(rsabuf);
ppl_logevent(("Failed to encrypt auth challenge"));
continue;
}
Bignum bn = bignum_from_bytes(rsabuf, s->authkey->bytes);
smemclr(rsabuf, s->authkey->bytes);
sfree(rsabuf);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE);
put_mp_ssh1(pktout, bn);
pq_push(s->ppl.out_pq, pktout);
freebn(bn);
}
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in "
"response to RSA auth challenge, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
{
ptrlen response = get_data(pktin, 16);
ptrlen expected = make_ptrlen(
s->auth_rsa_expected_response, 16);
if (!ptrlen_eq_ptrlen(response, expected)) {
ppl_logevent(("Wrong response to auth challenge"));
continue;
}
}
goto auth_success;
} else if (pktin->type == SSH1_CMSG_AUTH_TIS ||
pktin->type == SSH1_CMSG_AUTH_CCARD) {
char *challenge;
unsigned response_type;
ptrlen response;
s->current_method = (pktin->type == SSH1_CMSG_AUTH_TIS ?
AUTHMETHOD_TIS : AUTHMETHOD_CRYPTOCARD);
if (!(s->ap_methods & s->current_method))
continue;
challenge = auth_ssh1int_challenge(
s->authpolicy, s->current_method, s->username);
if (!challenge)
continue;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp,
(s->current_method == AUTHMETHOD_TIS ?
SSH1_SMSG_AUTH_TIS_CHALLENGE :
SSH1_SMSG_AUTH_CCARD_CHALLENGE));
put_stringz(pktout, challenge);
pq_push(s->ppl.out_pq, pktout);
sfree(challenge);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
response_type = (s->current_method == AUTHMETHOD_TIS ?
SSH1_CMSG_AUTH_TIS_RESPONSE :
SSH1_CMSG_AUTH_CCARD_RESPONSE);
if (pktin->type != response_type) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in "
"response to %s challenge, type %d (%s)",
(s->current_method == AUTHMETHOD_TIS ?
"TIS" : "CryptoCard"),
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
response = get_string(pktin);
if (auth_ssh1int_response(s->authpolicy, response))
goto auth_success;
}
}
auth_success:
if (!auth_successful(s->authpolicy, s->username, s->current_method)) {
ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1"
" cannot perform them");
return;
}
/* Signal successful authentication */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
ssh1_connection_set_protoflags(
s->successor_layer, s->local_protoflags, s->remote_protoflags);
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */
ssh_ppl_replace(&s->ppl, successor);
return; /* we've just freed s, so avoid even touching s->crState */
}
crFinishV;
}