mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-25 01:02:24 +00:00
Uppity: add challenge-response auth methods.
This adds the server side of the SSH-2 keyboard-interactive protocol, and the pair of very similar SSH-1 methods AUTH_TIS and AUTH_CCARD (which basically differ only in message numbers, and each involve a single challenge from the server and a response from the user).
This commit is contained in:
parent
5f03613614
commit
c9e6118246
@ -152,7 +152,10 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
|
|||||||
s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD);
|
s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD);
|
||||||
if (s->ap_methods & AUTHMETHOD_PUBLICKEY)
|
if (s->ap_methods & AUTHMETHOD_PUBLICKEY)
|
||||||
s->supported_auths_mask |= (1U << SSH1_AUTH_RSA);
|
s->supported_auths_mask |= (1U << SSH1_AUTH_RSA);
|
||||||
/* FIXME: TIS, CCARD */
|
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++)
|
for (i = 0; i < 8; i++)
|
||||||
s->cookie[i] = random_byte();
|
s->cookie[i] = random_byte();
|
||||||
@ -349,6 +352,47 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
|
|||||||
}
|
}
|
||||||
|
|
||||||
goto auth_success;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ struct ssh2_userauth_server_state {
|
|||||||
unsigned methods, this_method;
|
unsigned methods, this_method;
|
||||||
int partial_success;
|
int partial_success;
|
||||||
|
|
||||||
|
AuthKbdInt *aki;
|
||||||
|
|
||||||
PacketProtocolLayer ppl;
|
PacketProtocolLayer ppl;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,6 +48,21 @@ static const struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = {
|
|||||||
"ssh-userauth",
|
"ssh-userauth",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void free_auth_kbdint(AuthKbdInt *aki)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!aki)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sfree(aki->title);
|
||||||
|
sfree(aki->instruction);
|
||||||
|
for (i = 0; i < aki->nprompts; i++)
|
||||||
|
sfree(aki->prompts[i].prompt);
|
||||||
|
sfree(aki->prompts);
|
||||||
|
sfree(aki);
|
||||||
|
}
|
||||||
|
|
||||||
PacketProtocolLayer *ssh2_userauth_server_new(
|
PacketProtocolLayer *ssh2_userauth_server_new(
|
||||||
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy)
|
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy)
|
||||||
{
|
{
|
||||||
@ -76,6 +93,8 @@ static void ssh2_userauth_server_free(PacketProtocolLayer *ppl)
|
|||||||
if (s->successor_layer)
|
if (s->successor_layer)
|
||||||
ssh_ppl_free(s->successor_layer);
|
ssh_ppl_free(s->successor_layer);
|
||||||
|
|
||||||
|
free_auth_kbdint(s->aki);
|
||||||
|
|
||||||
sfree(s);
|
sfree(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,6 +230,66 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
|
|||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
goto failure;
|
goto failure;
|
||||||
|
} else if (ptrlen_eq_string(s->method, "keyboard-interactive")) {
|
||||||
|
int i, ok;
|
||||||
|
unsigned n;
|
||||||
|
|
||||||
|
s->this_method = AUTHMETHOD_KBDINT;
|
||||||
|
if (!(s->methods & s->this_method))
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
do {
|
||||||
|
s->aki = auth_kbdint_prompts(s->authpolicy, s->username);
|
||||||
|
if (!s->aki)
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
pktout = ssh_bpp_new_pktout(
|
||||||
|
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST);
|
||||||
|
put_stringz(pktout, s->aki->title);
|
||||||
|
put_stringz(pktout, s->aki->instruction);
|
||||||
|
put_stringz(pktout, ""); /* language tag */
|
||||||
|
put_uint32(pktout, s->aki->nprompts);
|
||||||
|
for (i = 0; i < s->aki->nprompts; i++) {
|
||||||
|
put_stringz(pktout, s->aki->prompts[i].prompt);
|
||||||
|
put_bool(pktout, s->aki->prompts[i].echo);
|
||||||
|
}
|
||||||
|
pq_push(s->ppl.out_pq, pktout);
|
||||||
|
|
||||||
|
crMaybeWaitUntilV(
|
||||||
|
(pktin = ssh2_userauth_server_pop(s)) != NULL);
|
||||||
|
if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) {
|
||||||
|
ssh_proto_error(
|
||||||
|
s->ppl.ssh, "Received unexpected packet when "
|
||||||
|
"expecting USERAUTH_INFO_RESPONSE, type %d (%s)",
|
||||||
|
pktin->type,
|
||||||
|
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
|
||||||
|
s->ppl.bpp->pls->actx, pktin->type));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = get_uint32(pktin);
|
||||||
|
if (n != s->aki->nprompts) {
|
||||||
|
ssh_proto_error(
|
||||||
|
s->ppl.ssh, "Received %u keyboard-interactive "
|
||||||
|
"responses after sending %u prompts",
|
||||||
|
n, s->aki->nprompts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ptrlen *responses = snewn(s->aki->nprompts, ptrlen);
|
||||||
|
for (i = 0; i < s->aki->nprompts; i++)
|
||||||
|
responses[i] = get_string(pktin);
|
||||||
|
ok = auth_kbdint_responses(s->authpolicy, responses);
|
||||||
|
sfree(responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_auth_kbdint(s->aki);
|
||||||
|
s->aki = NULL;
|
||||||
|
} while (ok == 0);
|
||||||
|
|
||||||
|
if (ok <= 0)
|
||||||
|
goto failure;
|
||||||
} else {
|
} else {
|
||||||
goto failure;
|
goto failure;
|
||||||
}
|
}
|
||||||
@ -246,6 +325,8 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
|
|||||||
add_to_commasep(list, "password");
|
add_to_commasep(list, "password");
|
||||||
if (s->methods & AUTHMETHOD_PUBLICKEY)
|
if (s->methods & AUTHMETHOD_PUBLICKEY)
|
||||||
add_to_commasep(list, "publickey");
|
add_to_commasep(list, "publickey");
|
||||||
|
if (s->methods & AUTHMETHOD_KBDINT)
|
||||||
|
add_to_commasep(list, "keyboard-interactive");
|
||||||
put_stringsb(pktout, list);
|
put_stringsb(pktout, list);
|
||||||
}
|
}
|
||||||
put_bool(pktout, s->partial_success);
|
put_bool(pktout, s->partial_success);
|
||||||
|
28
sshserver.h
28
sshserver.h
@ -13,6 +13,9 @@ void platform_logevent(const char *msg);
|
|||||||
X(NONE) \
|
X(NONE) \
|
||||||
X(PASSWORD) \
|
X(PASSWORD) \
|
||||||
X(PUBLICKEY) \
|
X(PUBLICKEY) \
|
||||||
|
X(KBDINT) \
|
||||||
|
X(TIS) \
|
||||||
|
X(CRYPTOCARD) \
|
||||||
/* end of list */
|
/* end of list */
|
||||||
|
|
||||||
#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name,
|
#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name,
|
||||||
@ -21,6 +24,18 @@ enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy };
|
|||||||
AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name,
|
AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name,
|
||||||
enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy };
|
enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy };
|
||||||
|
|
||||||
|
typedef struct AuthKbdInt AuthKbdInt;
|
||||||
|
typedef struct AuthKbdIntPrompt AuthKbdIntPrompt;
|
||||||
|
struct AuthKbdInt {
|
||||||
|
char *title, *instruction; /* both need freeing */
|
||||||
|
int nprompts;
|
||||||
|
AuthKbdIntPrompt *prompts; /* the array itself needs freeing */
|
||||||
|
};
|
||||||
|
struct AuthKbdIntPrompt {
|
||||||
|
char *prompt; /* needs freeing */
|
||||||
|
int echo;
|
||||||
|
};
|
||||||
|
|
||||||
unsigned auth_methods(AuthPolicy *);
|
unsigned auth_methods(AuthPolicy *);
|
||||||
int auth_none(AuthPolicy *, ptrlen username);
|
int auth_none(AuthPolicy *, ptrlen username);
|
||||||
int auth_password(AuthPolicy *, ptrlen username, ptrlen password);
|
int auth_password(AuthPolicy *, ptrlen username, ptrlen password);
|
||||||
@ -28,6 +43,19 @@ int auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob);
|
|||||||
/* auth_publickey_ssh1 must return the whole public key given the modulus,
|
/* auth_publickey_ssh1 must return the whole public key given the modulus,
|
||||||
* because the SSH-1 client never transmits the exponent over the wire.
|
* because the SSH-1 client never transmits the exponent over the wire.
|
||||||
* The key remains owned by the AuthPolicy. */
|
* The key remains owned by the AuthPolicy. */
|
||||||
|
|
||||||
|
AuthKbdInt *auth_kbdint_prompts(AuthPolicy *, ptrlen username);
|
||||||
|
/* auth_kbdint_prompts returns NULL to trigger auth failure */
|
||||||
|
int auth_kbdint_responses(AuthPolicy *, const ptrlen *responses);
|
||||||
|
/* auth_kbdint_responses returns >0 for success, <0 for failure, and 0
|
||||||
|
* to indicate that we haven't decided yet and further prompts are
|
||||||
|
* coming */
|
||||||
|
|
||||||
|
/* The very similar SSH-1 TIS and CryptoCard methods are combined into
|
||||||
|
* a single API for AuthPolicy, which takes a method argument */
|
||||||
|
char *auth_ssh1int_challenge(AuthPolicy *, unsigned method, ptrlen username);
|
||||||
|
int auth_ssh1int_response(AuthPolicy *, ptrlen response);
|
||||||
|
|
||||||
struct RSAKey *auth_publickey_ssh1(
|
struct RSAKey *auth_publickey_ssh1(
|
||||||
AuthPolicy *ap, ptrlen username, Bignum rsa_modulus);
|
AuthPolicy *ap, ptrlen username, Bignum rsa_modulus);
|
||||||
/* auth_successful returns FALSE if further authentication is needed */
|
/* auth_successful returns FALSE if further authentication is needed */
|
||||||
|
@ -164,10 +164,12 @@ struct AuthPolicy_ssh2_pubkey {
|
|||||||
struct AuthPolicy {
|
struct AuthPolicy {
|
||||||
struct AuthPolicy_ssh1_pubkey *ssh1keys;
|
struct AuthPolicy_ssh1_pubkey *ssh1keys;
|
||||||
struct AuthPolicy_ssh2_pubkey *ssh2keys;
|
struct AuthPolicy_ssh2_pubkey *ssh2keys;
|
||||||
|
int kbdint_state;
|
||||||
};
|
};
|
||||||
unsigned auth_methods(AuthPolicy *ap)
|
unsigned auth_methods(AuthPolicy *ap)
|
||||||
{
|
{
|
||||||
return AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD;
|
return (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | AUTHMETHOD_KBDINT |
|
||||||
|
AUTHMETHOD_TIS | AUTHMETHOD_CRYPTOCARD);
|
||||||
}
|
}
|
||||||
int auth_none(AuthPolicy *ap, ptrlen username)
|
int auth_none(AuthPolicy *ap, ptrlen username)
|
||||||
{
|
{
|
||||||
@ -196,6 +198,65 @@ struct RSAKey *auth_publickey_ssh1(
|
|||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
|
||||||
|
{
|
||||||
|
AuthKbdInt *aki = snew(AuthKbdInt);
|
||||||
|
|
||||||
|
switch (ap->kbdint_state) {
|
||||||
|
case 0:
|
||||||
|
aki->title = dupstr("Initial double prompt");
|
||||||
|
aki->instruction =
|
||||||
|
dupstr("First prompt should echo, second should not");
|
||||||
|
aki->nprompts = 2;
|
||||||
|
aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt);
|
||||||
|
aki->prompts[0].prompt = dupstr("Echoey prompt: ");
|
||||||
|
aki->prompts[0].echo = TRUE;
|
||||||
|
aki->prompts[1].prompt = dupstr("Silent prompt: ");
|
||||||
|
aki->prompts[1].echo = FALSE;
|
||||||
|
return aki;
|
||||||
|
case 1:
|
||||||
|
aki->title = dupstr("Zero-prompt step");
|
||||||
|
aki->instruction = dupstr("Shouldn't see any prompts this time");
|
||||||
|
aki->nprompts = 0;
|
||||||
|
aki->prompts = NULL;
|
||||||
|
return aki;
|
||||||
|
default:
|
||||||
|
ap->kbdint_state = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
|
||||||
|
{
|
||||||
|
switch (ap->kbdint_state) {
|
||||||
|
case 0:
|
||||||
|
if (ptrlen_eq_string(responses[0], "stoat") &&
|
||||||
|
ptrlen_eq_string(responses[1], "weasel")) {
|
||||||
|
ap->kbdint_state++;
|
||||||
|
return 0; /* those are the expected responses */
|
||||||
|
} else {
|
||||||
|
ap->kbdint_state = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
return +1; /* succeed after the zero-prompt step */
|
||||||
|
default:
|
||||||
|
ap->kbdint_state = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
|
||||||
|
{
|
||||||
|
/* FIXME: test returning a challenge string without \n, and ensure
|
||||||
|
* it gets printed as a prompt in its own right, without PuTTY
|
||||||
|
* making up a "Response: " prompt to follow it */
|
||||||
|
return dupprintf("This is a dummy %s challenge!\n",
|
||||||
|
(method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard"));
|
||||||
|
}
|
||||||
|
int auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
|
||||||
|
{
|
||||||
|
return ptrlen_eq_string(response, "otter");
|
||||||
|
}
|
||||||
int auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
|
int auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
|
||||||
{
|
{
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -280,6 +341,7 @@ int main(int argc, char **argv)
|
|||||||
Conf *conf = conf_new();
|
Conf *conf = conf_new();
|
||||||
load_open_settings(NULL, conf);
|
load_open_settings(NULL, conf);
|
||||||
|
|
||||||
|
ap.kbdint_state = 0;
|
||||||
ap.ssh1keys = NULL;
|
ap.ssh1keys = NULL;
|
||||||
ap.ssh2keys = NULL;
|
ap.ssh2keys = NULL;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user