version 1.237, 2019/06/28 13:35:04 |
version 1.238, 2019/10/31 21:22:01 |
|
|
#include <sys/socket.h> |
#include <sys/socket.h> |
#include <sys/stat.h> |
#include <sys/stat.h> |
#include <sys/un.h> |
#include <sys/un.h> |
|
#include <sys/wait.h> |
|
|
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
#include <openssl/evp.h> |
#include <openssl/evp.h> |
|
|
#include "digest.h" |
#include "digest.h" |
#include "ssherr.h" |
#include "ssherr.h" |
#include "match.h" |
#include "match.h" |
|
#include "msg.h" |
#ifdef ENABLE_PKCS11 |
#include "pathnames.h" |
#include "ssh-pkcs11.h" |
#include "ssh-pkcs11.h" |
#endif |
#include "ssh-sk.h" |
|
|
#ifndef DEFAULT_PKCS11_WHITELIST |
#ifndef DEFAULT_PROVIDER_WHITELIST |
# define DEFAULT_PKCS11_WHITELIST "/usr/lib*/*,/usr/local/lib*/*" |
# define DEFAULT_PROVIDER_WHITELIST "/usr/lib*/*,/usr/local/lib*/*" |
#endif |
#endif |
|
|
/* Maximum accepted message length */ |
/* Maximum accepted message length */ |
|
|
char *provider; |
char *provider; |
time_t death; |
time_t death; |
u_int confirm; |
u_int confirm; |
|
char *sk_provider; |
} Identity; |
} Identity; |
|
|
struct idtable { |
struct idtable { |
|
|
char socket_name[PATH_MAX]; |
char socket_name[PATH_MAX]; |
char socket_dir[PATH_MAX]; |
char socket_dir[PATH_MAX]; |
|
|
/* PKCS#11 path whitelist */ |
/* PKCS#11/Security key path whitelist */ |
static char *pkcs11_whitelist; |
static char *provider_whitelist; |
|
|
/* locking */ |
/* locking */ |
#define LOCK_SIZE 32 |
#define LOCK_SIZE 32 |
|
|
sshkey_free(id->key); |
sshkey_free(id->key); |
free(id->provider); |
free(id->provider); |
free(id->comment); |
free(id->comment); |
|
free(id->sk_provider); |
free(id); |
free(id); |
} |
} |
|
|
|
|
return NULL; |
return NULL; |
} |
} |
|
|
|
static int |
|
provider_sign(const char *provider, struct sshkey *key, |
|
u_char **sigp, size_t *lenp, |
|
const u_char *data, size_t datalen, |
|
const char *alg, u_int compat) |
|
{ |
|
int status, pair[2], r = SSH_ERR_INTERNAL_ERROR; |
|
pid_t pid; |
|
char *helper, *verbosity = NULL; |
|
struct sshbuf *kbuf, *req, *resp; |
|
u_char version; |
|
|
|
debug3("%s: start for provider %s", __func__, provider); |
|
|
|
*sigp = NULL; |
|
*lenp = 0; |
|
|
|
helper = getenv("SSH_SK_HELPER"); |
|
if (helper == NULL || strlen(helper) == 0) |
|
helper = _PATH_SSH_SK_HELPER; |
|
if (log_level_get() >= SYSLOG_LEVEL_DEBUG1) |
|
verbosity = "-vvv"; |
|
|
|
/* Start helper */ |
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { |
|
error("socketpair: %s", strerror(errno)); |
|
return SSH_ERR_SYSTEM_ERROR; |
|
} |
|
if ((pid = fork()) == -1) { |
|
error("fork: %s", strerror(errno)); |
|
close(pair[0]); |
|
close(pair[1]); |
|
return SSH_ERR_SYSTEM_ERROR; |
|
} |
|
if (pid == 0) { |
|
if ((dup2(pair[1], STDIN_FILENO) == -1) || |
|
(dup2(pair[1], STDOUT_FILENO) == -1)) |
|
fatal("%s: dup2: %s", __func__, ssh_err(r)); |
|
close(pair[0]); |
|
close(pair[1]); |
|
closefrom(STDERR_FILENO + 1); |
|
debug("%s: starting %s %s", __func__, helper, |
|
verbosity == NULL ? "" : verbosity); |
|
execlp(helper, helper, verbosity, (char *)NULL); |
|
fatal("%s: execlp: %s", __func__, strerror(errno)); |
|
} |
|
close(pair[1]); |
|
|
|
if ((kbuf = sshbuf_new()) == NULL || |
|
(req = sshbuf_new()) == NULL || |
|
(resp = sshbuf_new()) == NULL) |
|
fatal("%s: sshbuf_new failed", __func__); |
|
|
|
if ((r = sshkey_private_serialize(key, kbuf)) != 0 || |
|
(r = sshbuf_put_stringb(req, kbuf)) != 0 || |
|
(r = sshbuf_put_cstring(req, provider)) != 0 || |
|
(r = sshbuf_put_string(req, data, datalen)) != 0 || |
|
(r = sshbuf_put_u32(req, compat)) != 0) |
|
fatal("%s: compose: %s", __func__, ssh_err(r)); |
|
if ((r = ssh_msg_send(pair[0], SSH_SK_HELPER_VERSION, req)) != 0) { |
|
error("%s: send: %s", __func__, ssh_err(r)); |
|
goto out; |
|
} |
|
if ((r = ssh_msg_recv(pair[0], resp)) != 0) { |
|
error("%s: receive: %s", __func__, ssh_err(r)); |
|
goto out; |
|
} |
|
if ((r = sshbuf_get_u8(resp, &version)) != 0) { |
|
error("%s: parse version: %s", __func__, ssh_err(r)); |
|
goto out; |
|
} |
|
if (version != SSH_SK_HELPER_VERSION) { |
|
error("%s: unsupported version: got %u, expected %u", |
|
__func__, version, SSH_SK_HELPER_VERSION); |
|
r = SSH_ERR_INVALID_FORMAT; |
|
goto out; |
|
} |
|
if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) { |
|
error("%s: parse signature: %s", __func__, ssh_err(r)); |
|
r = SSH_ERR_INVALID_FORMAT; |
|
goto out; |
|
} |
|
if (sshbuf_len(resp) != 0) { |
|
error("%s: trailing data in response", __func__); |
|
r = SSH_ERR_INVALID_FORMAT; |
|
goto out; |
|
} |
|
/* success */ |
|
r = 0; |
|
out: |
|
while (waitpid(pid, &status, 0) == -1) { |
|
if (errno != EINTR) |
|
fatal("%s: waitpid: %s", __func__, ssh_err(r)); |
|
} |
|
if (!WIFEXITED(status)) { |
|
error("%s: helper %s exited abnormally", __func__, helper); |
|
if (r == 0) |
|
r = SSH_ERR_SYSTEM_ERROR; |
|
} else if (WEXITSTATUS(status) != 0) { |
|
error("%s: helper %s exited with non-zero exit status", |
|
__func__, helper); |
|
if (r == 0) |
|
r = SSH_ERR_SYSTEM_ERROR; |
|
} |
|
if (r != 0) { |
|
freezero(*sigp, *lenp); |
|
*sigp = NULL; |
|
*lenp = 0; |
|
} |
|
sshbuf_free(kbuf); |
|
sshbuf_free(req); |
|
sshbuf_free(resp); |
|
return r; |
|
} |
|
|
/* ssh2 only */ |
/* ssh2 only */ |
static void |
static void |
process_sign_request2(SocketEntry *e) |
process_sign_request2(SocketEntry *e) |
|
|
verbose("%s: user refused key", __func__); |
verbose("%s: user refused key", __func__); |
goto send; |
goto send; |
} |
} |
if ((r = sshkey_sign(id->key, &signature, &slen, |
if (id->sk_provider != NULL) { |
data, dlen, agent_decode_alg(key, flags), compat)) != 0) { |
if ((r = provider_sign(id->sk_provider, id->key, &signature, |
error("%s: sshkey_sign: %s", __func__, ssh_err(r)); |
&slen, data, dlen, agent_decode_alg(key, flags), |
goto send; |
compat)) != 0) { |
|
error("%s: sshkey_sign: %s", __func__, ssh_err(r)); |
|
goto send; |
|
} |
|
} else { |
|
if ((r = sshkey_sign(id->key, &signature, &slen, |
|
data, dlen, agent_decode_alg(key, flags), compat)) != 0) { |
|
error("%s: sshkey_sign: %s", __func__, ssh_err(r)); |
|
goto send; |
|
} |
} |
} |
/* Success */ |
/* Success */ |
ok = 0; |
ok = 0; |
|
|
Identity *id; |
Identity *id; |
int success = 0, confirm = 0; |
int success = 0, confirm = 0; |
u_int seconds, maxsign; |
u_int seconds, maxsign; |
char *comment = NULL; |
char *fp, *comment = NULL, *ext_name = NULL, *sk_provider = NULL; |
time_t death = 0; |
time_t death = 0; |
struct sshkey *k = NULL; |
struct sshkey *k = NULL; |
u_char ctype; |
u_char ctype; |
|
|
goto err; |
goto err; |
} |
} |
break; |
break; |
|
case SSH_AGENT_CONSTRAIN_EXTENSION: |
|
if ((r = sshbuf_get_cstring(e->request, |
|
&ext_name, NULL)) != 0) { |
|
error("%s: cannot parse extension: %s", |
|
__func__, ssh_err(r)); |
|
goto err; |
|
} |
|
debug("%s: constraint ext %s", __func__, ext_name); |
|
if (strcmp(ext_name, "sk-provider@openssh.com") == 0) { |
|
if (sk_provider != NULL) { |
|
error("%s already set", ext_name); |
|
goto err; |
|
} |
|
if ((r = sshbuf_get_cstring(e->request, |
|
&sk_provider, NULL)) != 0) { |
|
error("%s: cannot parse %s: %s", |
|
__func__, ext_name, ssh_err(r)); |
|
goto err; |
|
} |
|
} else { |
|
error("%s: unsupported constraint \"%s\"", |
|
__func__, ext_name); |
|
goto err; |
|
} |
|
free(ext_name); |
|
break; |
default: |
default: |
error("%s: Unknown constraint %d", __func__, ctype); |
error("%s: Unknown constraint %d", __func__, ctype); |
err: |
err: |
|
free(sk_provider); |
|
free(ext_name); |
sshbuf_reset(e->request); |
sshbuf_reset(e->request); |
free(comment); |
free(comment); |
sshkey_free(k); |
sshkey_free(k); |
goto send; |
goto send; |
} |
} |
} |
} |
|
if (sk_provider != NULL) { |
|
if (sshkey_type_plain(k->type) != KEY_ECDSA_SK) { |
|
error("Cannot add provider: %s is not a security key", |
|
sshkey_type(k)); |
|
free(sk_provider); |
|
goto send; |
|
} |
|
if (match_pattern_list(sk_provider, |
|
provider_whitelist, 0) != 1) { |
|
error("Refusing add key: provider %s not whitelisted", |
|
sk_provider); |
|
free(sk_provider); |
|
goto send; |
|
} |
|
} |
|
|
success = 1; |
success = 1; |
if (lifetime && !death) |
if (lifetime && !death) |
|
|
/* key state might have been updated */ |
/* key state might have been updated */ |
sshkey_free(id->key); |
sshkey_free(id->key); |
free(id->comment); |
free(id->comment); |
|
free(id->sk_provider); |
} |
} |
id->key = k; |
id->key = k; |
id->comment = comment; |
id->comment = comment; |
id->death = death; |
id->death = death; |
id->confirm = confirm; |
id->confirm = confirm; |
|
id->sk_provider = sk_provider; |
|
|
|
if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, |
|
SSH_FP_DEFAULT)) == NULL) |
|
fatal("%s: sshkey_fingerprint failed", __func__); |
|
debug("%s: add %s %s \"%.100s\" (life: %u) (confirm: %u) " |
|
"(provider: %s)", __func__, sshkey_ssh_name(k), fp, comment, |
|
seconds, confirm, sk_provider == NULL ? "none" : sk_provider); |
|
free(fp); |
send: |
send: |
send_status(e, success); |
send_status(e, success); |
} |
} |
|
|
provider, strerror(errno)); |
provider, strerror(errno)); |
goto send; |
goto send; |
} |
} |
if (match_pattern_list(canonical_provider, pkcs11_whitelist, 0) != 1) { |
if (match_pattern_list(canonical_provider, provider_whitelist, 0) != 1) { |
verbose("refusing PKCS#11 add of \"%.100s\": " |
verbose("refusing PKCS#11 add of \"%.100s\": " |
"provider not whitelisted", canonical_provider); |
"provider not whitelisted", canonical_provider); |
goto send; |
goto send; |
|
|
{ |
{ |
fprintf(stderr, |
fprintf(stderr, |
"usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" |
"usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" |
" [-P pkcs11_whitelist] [-t life] [command [arg ...]]\n" |
" [-P provider_whitelist] [-t life] [command [arg ...]]\n" |
" ssh-agent [-c | -s] -k\n"); |
" ssh-agent [-c | -s] -k\n"); |
exit(1); |
exit(1); |
} |
} |
|
|
k_flag++; |
k_flag++; |
break; |
break; |
case 'P': |
case 'P': |
if (pkcs11_whitelist != NULL) |
if (provider_whitelist != NULL) |
fatal("-P option already specified"); |
fatal("-P option already specified"); |
pkcs11_whitelist = xstrdup(optarg); |
provider_whitelist = xstrdup(optarg); |
break; |
break; |
case 's': |
case 's': |
if (c_flag) |
if (c_flag) |
|
|
if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) |
if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) |
usage(); |
usage(); |
|
|
if (pkcs11_whitelist == NULL) |
if (provider_whitelist == NULL) |
pkcs11_whitelist = xstrdup(DEFAULT_PKCS11_WHITELIST); |
provider_whitelist = xstrdup(DEFAULT_PROVIDER_WHITELIST); |
|
|
if (ac == 0 && !c_flag && !s_flag) { |
if (ac == 0 && !c_flag && !s_flag) { |
shell = getenv("SHELL"); |
shell = getenv("SHELL"); |