mirror of git://anongit.mindrot.org/openssh.git
upstream: ssh-agent side of destination constraints
Gives ssh-agent the ability to parse restrict-destination-v00@openssh.com constraints and to apply them to keys. Check constraints against the hostkeys recorded for a SocketEntry when attempting a signature, adding, listing or deleting keys. Note that the "delete all keys" request will remove constrained keys regardless of location. feedback Jann Horn & markus@ ok markus@ OpenBSD-Commit-ID: 84a7fb81106c2d609a6ac17469436df16d196319
This commit is contained in:
parent
ce943912df
commit
39f00dcf44
509
ssh-agent.c
509
ssh-agent.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: ssh-agent.c,v 1.280 2021/12/19 22:09:23 djm Exp $ */
|
||||
/* $OpenBSD: ssh-agent.c,v 1.281 2021/12/19 22:11:39 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -92,6 +92,7 @@
|
|||
#include "pathnames.h"
|
||||
#include "ssh-pkcs11.h"
|
||||
#include "sk-api.h"
|
||||
#include "myproposal.h"
|
||||
|
||||
#ifndef DEFAULT_ALLOWED_PROVIDERS
|
||||
# define DEFAULT_ALLOWED_PROVIDERS "/usr/lib*/*,/usr/local/lib*/*"
|
||||
|
@ -105,6 +106,8 @@
|
|||
#define AGENT_MAX_SESSION_IDS 16
|
||||
/* Maximum size of session ID */
|
||||
#define AGENT_MAX_SID_LEN 128
|
||||
/* Maximum number of destination constraints to accept on a key */
|
||||
#define AGENT_MAX_DEST_CONSTRAINTS 1024
|
||||
|
||||
/* XXX store hostkey_sid in a refcounted tree */
|
||||
|
||||
|
@ -141,6 +144,8 @@ typedef struct identity {
|
|||
time_t death;
|
||||
u_int confirm;
|
||||
char *sk_provider;
|
||||
struct dest_constraint *dest_constraints;
|
||||
size_t ndest_constraints;
|
||||
} Identity;
|
||||
|
||||
struct idtable {
|
||||
|
@ -212,6 +217,33 @@ idtab_init(void)
|
|||
idtab->nentries = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
free_dest_constraint_hop(struct dest_constraint_hop *dch)
|
||||
{
|
||||
u_int i;
|
||||
|
||||
if (dch == NULL)
|
||||
return;
|
||||
free(dch->user);
|
||||
free(dch->hostname);
|
||||
for (i = 0; i < dch->nkeys; i++)
|
||||
sshkey_free(dch->keys[i]);
|
||||
free(dch->keys);
|
||||
free(dch->key_is_ca);
|
||||
}
|
||||
|
||||
static void
|
||||
free_dest_constraints(struct dest_constraint *dcs, size_t ndcs)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ndcs; i++) {
|
||||
free_dest_constraint_hop(&dcs[i].from);
|
||||
free_dest_constraint_hop(&dcs[i].to);
|
||||
}
|
||||
free(dcs);
|
||||
}
|
||||
|
||||
static void
|
||||
free_identity(Identity *id)
|
||||
{
|
||||
|
@ -219,9 +251,220 @@ free_identity(Identity *id)
|
|||
free(id->provider);
|
||||
free(id->comment);
|
||||
free(id->sk_provider);
|
||||
free_dest_constraints(id->dest_constraints, id->ndest_constraints);
|
||||
free(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Match 'key' against the key/CA list in a destination constraint hop
|
||||
* Returns 0 on success or -1 otherwise.
|
||||
*/
|
||||
static int
|
||||
match_key_hop(const char *tag, const struct sshkey *key,
|
||||
const struct dest_constraint_hop *dch)
|
||||
{
|
||||
const char *reason = NULL;
|
||||
u_int i;
|
||||
char *fp;
|
||||
|
||||
if (key == NULL)
|
||||
return -1;
|
||||
/* XXX logspam */
|
||||
if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("fingerprint failed");
|
||||
debug3_f("%s: entering hostname %s, requested key %s %s, %u keys avail",
|
||||
tag, dch->hostname, sshkey_type(key), fp, dch->nkeys);
|
||||
free(fp);
|
||||
for (i = 0; i < dch->nkeys; i++) {
|
||||
if (dch->keys[i] == NULL)
|
||||
return -1;
|
||||
/* XXX logspam */
|
||||
if ((fp = sshkey_fingerprint(dch->keys[i], SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("fingerprint failed");
|
||||
debug3_f("%s: key %u: %s%s %s", tag, i,
|
||||
dch->key_is_ca[i] ? "CA " : "",
|
||||
sshkey_type(dch->keys[i]), fp);
|
||||
free(fp);
|
||||
if (!sshkey_is_cert(key)) {
|
||||
/* plain key */
|
||||
if (dch->key_is_ca[i] ||
|
||||
!sshkey_equal(key, dch->keys[i]))
|
||||
continue;
|
||||
return 0;
|
||||
}
|
||||
/* certificate */
|
||||
if (!dch->key_is_ca[i])
|
||||
continue;
|
||||
if (key->cert == NULL || key->cert->signature_key == NULL)
|
||||
return -1; /* shouldn't happen */
|
||||
if (!sshkey_equal(key->cert->signature_key, dch->keys[i]))
|
||||
continue;
|
||||
if (sshkey_cert_check_host(key, dch->hostname, 1,
|
||||
SSH_ALLOWED_CA_SIGALGS, &reason) != 0) {
|
||||
debug_f("cert %s / hostname %s rejected: %s",
|
||||
key->cert->key_id, dch->hostname, reason);
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check destination constraints on an identity against the hostkey/user */
|
||||
static int
|
||||
permitted_by_dest_constraints(const struct sshkey *fromkey,
|
||||
const struct sshkey *tokey, Identity *id, const char *user,
|
||||
const char **hostnamep)
|
||||
{
|
||||
size_t i;
|
||||
struct dest_constraint *d;
|
||||
|
||||
if (hostnamep != NULL)
|
||||
*hostnamep = NULL;
|
||||
for (i = 0; i < id->ndest_constraints; i++) {
|
||||
d = id->dest_constraints + i;
|
||||
/* XXX remove logspam */
|
||||
debug2_f("constraint %zu %s%s%s (%u keys) > %s%s%s (%u keys)",
|
||||
i, d->from.user ? d->from.user : "",
|
||||
d->from.user ? "@" : "",
|
||||
d->from.hostname ? d->from.hostname : "(ORIGIN)",
|
||||
d->from.nkeys,
|
||||
d->to.user ? d->to.user : "", d->to.user ? "@" : "",
|
||||
d->to.hostname ? d->to.hostname : "(ANY)", d->to.nkeys);
|
||||
|
||||
/* Match 'from' key */
|
||||
if (fromkey == NULL) {
|
||||
/* We are matching the first hop */
|
||||
if (d->from.hostname != NULL || d->from.nkeys != 0)
|
||||
continue;
|
||||
} else if (match_key_hop("from", fromkey, &d->from) != 0)
|
||||
continue;
|
||||
|
||||
/* Match 'to' key */
|
||||
if (tokey != NULL && match_key_hop("to", tokey, &d->to) != 0)
|
||||
continue;
|
||||
|
||||
/* Match user if specified */
|
||||
if (d->to.user != NULL && user != NULL &&
|
||||
!match_pattern(user, d->to.user))
|
||||
continue;
|
||||
|
||||
/* successfully matched this constraint */
|
||||
if (hostnamep != NULL)
|
||||
*hostnamep = d->to.hostname;
|
||||
debug2_f("allowed for hostname %s",
|
||||
d->to.hostname == NULL ? "*" : d->to.hostname);
|
||||
return 0;
|
||||
}
|
||||
/* no match */
|
||||
debug2_f("%s identity \"%s\" not permitted for this destination",
|
||||
sshkey_type(id->key), id->comment);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether hostkeys on a SocketEntry and the optionally specified user
|
||||
* are permitted by the destination constraints on the Identity.
|
||||
* Returns 0 on success or -1 otherwise.
|
||||
*/
|
||||
static int
|
||||
identity_permitted(Identity *id, SocketEntry *e, char *user,
|
||||
const char **forward_hostnamep, const char **last_hostnamep)
|
||||
{
|
||||
size_t i;
|
||||
const char **hp;
|
||||
struct hostkey_sid *hks;
|
||||
const struct sshkey *fromkey = NULL;
|
||||
const char *test_user;
|
||||
char *fp1, *fp2;
|
||||
|
||||
/* XXX remove logspam */
|
||||
debug3_f("entering: key %s comment \"%s\", %zu socket bindings, "
|
||||
"%zu constraints", sshkey_type(id->key), id->comment,
|
||||
e->nsession_ids, id->ndest_constraints);
|
||||
if (id->ndest_constraints == 0)
|
||||
return 0; /* unconstrained */
|
||||
if (e->nsession_ids == 0)
|
||||
return 0; /* local use */
|
||||
/*
|
||||
* Walk through the hops recorded by session_id and try to find a
|
||||
* constraint that satisfies each.
|
||||
*/
|
||||
for (i = 0; i < e->nsession_ids; i++) {
|
||||
hks = e->session_ids + i;
|
||||
if (hks->key == NULL)
|
||||
fatal_f("internal error: no bound key");
|
||||
/* XXX remove logspam */
|
||||
fp1 = fp2 = NULL;
|
||||
if (fromkey != NULL &&
|
||||
(fp1 = sshkey_fingerprint(fromkey, SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("fingerprint failed");
|
||||
if ((fp2 = sshkey_fingerprint(hks->key, SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("fingerprint failed");
|
||||
debug3_f("socketentry fd=%d, entry %zu %s, "
|
||||
"from hostkey %s %s to user %s hostkey %s %s",
|
||||
e->fd, i, hks->forwarded ? "FORWARD" : "AUTH",
|
||||
fromkey ? sshkey_type(fromkey) : "(ORIGIN)",
|
||||
fromkey ? fp1 : "", user ? user : "(ANY)",
|
||||
sshkey_type(hks->key), fp2);
|
||||
free(fp1);
|
||||
free(fp2);
|
||||
/*
|
||||
* Record the hostnames for the initial forwarding and
|
||||
* the final destination.
|
||||
*/
|
||||
hp = NULL;
|
||||
if (i == e->nsession_ids - 1)
|
||||
hp = last_hostnamep;
|
||||
else if (i == 0)
|
||||
hp = forward_hostnamep;
|
||||
/* Special handling for final recorded binding */
|
||||
test_user = NULL;
|
||||
if (i == e->nsession_ids - 1) {
|
||||
/* Can only check user at final hop */
|
||||
test_user = user;
|
||||
/*
|
||||
* user is only presented for signature requests.
|
||||
* If this is the case, make sure last binding is not
|
||||
* for a forwarding.
|
||||
*/
|
||||
if (hks->forwarded && user != NULL) {
|
||||
error_f("tried to sign on forwarding hop");
|
||||
return -1;
|
||||
}
|
||||
} else if (!hks->forwarded) {
|
||||
error_f("tried to forward though signing bind");
|
||||
return -1;
|
||||
}
|
||||
if (permitted_by_dest_constraints(fromkey, hks->key, id,
|
||||
test_user, hp) != 0)
|
||||
return -1;
|
||||
fromkey = hks->key;
|
||||
}
|
||||
/*
|
||||
* Another special case: if the last bound session ID was for a
|
||||
* forwarding, and this function is not being called to check a sign
|
||||
* request (i.e. no 'user' supplied), then only permit the key if
|
||||
* there is a permission that would allow it to be used at another
|
||||
* destination. This hides keys that are allowed to be used to
|
||||
* authenicate *to* a host but not permitted for *use* beyond it.
|
||||
*/
|
||||
hks = &e->session_ids[e->nsession_ids - 1];
|
||||
if (hks->forwarded && user == NULL &&
|
||||
permitted_by_dest_constraints(hks->key, NULL, id,
|
||||
NULL, NULL) != 0) {
|
||||
debug3_f("key permitted at host but not after");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* return matching private key for given public key */
|
||||
static Identity *
|
||||
lookup_identity(struct sshkey *key)
|
||||
|
@ -269,27 +512,36 @@ static void
|
|||
process_request_identities(SocketEntry *e)
|
||||
{
|
||||
Identity *id;
|
||||
struct sshbuf *msg;
|
||||
struct sshbuf *msg, *keys;
|
||||
int r;
|
||||
u_int nentries = 0;
|
||||
|
||||
debug2_f("entering");
|
||||
|
||||
if ((msg = sshbuf_new()) == NULL)
|
||||
if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL)
|
||||
fatal_f("sshbuf_new failed");
|
||||
if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 ||
|
||||
(r = sshbuf_put_u32(msg, idtab->nentries)) != 0)
|
||||
fatal_fr(r, "compose");
|
||||
TAILQ_FOREACH(id, &idtab->idlist, next) {
|
||||
if ((r = sshkey_puts_opts(id->key, msg,
|
||||
/* identity not visible, don't include in response */
|
||||
if (identity_permitted(id, e, NULL, NULL, NULL) != 0)
|
||||
continue;
|
||||
if ((r = sshkey_puts_opts(id->key, keys,
|
||||
SSHKEY_SERIALIZE_INFO)) != 0 ||
|
||||
(r = sshbuf_put_cstring(msg, id->comment)) != 0) {
|
||||
(r = sshbuf_put_cstring(keys, id->comment)) != 0) {
|
||||
error_fr(r, "compose key/comment");
|
||||
continue;
|
||||
}
|
||||
nentries++;
|
||||
}
|
||||
debug2_f("replying with %u allowed of %u available keys",
|
||||
nentries, idtab->nentries);
|
||||
if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 ||
|
||||
(r = sshbuf_put_u32(msg, nentries)) != 0 ||
|
||||
(r = sshbuf_putb(msg, keys)) != 0)
|
||||
fatal_fr(r, "compose");
|
||||
if ((r = sshbuf_put_stringb(e->output, msg)) != 0)
|
||||
fatal_fr(r, "enqueue");
|
||||
sshbuf_free(msg);
|
||||
sshbuf_free(keys);
|
||||
}
|
||||
|
||||
|
||||
|
@ -460,10 +712,11 @@ static void
|
|||
process_sign_request2(SocketEntry *e)
|
||||
{
|
||||
u_char *signature = NULL;
|
||||
size_t i, slen = 0;
|
||||
size_t slen = 0;
|
||||
u_int compat = 0, flags;
|
||||
int r, ok = -1;
|
||||
char *fp = NULL, *user = NULL, *sig_dest = NULL;
|
||||
const char *fwd_host = NULL, *dest_host = NULL;
|
||||
struct sshbuf *msg = NULL, *data = NULL, *sid = NULL;
|
||||
struct sshkey *key = NULL;
|
||||
struct identity *id;
|
||||
|
@ -484,31 +737,41 @@ process_sign_request2(SocketEntry *e)
|
|||
verbose_f("%s key not found", sshkey_type(key));
|
||||
goto send;
|
||||
}
|
||||
/*
|
||||
* 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 ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("fingerprint failed");
|
||||
|
||||
if (id->ndest_constraints != 0) {
|
||||
if (e->nsession_ids == 0) {
|
||||
logit_f("refusing use of destination-constrained key "
|
||||
"to sign on unbound connection");
|
||||
goto send;
|
||||
}
|
||||
if (parse_userauth_request(data, key, &user, &sid) != 0) {
|
||||
logit_f("refusing use of destination-constrained key "
|
||||
"to sign an unidentified signature");
|
||||
goto send;
|
||||
}
|
||||
/* XXX logspam */
|
||||
debug_f("user=%s", user);
|
||||
if (identity_permitted(id, e, user, &fwd_host, &dest_host) != 0)
|
||||
goto send;
|
||||
/* XXX display fwd_host/dest_host in askpass UI */
|
||||
/*
|
||||
* Ensure that the session ID is the most recent one
|
||||
* registered on the socket - it should have been bound by
|
||||
* ssh immediately before userauth.
|
||||
*/
|
||||
if (buf_equal(sid,
|
||||
e->session_ids[e->nsession_ids - 1].sid) != 0) {
|
||||
error_f("unexpected session ID (%zu listed) on "
|
||||
"signature request for target user %s with "
|
||||
"key %s %s", e->nsession_ids, user,
|
||||
sshkey_type(id->key), fp);
|
||||
goto send;
|
||||
}
|
||||
xasprintf(&sig_dest, "public key authentication request for "
|
||||
"user \"%s\" to listed host", user);
|
||||
}
|
||||
if (id->confirm && confirm_key(id, sig_dest) != 0) {
|
||||
verbose_f("user refused key");
|
||||
|
@ -521,9 +784,6 @@ process_sign_request2(SocketEntry *e)
|
|||
goto send;
|
||||
}
|
||||
if ((id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD)) {
|
||||
if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("fingerprint failed");
|
||||
notifier = notify_start(0,
|
||||
"Confirm user presence for key %s %s%s%s",
|
||||
sshkey_type(id->key), fp,
|
||||
|
@ -580,6 +840,9 @@ process_remove_identity(SocketEntry *e)
|
|||
debug_f("key not found");
|
||||
goto done;
|
||||
}
|
||||
/* identity not visible, cannot be removed */
|
||||
if (identity_permitted(id, e, NULL, NULL, NULL) != 0)
|
||||
goto done; /* error already logged */
|
||||
/* We have this key, free it. */
|
||||
if (idtab->nentries < 1)
|
||||
fatal_f("internal error: nentries %d", idtab->nentries);
|
||||
|
@ -639,10 +902,119 @@ reaper(void)
|
|||
}
|
||||
|
||||
static int
|
||||
parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp)
|
||||
parse_dest_constraint_hop(struct sshbuf *b, struct dest_constraint_hop *dch)
|
||||
{
|
||||
u_char key_is_ca;
|
||||
size_t elen = 0;
|
||||
int r;
|
||||
struct sshkey *k = NULL;
|
||||
char *fp;
|
||||
|
||||
memset(dch, '\0', sizeof(*dch));
|
||||
if ((r = sshbuf_get_cstring(b, &dch->user, NULL)) != 0 ||
|
||||
(r = sshbuf_get_cstring(b, &dch->hostname, NULL)) != 0 ||
|
||||
(r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) {
|
||||
error_fr(r, "parse");
|
||||
goto out;
|
||||
}
|
||||
if (elen != 0) {
|
||||
error_f("unsupported extensions (len %zu)", elen);
|
||||
r = SSH_ERR_FEATURE_UNSUPPORTED;
|
||||
goto out;
|
||||
}
|
||||
if (*dch->hostname == '\0') {
|
||||
free(dch->hostname);
|
||||
dch->hostname = NULL;
|
||||
}
|
||||
if (*dch->user == '\0') {
|
||||
free(dch->user);
|
||||
dch->user = NULL;
|
||||
}
|
||||
while (sshbuf_len(b) != 0) {
|
||||
dch->keys = xrecallocarray(dch->keys, dch->nkeys,
|
||||
dch->nkeys + 1, sizeof(*dch->keys));
|
||||
dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys,
|
||||
dch->nkeys + 1, sizeof(*dch->key_is_ca));
|
||||
if ((r = sshkey_froms(b, &k)) != 0 ||
|
||||
(r = sshbuf_get_u8(b, &key_is_ca)) != 0)
|
||||
goto out;
|
||||
if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("fingerprint failed");
|
||||
debug3_f("%s%s%s: adding %skey %s %s",
|
||||
dch->user == NULL ? "" : dch->user,
|
||||
dch->user == NULL ? "" : "@",
|
||||
dch->hostname, key_is_ca ? "CA " : "", sshkey_type(k), fp);
|
||||
free(fp);
|
||||
dch->keys[dch->nkeys] = k;
|
||||
dch->key_is_ca[dch->nkeys] = key_is_ca != 0;
|
||||
dch->nkeys++;
|
||||
k = NULL; /* transferred */
|
||||
}
|
||||
/* success */
|
||||
r = 0;
|
||||
out:
|
||||
sshkey_free(k);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_dest_constraint(struct sshbuf *m, struct dest_constraint *dc)
|
||||
{
|
||||
struct sshbuf *b = NULL, *frombuf = NULL, *tobuf = NULL;
|
||||
int r;
|
||||
size_t elen = 0;
|
||||
|
||||
debug3_f("entering");
|
||||
|
||||
memset(dc, '\0', sizeof(*dc));
|
||||
if ((r = sshbuf_froms(m, &b)) != 0 ||
|
||||
(r = sshbuf_froms(b, &frombuf)) != 0 ||
|
||||
(r = sshbuf_froms(b, &tobuf)) != 0 ||
|
||||
(r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) {
|
||||
error_fr(r, "parse");
|
||||
goto out;
|
||||
}
|
||||
if ((r = parse_dest_constraint_hop(frombuf, &dc->from) != 0) ||
|
||||
(r = parse_dest_constraint_hop(tobuf, &dc->to) != 0))
|
||||
goto out; /* already logged */
|
||||
if (elen != 0) {
|
||||
error_f("unsupported extensions (len %zu)", elen);
|
||||
r = SSH_ERR_FEATURE_UNSUPPORTED;
|
||||
goto out;
|
||||
}
|
||||
debug2_f("parsed %s (%u keys) > %s%s%s (%u keys)",
|
||||
dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys,
|
||||
dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "",
|
||||
dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys);
|
||||
/* check consistency */
|
||||
if ((dc->from.hostname == NULL) != (dc->from.nkeys == 0) ||
|
||||
dc->from.user != NULL) {
|
||||
error_f("inconsistent \"from\" specification");
|
||||
r = SSH_ERR_INVALID_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
if (dc->to.hostname == NULL || dc->to.nkeys == 0) {
|
||||
error_f("incomplete \"to\" specification");
|
||||
r = SSH_ERR_INVALID_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
/* success */
|
||||
r = 0;
|
||||
out:
|
||||
sshbuf_free(b);
|
||||
sshbuf_free(frombuf);
|
||||
sshbuf_free(tobuf);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp,
|
||||
struct dest_constraint **dcsp, size_t *ndcsp)
|
||||
{
|
||||
char *ext_name = NULL;
|
||||
int r;
|
||||
struct sshbuf *b = NULL;
|
||||
|
||||
if ((r = sshbuf_get_cstring(m, &ext_name, NULL)) != 0) {
|
||||
error_fr(r, "parse constraint extension");
|
||||
|
@ -664,6 +1036,27 @@ parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp)
|
|||
error_fr(r, "parse %s", ext_name);
|
||||
goto out;
|
||||
}
|
||||
} else if (strcmp(ext_name,
|
||||
"restrict-destination-v00@openssh.com") == 0) {
|
||||
if (*dcsp != NULL) {
|
||||
error_f("%s already set", ext_name);
|
||||
goto out;
|
||||
}
|
||||
if ((r = sshbuf_froms(m, &b)) != 0) {
|
||||
error_fr(r, "parse %s outer", ext_name);
|
||||
goto out;
|
||||
}
|
||||
while (sshbuf_len(b) != 0) {
|
||||
if (*ndcsp >= AGENT_MAX_DEST_CONSTRAINTS) {
|
||||
error_f("too many %s constraints", ext_name);
|
||||
goto out;
|
||||
}
|
||||
*dcsp = xrecallocarray(*dcsp, *ndcsp, *ndcsp + 1,
|
||||
sizeof(**dcsp));
|
||||
if ((r = parse_dest_constraint(b,
|
||||
*dcsp + (*ndcsp)++)) != 0)
|
||||
goto out; /* error already logged */
|
||||
}
|
||||
} else {
|
||||
error_f("unsupported constraint \"%s\"", ext_name);
|
||||
r = SSH_ERR_FEATURE_UNSUPPORTED;
|
||||
|
@ -673,12 +1066,14 @@ parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp)
|
|||
r = 0;
|
||||
out:
|
||||
free(ext_name);
|
||||
sshbuf_free(b);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp,
|
||||
u_int *secondsp, int *confirmp, char **sk_providerp)
|
||||
u_int *secondsp, int *confirmp, char **sk_providerp,
|
||||
struct dest_constraint **dcsp, size_t *ndcsp)
|
||||
{
|
||||
u_char ctype;
|
||||
int r;
|
||||
|
@ -733,7 +1128,7 @@ parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp,
|
|||
break;
|
||||
case SSH_AGENT_CONSTRAIN_EXTENSION:
|
||||
if ((r = parse_key_constraint_extension(m,
|
||||
sk_providerp)) != 0)
|
||||
sk_providerp, dcsp, ndcsp)) != 0)
|
||||
goto out; /* error already logged */
|
||||
break;
|
||||
default:
|
||||
|
@ -757,6 +1152,8 @@ process_add_identity(SocketEntry *e)
|
|||
char canonical_provider[PATH_MAX];
|
||||
time_t death = 0;
|
||||
u_int seconds = 0;
|
||||
struct dest_constraint *dest_constraints = NULL;
|
||||
size_t ndest_constraints = 0;
|
||||
struct sshkey *k = NULL;
|
||||
int r = SSH_ERR_INTERNAL_ERROR;
|
||||
|
||||
|
@ -768,7 +1165,7 @@ process_add_identity(SocketEntry *e)
|
|||
goto out;
|
||||
}
|
||||
if (parse_key_constraints(e->request, k, &death, &seconds, &confirm,
|
||||
&sk_provider) != 0) {
|
||||
&sk_provider, &dest_constraints, &ndest_constraints) != 0) {
|
||||
error_f("failed to parse constraints");
|
||||
sshbuf_reset(e->request);
|
||||
goto out;
|
||||
|
@ -811,10 +1208,15 @@ process_add_identity(SocketEntry *e)
|
|||
/* Increment the number of identities. */
|
||||
idtab->nentries++;
|
||||
} else {
|
||||
/* identity not visible, do not update */
|
||||
if (identity_permitted(id, e, NULL, NULL, NULL) != 0)
|
||||
goto out; /* error already logged */
|
||||
/* key state might have been updated */
|
||||
sshkey_free(id->key);
|
||||
free(id->comment);
|
||||
free(id->sk_provider);
|
||||
free_dest_constraints(id->dest_constraints,
|
||||
id->ndest_constraints);
|
||||
}
|
||||
/* success */
|
||||
id->key = k;
|
||||
|
@ -822,23 +1224,29 @@ process_add_identity(SocketEntry *e)
|
|||
id->death = death;
|
||||
id->confirm = confirm;
|
||||
id->sk_provider = sk_provider;
|
||||
id->dest_constraints = dest_constraints;
|
||||
id->ndest_constraints = ndest_constraints;
|
||||
|
||||
if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT,
|
||||
SSH_FP_DEFAULT)) == NULL)
|
||||
fatal_f("sshkey_fingerprint failed");
|
||||
debug_f("add %s %s \"%.100s\" (life: %u) (confirm: %u) "
|
||||
"(provider: %s)", sshkey_ssh_name(k), fp, comment, seconds,
|
||||
confirm, sk_provider == NULL ? "none" : sk_provider);
|
||||
"(provider: %s) (destination constraints: %zu)",
|
||||
sshkey_ssh_name(k), fp, comment, seconds, confirm,
|
||||
sk_provider == NULL ? "none" : sk_provider, ndest_constraints);
|
||||
free(fp);
|
||||
/* transferred */
|
||||
k = NULL;
|
||||
comment = NULL;
|
||||
sk_provider = NULL;
|
||||
dest_constraints = NULL;
|
||||
ndest_constraints = 0;
|
||||
success = 1;
|
||||
out:
|
||||
free(sk_provider);
|
||||
free(comment);
|
||||
sshkey_free(k);
|
||||
free_dest_constraints(dest_constraints, ndest_constraints);
|
||||
send_status(e, success);
|
||||
}
|
||||
|
||||
|
@ -921,6 +1329,8 @@ process_add_smartcard_key(SocketEntry *e)
|
|||
time_t death = 0;
|
||||
struct sshkey **keys = NULL, *k;
|
||||
Identity *id;
|
||||
struct dest_constraint *dest_constraints = NULL;
|
||||
size_t ndest_constraints = 0;
|
||||
|
||||
debug2_f("entering");
|
||||
if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 ||
|
||||
|
@ -929,7 +1339,7 @@ process_add_smartcard_key(SocketEntry *e)
|
|||
goto send;
|
||||
}
|
||||
if (parse_key_constraints(e->request, NULL, &death, &seconds, &confirm,
|
||||
NULL) != 0) {
|
||||
NULL, &dest_constraints, &ndest_constraints) != 0) {
|
||||
error_f("failed to parse constraints");
|
||||
goto send;
|
||||
}
|
||||
|
@ -963,6 +1373,10 @@ process_add_smartcard_key(SocketEntry *e)
|
|||
}
|
||||
id->death = death;
|
||||
id->confirm = confirm;
|
||||
id->dest_constraints = dest_constraints;
|
||||
id->ndest_constraints = ndest_constraints;
|
||||
dest_constraints = NULL; /* transferred */
|
||||
ndest_constraints = 0;
|
||||
TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
|
||||
idtab->nentries++;
|
||||
success = 1;
|
||||
|
@ -976,6 +1390,7 @@ send:
|
|||
free(provider);
|
||||
free(keys);
|
||||
free(comments);
|
||||
free_dest_constraints(dest_constraints, ndest_constraints);
|
||||
send_status(e, success);
|
||||
}
|
||||
|
||||
|
@ -1029,8 +1444,8 @@ process_ext_session_bind(SocketEntry *e)
|
|||
struct sshkey *key = NULL;
|
||||
struct sshbuf *sid = NULL, *sig = NULL;
|
||||
char *fp = NULL;
|
||||
u_char fwd;
|
||||
size_t i;
|
||||
u_char fwd = 0;
|
||||
|
||||
debug2_f("entering");
|
||||
if ((r = sshkey_froms(e->request, &key)) != 0 ||
|
||||
|
@ -1051,6 +1466,12 @@ process_ext_session_bind(SocketEntry *e)
|
|||
}
|
||||
/* check whether sid/key already recorded */
|
||||
for (i = 0; i < e->nsession_ids; i++) {
|
||||
if (!e->session_ids[i].forwarded) {
|
||||
error_f("attempt to bind session ID to socket "
|
||||
"previously bound for authentication attempt");
|
||||
r = -1;
|
||||
goto out;
|
||||
}
|
||||
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) {
|
||||
|
|
Loading…
Reference in New Issue