version 1.31, 2000/04/29 18:11:52 |
version 1.31.2.3, 2001/03/12 15:44:15 |
|
|
* 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: Wed Mar 29 03:46:59 1995 ylo |
|
* The authentication agent program. |
* The authentication agent program. |
|
* |
|
* 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". |
|
* |
|
* SSH2 implementation, |
|
* Copyright (c) 2000 Markus Friedl. All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
*/ |
|
|
#include "includes.h" |
#include "includes.h" |
RCSID("$OpenBSD$"); |
RCSID("$OpenBSD$"); |
|
|
|
#include <openssl/evp.h> |
|
#include <openssl/md5.h> |
|
|
#include "ssh.h" |
#include "ssh.h" |
#include "rsa.h" |
#include "rsa.h" |
#include "authfd.h" |
|
#include "buffer.h" |
#include "buffer.h" |
#include "bufaux.h" |
#include "bufaux.h" |
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "packet.h" |
#include "packet.h" |
#include "getput.h" |
#include "getput.h" |
#include "mpaux.h" |
#include "mpaux.h" |
|
#include "key.h" |
|
#include "authfd.h" |
|
#include "cipher.h" |
|
#include "kex.h" |
|
#include "compat.h" |
|
#include "log.h" |
|
|
#include <openssl/md5.h> |
|
|
|
typedef struct { |
typedef struct { |
int fd; |
int fd; |
enum { |
enum { |
|
|
Buffer output; |
Buffer output; |
} SocketEntry; |
} SocketEntry; |
|
|
unsigned int sockets_alloc = 0; |
u_int sockets_alloc = 0; |
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; |
|
|
|
int prepare_select(fd_set **, fd_set **, int *); |
|
|
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_RSA1) { |
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 { |
|
u_char *blob; |
|
u_int blen; |
|
key_to_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]; |
u_char buf[32], mdbuf[16], session_id[16]; |
unsigned int response_type; |
u_int response_type; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
pub_e = BN_new(); |
key = key_new(KEY_RSA1); |
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. */ |
|
if (rsa_private_decrypt(challenge, challenge, private->rsa) <= 0) |
|
goto failure; |
|
|
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; |
u_char *blob, *data, *signature = NULL; |
|
u_int blen, dlen, slen = 0; |
|
int flags; |
|
Buffer msg; |
|
int ok = -1; |
|
|
dummy = BN_new(); |
datafellows = 0; |
n = BN_new(); |
|
|
|
/* Get the key from the packet. */ |
blob = buffer_get_string(&e->input, &blen); |
bits = buffer_get_int(&e->input); |
data = buffer_get_string(&e->input, &dlen); |
buffer_get_bignum(&e->input, dummy); |
|
buffer_get_bignum(&e->input, n); |
|
|
|
if (bits != BN_num_bits(n)) |
flags = buffer_get_int(&e->input); |
log("Warning: identity keysize mismatch: actual %d, announced %d", |
if (flags & SSH_AGENT_OLD_SIGNATURE) |
BN_num_bits(n), bits); |
datafellows = SSH_BUG_SIGBLOB; |
|
|
/* Check if we have the key. */ |
key = key_from_blob(blob, blen); |
for (i = 0; i < num_identities; i++) |
if (key != NULL) { |
if (BN_cmp(identities[i].key->n, n) == 0) { |
private = lookup_private_key(key, NULL, 2); |
|
if (private != NULL) |
|
ok = key_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); |
|
} |
|
|
|
/* shared */ |
|
void |
|
process_remove_identity(SocketEntry *e, int version) |
|
{ |
|
Key *key = NULL, *private; |
|
u_char *blob; |
|
u_int blen; |
|
u_int bits; |
|
int success = 0; |
|
|
|
switch(version){ |
|
case 1: |
|
key = key_new(KEY_RSA1); |
|
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 = 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 move |
* data from the last entry. |
* all the entries between the empty slot and the end |
|
* of the array. |
*/ |
*/ |
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 (tab->nentries < 1) |
num_identities--; |
fatal("process_remove_identity: " |
BN_clear_free(dummy); |
"internal error: tab->nentries %d", |
BN_clear_free(n); |
tab->nentries); |
|
if (idx != tab->nentries - 1) { |
/* Send success. */ |
int i; |
buffer_put_int(&e->output, 1); |
for (i = idx; i < tab->nentries - 1; i++) |
buffer_put_char(&e->output, SSH_AGENT_SUCCESS); |
tab->identities[i] = tab->identities[i+1]; |
return; |
} |
|
tab->identities[tab->nentries - 1].key = NULL; |
|
tab->identities[tab->nentries - 1].comment = NULL; |
|
tab->nentries--; |
|
success = 1; |
} |
} |
/* 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; |
u_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) |
generate_additional_parameters(RSA *rsa) |
{ |
{ |
RSA *k; |
|
int i; |
|
BIGNUM *aux; |
BIGNUM *aux; |
BN_CTX *ctx; |
BN_CTX *ctx; |
|
|
if (num_identities == 0) |
|
identities = xmalloc(sizeof(Identity)); |
|
else |
|
identities = xrealloc(identities, (num_identities + 1) * sizeof(Identity)); |
|
|
|
identities[num_identities].key = RSA_new(); |
|
k = identities[num_identities].key; |
|
buffer_get_int(&e->input); /* bits */ |
|
k->n = BN_new(); |
|
buffer_get_bignum(&e->input, k->n); |
|
k->e = BN_new(); |
|
buffer_get_bignum(&e->input, k->e); |
|
k->d = 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 */ |
/* Generate additional parameters */ |
aux = BN_new(); |
aux = BN_new(); |
ctx = BN_CTX_new(); |
ctx = BN_CTX_new(); |
|
|
BN_sub(aux, k->q, BN_value_one()); |
BN_sub(aux, rsa->q, BN_value_one()); |
k->dmq1 = BN_new(); |
BN_mod(rsa->dmq1, rsa->d, aux, ctx); |
BN_mod(k->dmq1, k->d, aux, ctx); |
|
|
|
BN_sub(aux, k->p, BN_value_one()); |
BN_sub(aux, rsa->p, BN_value_one()); |
k->dmp1 = BN_new(); |
BN_mod(rsa->dmp1, rsa->d, aux, ctx); |
BN_mod(k->dmp1, k->d, aux, ctx); |
|
|
|
BN_clear_free(aux); |
BN_clear_free(aux); |
BN_CTX_free(ctx); |
BN_CTX_free(ctx); |
|
} |
|
|
identities[num_identities].comment = buffer_get_string(&e->input, NULL); |
void |
|
process_add_identity(SocketEntry *e, int version) |
|
{ |
|
Key *k = NULL; |
|
char *type_name; |
|
char *comment; |
|
int type, success = 0; |
|
Idtab *tab = idtab_lookup(version); |
|
|
/* Check if we already have the key. */ |
switch (version) { |
for (i = 0; i < num_identities; i++) |
case 1: |
if (BN_cmp(identities[i].key->n, k->n) == 0) { |
k = key_new_private(KEY_RSA1); |
/* |
buffer_get_int(&e->input); /* ignored */ |
* We already have this key. Clear and free the new |
buffer_get_bignum(&e->input, k->rsa->n); |
* data and return success. |
buffer_get_bignum(&e->input, k->rsa->e); |
*/ |
buffer_get_bignum(&e->input, k->rsa->d); |
RSA_free(k); |
buffer_get_bignum(&e->input, k->rsa->iqmp); |
xfree(identities[num_identities].comment); |
|
|
|
/* Send success. */ |
/* SSH and SSL have p and q swapped */ |
buffer_put_int(&e->output, 1); |
buffer_get_bignum(&e->input, k->rsa->q); /* p */ |
buffer_put_char(&e->output, SSH_AGENT_SUCCESS); |
buffer_get_bignum(&e->input, k->rsa->p); /* q */ |
return; |
|
} |
|
/* Increment the number of identities. */ |
|
num_identities++; |
|
|
|
/* Send a success message. */ |
/* Generate additional parameters */ |
|
generate_additional_parameters(k->rsa); |
|
break; |
|
case 2: |
|
type_name = buffer_get_string(&e->input, NULL); |
|
type = key_type_from_name(type_name); |
|
xfree(type_name); |
|
switch(type) { |
|
case KEY_DSA: |
|
k = key_new_private(type); |
|
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; |
|
case KEY_RSA: |
|
k = key_new_private(type); |
|
buffer_get_bignum2(&e->input, k->rsa->n); |
|
buffer_get_bignum2(&e->input, k->rsa->e); |
|
buffer_get_bignum2(&e->input, k->rsa->d); |
|
buffer_get_bignum2(&e->input, k->rsa->iqmp); |
|
buffer_get_bignum2(&e->input, k->rsa->p); |
|
buffer_get_bignum2(&e->input, k->rsa->q); |
|
|
|
/* Generate additional parameters */ |
|
generate_additional_parameters(k->rsa); |
|
break; |
|
default: |
|
buffer_clear(&e->input); |
|
goto send; |
|
} |
|
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) |
{ |
{ |
unsigned int msg_len; |
u_int msg_len; |
unsigned int type; |
u_int type; |
unsigned char *cp; |
u_char *cp; |
if (buffer_len(&e->input) < 5) |
if (buffer_len(&e->input) < 5) |
return; /* Incomplete message. */ |
return; /* Incomplete message. */ |
cp = (unsigned char *) buffer_ptr(&e->input); |
cp = (u_char *) buffer_ptr(&e->input); |
msg_len = GET_32BIT(cp); |
msg_len = GET_32BIT(cp); |
if (msg_len > 256 * 1024) { |
if (msg_len > 256 * 1024) { |
shutdown(e->fd, SHUT_RDWR); |
shutdown(e->fd, SHUT_RDWR); |
|
|
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); |
|
|
void |
void |
new_socket(int type, int fd) |
new_socket(int type, int fd) |
{ |
{ |
unsigned int i, old_alloc; |
u_int i, old_alloc; |
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) |
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) |
error("fcntl O_NONBLOCK: %s", strerror(errno)); |
error("fcntl O_NONBLOCK: %s", strerror(errno)); |
|
|
|
|
buffer_init(&sockets[old_alloc].output); |
buffer_init(&sockets[old_alloc].output); |
} |
} |
|
|
void |
int |
prepare_select(fd_set *readset, fd_set *writeset) |
prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl) |
{ |
{ |
unsigned int i; |
u_int i, sz; |
for (i = 0; i < sockets_alloc; i++) |
int n = 0; |
|
|
|
for (i = 0; i < sockets_alloc; i++) { |
switch (sockets[i].type) { |
switch (sockets[i].type) { |
case AUTH_SOCKET: |
case AUTH_SOCKET: |
case AUTH_CONNECTION: |
case AUTH_CONNECTION: |
FD_SET(sockets[i].fd, readset); |
n = MAX(n, sockets[i].fd); |
if (buffer_len(&sockets[i].output) > 0) |
|
FD_SET(sockets[i].fd, writeset); |
|
break; |
break; |
case AUTH_UNUSED: |
case AUTH_UNUSED: |
break; |
break; |
|
|
fatal("Unknown socket type %d", sockets[i].type); |
fatal("Unknown socket type %d", sockets[i].type); |
break; |
break; |
} |
} |
|
} |
|
|
|
sz = howmany(n+1, NFDBITS) * sizeof(fd_mask); |
|
if (*fdrp == NULL || n > *fdl) { |
|
if (*fdrp) |
|
free(*fdrp); |
|
if (*fdwp) |
|
free(*fdwp); |
|
*fdrp = xmalloc(sz); |
|
*fdwp = xmalloc(sz); |
|
*fdl = n; |
|
} |
|
memset(*fdrp, 0, sz); |
|
memset(*fdwp, 0, sz); |
|
|
|
for (i = 0; i < sockets_alloc; i++) { |
|
switch (sockets[i].type) { |
|
case AUTH_SOCKET: |
|
case AUTH_CONNECTION: |
|
FD_SET(sockets[i].fd, *fdrp); |
|
if (buffer_len(&sockets[i].output) > 0) |
|
FD_SET(sockets[i].fd, *fdwp); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
return (1); |
} |
} |
|
|
void |
void |
after_select(fd_set *readset, fd_set *writeset) |
after_select(fd_set *readset, fd_set *writeset) |
{ |
{ |
unsigned int i; |
u_int i; |
int len, sock; |
int len, sock; |
socklen_t slen; |
socklen_t slen; |
char buf[1024]; |
char buf[1024]; |
|
|
case AUTH_SOCKET: |
case AUTH_SOCKET: |
if (FD_ISSET(sockets[i].fd, readset)) { |
if (FD_ISSET(sockets[i].fd, readset)) { |
slen = sizeof(sunaddr); |
slen = sizeof(sunaddr); |
sock = accept(sockets[i].fd, (struct sockaddr *) & sunaddr, &slen); |
sock = accept(sockets[i].fd, |
|
(struct sockaddr *) &sunaddr, &slen); |
if (sock < 0) { |
if (sock < 0) { |
perror("accept from AUTH_SOCKET"); |
perror("accept from AUTH_SOCKET"); |
break; |
break; |
|
|
case AUTH_CONNECTION: |
case AUTH_CONNECTION: |
if (buffer_len(&sockets[i].output) > 0 && |
if (buffer_len(&sockets[i].output) > 0 && |
FD_ISSET(sockets[i].fd, writeset)) { |
FD_ISSET(sockets[i].fd, writeset)) { |
len = write(sockets[i].fd, buffer_ptr(&sockets[i].output), |
len = write(sockets[i].fd, |
buffer_len(&sockets[i].output)); |
buffer_ptr(&sockets[i].output), |
|
buffer_len(&sockets[i].output)); |
if (len <= 0) { |
if (len <= 0) { |
shutdown(sockets[i].fd, SHUT_RDWR); |
shutdown(sockets[i].fd, SHUT_RDWR); |
close(sockets[i].fd); |
close(sockets[i].fd); |
|
|
void |
void |
check_parent_exists(int sig) |
check_parent_exists(int sig) |
{ |
{ |
|
int save_errno = errno; |
|
|
if (parent_pid != -1 && kill(parent_pid, 0) < 0) { |
if (parent_pid != -1 && kill(parent_pid, 0) < 0) { |
/* printf("Parent has died - Authentication agent exiting.\n"); */ |
/* printf("Parent has died - Authentication agent exiting.\n"); */ |
exit(1); |
exit(1); |
} |
} |
signal(SIGALRM, check_parent_exists); |
signal(SIGALRM, check_parent_exists); |
alarm(10); |
alarm(10); |
|
errno = save_errno; |
} |
} |
|
|
void |
void |
cleanup_socket(void) |
cleanup_socket(void) |
{ |
{ |
remove(socket_name); |
if (socket_name[0]) |
rmdir(socket_dir); |
unlink(socket_name); |
|
if (socket_dir[0]) |
|
rmdir(socket_dir); |
} |
} |
|
|
void |
void |
|
|
} |
} |
|
|
void |
void |
usage() |
cleanup_handler(int sig) |
{ |
{ |
|
cleanup_socket(); |
|
_exit(2); |
|
} |
|
|
|
void |
|
usage(void) |
|
{ |
fprintf(stderr, "ssh-agent version %s\n", SSH_VERSION); |
fprintf(stderr, "ssh-agent version %s\n", SSH_VERSION); |
fprintf(stderr, "Usage: %s [-c | -s] [-k] [command {args...]]\n", |
fprintf(stderr, "Usage: %s [-c | -s] [-k] [command {args...]]\n", |
__progname); |
__progname); |
exit(1); |
exit(1); |
} |
} |
|
|
int |
int |
main(int ac, char **av) |
main(int ac, char **av) |
{ |
{ |
fd_set readset, writeset; |
|
int sock, c_flag = 0, k_flag = 0, s_flag = 0, ch; |
int sock, c_flag = 0, k_flag = 0, s_flag = 0, ch; |
struct sockaddr_un sunaddr; |
struct sockaddr_un sunaddr; |
|
struct rlimit rlim; |
pid_t pid; |
pid_t pid; |
char *shell, *format, *pidstr, pidstrbuf[1 + 3 * sizeof pid]; |
char *shell, *format, *pidstr, pidstrbuf[1 + 3 * sizeof pid]; |
|
extern int optind; |
|
fd_set *readsetp = NULL, *writesetp = NULL; |
|
|
/* check if RSA support exists */ |
|
if (rsa_alive() == 0) { |
|
fprintf(stderr, |
|
"%s: no RSA support in libssl and libcrypto. See ssl(8).\n", |
|
__progname); |
|
exit(1); |
|
} |
|
while ((ch = getopt(ac, av, "cks")) != -1) { |
while ((ch = getopt(ac, av, "cks")) != -1) { |
switch (ch) { |
switch (ch) { |
case 'c': |
case 'c': |
|
|
pidstr = getenv(SSH_AGENTPID_ENV_NAME); |
pidstr = getenv(SSH_AGENTPID_ENV_NAME); |
if (pidstr == NULL) { |
if (pidstr == NULL) { |
fprintf(stderr, "%s not set, cannot kill agent\n", |
fprintf(stderr, "%s not set, cannot kill agent\n", |
SSH_AGENTPID_ENV_NAME); |
SSH_AGENTPID_ENV_NAME); |
exit(1); |
exit(1); |
} |
} |
pid = atoi(pidstr); |
pid = atoi(pidstr); |
if (pid < 1) { /* XXX PID_MAX check too */ |
if (pid < 1) { |
/* Yes, PID_MAX check please */ |
|
fprintf(stderr, "%s=\"%s\", which is not a good PID\n", |
fprintf(stderr, "%s=\"%s\", which is not a good PID\n", |
SSH_AGENTPID_ENV_NAME, pidstr); |
SSH_AGENTPID_ENV_NAME, pidstr); |
exit(1); |
exit(1); |
} |
} |
if (kill(pid, SIGTERM) == -1) { |
if (kill(pid, SIGTERM) == -1) { |
|
|
exit(1); |
exit(1); |
} |
} |
snprintf(socket_name, sizeof socket_name, "%s/agent.%d", socket_dir, |
snprintf(socket_name, sizeof socket_name, "%s/agent.%d", socket_dir, |
parent_pid); |
parent_pid); |
|
|
/* |
/* |
* Create socket early so it will exist before command gets run from |
* Create socket early so it will exist before command gets run from |
|
|
perror("listen"); |
perror("listen"); |
cleanup_exit(1); |
cleanup_exit(1); |
} |
} |
|
|
/* |
/* |
* Fork, and have the parent execute the command, if any, or present |
* Fork, and have the parent execute the command, if any, or present |
* the socket data. The child continues as the authentication agent. |
* the socket data. The child continues as the authentication agent. |
|
|
if (ac == 0) { |
if (ac == 0) { |
format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n"; |
format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n"; |
printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, |
printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, |
SSH_AUTHSOCKET_ENV_NAME); |
SSH_AUTHSOCKET_ENV_NAME); |
printf(format, SSH_AGENTPID_ENV_NAME, pidstrbuf, |
printf(format, SSH_AGENTPID_ENV_NAME, pidstrbuf, |
SSH_AGENTPID_ENV_NAME); |
SSH_AGENTPID_ENV_NAME); |
printf("echo Agent pid %d;\n", pid); |
printf("echo Agent pid %d;\n", pid); |
exit(0); |
exit(0); |
} |
} |
setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1); |
if (setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1) == -1 || |
setenv(SSH_AGENTPID_ENV_NAME, pidstrbuf, 1); |
setenv(SSH_AGENTPID_ENV_NAME, pidstrbuf, 1) == -1) { |
|
perror("setenv"); |
|
exit(1); |
|
} |
execvp(av[0], av); |
execvp(av[0], av); |
perror(av[0]); |
perror(av[0]); |
exit(1); |
exit(1); |
|
|
close(1); |
close(1); |
close(2); |
close(2); |
|
|
|
/* deny core dumps, since memory contains unencrypted private keys */ |
|
rlim.rlim_cur = rlim.rlim_max = 0; |
|
if (setrlimit(RLIMIT_CORE, &rlim) < 0) { |
|
perror("setrlimit rlimit_core failed"); |
|
cleanup_exit(1); |
|
} |
if (setsid() == -1) { |
if (setsid() == -1) { |
perror("setsid"); |
perror("setsid"); |
cleanup_exit(1); |
cleanup_exit(1); |
|
|
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_handler); |
signal(SIGTERM, cleanup_exit); |
signal(SIGTERM, cleanup_handler); |
while (1) { |
while (1) { |
FD_ZERO(&readset); |
prepare_select(&readsetp, &writesetp, &max_fd); |
FD_ZERO(&writeset); |
if (select(max_fd + 1, readsetp, writesetp, NULL, NULL) < 0) { |
prepare_select(&readset, &writeset); |
|
if (select(max_fd + 1, &readset, &writeset, NULL, NULL) < 0) { |
|
if (errno == EINTR) |
if (errno == EINTR) |
continue; |
continue; |
exit(1); |
exit(1); |
} |
} |
after_select(&readset, &writeset); |
after_select(readsetp, writesetp); |
} |
} |
/* NOTREACHED */ |
/* NOTREACHED */ |
} |
} |