mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-03-16 12:03:03 -05:00
377 lines
12 KiB
C
377 lines
12 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);
|
||
|
/* FIXME: TIS, 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))
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|