version 1.3.2.4, 2001/03/21 18:53:15 |
version 1.4, 2000/07/16 08:27:22 |
|
|
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
* All rights reserved |
* All rights reserved |
|
* Created: Sat Mar 18 22:15:47 1995 ylo |
* Code to connect to a remote host, and to perform the client side of the |
* Code to connect to a remote host, and to perform the client side of the |
* login (authentication) dialog. |
* login (authentication) dialog. |
* |
* |
* As far as I am concerned, the code I have written for this software |
|
* can be used freely for any purpose. Any derived versions of this |
|
* software must be clearly marked as such, and if the derived work is |
|
* incompatible with the protocol description in the RFC file, it must be |
|
* called by a name other than "ssh" or "Secure Shell". |
|
*/ |
*/ |
|
|
#include "includes.h" |
#include "includes.h" |
RCSID("$OpenBSD$"); |
RCSID("$OpenBSD$"); |
|
|
#include <openssl/bn.h> |
#include <openssl/bn.h> |
|
#include <openssl/dsa.h> |
|
#include <openssl/rsa.h> |
#include <openssl/evp.h> |
#include <openssl/evp.h> |
|
|
#ifdef KRB4 |
|
#include <krb.h> |
|
#endif |
|
#ifdef AFS |
|
#include <kafs.h> |
|
#include "radix.h" |
|
#endif |
|
|
|
#include "ssh.h" |
|
#include "ssh1.h" |
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "rsa.h" |
#include "rsa.h" |
|
#include "ssh.h" |
#include "buffer.h" |
#include "buffer.h" |
#include "packet.h" |
#include "packet.h" |
|
#include "cipher.h" |
#include "mpaux.h" |
#include "mpaux.h" |
#include "uidswap.h" |
#include "uidswap.h" |
#include "log.h" |
|
#include "readconf.h" |
#include "readconf.h" |
#include "key.h" |
#include "key.h" |
#include "authfd.h" |
#include "authfd.h" |
#include "sshconnect.h" |
#include "sshconnect.h" |
#include "authfile.h" |
#include "authfile.h" |
#include "readpass.h" |
|
#include "cipher.h" |
|
#include "canohost.h" |
|
|
|
/* Session id for the current session. */ |
/* Session id for the current session. */ |
u_char session_id[16]; |
unsigned char session_id[16]; |
u_int supported_authentications = 0; |
unsigned int supported_authentications = 0; |
|
|
extern Options options; |
extern Options options; |
extern char *__progname; |
extern char *__progname; |
|
|
* authenticate using the agent. |
* authenticate using the agent. |
*/ |
*/ |
int |
int |
try_agent_authentication(void) |
try_agent_authentication() |
{ |
{ |
int type; |
int status, type; |
char *comment; |
char *comment; |
AuthenticationConnection *auth; |
AuthenticationConnection *auth; |
u_char response[16]; |
unsigned char response[16]; |
u_int i; |
unsigned int i; |
int plen, clen; |
BIGNUM *e, *n, *challenge; |
Key *key; |
|
BIGNUM *challenge; |
|
|
|
/* Get connection to the agent. */ |
/* Get connection to the agent. */ |
auth = ssh_get_authentication_connection(); |
auth = ssh_get_authentication_connection(); |
if (!auth) |
if (!auth) |
return 0; |
return 0; |
|
|
|
e = BN_new(); |
|
n = BN_new(); |
challenge = BN_new(); |
challenge = BN_new(); |
|
|
/* Loop through identities served by the agent. */ |
/* Loop through identities served by the agent. */ |
for (key = ssh_get_first_identity(auth, &comment, 1); |
for (status = ssh_get_first_identity(auth, e, n, &comment); |
key != NULL; |
status; |
key = ssh_get_next_identity(auth, &comment, 1)) { |
status = ssh_get_next_identity(auth, e, n, &comment)) { |
|
int plen, clen; |
|
|
/* Try this identity. */ |
/* Try this identity. */ |
debug("Trying RSA authentication via agent with '%.100s'", comment); |
debug("Trying RSA authentication via agent with '%.100s'", comment); |
|
|
|
|
/* Tell the server that we are willing to authenticate using this key. */ |
/* Tell the server that we are willing to authenticate using this key. */ |
packet_start(SSH_CMSG_AUTH_RSA); |
packet_start(SSH_CMSG_AUTH_RSA); |
packet_put_bignum(key->rsa->n); |
packet_put_bignum(n); |
packet_send(); |
packet_send(); |
packet_write_wait(); |
packet_write_wait(); |
|
|
|
|
does not support RSA authentication. */ |
does not support RSA authentication. */ |
if (type == SSH_SMSG_FAILURE) { |
if (type == SSH_SMSG_FAILURE) { |
debug("Server refused our key."); |
debug("Server refused our key."); |
key_free(key); |
|
continue; |
continue; |
} |
} |
/* Otherwise it should have sent a challenge. */ |
/* Otherwise it should have sent a challenge. */ |
|
|
debug("Received RSA challenge from server."); |
debug("Received RSA challenge from server."); |
|
|
/* Ask the agent to decrypt the challenge. */ |
/* Ask the agent to decrypt the challenge. */ |
if (!ssh_decrypt_challenge(auth, key, challenge, session_id, 1, response)) { |
if (!ssh_decrypt_challenge(auth, e, n, challenge, |
/* |
session_id, 1, response)) { |
* The agent failed to authenticate this identifier |
/* The agent failed to authenticate this identifier although it |
* although it advertised it supports this. Just |
advertised it supports this. Just return a wrong value. */ |
* return a wrong value. |
|
*/ |
|
log("Authentication agent failed to decrypt challenge."); |
log("Authentication agent failed to decrypt challenge."); |
memset(response, 0, sizeof(response)); |
memset(response, 0, sizeof(response)); |
} |
} |
key_free(key); |
|
debug("Sending response to RSA challenge."); |
debug("Sending response to RSA challenge."); |
|
|
/* Send the decrypted challenge back to the server. */ |
/* Send the decrypted challenge back to the server. */ |
|
|
|
|
/* The server returns success if it accepted the authentication. */ |
/* The server returns success if it accepted the authentication. */ |
if (type == SSH_SMSG_SUCCESS) { |
if (type == SSH_SMSG_SUCCESS) { |
ssh_close_authentication_connection(auth); |
|
BN_clear_free(challenge); |
|
debug("RSA authentication accepted by server."); |
debug("RSA authentication accepted by server."); |
|
BN_clear_free(e); |
|
BN_clear_free(n); |
|
BN_clear_free(challenge); |
return 1; |
return 1; |
} |
} |
/* Otherwise it should return failure. */ |
/* Otherwise it should return failure. */ |
|
|
packet_disconnect("Protocol error waiting RSA auth response: %d", |
packet_disconnect("Protocol error waiting RSA auth response: %d", |
type); |
type); |
} |
} |
ssh_close_authentication_connection(auth); |
|
|
BN_clear_free(e); |
|
BN_clear_free(n); |
BN_clear_free(challenge); |
BN_clear_free(challenge); |
|
|
debug("RSA authentication using agent refused."); |
debug("RSA authentication using agent refused."); |
return 0; |
return 0; |
} |
} |
|
|
void |
void |
respond_to_rsa_challenge(BIGNUM * challenge, RSA * prv) |
respond_to_rsa_challenge(BIGNUM * challenge, RSA * prv) |
{ |
{ |
u_char buf[32], response[16]; |
unsigned char buf[32], response[16]; |
MD5_CTX md; |
MD5_CTX md; |
int i, len; |
int i, len; |
|
|
/* Decrypt the challenge using the private key. */ |
/* Decrypt the challenge using the private key. */ |
/* XXX think about Bleichenbacher, too */ |
rsa_private_decrypt(challenge, challenge, prv); |
if (rsa_private_decrypt(challenge, challenge, prv) <= 0) |
|
packet_disconnect( |
|
"respond_to_rsa_challenge: rsa_private_decrypt failed"); |
|
|
|
/* Compute the response. */ |
/* Compute the response. */ |
/* The response is MD5 of decrypted challenge plus session id. */ |
/* The response is MD5 of decrypted challenge plus session id. */ |
len = BN_num_bytes(challenge); |
len = BN_num_bytes(challenge); |
if (len <= 0 || len > sizeof(buf)) |
if (len <= 0 || len > sizeof(buf)) |
packet_disconnect( |
packet_disconnect("respond_to_rsa_challenge: bad challenge length %d", |
"respond_to_rsa_challenge: bad challenge length %d", len); |
len); |
|
|
memset(buf, 0, sizeof(buf)); |
memset(buf, 0, sizeof(buf)); |
BN_bn2bin(challenge, buf + sizeof(buf) - len); |
BN_bn2bin(challenge, buf + sizeof(buf) - len); |
|
|
int plen, clen; |
int plen, clen; |
|
|
/* Try to load identification for the authentication key. */ |
/* Try to load identification for the authentication key. */ |
public = key_new(KEY_RSA1); |
public = key_new(KEY_RSA); |
if (!load_public_key(authfile, public, &comment)) { |
if (!load_public_key(authfile, public, &comment)) { |
key_free(public); |
key_free(public); |
/* Could not load it. Fail. */ |
/* Could not load it. Fail. */ |
|
|
|
|
debug("Received RSA challenge from server."); |
debug("Received RSA challenge from server."); |
|
|
private = key_new(KEY_RSA1); |
private = key_new(KEY_RSA); |
/* |
/* |
* Load the private key. Try first with empty passphrase; if it |
* Load the private key. Try first with empty passphrase; if it |
* fails, ask for a passphrase. |
* fails, ask for a passphrase. |
|
|
/* Expect the server to reject it... */ |
/* Expect the server to reject it... */ |
packet_read_expect(&plen, SSH_SMSG_FAILURE); |
packet_read_expect(&plen, SSH_SMSG_FAILURE); |
xfree(comment); |
xfree(comment); |
key_free(private); |
|
BN_clear_free(challenge); |
|
return 0; |
return 0; |
} |
} |
/* Destroy the passphrase. */ |
/* Destroy the passphrase. */ |
|
|
|
|
#ifdef KRB4 |
#ifdef KRB4 |
int |
int |
try_kerberos_authentication(void) |
try_kerberos_authentication() |
{ |
{ |
KTEXT_ST auth; /* Kerberos data */ |
KTEXT_ST auth; /* Kerberos data */ |
char *reply; |
char *reply; |
|
|
if (stat(tkt_string(), &st) < 0) |
if (stat(tkt_string(), &st) < 0) |
return 0; |
return 0; |
|
|
strncpy(inst, (char *) krb_get_phost(get_canonical_hostname(1)), INST_SZ); |
strncpy(inst, (char *) krb_get_phost(get_canonical_hostname()), INST_SZ); |
|
|
realm = (char *) krb_realmofhost(get_canonical_hostname(1)); |
realm = (char *) krb_realmofhost(get_canonical_hostname()); |
if (!realm) { |
if (!realm) { |
debug("Kerberos V4: no realm for %s", get_canonical_hostname(1)); |
debug("Kerberos V4: no realm for %s", get_canonical_hostname()); |
return 0; |
return 0; |
} |
} |
/* This can really be anything. */ |
/* This can really be anything. */ |
|
|
debug("Kerberos V4 authentication accepted."); |
debug("Kerberos V4 authentication accepted."); |
|
|
/* Get server's response. */ |
/* Get server's response. */ |
reply = packet_get_string((u_int *) &auth.length); |
reply = packet_get_string((unsigned int *) &auth.length); |
memcpy(auth.dat, reply, auth.length); |
memcpy(auth.dat, reply, auth.length); |
xfree(reply); |
xfree(reply); |
|
|
|
|
|
|
#ifdef AFS |
#ifdef AFS |
int |
int |
send_kerberos_tgt(void) |
send_kerberos_tgt() |
{ |
{ |
CREDENTIALS *creds; |
CREDENTIALS *creds; |
char pname[ANAME_SZ], pinst[INST_SZ], prealm[REALM_SZ]; |
char pname[ANAME_SZ], pinst[INST_SZ], prealm[REALM_SZ]; |
|
|
debug("Kerberos V4 ticket expired: %s", TKT_FILE); |
debug("Kerberos V4 ticket expired: %s", TKT_FILE); |
return 0; |
return 0; |
} |
} |
creds_to_radix(creds, (u_char *)buffer, sizeof buffer); |
creds_to_radix(creds, (unsigned char *)buffer, sizeof buffer); |
xfree(creds); |
xfree(creds); |
|
|
packet_start(SSH_CMSG_HAVE_KERBEROS_TGT); |
packet_start(SSH_CMSG_HAVE_KERBEROS_TGT); |
|
|
p = buf; |
p = buf; |
|
|
/* Get secret token. */ |
/* Get secret token. */ |
memcpy(&creds.ticket_st.length, p, sizeof(u_int)); |
memcpy(&creds.ticket_st.length, p, sizeof(unsigned int)); |
if (creds.ticket_st.length > MAX_KTXT_LEN) |
if (creds.ticket_st.length > MAX_KTXT_LEN) |
break; |
break; |
p += sizeof(u_int); |
p += sizeof(unsigned int); |
memcpy(creds.ticket_st.dat, p, creds.ticket_st.length); |
memcpy(creds.ticket_st.dat, p, creds.ticket_st.length); |
p += creds.ticket_st.length; |
p += creds.ticket_st.length; |
|
|
|
|
creds.pinst[0] = '\0'; |
creds.pinst[0] = '\0'; |
|
|
/* Encode token, ship it off. */ |
/* Encode token, ship it off. */ |
if (creds_to_radix(&creds, (u_char *) buffer, sizeof buffer) <= 0) |
if (creds_to_radix(&creds, (unsigned char*) buffer, sizeof buffer) <= 0) |
break; |
break; |
packet_start(SSH_CMSG_HAVE_AFS_TOKEN); |
packet_start(SSH_CMSG_HAVE_AFS_TOKEN); |
packet_put_string(buffer, strlen(buffer)); |
packet_put_string(buffer, strlen(buffer)); |
|
|
* Note that the client code is not tied to s/key or TIS. |
* Note that the client code is not tied to s/key or TIS. |
*/ |
*/ |
int |
int |
try_challenge_reponse_authentication(void) |
try_skey_authentication() |
{ |
{ |
int type, i; |
int type, i; |
int payload_len; |
int payload_len; |
u_int clen; |
unsigned int clen; |
char prompt[1024]; |
|
char *challenge, *response; |
char *challenge, *response; |
|
|
debug("Doing challenge reponse authentication."); |
debug("Doing skey authentication."); |
|
|
for (i = 0; i < options.number_of_password_prompts; i++) { |
/* request a challenge */ |
/* request a challenge */ |
packet_start(SSH_CMSG_AUTH_TIS); |
packet_start(SSH_CMSG_AUTH_TIS); |
packet_send(); |
packet_send(); |
packet_write_wait(); |
packet_write_wait(); |
|
|
|
type = packet_read(&payload_len); |
type = packet_read(&payload_len); |
if (type != SSH_SMSG_FAILURE && |
if (type != SSH_SMSG_FAILURE && |
type != SSH_SMSG_AUTH_TIS_CHALLENGE) { |
type != SSH_SMSG_AUTH_TIS_CHALLENGE) { |
packet_disconnect("Protocol error: got %d in response " |
packet_disconnect("Protocol error: got %d in response " |
"to SSH_CMSG_AUTH_TIS", type); |
"to skey-auth", type); |
} |
} |
if (type != SSH_SMSG_AUTH_TIS_CHALLENGE) { |
if (type != SSH_SMSG_AUTH_TIS_CHALLENGE) { |
debug("No challenge."); |
debug("No challenge for skey authentication."); |
return 0; |
return 0; |
} |
} |
challenge = packet_get_string(&clen); |
challenge = packet_get_string(&clen); |
packet_integrity_check(payload_len, (4 + clen), type); |
packet_integrity_check(payload_len, (4 + clen), type); |
snprintf(prompt, sizeof prompt, "%s%s", challenge, |
if (options.cipher == SSH_CIPHER_NONE) |
strchr(challenge, '\n') ? "" : "\nResponse: "); |
log("WARNING: Encryption is disabled! " |
xfree(challenge); |
"Reponse will be transmitted in clear text."); |
|
fprintf(stderr, "%s\n", challenge); |
|
xfree(challenge); |
|
fflush(stderr); |
|
for (i = 0; i < options.number_of_password_prompts; i++) { |
if (i != 0) |
if (i != 0) |
error("Permission denied, please try again."); |
error("Permission denied, please try again."); |
if (options.cipher == SSH_CIPHER_NONE) |
response = read_passphrase("Response: ", 0); |
log("WARNING: Encryption is disabled! " |
|
"Reponse will be transmitted in clear text."); |
|
response = read_passphrase(prompt, 0); |
|
if (strcmp(response, "") == 0) { |
|
xfree(response); |
|
break; |
|
} |
|
packet_start(SSH_CMSG_AUTH_TIS_RESPONSE); |
packet_start(SSH_CMSG_AUTH_TIS_RESPONSE); |
ssh_put_password(response); |
packet_put_string(response, strlen(response)); |
memset(response, 0, strlen(response)); |
memset(response, 0, strlen(response)); |
xfree(response); |
xfree(response); |
packet_send(); |
packet_send(); |
|
|
return 1; |
return 1; |
if (type != SSH_SMSG_FAILURE) |
if (type != SSH_SMSG_FAILURE) |
packet_disconnect("Protocol error: got %d in response " |
packet_disconnect("Protocol error: got %d in response " |
"to SSH_CMSG_AUTH_TIS_RESPONSE", type); |
"to skey-auth-reponse", type); |
} |
} |
/* failure */ |
/* failure */ |
return 0; |
return 0; |
|
|
error("Permission denied, please try again."); |
error("Permission denied, please try again."); |
password = read_passphrase(prompt, 0); |
password = read_passphrase(prompt, 0); |
packet_start(SSH_CMSG_AUTH_PASSWORD); |
packet_start(SSH_CMSG_AUTH_PASSWORD); |
ssh_put_password(password); |
packet_put_string(password, strlen(password)); |
memset(password, 0, strlen(password)); |
memset(password, 0, strlen(password)); |
xfree(password); |
xfree(password); |
packet_send(); |
packet_send(); |
|
|
Key k; |
Key k; |
int bits, rbits; |
int bits, rbits; |
int ssh_cipher_default = SSH_CIPHER_3DES; |
int ssh_cipher_default = SSH_CIPHER_3DES; |
u_char session_key[SSH_SESSION_KEY_LENGTH]; |
unsigned char session_key[SSH_SESSION_KEY_LENGTH]; |
u_char cookie[8]; |
unsigned char cookie[8]; |
u_int supported_ciphers; |
unsigned int supported_ciphers; |
u_int server_flags, client_flags; |
unsigned int server_flags, client_flags; |
int payload_len, clen, sum_len = 0; |
int payload_len, clen, sum_len = 0; |
u_int32_t rand = 0; |
u_int32_t rand = 0; |
|
|
|
|
packet_integrity_check(payload_len, |
packet_integrity_check(payload_len, |
8 + 4 + sum_len + 0 + 4 + 0 + 0 + 4 + 4 + 4, |
8 + 4 + sum_len + 0 + 4 + 0 + 0 + 4 + 4 + 4, |
SSH_SMSG_PUBLIC_KEY); |
SSH_SMSG_PUBLIC_KEY); |
k.type = KEY_RSA1; |
k.type = KEY_RSA; |
k.rsa = host_key; |
k.rsa = host_key; |
check_host_key(host, hostaddr, &k, |
check_host_key(host, hostaddr, &k, |
options.user_hostfile, options.system_hostfile); |
options.user_hostfile, options.system_hostfile); |
|
|
RSA_free(public_key); |
RSA_free(public_key); |
RSA_free(host_key); |
RSA_free(host_key); |
|
|
if (options.cipher == SSH_CIPHER_NOT_SET) { |
if (options.cipher == SSH_CIPHER_ILLEGAL) { |
if (cipher_mask_ssh1(1) & supported_ciphers & (1 << ssh_cipher_default)) |
|
options.cipher = ssh_cipher_default; |
|
} else if (options.cipher == SSH_CIPHER_ILLEGAL || |
|
!(cipher_mask_ssh1(1) & (1 << options.cipher))) { |
|
log("No valid SSH1 cipher, using %.100s instead.", |
log("No valid SSH1 cipher, using %.100s instead.", |
cipher_name(ssh_cipher_default)); |
cipher_name(SSH_FALLBACK_CIPHER)); |
options.cipher = ssh_cipher_default; |
options.cipher = SSH_FALLBACK_CIPHER; |
|
} else if (options.cipher == SSH_CIPHER_NOT_SET) { |
|
if (cipher_mask1() & supported_ciphers & (1 << ssh_cipher_default)) |
|
options.cipher = ssh_cipher_default; |
|
else { |
|
debug("Cipher %s not supported, using %.100s instead.", |
|
cipher_name(ssh_cipher_default), |
|
cipher_name(SSH_FALLBACK_CIPHER)); |
|
options.cipher = SSH_FALLBACK_CIPHER; |
|
} |
} |
} |
/* Check that the selected cipher is supported. */ |
/* Check that the selected cipher is supported. */ |
if (!(supported_ciphers & (1 << options.cipher))) |
if (!(supported_ciphers & (1 << options.cipher))) |
|
|
*/ |
*/ |
void |
void |
ssh_userauth( |
ssh_userauth( |
const char *local_user, |
const char* local_user, |
const char *server_user, |
const char* server_user, |
char *host, |
char *host, |
int host_key_valid, RSA *own_host_key) |
int host_key_valid, RSA *own_host_key) |
{ |
{ |
|
|
|
|
/* Try RSA authentication for each identity. */ |
/* Try RSA authentication for each identity. */ |
for (i = 0; i < options.num_identity_files; i++) |
for (i = 0; i < options.num_identity_files; i++) |
if (options.identity_keys[i] != NULL && |
if (try_rsa_authentication(options.identity_files[i])) |
options.identity_keys[i]->type == KEY_RSA1 && |
|
try_rsa_authentication(options.identity_files[i])) |
|
return; |
return; |
} |
} |
/* Try challenge response authentication if the server supports it. */ |
/* Try skey authentication if the server supports it. */ |
if ((supported_authentications & (1 << SSH_AUTH_TIS)) && |
if ((supported_authentications & (1 << SSH_AUTH_TIS)) && |
options.challenge_reponse_authentication && !options.batch_mode) { |
options.skey_authentication && !options.batch_mode) { |
if (try_challenge_reponse_authentication()) |
if (try_skey_authentication()) |
return; |
return; |
} |
} |
/* Try password authentication if the server supports it. */ |
/* Try password authentication if the server supports it. */ |
|
|
options.password_authentication && !options.batch_mode) { |
options.password_authentication && !options.batch_mode) { |
char prompt[80]; |
char prompt[80]; |
|
|
snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ", |
snprintf(prompt, sizeof(prompt), "%.30s@%.40s's password: ", |
server_user, host); |
server_user, host); |
if (try_password_authentication(prompt)) |
if (try_password_authentication(prompt)) |
return; |
return; |