version 1.220, 2017/04/30 23:18:44 |
version 1.221, 2017/04/30 23:29:10 |
|
|
u_int confirm; |
u_int confirm; |
} Identity; |
} Identity; |
|
|
typedef struct { |
struct idtable { |
int nentries; |
int nentries; |
TAILQ_HEAD(idqueue, identity) idlist; |
TAILQ_HEAD(idqueue, identity) idlist; |
} Idtab; |
}; |
|
|
/* private key table, one per protocol version */ |
/* private key table */ |
Idtab idtable[3]; |
struct idtable *idtab; |
|
|
int max_fd = 0; |
int max_fd = 0; |
|
|
|
|
static void |
static void |
idtab_init(void) |
idtab_init(void) |
{ |
{ |
int i; |
idtab = xcalloc(1, sizeof(*idtab)); |
|
TAILQ_INIT(&idtab->idlist); |
for (i = 0; i <=2; i++) { |
idtab->nentries = 0; |
TAILQ_INIT(&idtable[i].idlist); |
|
idtable[i].nentries = 0; |
|
} |
|
} |
} |
|
|
/* return private key table for requested protocol version */ |
|
static Idtab * |
|
idtab_lookup(int version) |
|
{ |
|
if (version < 1 || version > 2) |
|
fatal("internal error, bad protocol version %d", version); |
|
return &idtable[version]; |
|
} |
|
|
|
static void |
static void |
free_identity(Identity *id) |
free_identity(Identity *id) |
{ |
{ |
|
|
|
|
/* return matching private key for given public key */ |
/* return matching private key for given public key */ |
static Identity * |
static Identity * |
lookup_identity(struct sshkey *key, int version) |
lookup_identity(struct sshkey *key) |
{ |
{ |
Identity *id; |
Identity *id; |
|
|
Idtab *tab = idtab_lookup(version); |
TAILQ_FOREACH(id, &idtab->idlist, next) { |
TAILQ_FOREACH(id, &tab->idlist, next) { |
|
if (sshkey_equal(key, id->key)) |
if (sshkey_equal(key, id->key)) |
return (id); |
return (id); |
} |
} |
|
|
|
|
/* send list of supported public keys to 'client' */ |
/* send list of supported public keys to 'client' */ |
static void |
static void |
process_request_identities(SocketEntry *e, int version) |
process_request_identities(SocketEntry *e) |
{ |
{ |
Idtab *tab = idtab_lookup(version); |
|
Identity *id; |
Identity *id; |
struct sshbuf *msg; |
struct sshbuf *msg; |
int r; |
int r; |
u_char *blob; |
|
size_t blen; |
|
|
|
if ((msg = sshbuf_new()) == NULL) |
if ((msg = sshbuf_new()) == NULL) |
fatal("%s: sshbuf_new failed", __func__); |
fatal("%s: sshbuf_new failed", __func__); |
if ((r = sshbuf_put_u8(msg, (version == 1) ? |
if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
SSH_AGENT_RSA_IDENTITIES_ANSWER : |
(r = sshbuf_put_u32(msg, idtab->nentries)) != 0) |
SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
|
(r = sshbuf_put_u32(msg, tab->nentries)) != 0) |
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
TAILQ_FOREACH(id, &tab->idlist, next) { |
TAILQ_FOREACH(id, &idtab->idlist, next) { |
if ((r = sshkey_to_blob(id->key, &blob, &blen)) != 0) { |
if ((r = sshkey_puts(id->key, msg)) != 0 || |
error("%s: sshkey_to_blob: %s", __func__, |
(r = sshbuf_put_cstring(msg, id->comment)) != 0) { |
|
error("%s: put key/comment: %s", __func__, |
ssh_err(r)); |
ssh_err(r)); |
continue; |
continue; |
} |
} |
if ((r = sshbuf_put_string(msg, blob, blen)) != 0) |
|
fatal("%s: buffer error: %s", |
|
__func__, ssh_err(r)); |
|
free(blob); |
|
if ((r = sshbuf_put_cstring(msg, id->comment)) != 0) |
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
|
} |
} |
if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
|
|
static void |
static void |
process_sign_request2(SocketEntry *e) |
process_sign_request2(SocketEntry *e) |
{ |
{ |
u_char *blob, *data, *signature = NULL; |
const u_char *data; |
size_t blen, dlen, slen = 0; |
u_char *signature = NULL; |
|
size_t dlen, slen = 0; |
u_int compat = 0, flags; |
u_int compat = 0, flags; |
int r, ok = -1; |
int r, ok = -1; |
struct sshbuf *msg; |
struct sshbuf *msg; |
struct sshkey *key; |
struct sshkey *key = NULL; |
struct identity *id; |
struct identity *id; |
|
|
if ((msg = sshbuf_new()) == NULL) |
if ((msg = sshbuf_new()) == NULL) |
fatal("%s: sshbuf_new failed", __func__); |
fatal("%s: sshbuf_new failed", __func__); |
if ((r = sshbuf_get_string(e->request, &blob, &blen)) != 0 || |
if ((r = sshkey_froms(e->request, &key)) != 0 || |
(r = sshbuf_get_string(e->request, &data, &dlen)) != 0 || |
(r = sshbuf_get_string_direct(e->request, &data, &dlen)) != 0 || |
(r = sshbuf_get_u32(e->request, &flags)) != 0) |
(r = sshbuf_get_u32(e->request, &flags)) != 0) |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
if (flags & SSH_AGENT_OLD_SIGNATURE) |
if (flags & SSH_AGENT_OLD_SIGNATURE) |
compat = SSH_BUG_SIGBLOB; |
compat = SSH_BUG_SIGBLOB; |
if ((r = sshkey_from_blob(blob, blen, &key)) != 0) { |
if ((id = lookup_identity(key)) == NULL) { |
error("%s: cannot parse key blob: %s", __func__, ssh_err(r)); |
|
goto send; |
|
} |
|
if ((id = lookup_identity(key, 2)) == NULL) { |
|
verbose("%s: %s key not found", __func__, sshkey_type(key)); |
verbose("%s: %s key not found", __func__, sshkey_type(key)); |
goto send; |
goto send; |
} |
} |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
|
|
sshbuf_free(msg); |
sshbuf_free(msg); |
free(data); |
|
free(blob); |
|
free(signature); |
free(signature); |
} |
} |
|
|
/* shared */ |
/* shared */ |
static void |
static void |
process_remove_identity(SocketEntry *e, int version) |
process_remove_identity(SocketEntry *e) |
{ |
{ |
size_t blen; |
|
int r, success = 0; |
int r, success = 0; |
struct sshkey *key = NULL; |
struct sshkey *key = NULL; |
u_char *blob; |
Identity *id; |
|
|
switch (version) { |
if ((r = sshkey_froms(e->request, &key)) != 0) { |
case 2: |
error("%s: get key: %s", __func__, ssh_err(r)); |
if ((r = sshbuf_get_string(e->request, &blob, &blen)) != 0) |
goto done; |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
|
if ((r = sshkey_from_blob(blob, blen, &key)) != 0) |
|
error("%s: sshkey_from_blob failed: %s", |
|
__func__, ssh_err(r)); |
|
free(blob); |
|
break; |
|
} |
} |
if (key != NULL) { |
if ((id = lookup_identity(key)) == NULL) { |
Identity *id = lookup_identity(key, version); |
debug("%s: key not found", __func__); |
if (id != NULL) { |
goto done; |
/* |
|
* We have this key. Free the old key. Since we |
|
* don't want to leave empty slots in the middle of |
|
* the array, we actually free the key there and move |
|
* all the entries between the empty slot and the end |
|
* of the array. |
|
*/ |
|
Idtab *tab = idtab_lookup(version); |
|
if (tab->nentries < 1) |
|
fatal("process_remove_identity: " |
|
"internal error: tab->nentries %d", |
|
tab->nentries); |
|
TAILQ_REMOVE(&tab->idlist, id, next); |
|
free_identity(id); |
|
tab->nentries--; |
|
success = 1; |
|
} |
|
sshkey_free(key); |
|
} |
} |
|
/* We have this key, free it. */ |
|
if (idtab->nentries < 1) |
|
fatal("%s: internal error: nentries %d", |
|
__func__, idtab->nentries); |
|
TAILQ_REMOVE(&idtab->idlist, id, next); |
|
free_identity(id); |
|
idtab->nentries--; |
|
sshkey_free(key); |
|
success = 1; |
|
done: |
send_status(e, success); |
send_status(e, success); |
} |
} |
|
|
static void |
static void |
process_remove_all_identities(SocketEntry *e, int version) |
process_remove_all_identities(SocketEntry *e) |
{ |
{ |
Idtab *tab = idtab_lookup(version); |
|
Identity *id; |
Identity *id; |
|
|
/* Loop over all identities and clear the keys. */ |
/* Loop over all identities and clear the keys. */ |
for (id = TAILQ_FIRST(&tab->idlist); id; |
for (id = TAILQ_FIRST(&idtab->idlist); id; |
id = TAILQ_FIRST(&tab->idlist)) { |
id = TAILQ_FIRST(&idtab->idlist)) { |
TAILQ_REMOVE(&tab->idlist, id, next); |
TAILQ_REMOVE(&idtab->idlist, id, next); |
free_identity(id); |
free_identity(id); |
} |
} |
|
|
/* Mark that there are no identities. */ |
/* Mark that there are no identities. */ |
tab->nentries = 0; |
idtab->nentries = 0; |
|
|
/* Send success. */ |
/* Send success. */ |
send_status(e, 1); |
send_status(e, 1); |
|
|
{ |
{ |
time_t deadline = 0, now = monotime(); |
time_t deadline = 0, now = monotime(); |
Identity *id, *nxt; |
Identity *id, *nxt; |
int version; |
|
Idtab *tab; |
|
|
|
for (version = 1; version < 3; version++) { |
for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) { |
tab = idtab_lookup(version); |
nxt = TAILQ_NEXT(id, next); |
for (id = TAILQ_FIRST(&tab->idlist); id; id = nxt) { |
if (id->death == 0) |
nxt = TAILQ_NEXT(id, next); |
continue; |
if (id->death == 0) |
if (now >= id->death) { |
continue; |
debug("expiring key '%s'", id->comment); |
if (now >= id->death) { |
TAILQ_REMOVE(&idtab->idlist, id, next); |
debug("expiring key '%s'", id->comment); |
free_identity(id); |
TAILQ_REMOVE(&tab->idlist, id, next); |
idtab->nentries--; |
free_identity(id); |
} else |
tab->nentries--; |
deadline = (deadline == 0) ? id->death : |
} else |
MINIMUM(deadline, id->death); |
deadline = (deadline == 0) ? id->death : |
|
MINIMUM(deadline, id->death); |
|
} |
|
} |
} |
if (deadline == 0 || deadline <= now) |
if (deadline == 0 || deadline <= now) |
return 0; |
return 0; |
|
|
return (deadline - now); |
return (deadline - now); |
} |
} |
|
|
/* |
|
* XXX this and the corresponding serialisation function probably belongs |
|
* in key.c |
|
*/ |
|
|
|
static void |
static void |
process_add_identity(SocketEntry *e, int version) |
process_add_identity(SocketEntry *e) |
{ |
{ |
Idtab *tab = idtab_lookup(version); |
|
Identity *id; |
Identity *id; |
int success = 0, confirm = 0; |
int success = 0, confirm = 0; |
u_int seconds; |
u_int seconds; |
|
|
u_char ctype; |
u_char ctype; |
int r = SSH_ERR_INTERNAL_ERROR; |
int r = SSH_ERR_INTERNAL_ERROR; |
|
|
switch (version) { |
if ((r = sshkey_private_deserialize(e->request, &k)) != 0 || |
case 2: |
k == NULL || |
r = sshkey_private_deserialize(e->request, &k); |
|
break; |
|
} |
|
if (r != 0 || k == NULL || |
|
(r = sshbuf_get_cstring(e->request, &comment, NULL)) != 0) { |
(r = sshbuf_get_cstring(e->request, &comment, NULL)) != 0) { |
error("%s: decode private key: %s", __func__, ssh_err(r)); |
error("%s: decode private key: %s", __func__, ssh_err(r)); |
goto err; |
goto err; |
|
|
success = 1; |
success = 1; |
if (lifetime && !death) |
if (lifetime && !death) |
death = monotime() + lifetime; |
death = monotime() + lifetime; |
if ((id = lookup_identity(k, version)) == NULL) { |
if ((id = lookup_identity(k)) == NULL) { |
id = xcalloc(1, sizeof(Identity)); |
id = xcalloc(1, sizeof(Identity)); |
id->key = k; |
id->key = k; |
TAILQ_INSERT_TAIL(&tab->idlist, id, next); |
TAILQ_INSERT_TAIL(&idtab->idlist, id, next); |
/* Increment the number of identities. */ |
/* Increment the number of identities. */ |
tab->nentries++; |
idtab->nentries++; |
} else { |
} else { |
sshkey_free(k); |
sshkey_free(k); |
free(id->comment); |
free(id->comment); |
|
|
} |
} |
|
|
static void |
static void |
no_identities(SocketEntry *e, u_int type) |
no_identities(SocketEntry *e) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
int r; |
int r; |
|
|
if ((msg = sshbuf_new()) == NULL) |
if ((msg = sshbuf_new()) == NULL) |
fatal("%s: sshbuf_new failed", __func__); |
fatal("%s: sshbuf_new failed", __func__); |
if ((r = sshbuf_put_u8(msg, |
if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
(type == SSH_AGENTC_REQUEST_RSA_IDENTITIES) ? |
|
SSH_AGENT_RSA_IDENTITIES_ANSWER : |
|
SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
|
(r = sshbuf_put_u32(msg, 0)) != 0 || |
(r = sshbuf_put_u32(msg, 0)) != 0 || |
(r = sshbuf_put_stringb(e->output, msg)) != 0) |
(r = sshbuf_put_stringb(e->output, msg)) != 0) |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
|
|
process_add_smartcard_key(SocketEntry *e) |
process_add_smartcard_key(SocketEntry *e) |
{ |
{ |
char *provider = NULL, *pin, canonical_provider[PATH_MAX]; |
char *provider = NULL, *pin, canonical_provider[PATH_MAX]; |
int r, i, version, count = 0, success = 0, confirm = 0; |
int r, i, count = 0, success = 0, confirm = 0; |
u_int seconds; |
u_int seconds; |
time_t death = 0; |
time_t death = 0; |
u_char type; |
u_char type; |
struct sshkey **keys = NULL, *k; |
struct sshkey **keys = NULL, *k; |
Identity *id; |
Identity *id; |
Idtab *tab; |
|
|
|
if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
(r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) |
(r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) |
|
|
confirm = 1; |
confirm = 1; |
break; |
break; |
default: |
default: |
error("process_add_smartcard_key: " |
error("%s: Unknown constraint type %d", __func__, type); |
"Unknown constraint type %d", type); |
|
goto send; |
goto send; |
} |
} |
} |
} |
|
|
count = pkcs11_add_provider(canonical_provider, pin, &keys); |
count = pkcs11_add_provider(canonical_provider, pin, &keys); |
for (i = 0; i < count; i++) { |
for (i = 0; i < count; i++) { |
k = keys[i]; |
k = keys[i]; |
version = 2; |
if (lookup_identity(k) == NULL) { |
tab = idtab_lookup(version); |
|
if (lookup_identity(k, version) == NULL) { |
|
id = xcalloc(1, sizeof(Identity)); |
id = xcalloc(1, sizeof(Identity)); |
id->key = k; |
id->key = k; |
id->provider = xstrdup(canonical_provider); |
id->provider = xstrdup(canonical_provider); |
id->comment = xstrdup(canonical_provider); /* XXX */ |
id->comment = xstrdup(canonical_provider); /* XXX */ |
id->death = death; |
id->death = death; |
id->confirm = confirm; |
id->confirm = confirm; |
TAILQ_INSERT_TAIL(&tab->idlist, id, next); |
TAILQ_INSERT_TAIL(&idtab->idlist, id, next); |
tab->nentries++; |
idtab->nentries++; |
success = 1; |
success = 1; |
} else { |
} else { |
sshkey_free(k); |
sshkey_free(k); |
|
|
process_remove_smartcard_key(SocketEntry *e) |
process_remove_smartcard_key(SocketEntry *e) |
{ |
{ |
char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; |
char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; |
int r, version, success = 0; |
int r, success = 0; |
Identity *id, *nxt; |
Identity *id, *nxt; |
Idtab *tab; |
|
|
|
if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
(r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) |
(r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) |
|
|
} |
} |
|
|
debug("%s: remove %.100s", __func__, canonical_provider); |
debug("%s: remove %.100s", __func__, canonical_provider); |
for (version = 1; version < 3; version++) { |
for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) { |
tab = idtab_lookup(version); |
nxt = TAILQ_NEXT(id, next); |
for (id = TAILQ_FIRST(&tab->idlist); id; id = nxt) { |
/* Skip file--based keys */ |
nxt = TAILQ_NEXT(id, next); |
if (id->provider == NULL) |
/* Skip file--based keys */ |
continue; |
if (id->provider == NULL) |
if (!strcmp(canonical_provider, id->provider)) { |
continue; |
TAILQ_REMOVE(&idtab->idlist, id, next); |
if (!strcmp(canonical_provider, id->provider)) { |
free_identity(id); |
TAILQ_REMOVE(&tab->idlist, id, next); |
idtab->nentries--; |
free_identity(id); |
|
tab->nentries--; |
|
} |
|
} |
} |
} |
} |
if (pkcs11_del_provider(canonical_provider) == 0) |
if (pkcs11_del_provider(canonical_provider) == 0) |
success = 1; |
success = 1; |
else |
else |
error("process_remove_smartcard_key:" |
error("%s: pkcs11_del_provider failed", __func__); |
" pkcs11_del_provider failed"); |
|
send: |
send: |
free(provider); |
free(provider); |
send_status(e, success); |
send_status(e, success); |
|
|
if (locked && type != SSH_AGENTC_UNLOCK) { |
if (locked && type != SSH_AGENTC_UNLOCK) { |
sshbuf_reset(e->request); |
sshbuf_reset(e->request); |
switch (type) { |
switch (type) { |
case SSH_AGENTC_REQUEST_RSA_IDENTITIES: |
|
case SSH2_AGENTC_REQUEST_IDENTITIES: |
case SSH2_AGENTC_REQUEST_IDENTITIES: |
/* send empty lists */ |
/* send empty lists */ |
no_identities(e, type); |
no_identities(e); |
break; |
break; |
default: |
default: |
/* send a fail message for all other request types */ |
/* send a fail message for all other request types */ |
|
|
process_lock_agent(e, type == SSH_AGENTC_LOCK); |
process_lock_agent(e, type == SSH_AGENTC_LOCK); |
break; |
break; |
case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: |
case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: |
process_remove_all_identities(e, 1); /* safe for !WITH_SSH1 */ |
process_remove_all_identities(e); /* safe for !WITH_SSH1 */ |
break; |
break; |
/* ssh2 */ |
/* ssh2 */ |
case SSH2_AGENTC_SIGN_REQUEST: |
case SSH2_AGENTC_SIGN_REQUEST: |
process_sign_request2(e); |
process_sign_request2(e); |
break; |
break; |
case SSH2_AGENTC_REQUEST_IDENTITIES: |
case SSH2_AGENTC_REQUEST_IDENTITIES: |
process_request_identities(e, 2); |
process_request_identities(e); |
break; |
break; |
case SSH2_AGENTC_ADD_IDENTITY: |
case SSH2_AGENTC_ADD_IDENTITY: |
case SSH2_AGENTC_ADD_ID_CONSTRAINED: |
case SSH2_AGENTC_ADD_ID_CONSTRAINED: |
process_add_identity(e, 2); |
process_add_identity(e); |
break; |
break; |
case SSH2_AGENTC_REMOVE_IDENTITY: |
case SSH2_AGENTC_REMOVE_IDENTITY: |
process_remove_identity(e, 2); |
process_remove_identity(e); |
break; |
break; |
case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: |
case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: |
process_remove_all_identities(e, 2); |
process_remove_all_identities(e); |
break; |
break; |
#ifdef ENABLE_PKCS11 |
#ifdef ENABLE_PKCS11 |
case SSH_AGENTC_ADD_SMARTCARD_KEY: |
case SSH_AGENTC_ADD_SMARTCARD_KEY: |