upstream: When downloading resident keys from a FIDO token, pass
back the user ID that was used when the key was created and append it to the filename the key is written to (if it is not the default). Avoids keys being clobbered if the user created multiple resident keys with the same application string but different user IDs. feedback Pedro Martelletto; ok markus NB. increments SSH_SK_VERSION_MAJOR OpenBSD-Commit-ID: dbd658b5950f583106d945641a634bc6562dd3a3
This commit is contained in:
parent
d4bed54456
commit
0001d04e55
6
sk-api.h
6
sk-api.h
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: sk-api.h,v 1.12 2021/02/18 02:15:07 djm Exp $ */
|
/* $OpenBSD: sk-api.h,v 1.13 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Google LLC
|
* Copyright (c) 2019 Google LLC
|
||||||
*
|
*
|
||||||
|
@ -66,6 +66,8 @@ struct sk_resident_key {
|
||||||
char *application;
|
char *application;
|
||||||
struct sk_enroll_response key;
|
struct sk_enroll_response key;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
|
uint8_t *user_id;
|
||||||
|
size_t user_id_len;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sk_option {
|
struct sk_option {
|
||||||
|
@ -74,7 +76,7 @@ struct sk_option {
|
||||||
uint8_t required;
|
uint8_t required;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SSH_SK_VERSION_MAJOR 0x00070000 /* current API version */
|
#define SSH_SK_VERSION_MAJOR 0x00080000 /* current API version */
|
||||||
#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000
|
#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000
|
||||||
|
|
||||||
/* Return the version of the middleware API */
|
/* Return the version of the middleware API */
|
||||||
|
|
35
sk-usbhid.c
35
sk-usbhid.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: sk-usbhid.c,v 1.31 2021/10/01 04:50:36 djm Exp $ */
|
/* $OpenBSD: sk-usbhid.c,v 1.32 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Markus Friedl
|
* Copyright (c) 2019 Markus Friedl
|
||||||
* Copyright (c) 2020 Pedro Martelletto
|
* Copyright (c) 2020 Pedro Martelletto
|
||||||
|
@ -1088,9 +1088,11 @@ read_rks(struct sk_usbhid *sk, const char *pin,
|
||||||
fido_credman_metadata_t *metadata = NULL;
|
fido_credman_metadata_t *metadata = NULL;
|
||||||
fido_credman_rp_t *rp = NULL;
|
fido_credman_rp_t *rp = NULL;
|
||||||
fido_credman_rk_t *rk = NULL;
|
fido_credman_rk_t *rk = NULL;
|
||||||
size_t i, j, nrp, nrk;
|
size_t i, j, nrp, nrk, user_id_len;
|
||||||
const fido_cred_t *cred;
|
const fido_cred_t *cred;
|
||||||
|
const char *rp_id, *rp_name, *user_name;
|
||||||
struct sk_resident_key *srk = NULL, **tmp;
|
struct sk_resident_key *srk = NULL, **tmp;
|
||||||
|
const u_char *user_id;
|
||||||
|
|
||||||
if (pin == NULL) {
|
if (pin == NULL) {
|
||||||
skdebug(__func__, "no PIN specified");
|
skdebug(__func__, "no PIN specified");
|
||||||
|
@ -1132,12 +1134,16 @@ read_rks(struct sk_usbhid *sk, const char *pin,
|
||||||
|
|
||||||
/* Iterate over RP IDs that have resident keys */
|
/* Iterate over RP IDs that have resident keys */
|
||||||
for (i = 0; i < nrp; i++) {
|
for (i = 0; i < nrp; i++) {
|
||||||
|
rp_id = fido_credman_rp_id(rp, i);
|
||||||
|
rp_name = fido_credman_rp_name(rp, i);
|
||||||
skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu",
|
skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu",
|
||||||
i, fido_credman_rp_name(rp, i), fido_credman_rp_id(rp, i),
|
i, rp_name == NULL ? "(none)" : rp_name,
|
||||||
|
rp_id == NULL ? "(none)" : rp_id,
|
||||||
fido_credman_rp_id_hash_len(rp, i));
|
fido_credman_rp_id_hash_len(rp, i));
|
||||||
|
|
||||||
/* Skip non-SSH RP IDs */
|
/* Skip non-SSH RP IDs */
|
||||||
if (strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0)
|
if (rp_id == NULL ||
|
||||||
|
strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
fido_credman_rk_free(&rk);
|
fido_credman_rk_free(&rk);
|
||||||
|
@ -1161,17 +1167,23 @@ read_rks(struct sk_usbhid *sk, const char *pin,
|
||||||
skdebug(__func__, "no RK in slot %zu", j);
|
skdebug(__func__, "no RK in slot %zu", j);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
skdebug(__func__, "Device %s RP \"%s\" slot %zu: "
|
if ((user_name = fido_cred_user_name(cred)) == NULL)
|
||||||
"type %d flags 0x%02x prot 0x%02x", sk->path,
|
user_name = "";
|
||||||
fido_credman_rp_id(rp, i), j, fido_cred_type(cred),
|
user_id = fido_cred_user_id_ptr(cred);
|
||||||
|
user_id_len = fido_cred_user_id_len(cred);
|
||||||
|
skdebug(__func__, "Device %s RP \"%s\" user \"%s\" "
|
||||||
|
"uidlen %zu slot %zu: type %d flags 0x%02x "
|
||||||
|
"prot 0x%02x", sk->path, rp_id, user_name,
|
||||||
|
user_id_len, j, fido_cred_type(cred),
|
||||||
fido_cred_flags(cred), fido_cred_prot(cred));
|
fido_cred_flags(cred), fido_cred_prot(cred));
|
||||||
|
|
||||||
/* build response entry */
|
/* build response entry */
|
||||||
if ((srk = calloc(1, sizeof(*srk))) == NULL ||
|
if ((srk = calloc(1, sizeof(*srk))) == NULL ||
|
||||||
(srk->key.key_handle = calloc(1,
|
(srk->key.key_handle = calloc(1,
|
||||||
fido_cred_id_len(cred))) == NULL ||
|
fido_cred_id_len(cred))) == NULL ||
|
||||||
(srk->application = strdup(fido_credman_rp_id(rp,
|
(srk->application = strdup(rp_id)) == NULL ||
|
||||||
i))) == NULL) {
|
(user_id_len > 0 &&
|
||||||
|
(srk->user_id = calloc(1, user_id_len)) == NULL)) {
|
||||||
skdebug(__func__, "alloc sk_resident_key");
|
skdebug(__func__, "alloc sk_resident_key");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -1179,6 +1191,9 @@ read_rks(struct sk_usbhid *sk, const char *pin,
|
||||||
srk->key.key_handle_len = fido_cred_id_len(cred);
|
srk->key.key_handle_len = fido_cred_id_len(cred);
|
||||||
memcpy(srk->key.key_handle, fido_cred_id_ptr(cred),
|
memcpy(srk->key.key_handle, fido_cred_id_ptr(cred),
|
||||||
srk->key.key_handle_len);
|
srk->key.key_handle_len);
|
||||||
|
srk->user_id_len = user_id_len;
|
||||||
|
if (srk->user_id_len != 0)
|
||||||
|
memcpy(srk->user_id, user_id, srk->user_id_len);
|
||||||
|
|
||||||
switch (fido_cred_type(cred)) {
|
switch (fido_cred_type(cred)) {
|
||||||
case COSE_ES256:
|
case COSE_ES256:
|
||||||
|
@ -1219,6 +1234,7 @@ read_rks(struct sk_usbhid *sk, const char *pin,
|
||||||
free(srk->application);
|
free(srk->application);
|
||||||
freezero(srk->key.public_key, srk->key.public_key_len);
|
freezero(srk->key.public_key, srk->key.public_key_len);
|
||||||
freezero(srk->key.key_handle, srk->key.key_handle_len);
|
freezero(srk->key.key_handle, srk->key.key_handle_len);
|
||||||
|
freezero(srk->user_id, srk->user_id_len);
|
||||||
freezero(srk, sizeof(*srk));
|
freezero(srk, sizeof(*srk));
|
||||||
}
|
}
|
||||||
fido_credman_rp_free(&rp);
|
fido_credman_rp_free(&rp);
|
||||||
|
@ -1271,6 +1287,7 @@ sk_load_resident_keys(const char *pin, struct sk_option **options,
|
||||||
free(rks[i]->application);
|
free(rks[i]->application);
|
||||||
freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
|
freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
|
||||||
freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
|
freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
|
||||||
|
freezero(rks[i]->user_id, rks[i]->user_id_len);
|
||||||
freezero(rks[i], sizeof(*rks[i]));
|
freezero(rks[i], sizeof(*rks[i]));
|
||||||
}
|
}
|
||||||
free(rks);
|
free(rks);
|
||||||
|
|
28
ssh-add.c
28
ssh-add.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: ssh-add.c,v 1.160 2021/04/03 06:18:41 djm Exp $ */
|
/* $OpenBSD: ssh-add.c,v 1.161 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
|
@ -580,25 +580,26 @@ lock_agent(int agent_fd, int lock)
|
||||||
static int
|
static int
|
||||||
load_resident_keys(int agent_fd, const char *skprovider, int qflag)
|
load_resident_keys(int agent_fd, const char *skprovider, int qflag)
|
||||||
{
|
{
|
||||||
struct sshkey **keys;
|
struct sshsk_resident_key **srks;
|
||||||
size_t nkeys, i;
|
size_t nsrks, i;
|
||||||
|
struct sshkey *key;
|
||||||
int r, ok = 0;
|
int r, ok = 0;
|
||||||
char *fp;
|
char *fp;
|
||||||
|
|
||||||
pass = read_passphrase("Enter PIN for authenticator: ", RP_ALLOW_STDIN);
|
pass = read_passphrase("Enter PIN for authenticator: ", RP_ALLOW_STDIN);
|
||||||
if ((r = sshsk_load_resident(skprovider, NULL, pass,
|
if ((r = sshsk_load_resident(skprovider, NULL, pass, 0,
|
||||||
&keys, &nkeys)) != 0) {
|
&srks, &nsrks)) != 0) {
|
||||||
error_r(r, "Unable to load resident keys");
|
error_r(r, "Unable to load resident keys");
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
for (i = 0; i < nkeys; i++) {
|
for (i = 0; i < nsrks; i++) {
|
||||||
if ((fp = sshkey_fingerprint(keys[i],
|
key = srks[i]->key;
|
||||||
|
if ((fp = sshkey_fingerprint(key,
|
||||||
fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
|
fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
|
||||||
fatal_f("sshkey_fingerprint failed");
|
fatal_f("sshkey_fingerprint failed");
|
||||||
if ((r = ssh_add_identity_constrained(agent_fd, keys[i], "",
|
if ((r = ssh_add_identity_constrained(agent_fd, key, "",
|
||||||
lifetime, confirm, maxsign, skprovider)) != 0) {
|
lifetime, confirm, maxsign, skprovider)) != 0) {
|
||||||
error("Unable to add key %s %s",
|
error("Unable to add key %s %s", sshkey_type(key), fp);
|
||||||
sshkey_type(keys[i]), fp);
|
|
||||||
free(fp);
|
free(fp);
|
||||||
ok = r;
|
ok = r;
|
||||||
continue;
|
continue;
|
||||||
|
@ -607,7 +608,7 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag)
|
||||||
ok = 1;
|
ok = 1;
|
||||||
if (!qflag) {
|
if (!qflag) {
|
||||||
fprintf(stderr, "Resident identity added: %s %s\n",
|
fprintf(stderr, "Resident identity added: %s %s\n",
|
||||||
sshkey_type(keys[i]), fp);
|
sshkey_type(key), fp);
|
||||||
if (lifetime != 0) {
|
if (lifetime != 0) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Lifetime set to %d seconds\n", lifetime);
|
"Lifetime set to %d seconds\n", lifetime);
|
||||||
|
@ -618,10 +619,9 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(fp);
|
free(fp);
|
||||||
sshkey_free(keys[i]);
|
|
||||||
}
|
}
|
||||||
free(keys);
|
sshsk_free_resident_keys(srks, nsrks);
|
||||||
if (nkeys == 0)
|
if (nsrks == 0)
|
||||||
return SSH_ERR_KEY_NOT_FOUND;
|
return SSH_ERR_KEY_NOT_FOUND;
|
||||||
return ok == 1 ? 0 : ok;
|
return ok == 1 ? 0 : ok;
|
||||||
}
|
}
|
||||||
|
|
93
ssh-keygen.c
93
ssh-keygen.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: ssh-keygen.c,v 1.438 2021/10/02 03:17:01 dtucker Exp $ */
|
/* $OpenBSD: ssh-keygen.c,v 1.439 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -2996,24 +2996,52 @@ passphrase_again:
|
||||||
return passphrase1;
|
return passphrase1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
static char *
|
||||||
skip_ssh_url_preamble(const char *s)
|
sk_suffix(const char *application, const uint8_t *user, size_t userlen)
|
||||||
{
|
{
|
||||||
if (strncmp(s, "ssh://", 6) == 0)
|
char *ret, *cp;
|
||||||
return s + 6;
|
size_t slen, i;
|
||||||
else if (strncmp(s, "ssh:", 4) == 0)
|
|
||||||
return s + 4;
|
/* Trim off URL-like preamble */
|
||||||
return s;
|
if (strncmp(application, "ssh://", 6) == 0)
|
||||||
|
ret = xstrdup(application + 6);
|
||||||
|
else if (strncmp(application, "ssh:", 4) == 0)
|
||||||
|
ret = xstrdup(application + 4);
|
||||||
|
else
|
||||||
|
ret = xstrdup(application);
|
||||||
|
|
||||||
|
/* Count trailing zeros in user */
|
||||||
|
for (i = 0; i < userlen; i++) {
|
||||||
|
if (user[userlen - i - 1] != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i >= userlen)
|
||||||
|
return ret; /* user-id was default all-zeros */
|
||||||
|
|
||||||
|
/* Append user-id, escaping non-UTF-8 characters */
|
||||||
|
slen = userlen - i;
|
||||||
|
if (asmprintf(&cp, INT_MAX, NULL, "%.*s", (int)slen, user) == -1)
|
||||||
|
fatal_f("asmprintf failed");
|
||||||
|
/* Don't emit a user-id that contains path or control characters */
|
||||||
|
if (strchr(cp, '/') != NULL || strstr(cp, "..") != NULL ||
|
||||||
|
strchr(cp, '\\') != NULL) {
|
||||||
|
free(cp);
|
||||||
|
cp = tohex(user, slen);
|
||||||
|
}
|
||||||
|
xextendf(&ret, "_", "%s", cp);
|
||||||
|
free(cp);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
do_download_sk(const char *skprovider, const char *device)
|
do_download_sk(const char *skprovider, const char *device)
|
||||||
{
|
{
|
||||||
struct sshkey **keys;
|
struct sshsk_resident_key **srks;
|
||||||
size_t nkeys, i;
|
size_t nsrks, i;
|
||||||
int r, ret = -1;
|
int r, ret = -1;
|
||||||
char *fp, *pin = NULL, *pass = NULL, *path, *pubpath;
|
char *fp, *pin = NULL, *pass = NULL, *path, *pubpath;
|
||||||
const char *ext;
|
const char *ext;
|
||||||
|
struct sshkey *key;
|
||||||
|
|
||||||
if (skprovider == NULL)
|
if (skprovider == NULL)
|
||||||
fatal("Cannot download keys without provider");
|
fatal("Cannot download keys without provider");
|
||||||
|
@ -3023,34 +3051,34 @@ do_download_sk(const char *skprovider, const char *device)
|
||||||
printf("You may need to touch your authenticator "
|
printf("You may need to touch your authenticator "
|
||||||
"to authorize key download.\n");
|
"to authorize key download.\n");
|
||||||
}
|
}
|
||||||
if ((r = sshsk_load_resident(skprovider, device, pin,
|
if ((r = sshsk_load_resident(skprovider, device, pin, 0,
|
||||||
&keys, &nkeys)) != 0) {
|
&srks, &nsrks)) != 0) {
|
||||||
if (pin != NULL)
|
if (pin != NULL)
|
||||||
freezero(pin, strlen(pin));
|
freezero(pin, strlen(pin));
|
||||||
error_r(r, "Unable to load resident keys");
|
error_r(r, "Unable to load resident keys");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (nkeys == 0)
|
if (nsrks == 0)
|
||||||
logit("No keys to download");
|
logit("No keys to download");
|
||||||
if (pin != NULL)
|
if (pin != NULL)
|
||||||
freezero(pin, strlen(pin));
|
freezero(pin, strlen(pin));
|
||||||
|
|
||||||
for (i = 0; i < nkeys; i++) {
|
for (i = 0; i < nsrks; i++) {
|
||||||
if (keys[i]->type != KEY_ECDSA_SK &&
|
key = srks[i]->key;
|
||||||
keys[i]->type != KEY_ED25519_SK) {
|
if (key->type != KEY_ECDSA_SK && key->type != KEY_ED25519_SK) {
|
||||||
error("Unsupported key type %s (%d)",
|
error("Unsupported key type %s (%d)",
|
||||||
sshkey_type(keys[i]), keys[i]->type);
|
sshkey_type(key), key->type);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((fp = sshkey_fingerprint(keys[i],
|
if ((fp = sshkey_fingerprint(key, fingerprint_hash,
|
||||||
fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
|
SSH_FP_DEFAULT)) == NULL)
|
||||||
fatal_f("sshkey_fingerprint failed");
|
fatal_f("sshkey_fingerprint failed");
|
||||||
debug_f("key %zu: %s %s %s (flags 0x%02x)", i,
|
debug_f("key %zu: %s %s %s (flags 0x%02x)", i,
|
||||||
sshkey_type(keys[i]), fp, keys[i]->sk_application,
|
sshkey_type(key), fp, key->sk_application, key->sk_flags);
|
||||||
keys[i]->sk_flags);
|
ext = sk_suffix(key->sk_application,
|
||||||
ext = skip_ssh_url_preamble(keys[i]->sk_application);
|
srks[i]->user_id, srks[i]->user_id_len);
|
||||||
xasprintf(&path, "id_%s_rk%s%s",
|
xasprintf(&path, "id_%s_rk%s%s",
|
||||||
keys[i]->type == KEY_ECDSA_SK ? "ecdsa_sk" : "ed25519_sk",
|
key->type == KEY_ECDSA_SK ? "ecdsa_sk" : "ed25519_sk",
|
||||||
*ext == '\0' ? "" : "_", ext);
|
*ext == '\0' ? "" : "_", ext);
|
||||||
|
|
||||||
/* If the file already exists, ask the user to confirm. */
|
/* If the file already exists, ask the user to confirm. */
|
||||||
|
@ -3062,26 +3090,25 @@ do_download_sk(const char *skprovider, const char *device)
|
||||||
/* Save the key with the application string as the comment */
|
/* Save the key with the application string as the comment */
|
||||||
if (pass == NULL)
|
if (pass == NULL)
|
||||||
pass = private_key_passphrase();
|
pass = private_key_passphrase();
|
||||||
if ((r = sshkey_save_private(keys[i], path, pass,
|
if ((r = sshkey_save_private(key, path, pass,
|
||||||
keys[i]->sk_application, private_key_format,
|
key->sk_application, private_key_format,
|
||||||
openssh_format_cipher, rounds)) != 0) {
|
openssh_format_cipher, rounds)) != 0) {
|
||||||
error_r(r, "Saving key \"%s\" failed", path);
|
error_r(r, "Saving key \"%s\" failed", path);
|
||||||
free(path);
|
free(path);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
printf("Saved %s key%s%s to %s\n",
|
printf("Saved %s key%s%s to %s\n", sshkey_type(key),
|
||||||
sshkey_type(keys[i]),
|
|
||||||
*ext != '\0' ? " " : "",
|
*ext != '\0' ? " " : "",
|
||||||
*ext != '\0' ? keys[i]->sk_application : "",
|
*ext != '\0' ? key->sk_application : "",
|
||||||
path);
|
path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Save public key too */
|
/* Save public key too */
|
||||||
xasprintf(&pubpath, "%s.pub", path);
|
xasprintf(&pubpath, "%s.pub", path);
|
||||||
free(path);
|
free(path);
|
||||||
if ((r = sshkey_save_public(keys[i], pubpath,
|
if ((r = sshkey_save_public(key, pubpath,
|
||||||
keys[i]->sk_application)) != 0) {
|
key->sk_application)) != 0) {
|
||||||
error_r(r, "Saving public key \"%s\" failed", pubpath);
|
error_r(r, "Saving public key \"%s\" failed", pubpath);
|
||||||
free(pubpath);
|
free(pubpath);
|
||||||
break;
|
break;
|
||||||
|
@ -3089,13 +3116,11 @@ do_download_sk(const char *skprovider, const char *device)
|
||||||
free(pubpath);
|
free(pubpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i >= nkeys)
|
if (i >= nsrks)
|
||||||
ret = 0; /* success */
|
ret = 0; /* success */
|
||||||
if (pass != NULL)
|
if (pass != NULL)
|
||||||
freezero(pass, strlen(pass));
|
freezero(pass, strlen(pass));
|
||||||
for (i = 0; i < nkeys; i++)
|
sshsk_free_resident_keys(srks, nsrks);
|
||||||
sshkey_free(keys[i]);
|
|
||||||
free(keys);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: ssh-sk-client.c,v 1.9 2021/04/03 06:18:41 djm Exp $ */
|
/* $OpenBSD: ssh-sk-client.c,v 1.10 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Google LLC
|
* Copyright (c) 2019 Google LLC
|
||||||
*
|
*
|
||||||
|
@ -375,17 +375,44 @@ sshsk_enroll(int type, const char *provider_path, const char *device,
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sshsk_free_resident_key(struct sshsk_resident_key *srk)
|
||||||
|
{
|
||||||
|
if (srk == NULL)
|
||||||
|
return;
|
||||||
|
sshkey_free(srk->key);
|
||||||
|
freezero(srk->user_id, srk->user_id_len);
|
||||||
|
free(srk);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (srks == NULL || nsrks == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < nsrks; i++)
|
||||||
|
sshsk_free_resident_key(srks[i]);
|
||||||
|
free(srks);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
sshsk_load_resident(const char *provider_path, const char *device,
|
sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
const char *pin, struct sshkey ***keysp, size_t *nkeysp)
|
const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
|
||||||
|
size_t *nsrksp)
|
||||||
{
|
{
|
||||||
int oerrno, r = SSH_ERR_INTERNAL_ERROR;
|
int oerrno, r = SSH_ERR_INTERNAL_ERROR;
|
||||||
struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
|
struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
|
||||||
struct sshkey *key = NULL, **keys = NULL, **tmp;
|
struct sshkey *key = NULL;
|
||||||
size_t i, nkeys = 0;
|
struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp;
|
||||||
|
u_char *userid = NULL;
|
||||||
|
size_t userid_len = 0, nsrks = 0;
|
||||||
|
|
||||||
*keysp = NULL;
|
*srksp = NULL;
|
||||||
*nkeysp = 0;
|
*nsrksp = 0;
|
||||||
|
|
||||||
if ((resp = sshbuf_new()) == NULL ||
|
if ((resp = sshbuf_new()) == NULL ||
|
||||||
(kbuf = sshbuf_new()) == NULL ||
|
(kbuf = sshbuf_new()) == NULL ||
|
||||||
|
@ -396,7 +423,8 @@ sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
|
|
||||||
if ((r = sshbuf_put_cstring(req, provider_path)) != 0 ||
|
if ((r = sshbuf_put_cstring(req, provider_path)) != 0 ||
|
||||||
(r = sshbuf_put_cstring(req, device)) != 0 ||
|
(r = sshbuf_put_cstring(req, device)) != 0 ||
|
||||||
(r = sshbuf_put_cstring(req, pin)) != 0) {
|
(r = sshbuf_put_cstring(req, pin)) != 0 ||
|
||||||
|
(r = sshbuf_put_u32(req, flags)) != 0) {
|
||||||
error_fr(r, "compose");
|
error_fr(r, "compose");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -405,10 +433,11 @@ sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
while (sshbuf_len(resp) != 0) {
|
while (sshbuf_len(resp) != 0) {
|
||||||
/* key, comment */
|
/* key, comment, user_id */
|
||||||
if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
|
if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
|
||||||
(r = sshbuf_get_cstring(resp, NULL, NULL)) != 0) {
|
(r = sshbuf_get_cstring(resp, NULL, NULL)) != 0 ||
|
||||||
error_fr(r, "parse signature");
|
(r = sshbuf_get_string(resp, &userid, &userid_len)) != 0) {
|
||||||
|
error_fr(r, "parse");
|
||||||
r = SSH_ERR_INVALID_FORMAT;
|
r = SSH_ERR_INVALID_FORMAT;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -416,29 +445,40 @@ sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
error_fr(r, "decode key");
|
error_fr(r, "decode key");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
|
if ((srk = calloc(1, sizeof(*srk))) == NULL) {
|
||||||
sizeof(*keys))) == NULL) {
|
error_f("calloc failed");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
srk->key = key;
|
||||||
|
key = NULL;
|
||||||
|
srk->user_id = userid;
|
||||||
|
srk->user_id_len = userid_len;
|
||||||
|
userid = NULL;
|
||||||
|
userid_len = 0;
|
||||||
|
if ((tmp = recallocarray(srks, nsrks, nsrks + 1,
|
||||||
|
sizeof(*srks))) == NULL) {
|
||||||
error_f("recallocarray keys failed");
|
error_f("recallocarray keys failed");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
debug_f("keys[%zu]: %s %s", nkeys, sshkey_type(key),
|
debug_f("srks[%zu]: %s %s uidlen %zu", nsrks,
|
||||||
key->sk_application);
|
sshkey_type(srk->key), srk->key->sk_application,
|
||||||
keys = tmp;
|
srk->user_id_len);
|
||||||
keys[nkeys++] = key;
|
srks = tmp;
|
||||||
key = NULL;
|
srks[nsrks++] = srk;
|
||||||
|
srk = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* success */
|
/* success */
|
||||||
r = 0;
|
r = 0;
|
||||||
*keysp = keys;
|
*srksp = srks;
|
||||||
*nkeysp = nkeys;
|
*nsrksp = nsrks;
|
||||||
keys = NULL;
|
srks = NULL;
|
||||||
nkeys = 0;
|
nsrks = 0;
|
||||||
out:
|
out:
|
||||||
oerrno = errno;
|
oerrno = errno;
|
||||||
for (i = 0; i < nkeys; i++)
|
sshsk_free_resident_key(srk);
|
||||||
sshkey_free(keys[i]);
|
sshsk_free_resident_keys(srks, nsrks);
|
||||||
free(keys);
|
freezero(userid, userid_len);
|
||||||
sshkey_free(key);
|
sshkey_free(key);
|
||||||
sshbuf_free(kbuf);
|
sshbuf_free(kbuf);
|
||||||
sshbuf_free(req);
|
sshbuf_free(req);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: ssh-sk-helper.c,v 1.11 2020/10/18 11:32:02 djm Exp $ */
|
/* $OpenBSD: ssh-sk-helper.c,v 1.12 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Google LLC
|
* Copyright (c) 2019 Google LLC
|
||||||
*
|
*
|
||||||
|
@ -216,15 +216,17 @@ process_load_resident(struct sshbuf *req)
|
||||||
int r;
|
int r;
|
||||||
char *provider, *pin, *device;
|
char *provider, *pin, *device;
|
||||||
struct sshbuf *kbuf, *resp;
|
struct sshbuf *kbuf, *resp;
|
||||||
struct sshkey **keys = NULL;
|
struct sshsk_resident_key **srks = NULL;
|
||||||
size_t nkeys = 0, i;
|
size_t nsrks = 0, i;
|
||||||
|
u_int flags;
|
||||||
|
|
||||||
if ((kbuf = sshbuf_new()) == NULL)
|
if ((kbuf = sshbuf_new()) == NULL)
|
||||||
fatal("%s: sshbuf_new failed", __progname);
|
fatal("%s: sshbuf_new failed", __progname);
|
||||||
|
|
||||||
if ((r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
|
if ((r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
|
||||||
(r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
|
(r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
|
||||||
(r = sshbuf_get_cstring(req, &pin, NULL)) != 0)
|
(r = sshbuf_get_cstring(req, &pin, NULL)) != 0 ||
|
||||||
|
(r = sshbuf_get_u32(req, &flags)) != 0)
|
||||||
fatal_r(r, "%s: parse", __progname);
|
fatal_r(r, "%s: parse", __progname);
|
||||||
if (sshbuf_len(req) != 0)
|
if (sshbuf_len(req) != 0)
|
||||||
fatal("%s: trailing data in request", __progname);
|
fatal("%s: trailing data in request", __progname);
|
||||||
|
@ -232,9 +234,9 @@ process_load_resident(struct sshbuf *req)
|
||||||
null_empty(&device);
|
null_empty(&device);
|
||||||
null_empty(&pin);
|
null_empty(&pin);
|
||||||
|
|
||||||
if ((r = sshsk_load_resident(provider, device, pin,
|
if ((r = sshsk_load_resident(provider, device, pin, flags,
|
||||||
&keys, &nkeys)) != 0) {
|
&srks, &nsrks)) != 0) {
|
||||||
resp = reply_error(r, " sshsk_load_resident failed: %s",
|
resp = reply_error(r, "sshsk_load_resident failed: %s",
|
||||||
ssh_err(r));
|
ssh_err(r));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -245,21 +247,22 @@ process_load_resident(struct sshbuf *req)
|
||||||
if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
|
if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
|
||||||
fatal_r(r, "%s: compose", __progname);
|
fatal_r(r, "%s: compose", __progname);
|
||||||
|
|
||||||
for (i = 0; i < nkeys; i++) {
|
for (i = 0; i < nsrks; i++) {
|
||||||
debug_f("key %zu %s %s", i, sshkey_type(keys[i]),
|
debug_f("key %zu %s %s uidlen %zu", i,
|
||||||
keys[i]->sk_application);
|
sshkey_type(srks[i]->key), srks[i]->key->sk_application,
|
||||||
|
srks[i]->user_id_len);
|
||||||
sshbuf_reset(kbuf);
|
sshbuf_reset(kbuf);
|
||||||
if ((r = sshkey_private_serialize(keys[i], kbuf)) != 0)
|
if ((r = sshkey_private_serialize(srks[i]->key, kbuf)) != 0)
|
||||||
fatal_r(r, "%s: encode key", __progname);
|
fatal_r(r, "%s: encode key", __progname);
|
||||||
if ((r = sshbuf_put_stringb(resp, kbuf)) != 0 ||
|
if ((r = sshbuf_put_stringb(resp, kbuf)) != 0 ||
|
||||||
(r = sshbuf_put_cstring(resp, "")) != 0) /* comment */
|
(r = sshbuf_put_cstring(resp, "")) != 0 || /* comment */
|
||||||
|
(r = sshbuf_put_string(resp, srks[i]->user_id,
|
||||||
|
srks[i]->user_id_len)) != 0)
|
||||||
fatal_r(r, "%s: compose key", __progname);
|
fatal_r(r, "%s: compose key", __progname);
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
for (i = 0; i < nkeys; i++)
|
sshsk_free_resident_keys(srks, nsrks);
|
||||||
sshkey_free(keys[i]);
|
|
||||||
free(keys);
|
|
||||||
sshbuf_free(kbuf);
|
sshbuf_free(kbuf);
|
||||||
free(provider);
|
free(provider);
|
||||||
if (pin != NULL)
|
if (pin != NULL)
|
||||||
|
|
90
ssh-sk.c
90
ssh-sk.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: ssh-sk.c,v 1.35 2021/02/26 00:16:58 djm Exp $ */
|
/* $OpenBSD: ssh-sk.c,v 1.36 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Google LLC
|
* Copyright (c) 2019 Google LLC
|
||||||
*
|
*
|
||||||
|
@ -752,25 +752,51 @@ sshsk_free_sk_resident_keys(struct sk_resident_key **rks, size_t nrks)
|
||||||
free(rks);
|
free(rks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sshsk_free_resident_key(struct sshsk_resident_key *srk)
|
||||||
|
{
|
||||||
|
if (srk == NULL)
|
||||||
|
return;
|
||||||
|
sshkey_free(srk->key);
|
||||||
|
freezero(srk->user_id, srk->user_id_len);
|
||||||
|
free(srk);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (srks == NULL || nsrks == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < nsrks; i++)
|
||||||
|
sshsk_free_resident_key(srks[i]);
|
||||||
|
free(srks);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
sshsk_load_resident(const char *provider_path, const char *device,
|
sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
const char *pin, struct sshkey ***keysp, size_t *nkeysp)
|
const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
|
||||||
|
size_t *nsrksp)
|
||||||
{
|
{
|
||||||
struct sshsk_provider *skp = NULL;
|
struct sshsk_provider *skp = NULL;
|
||||||
int r = SSH_ERR_INTERNAL_ERROR;
|
int r = SSH_ERR_INTERNAL_ERROR;
|
||||||
struct sk_resident_key **rks = NULL;
|
struct sk_resident_key **rks = NULL;
|
||||||
size_t i, nrks = 0, nkeys = 0;
|
size_t i, nrks = 0, nsrks = 0;
|
||||||
struct sshkey *key = NULL, **keys = NULL, **tmp;
|
struct sshkey *key = NULL;
|
||||||
uint8_t flags;
|
struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp;
|
||||||
|
uint8_t sk_flags;
|
||||||
struct sk_option **opts = NULL;
|
struct sk_option **opts = NULL;
|
||||||
|
|
||||||
debug_f("provider \"%s\"%s", provider_path,
|
debug_f("provider \"%s\"%s", provider_path,
|
||||||
(pin != NULL && *pin != '\0') ? ", have-pin": "");
|
(pin != NULL && *pin != '\0') ? ", have-pin": "");
|
||||||
|
|
||||||
if (keysp == NULL || nkeysp == NULL)
|
if (srksp == NULL || nsrksp == NULL)
|
||||||
return SSH_ERR_INVALID_ARGUMENT;
|
return SSH_ERR_INVALID_ARGUMENT;
|
||||||
*keysp = NULL;
|
*srksp = NULL;
|
||||||
*nkeysp = 0;
|
*nsrksp = 0;
|
||||||
|
|
||||||
if ((r = make_options(device, NULL, &opts)) != 0)
|
if ((r = make_options(device, NULL, &opts)) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -784,8 +810,9 @@ sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
for (i = 0; i < nrks; i++) {
|
for (i = 0; i < nrks; i++) {
|
||||||
debug3_f("rk %zu: slot = %zu, alg = %d, application = \"%s\"",
|
debug3_f("rk %zu: slot %zu, alg %d, app \"%s\", uidlen %zu",
|
||||||
i, rks[i]->slot, rks[i]->alg, rks[i]->application);
|
i, rks[i]->slot, rks[i]->alg, rks[i]->application,
|
||||||
|
rks[i]->user_id_len);
|
||||||
/* XXX need better filter here */
|
/* XXX need better filter here */
|
||||||
if (strncmp(rks[i]->application, "ssh:", 4) != 0)
|
if (strncmp(rks[i]->application, "ssh:", 4) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
@ -796,39 +823,50 @@ sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
default:
|
default:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
flags = SSH_SK_USER_PRESENCE_REQD|SSH_SK_RESIDENT_KEY;
|
sk_flags = SSH_SK_USER_PRESENCE_REQD|SSH_SK_RESIDENT_KEY;
|
||||||
if ((rks[i]->flags & SSH_SK_USER_VERIFICATION_REQD))
|
if ((rks[i]->flags & SSH_SK_USER_VERIFICATION_REQD))
|
||||||
flags |= SSH_SK_USER_VERIFICATION_REQD;
|
sk_flags |= SSH_SK_USER_VERIFICATION_REQD;
|
||||||
if ((r = sshsk_key_from_response(rks[i]->alg,
|
if ((r = sshsk_key_from_response(rks[i]->alg,
|
||||||
rks[i]->application, flags, &rks[i]->key, &key)) != 0)
|
rks[i]->application, sk_flags, &rks[i]->key, &key)) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
|
if ((srk = calloc(1, sizeof(*srk))) == NULL) {
|
||||||
|
error_f("calloc failed");
|
||||||
|
r = SSH_ERR_ALLOC_FAIL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
srk->key = key;
|
||||||
|
key = NULL; /* transferred */
|
||||||
|
if ((srk->user_id = calloc(1, rks[i]->user_id_len)) == NULL) {
|
||||||
|
error_f("calloc failed");
|
||||||
|
r = SSH_ERR_ALLOC_FAIL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
memcpy(srk->user_id, rks[i]->user_id, rks[i]->user_id_len);
|
||||||
|
srk->user_id_len = rks[i]->user_id_len;
|
||||||
|
if ((tmp = recallocarray(srks, nsrks, nsrks + 1,
|
||||||
sizeof(*tmp))) == NULL) {
|
sizeof(*tmp))) == NULL) {
|
||||||
error_f("recallocarray failed");
|
error_f("recallocarray failed");
|
||||||
r = SSH_ERR_ALLOC_FAIL;
|
r = SSH_ERR_ALLOC_FAIL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
keys = tmp;
|
srks = tmp;
|
||||||
keys[nkeys++] = key;
|
srks[nsrks++] = srk;
|
||||||
key = NULL;
|
srk = NULL;
|
||||||
/* XXX synthesise comment */
|
/* XXX synthesise comment */
|
||||||
}
|
}
|
||||||
/* success */
|
/* success */
|
||||||
*keysp = keys;
|
*srksp = srks;
|
||||||
*nkeysp = nkeys;
|
*nsrksp = nsrks;
|
||||||
keys = NULL;
|
srks = NULL;
|
||||||
nkeys = 0;
|
nsrks = 0;
|
||||||
r = 0;
|
r = 0;
|
||||||
out:
|
out:
|
||||||
sshsk_free_options(opts);
|
sshsk_free_options(opts);
|
||||||
sshsk_free(skp);
|
sshsk_free(skp);
|
||||||
sshsk_free_sk_resident_keys(rks, nrks);
|
sshsk_free_sk_resident_keys(rks, nrks);
|
||||||
sshkey_free(key);
|
sshkey_free(key);
|
||||||
if (nkeys != 0) {
|
sshsk_free_resident_key(srk);
|
||||||
for (i = 0; i < nkeys; i++)
|
sshsk_free_resident_keys(srks, nsrks);
|
||||||
sshkey_free(keys[i]);
|
|
||||||
free(keys);
|
|
||||||
}
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
ssh-sk.h
14
ssh-sk.h
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: ssh-sk.h,v 1.10 2020/01/10 23:43:26 djm Exp $ */
|
/* $OpenBSD: ssh-sk.h,v 1.11 2021/10/28 02:54:18 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Google LLC
|
* Copyright (c) 2019 Google LLC
|
||||||
*
|
*
|
||||||
|
@ -31,6 +31,12 @@ struct sk_option;
|
||||||
#define SSH_SK_HELPER_ENROLL 2
|
#define SSH_SK_HELPER_ENROLL 2
|
||||||
#define SSH_SK_HELPER_LOAD_RESIDENT 3
|
#define SSH_SK_HELPER_LOAD_RESIDENT 3
|
||||||
|
|
||||||
|
struct sshsk_resident_key {
|
||||||
|
struct sshkey *key;
|
||||||
|
uint8_t *user_id;
|
||||||
|
size_t user_id_len;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enroll (generate) a new security-key hosted private key of given type
|
* Enroll (generate) a new security-key hosted private key of given type
|
||||||
* via the specified provider middleware.
|
* via the specified provider middleware.
|
||||||
|
@ -63,7 +69,11 @@ int sshsk_sign(const char *provider_path, struct sshkey *key,
|
||||||
* Returns 0 on success or a ssherr.h error code on failure.
|
* Returns 0 on success or a ssherr.h error code on failure.
|
||||||
*/
|
*/
|
||||||
int sshsk_load_resident(const char *provider_path, const char *device,
|
int sshsk_load_resident(const char *provider_path, const char *device,
|
||||||
const char *pin, struct sshkey ***keysp, size_t *nkeysp);
|
const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
|
||||||
|
size_t *nsrksp);
|
||||||
|
|
||||||
|
/* Free an array of sshsk_resident_key (as returned from sshsk_load_resident) */
|
||||||
|
void sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks);
|
||||||
|
|
||||||
#endif /* _SSH_SK_H */
|
#endif /* _SSH_SK_H */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue