version 1.279, 2021/11/18 03:31:44 |
version 1.280, 2021/12/19 22:09:23 |
|
|
#endif |
#endif |
|
|
/* Maximum accepted message length */ |
/* Maximum accepted message length */ |
#define AGENT_MAX_LEN (256*1024) |
#define AGENT_MAX_LEN (256*1024) |
/* Maximum bytes to read from client socket */ |
/* Maximum bytes to read from client socket */ |
#define AGENT_RBUF_LEN (4096) |
#define AGENT_RBUF_LEN (4096) |
|
/* Maximum number of recorded session IDs/hostkeys per connection */ |
|
#define AGENT_MAX_SESSION_IDS 16 |
|
/* Maximum size of session ID */ |
|
#define AGENT_MAX_SID_LEN 128 |
|
|
|
/* XXX store hostkey_sid in a refcounted tree */ |
|
|
typedef enum { |
typedef enum { |
AUTH_UNUSED = 0, |
AUTH_UNUSED = 0, |
AUTH_SOCKET = 1, |
AUTH_SOCKET = 1, |
AUTH_CONNECTION = 2, |
AUTH_CONNECTION = 2, |
} sock_type; |
} sock_type; |
|
|
|
struct hostkey_sid { |
|
struct sshkey *key; |
|
struct sshbuf *sid; |
|
int forwarded; |
|
}; |
|
|
typedef struct socket_entry { |
typedef struct socket_entry { |
int fd; |
int fd; |
sock_type type; |
sock_type type; |
struct sshbuf *input; |
struct sshbuf *input; |
struct sshbuf *output; |
struct sshbuf *output; |
struct sshbuf *request; |
struct sshbuf *request; |
|
size_t nsession_ids; |
|
struct hostkey_sid *session_ids; |
} SocketEntry; |
} SocketEntry; |
|
|
u_int sockets_alloc = 0; |
u_int sockets_alloc = 0; |
|
|
static void |
static void |
close_socket(SocketEntry *e) |
close_socket(SocketEntry *e) |
{ |
{ |
|
size_t i; |
|
|
close(e->fd); |
close(e->fd); |
sshbuf_free(e->input); |
sshbuf_free(e->input); |
sshbuf_free(e->output); |
sshbuf_free(e->output); |
sshbuf_free(e->request); |
sshbuf_free(e->request); |
|
for (i = 0; i < e->nsession_ids; i++) { |
|
sshkey_free(e->session_ids[i].key); |
|
sshbuf_free(e->session_ids[i].sid); |
|
} |
|
free(e->session_ids); |
memset(e, '\0', sizeof(*e)); |
memset(e, '\0', sizeof(*e)); |
e->fd = -1; |
e->fd = -1; |
e->type = AUTH_UNUSED; |
e->type = AUTH_UNUSED; |
|
|
return 0; |
return 0; |
} |
} |
|
|
|
static int |
|
buf_equal(const struct sshbuf *a, const struct sshbuf *b) |
|
{ |
|
if (sshbuf_ptr(a) == NULL || sshbuf_ptr(b) == NULL) |
|
return SSH_ERR_INVALID_ARGUMENT; |
|
if (sshbuf_len(a) != sshbuf_len(b)) |
|
return SSH_ERR_INVALID_FORMAT; |
|
if (timingsafe_bcmp(sshbuf_ptr(a), sshbuf_ptr(b), sshbuf_len(a)) != 0) |
|
return SSH_ERR_INVALID_FORMAT; |
|
return 0; |
|
} |
|
|
/* ssh2 only */ |
/* ssh2 only */ |
static void |
static void |
process_sign_request2(SocketEntry *e) |
process_sign_request2(SocketEntry *e) |
{ |
{ |
u_char *signature = NULL; |
u_char *signature = NULL; |
size_t slen = 0; |
size_t i, slen = 0; |
u_int compat = 0, flags; |
u_int compat = 0, flags; |
int r, ok = -1; |
int r, ok = -1; |
char *fp = NULL; |
char *fp = NULL, *user = NULL, *sig_dest = NULL; |
struct sshbuf *msg = NULL, *data = NULL; |
struct sshbuf *msg = NULL, *data = NULL, *sid = NULL; |
struct sshkey *key = NULL; |
struct sshkey *key = NULL; |
struct identity *id; |
struct identity *id; |
struct notifier_ctx *notifier = NULL; |
struct notifier_ctx *notifier = NULL; |
|
|
verbose_f("%s key not found", sshkey_type(key)); |
verbose_f("%s key not found", sshkey_type(key)); |
goto send; |
goto send; |
} |
} |
if (id->confirm && confirm_key(id, NULL) != 0) { |
/* |
|
* If session IDs were recorded for this socket, then use them to |
|
* annotate the confirmation messages with the host keys. |
|
*/ |
|
if (e->nsession_ids > 0 && |
|
parse_userauth_request(data, key, &user, &sid) == 0) { |
|
/* |
|
* session ID from userauth request should match the final |
|
* ID in the list recorded in the socket, unless the ssh |
|
* client at that point lacks the binding extension (or if |
|
* an attacker is trying to steal use of the agent). |
|
*/ |
|
i = e->nsession_ids - 1; |
|
if (buf_equal(sid, e->session_ids[i].sid) == 0) { |
|
if ((fp = sshkey_fingerprint(e->session_ids[i].key, |
|
SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) |
|
fatal_f("fingerprint failed"); |
|
debug3_f("destination %s %s (slot %zu)", |
|
sshkey_type(e->session_ids[i].key), fp, i); |
|
xasprintf(&sig_dest, "public key request for " |
|
"target user \"%s\" to %s %s", user, |
|
sshkey_type(e->session_ids[i].key), fp); |
|
free(fp); |
|
fp = NULL; |
|
} |
|
} |
|
if (id->confirm && confirm_key(id, sig_dest) != 0) { |
verbose_f("user refused key"); |
verbose_f("user refused key"); |
goto send; |
goto send; |
} |
} |
|
|
SSH_FP_DEFAULT)) == NULL) |
SSH_FP_DEFAULT)) == NULL) |
fatal_f("fingerprint failed"); |
fatal_f("fingerprint failed"); |
notifier = notify_start(0, |
notifier = notify_start(0, |
"Confirm user presence for key %s %s", |
"Confirm user presence for key %s %s%s%s", |
sshkey_type(id->key), fp); |
sshkey_type(id->key), fp, |
|
sig_dest == NULL ? "" : "\n", |
|
sig_dest == NULL ? "" : sig_dest); |
} |
} |
} |
} |
/* XXX support PIN required FIDO keys */ |
/* XXX support PIN required FIDO keys */ |
|
|
if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
fatal_fr(r, "enqueue"); |
fatal_fr(r, "enqueue"); |
|
|
|
sshbuf_free(sid); |
sshbuf_free(data); |
sshbuf_free(data); |
sshbuf_free(msg); |
sshbuf_free(msg); |
sshkey_free(key); |
sshkey_free(key); |
free(fp); |
free(fp); |
free(signature); |
free(signature); |
|
free(sig_dest); |
|
free(user); |
} |
} |
|
|
/* shared */ |
/* shared */ |
|
|
} |
} |
#endif /* ENABLE_PKCS11 */ |
#endif /* ENABLE_PKCS11 */ |
|
|
|
static int |
|
process_ext_session_bind(SocketEntry *e) |
|
{ |
|
int r, sid_match, key_match; |
|
struct sshkey *key = NULL; |
|
struct sshbuf *sid = NULL, *sig = NULL; |
|
char *fp = NULL; |
|
u_char fwd; |
|
size_t i; |
|
|
|
debug2_f("entering"); |
|
if ((r = sshkey_froms(e->request, &key)) != 0 || |
|
(r = sshbuf_froms(e->request, &sid)) != 0 || |
|
(r = sshbuf_froms(e->request, &sig)) != 0 || |
|
(r = sshbuf_get_u8(e->request, &fwd)) != 0) { |
|
error_fr(r, "parse"); |
|
goto out; |
|
} |
|
if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
|
SSH_FP_DEFAULT)) == NULL) |
|
fatal_f("fingerprint failed"); |
|
/* check signature with hostkey on session ID */ |
|
if ((r = sshkey_verify(key, sshbuf_ptr(sig), sshbuf_len(sig), |
|
sshbuf_ptr(sid), sshbuf_len(sid), NULL, 0, NULL)) != 0) { |
|
error_fr(r, "sshkey_verify for %s %s", sshkey_type(key), fp); |
|
goto out; |
|
} |
|
/* check whether sid/key already recorded */ |
|
for (i = 0; i < e->nsession_ids; i++) { |
|
sid_match = buf_equal(sid, e->session_ids[i].sid) == 0; |
|
key_match = sshkey_equal(key, e->session_ids[i].key); |
|
if (sid_match && key_match) { |
|
debug_f("session ID already recorded for %s %s", |
|
sshkey_type(key), fp); |
|
r = 0; |
|
goto out; |
|
} else if (sid_match) { |
|
error_f("session ID recorded against different key " |
|
"for %s %s", sshkey_type(key), fp); |
|
r = -1; |
|
goto out; |
|
} |
|
/* |
|
* new sid with previously-seen key can happen, e.g. multiple |
|
* connections to the same host. |
|
*/ |
|
} |
|
/* record new key/sid */ |
|
if (e->nsession_ids >= AGENT_MAX_SESSION_IDS) { |
|
error_f("too many session IDs recorded"); |
|
goto out; |
|
} |
|
e->session_ids = xrecallocarray(e->session_ids, e->nsession_ids, |
|
e->nsession_ids + 1, sizeof(*e->session_ids)); |
|
i = e->nsession_ids++; |
|
debug_f("recorded %s %s (slot %zu of %d)", sshkey_type(key), fp, i, |
|
AGENT_MAX_SESSION_IDS); |
|
e->session_ids[i].key = key; |
|
e->session_ids[i].forwarded = fwd != 0; |
|
key = NULL; /* transferred */ |
|
/* can't transfer sid; it's refcounted and scoped to request's life */ |
|
if ((e->session_ids[i].sid = sshbuf_new()) == NULL) |
|
fatal_f("sshbuf_new"); |
|
if ((r = sshbuf_putb(e->session_ids[i].sid, sid)) != 0) |
|
fatal_fr(r, "sshbuf_putb session ID"); |
|
/* success */ |
|
r = 0; |
|
out: |
|
sshkey_free(key); |
|
sshbuf_free(sid); |
|
sshbuf_free(sig); |
|
return r == 0 ? 1 : 0; |
|
} |
|
|
|
static void |
|
process_extension(SocketEntry *e) |
|
{ |
|
int r, success = 0; |
|
char *name; |
|
|
|
debug2_f("entering"); |
|
if ((r = sshbuf_get_cstring(e->request, &name, NULL)) != 0) { |
|
error_fr(r, "parse"); |
|
goto send; |
|
} |
|
if (strcmp(name, "session-bind@openssh.com") == 0) |
|
success = process_ext_session_bind(e); |
|
else |
|
debug_f("unsupported extension \"%s\"", name); |
|
send: |
|
send_status(e, success); |
|
} |
/* |
/* |
* dispatch incoming message. |
* dispatch incoming message. |
* returns 1 on success, 0 for incomplete messages or -1 on error. |
* returns 1 on success, 0 for incomplete messages or -1 on error. |
|
|
process_remove_smartcard_key(e); |
process_remove_smartcard_key(e); |
break; |
break; |
#endif /* ENABLE_PKCS11 */ |
#endif /* ENABLE_PKCS11 */ |
|
case SSH_AGENTC_EXTENSION: |
|
process_extension(e); |
|
break; |
default: |
default: |
/* Unknown message. Respond with failure. */ |
/* Unknown message. Respond with failure. */ |
error("Unknown message %d", type); |
error("Unknown message %d", type); |