version 1.32, 2000/07/16 08:27:21 |
version 1.33, 2000/08/19 21:34:43 |
|
|
* All rights reserved |
* All rights reserved |
* Created: Wed Mar 29 03:46:59 1995 ylo |
* Created: Wed Mar 29 03:46:59 1995 ylo |
* The authentication agent program. |
* The authentication agent program. |
|
* |
|
* SSH2 implementation, |
|
* Copyright (c) 2000 Markus Friedl. All rights reserved. |
*/ |
*/ |
|
|
#include "includes.h" |
#include "includes.h" |
|
|
#include "getput.h" |
#include "getput.h" |
#include "mpaux.h" |
#include "mpaux.h" |
|
|
|
#include <openssl/evp.h> |
#include <openssl/md5.h> |
#include <openssl/md5.h> |
#include <openssl/dsa.h> |
#include <openssl/dsa.h> |
#include <openssl/rsa.h> |
#include <openssl/rsa.h> |
#include "key.h" |
#include "key.h" |
#include "authfd.h" |
#include "authfd.h" |
|
#include "dsa.h" |
|
#include "kex.h" |
|
|
typedef struct { |
typedef struct { |
int fd; |
int fd; |
|
|
SocketEntry *sockets = NULL; |
SocketEntry *sockets = NULL; |
|
|
typedef struct { |
typedef struct { |
RSA *key; |
Key *key; |
char *comment; |
char *comment; |
} Identity; |
} Identity; |
|
|
unsigned int num_identities = 0; |
typedef struct { |
Identity *identities = NULL; |
int nentries; |
|
Identity *identities; |
|
} Idtab; |
|
|
|
/* private key table, one per protocol version */ |
|
Idtab idtable[3]; |
|
|
int max_fd = 0; |
int max_fd = 0; |
|
|
/* pid of shell == parent of agent */ |
/* pid of shell == parent of agent */ |
|
|
extern char *__progname; |
extern char *__progname; |
|
|
void |
void |
process_request_identity(SocketEntry *e) |
idtab_init(void) |
{ |
{ |
|
int i; |
|
for (i = 0; i <=2; i++){ |
|
idtable[i].identities = NULL; |
|
idtable[i].nentries = 0; |
|
} |
|
} |
|
|
|
/* return private key table for requested protocol version */ |
|
Idtab * |
|
idtab_lookup(int version) |
|
{ |
|
if (version < 1 || version > 2) |
|
fatal("internal error, bad protocol version %d", version); |
|
return &idtable[version]; |
|
} |
|
|
|
/* return matching private key for given public key */ |
|
Key * |
|
lookup_private_key(Key *key, int *idx, int version) |
|
{ |
|
int i; |
|
Idtab *tab = idtab_lookup(version); |
|
for (i = 0; i < tab->nentries; i++) { |
|
if (key_equal(key, tab->identities[i].key)) { |
|
if (idx != NULL) |
|
*idx = i; |
|
return tab->identities[i].key; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
/* send list of supported public keys to 'client' */ |
|
void |
|
process_request_identities(SocketEntry *e, int version) |
|
{ |
|
Idtab *tab = idtab_lookup(version); |
Buffer msg; |
Buffer msg; |
int i; |
int i; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
buffer_put_char(&msg, SSH_AGENT_RSA_IDENTITIES_ANSWER); |
buffer_put_char(&msg, (version == 1) ? |
buffer_put_int(&msg, num_identities); |
SSH_AGENT_RSA_IDENTITIES_ANSWER : SSH2_AGENT_IDENTITIES_ANSWER); |
for (i = 0; i < num_identities; i++) { |
buffer_put_int(&msg, tab->nentries); |
buffer_put_int(&msg, BN_num_bits(identities[i].key->n)); |
for (i = 0; i < tab->nentries; i++) { |
buffer_put_bignum(&msg, identities[i].key->e); |
Identity *id = &tab->identities[i]; |
buffer_put_bignum(&msg, identities[i].key->n); |
if (id->key->type == KEY_RSA) { |
buffer_put_string(&msg, identities[i].comment, |
buffer_put_int(&msg, BN_num_bits(id->key->rsa->n)); |
strlen(identities[i].comment)); |
buffer_put_bignum(&msg, id->key->rsa->e); |
|
buffer_put_bignum(&msg, id->key->rsa->n); |
|
} else { |
|
unsigned char *blob; |
|
unsigned int blen; |
|
dsa_make_key_blob(id->key, &blob, &blen); |
|
buffer_put_string(&msg, blob, blen); |
|
xfree(blob); |
|
} |
|
buffer_put_cstring(&msg, id->comment); |
} |
} |
buffer_put_int(&e->output, buffer_len(&msg)); |
buffer_put_int(&e->output, buffer_len(&msg)); |
buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg)); |
buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg)); |
buffer_free(&msg); |
buffer_free(&msg); |
} |
} |
|
|
|
/* ssh1 only */ |
void |
void |
process_authentication_challenge(SocketEntry *e) |
process_authentication_challenge1(SocketEntry *e) |
{ |
{ |
int i, pub_bits, len; |
Key *key, *private; |
BIGNUM *pub_e, *pub_n, *challenge; |
BIGNUM *challenge; |
|
int i, len; |
Buffer msg; |
Buffer msg; |
MD5_CTX md; |
MD5_CTX md; |
unsigned char buf[32], mdbuf[16], session_id[16]; |
unsigned char buf[32], mdbuf[16], session_id[16]; |
unsigned int response_type; |
unsigned int response_type; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
pub_e = BN_new(); |
key = key_new(KEY_RSA); |
pub_n = BN_new(); |
|
challenge = BN_new(); |
challenge = BN_new(); |
pub_bits = buffer_get_int(&e->input); |
|
buffer_get_bignum(&e->input, pub_e); |
buffer_get_int(&e->input); /* ignored */ |
buffer_get_bignum(&e->input, pub_n); |
buffer_get_bignum(&e->input, key->rsa->e); |
|
buffer_get_bignum(&e->input, key->rsa->n); |
buffer_get_bignum(&e->input, challenge); |
buffer_get_bignum(&e->input, challenge); |
if (buffer_len(&e->input) == 0) { |
|
/* Compatibility code for old servers. */ |
|
memset(session_id, 0, 16); |
|
response_type = 0; |
|
} else { |
|
/* New code. */ |
|
buffer_get(&e->input, (char *) session_id, 16); |
|
response_type = buffer_get_int(&e->input); |
|
} |
|
for (i = 0; i < num_identities; i++) |
|
if (pub_bits == BN_num_bits(identities[i].key->n) && |
|
BN_cmp(pub_e, identities[i].key->e) == 0 && |
|
BN_cmp(pub_n, identities[i].key->n) == 0) { |
|
/* Decrypt the challenge using the private key. */ |
|
rsa_private_decrypt(challenge, challenge, identities[i].key); |
|
|
|
/* Compute the desired response. */ |
/* Only protocol 1.1 is supported */ |
switch (response_type) { |
if (buffer_len(&e->input) == 0) |
case 0:/* As of protocol 1.0 */ |
goto failure; |
/* This response type is no longer supported. */ |
buffer_get(&e->input, (char *) session_id, 16); |
log("Compatibility with ssh protocol 1.0 no longer supported."); |
response_type = buffer_get_int(&e->input); |
buffer_put_char(&msg, SSH_AGENT_FAILURE); |
if (response_type != 1) |
goto send; |
goto failure; |
|
|
case 1:/* As of protocol 1.1 */ |
private = lookup_private_key(key, NULL, 1); |
/* The response is MD5 of decrypted challenge plus session id. */ |
if (private != NULL) { |
len = BN_num_bytes(challenge); |
/* Decrypt the challenge using the private key. */ |
|
rsa_private_decrypt(challenge, challenge, private->rsa); |
|
|
if (len <= 0 || len > 32) { |
/* The response is MD5 of decrypted challenge plus session id. */ |
fatal("process_authentication_challenge: " |
len = BN_num_bytes(challenge); |
"bad challenge length %d", len); |
if (len <= 0 || len > 32) { |
} |
log("process_authentication_challenge: bad challenge length %d", len); |
memset(buf, 0, 32); |
goto failure; |
BN_bn2bin(challenge, buf + 32 - len); |
} |
MD5_Init(&md); |
memset(buf, 0, 32); |
MD5_Update(&md, buf, 32); |
BN_bn2bin(challenge, buf + 32 - len); |
MD5_Update(&md, session_id, 16); |
MD5_Init(&md); |
MD5_Final(mdbuf, &md); |
MD5_Update(&md, buf, 32); |
break; |
MD5_Update(&md, session_id, 16); |
|
MD5_Final(mdbuf, &md); |
|
|
default: |
/* Send the response. */ |
fatal("process_authentication_challenge: bad response_type %d", |
buffer_put_char(&msg, SSH_AGENT_RSA_RESPONSE); |
response_type); |
for (i = 0; i < 16; i++) |
break; |
buffer_put_char(&msg, mdbuf[i]); |
} |
goto send; |
|
} |
|
|
/* Send the response. */ |
failure: |
buffer_put_char(&msg, SSH_AGENT_RSA_RESPONSE); |
/* Unknown identity or protocol error. Send failure. */ |
for (i = 0; i < 16; i++) |
|
buffer_put_char(&msg, mdbuf[i]); |
|
|
|
goto send; |
|
} |
|
/* Unknown identity. Send failure. */ |
|
buffer_put_char(&msg, SSH_AGENT_FAILURE); |
buffer_put_char(&msg, SSH_AGENT_FAILURE); |
send: |
send: |
buffer_put_int(&e->output, buffer_len(&msg)); |
buffer_put_int(&e->output, buffer_len(&msg)); |
buffer_append(&e->output, buffer_ptr(&msg), |
buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg)); |
buffer_len(&msg)); |
key_free(key); |
buffer_free(&msg); |
|
BN_clear_free(pub_e); |
|
BN_clear_free(pub_n); |
|
BN_clear_free(challenge); |
BN_clear_free(challenge); |
|
buffer_free(&msg); |
} |
} |
|
|
|
/* ssh2 only */ |
void |
void |
process_remove_identity(SocketEntry *e) |
process_sign_request2(SocketEntry *e) |
{ |
{ |
unsigned int bits; |
extern int datafellows; |
unsigned int i; |
Key *key, *private; |
BIGNUM *dummy, *n; |
unsigned char *blob, *data, *signature = NULL; |
|
unsigned int blen, dlen, slen = 0; |
|
Buffer msg; |
|
int ok = -1; |
|
|
dummy = BN_new(); |
datafellows = 0; |
n = BN_new(); |
|
|
blob = buffer_get_string(&e->input, &blen); |
|
data = buffer_get_string(&e->input, &dlen); |
|
|
/* Get the key from the packet. */ |
key = dsa_key_from_blob(blob, blen); |
bits = buffer_get_int(&e->input); |
if (key != NULL) { |
buffer_get_bignum(&e->input, dummy); |
private = lookup_private_key(key, NULL, 2); |
buffer_get_bignum(&e->input, n); |
if (private != NULL) |
|
ok = dsa_sign(private, &signature, &slen, data, dlen); |
|
} |
|
key_free(key); |
|
buffer_init(&msg); |
|
if (ok == 0) { |
|
buffer_put_char(&msg, SSH2_AGENT_SIGN_RESPONSE); |
|
buffer_put_string(&msg, signature, slen); |
|
} else { |
|
buffer_put_char(&msg, SSH_AGENT_FAILURE); |
|
} |
|
buffer_put_int(&e->output, buffer_len(&msg)); |
|
buffer_append(&e->output, buffer_ptr(&msg), |
|
buffer_len(&msg)); |
|
buffer_free(&msg); |
|
xfree(data); |
|
xfree(blob); |
|
if (signature != NULL) |
|
xfree(signature); |
|
} |
|
|
if (bits != BN_num_bits(n)) |
/* shared */ |
log("Warning: identity keysize mismatch: actual %d, announced %d", |
void |
BN_num_bits(n), bits); |
process_remove_identity(SocketEntry *e, int version) |
|
{ |
|
Key *key = NULL, *private; |
|
unsigned char *blob; |
|
unsigned int blen; |
|
unsigned int bits; |
|
int success = 0; |
|
|
/* Check if we have the key. */ |
switch(version){ |
for (i = 0; i < num_identities; i++) |
case 1: |
if (BN_cmp(identities[i].key->n, n) == 0) { |
key = key_new(KEY_RSA); |
|
bits = buffer_get_int(&e->input); |
|
buffer_get_bignum(&e->input, key->rsa->e); |
|
buffer_get_bignum(&e->input, key->rsa->n); |
|
|
|
if (bits != key_size(key)) |
|
log("Warning: identity keysize mismatch: actual %d, announced %d", |
|
key_size(key), bits); |
|
break; |
|
case 2: |
|
blob = buffer_get_string(&e->input, &blen); |
|
key = dsa_key_from_blob(blob, blen); |
|
xfree(blob); |
|
break; |
|
} |
|
if (key != NULL) { |
|
int idx; |
|
private = lookup_private_key(key, &idx, version); |
|
if (private != NULL) { |
/* |
/* |
* We have this key. Free the old key. Since we |
* We have this key. Free the old key. Since we |
* don\'t want to leave empty slots in the middle of |
* don\'t want to leave empty slots in the middle of |
* the array, we actually free the key there and copy |
* the array, we actually free the key there and copy |
* data from the last entry. |
* data from the last entry. |
*/ |
*/ |
RSA_free(identities[i].key); |
Idtab *tab = idtab_lookup(version); |
xfree(identities[i].comment); |
key_free(tab->identities[idx].key); |
if (i < num_identities - 1) |
xfree(tab->identities[idx].comment); |
identities[i] = identities[num_identities - 1]; |
if (idx != tab->nentries) |
num_identities--; |
tab->identities[idx] = tab->identities[tab->nentries]; |
BN_clear_free(dummy); |
tab->nentries--; |
BN_clear_free(n); |
success = 1; |
|
|
/* Send success. */ |
|
buffer_put_int(&e->output, 1); |
|
buffer_put_char(&e->output, SSH_AGENT_SUCCESS); |
|
return; |
|
} |
} |
/* We did not have the key. */ |
key_free(key); |
BN_clear(dummy); |
} |
BN_clear(n); |
|
|
|
/* Send failure. */ |
|
buffer_put_int(&e->output, 1); |
buffer_put_int(&e->output, 1); |
buffer_put_char(&e->output, SSH_AGENT_FAILURE); |
buffer_put_char(&e->output, |
|
success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE); |
} |
} |
|
|
/* |
|
* Removes all identities from the agent. |
|
*/ |
|
void |
void |
process_remove_all_identities(SocketEntry *e) |
process_remove_all_identities(SocketEntry *e, int version) |
{ |
{ |
unsigned int i; |
unsigned int i; |
|
Idtab *tab = idtab_lookup(version); |
|
|
/* Loop over all identities and clear the keys. */ |
/* Loop over all identities and clear the keys. */ |
for (i = 0; i < num_identities; i++) { |
for (i = 0; i < tab->nentries; i++) { |
RSA_free(identities[i].key); |
key_free(tab->identities[i].key); |
xfree(identities[i].comment); |
xfree(tab->identities[i].comment); |
} |
} |
|
|
/* Mark that there are no identities. */ |
/* Mark that there are no identities. */ |
num_identities = 0; |
tab->nentries = 0; |
|
|
/* Send success. */ |
/* Send success. */ |
buffer_put_int(&e->output, 1); |
buffer_put_int(&e->output, 1); |
|
|
return; |
return; |
} |
} |
|
|
/* |
|
* Adds an identity to the agent. |
|
*/ |
|
void |
void |
process_add_identity(SocketEntry *e) |
process_add_identity(SocketEntry *e, int version) |
{ |
{ |
RSA *k; |
Key *k = NULL; |
int i; |
RSA *rsa; |
BIGNUM *aux; |
BIGNUM *aux; |
BN_CTX *ctx; |
BN_CTX *ctx; |
|
char *type; |
|
char *comment; |
|
int success = 0; |
|
Idtab *tab = idtab_lookup(version); |
|
|
if (num_identities == 0) |
switch (version) { |
identities = xmalloc(sizeof(Identity)); |
case 1: |
else |
k = key_new(KEY_RSA); |
identities = xrealloc(identities, (num_identities + 1) * sizeof(Identity)); |
rsa = k->rsa; |
|
|
identities[num_identities].key = RSA_new(); |
/* allocate mem for private key */ |
k = identities[num_identities].key; |
/* XXX rsa->n and rsa->e are already allocated */ |
buffer_get_int(&e->input); /* bits */ |
rsa->d = BN_new(); |
k->n = BN_new(); |
rsa->iqmp = BN_new(); |
buffer_get_bignum(&e->input, k->n); |
rsa->q = BN_new(); |
k->e = BN_new(); |
rsa->p = BN_new(); |
buffer_get_bignum(&e->input, k->e); |
rsa->dmq1 = BN_new(); |
k->d = BN_new(); |
rsa->dmp1 = BN_new(); |
buffer_get_bignum(&e->input, k->d); |
|
k->iqmp = BN_new(); |
|
buffer_get_bignum(&e->input, k->iqmp); |
|
/* SSH and SSL have p and q swapped */ |
|
k->q = BN_new(); |
|
buffer_get_bignum(&e->input, k->q); /* p */ |
|
k->p = BN_new(); |
|
buffer_get_bignum(&e->input, k->p); /* q */ |
|
|
|
/* Generate additional parameters */ |
buffer_get_int(&e->input); /* ignored */ |
aux = BN_new(); |
|
ctx = BN_CTX_new(); |
|
|
|
BN_sub(aux, k->q, BN_value_one()); |
buffer_get_bignum(&e->input, rsa->n); |
k->dmq1 = BN_new(); |
buffer_get_bignum(&e->input, rsa->e); |
BN_mod(k->dmq1, k->d, aux, ctx); |
buffer_get_bignum(&e->input, rsa->d); |
|
buffer_get_bignum(&e->input, rsa->iqmp); |
|
|
BN_sub(aux, k->p, BN_value_one()); |
/* SSH and SSL have p and q swapped */ |
k->dmp1 = BN_new(); |
buffer_get_bignum(&e->input, rsa->q); /* p */ |
BN_mod(k->dmp1, k->d, aux, ctx); |
buffer_get_bignum(&e->input, rsa->p); /* q */ |
|
|
BN_clear_free(aux); |
/* Generate additional parameters */ |
BN_CTX_free(ctx); |
aux = BN_new(); |
|
ctx = BN_CTX_new(); |
|
|
identities[num_identities].comment = buffer_get_string(&e->input, NULL); |
BN_sub(aux, rsa->q, BN_value_one()); |
|
BN_mod(rsa->dmq1, rsa->d, aux, ctx); |
|
|
/* Check if we already have the key. */ |
BN_sub(aux, rsa->p, BN_value_one()); |
for (i = 0; i < num_identities; i++) |
BN_mod(rsa->dmp1, rsa->d, aux, ctx); |
if (BN_cmp(identities[i].key->n, k->n) == 0) { |
|
/* |
|
* We already have this key. Clear and free the new |
|
* data and return success. |
|
*/ |
|
RSA_free(k); |
|
xfree(identities[num_identities].comment); |
|
|
|
/* Send success. */ |
BN_clear_free(aux); |
buffer_put_int(&e->output, 1); |
BN_CTX_free(ctx); |
buffer_put_char(&e->output, SSH_AGENT_SUCCESS); |
|
return; |
break; |
|
case 2: |
|
type = buffer_get_string(&e->input, NULL); |
|
if (strcmp(type, KEX_DSS)) { |
|
buffer_clear(&e->input); |
|
xfree(type); |
|
goto send; |
} |
} |
/* Increment the number of identities. */ |
xfree(type); |
num_identities++; |
|
|
|
/* Send a success message. */ |
k = key_new(KEY_DSA); |
|
|
|
/* allocate mem for private key */ |
|
k->dsa->priv_key = BN_new(); |
|
|
|
buffer_get_bignum2(&e->input, k->dsa->p); |
|
buffer_get_bignum2(&e->input, k->dsa->q); |
|
buffer_get_bignum2(&e->input, k->dsa->g); |
|
buffer_get_bignum2(&e->input, k->dsa->pub_key); |
|
buffer_get_bignum2(&e->input, k->dsa->priv_key); |
|
|
|
break; |
|
} |
|
|
|
comment = buffer_get_string(&e->input, NULL); |
|
if (k == NULL) { |
|
xfree(comment); |
|
goto send; |
|
} |
|
success = 1; |
|
if (lookup_private_key(k, NULL, version) == NULL) { |
|
if (tab->nentries == 0) |
|
tab->identities = xmalloc(sizeof(Identity)); |
|
else |
|
tab->identities = xrealloc(tab->identities, |
|
(tab->nentries + 1) * sizeof(Identity)); |
|
tab->identities[tab->nentries].key = k; |
|
tab->identities[tab->nentries].comment = comment; |
|
/* Increment the number of identities. */ |
|
tab->nentries++; |
|
} else { |
|
key_free(k); |
|
xfree(comment); |
|
} |
|
send: |
buffer_put_int(&e->output, 1); |
buffer_put_int(&e->output, 1); |
buffer_put_char(&e->output, SSH_AGENT_SUCCESS); |
buffer_put_char(&e->output, |
|
success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE); |
} |
} |
|
|
|
/* dispatch incoming messages */ |
|
|
void |
void |
process_message(SocketEntry *e) |
process_message(SocketEntry *e) |
{ |
{ |
|
|
type = buffer_get_char(&e->input); |
type = buffer_get_char(&e->input); |
|
|
switch (type) { |
switch (type) { |
case SSH_AGENTC_REQUEST_RSA_IDENTITIES: |
/* ssh1 */ |
process_request_identity(e); |
|
break; |
|
case SSH_AGENTC_RSA_CHALLENGE: |
case SSH_AGENTC_RSA_CHALLENGE: |
process_authentication_challenge(e); |
process_authentication_challenge1(e); |
break; |
break; |
|
case SSH_AGENTC_REQUEST_RSA_IDENTITIES: |
|
process_request_identities(e, 1); |
|
break; |
case SSH_AGENTC_ADD_RSA_IDENTITY: |
case SSH_AGENTC_ADD_RSA_IDENTITY: |
process_add_identity(e); |
process_add_identity(e, 1); |
break; |
break; |
case SSH_AGENTC_REMOVE_RSA_IDENTITY: |
case SSH_AGENTC_REMOVE_RSA_IDENTITY: |
process_remove_identity(e); |
process_remove_identity(e, 1); |
break; |
break; |
case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: |
case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: |
process_remove_all_identities(e); |
process_remove_all_identities(e, 1); |
break; |
break; |
|
/* ssh2 */ |
|
case SSH2_AGENTC_SIGN_REQUEST: |
|
process_sign_request2(e); |
|
break; |
|
case SSH2_AGENTC_REQUEST_IDENTITIES: |
|
process_request_identities(e, 2); |
|
break; |
|
case SSH2_AGENTC_ADD_IDENTITY: |
|
process_add_identity(e, 2); |
|
break; |
|
case SSH2_AGENTC_REMOVE_IDENTITY: |
|
process_remove_identity(e, 2); |
|
break; |
|
case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: |
|
process_remove_all_identities(e, 2); |
|
break; |
default: |
default: |
/* Unknown message. Respond with failure. */ |
/* Unknown message. Respond with failure. */ |
error("Unknown message %d", type); |
error("Unknown message %d", type); |
|
|
signal(SIGALRM, check_parent_exists); |
signal(SIGALRM, check_parent_exists); |
alarm(10); |
alarm(10); |
} |
} |
|
idtab_init(); |
signal(SIGINT, SIG_IGN); |
signal(SIGINT, SIG_IGN); |
signal(SIGPIPE, SIG_IGN); |
signal(SIGPIPE, SIG_IGN); |
signal(SIGHUP, cleanup_exit); |
signal(SIGHUP, cleanup_exit); |