version 1.21, 2020/08/27 01:06:18 |
version 1.22, 2020/08/27 01:07:51 |
|
|
/* |
/* |
* Copyright (c) 2019 Markus Friedl |
* Copyright (c) 2019 Markus Friedl |
|
* Copyright (c) 2020 Pedro Martelletto |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
|
#ifndef SK_STANDALONE |
#ifndef SK_STANDALONE |
# include "log.h" |
# include "log.h" |
# include "xmalloc.h" |
# include "xmalloc.h" |
|
# include "misc.h" |
/* |
/* |
* If building as part of OpenSSH, then rename exported functions. |
* If building as part of OpenSSH, then rename exported functions. |
* This must be done before including sk-api.h. |
* This must be done before including sk-api.h. |
|
|
#define SSH_FIDO_INIT_ARG 0 |
#define SSH_FIDO_INIT_ARG 0 |
#endif |
#endif |
|
|
#define MAX_FIDO_DEVICES 256 |
#define MAX_FIDO_DEVICES 8 |
|
#define FIDO_POLL_MS 50 |
|
#define SELECT_MS 15000 |
|
#define POLL_SLEEP_NS 200000000 |
|
|
/* Compatibility with OpenSSH 1.0.x */ |
/* Compatibility with OpenSSH 1.0.x */ |
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) |
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) |
|
|
} while (0) |
} while (0) |
#endif |
#endif |
|
|
|
struct sk_usbhid { |
|
fido_dev_t *dev; |
|
char *path; |
|
}; |
|
|
/* Return the version of the middleware API */ |
/* Return the version of the middleware API */ |
uint32_t sk_api_version(void); |
uint32_t sk_api_version(void); |
|
|
|
|
return SSH_SK_VERSION_MAJOR; |
return SSH_SK_VERSION_MAJOR; |
} |
} |
|
|
/* Select the first identified FIDO device attached to the system */ |
static struct sk_usbhid * |
static char * |
sk_open(const char *path) |
pick_first_device(void) |
|
{ |
{ |
char *ret = NULL; |
struct sk_usbhid *sk; |
fido_dev_info_t *devlist = NULL; |
|
size_t olen = 0; |
|
int r; |
int r; |
const fido_dev_info_t *di; |
|
|
|
if ((devlist = fido_dev_info_new(1)) == NULL) { |
if (path == NULL) { |
skdebug(__func__, "fido_dev_info_new failed"); |
skdebug(__func__, "path == NULL"); |
goto out; |
return NULL; |
} |
} |
if ((r = fido_dev_info_manifest(devlist, 1, &olen)) != FIDO_OK) { |
if ((sk = calloc(1, sizeof(*sk))) == NULL) { |
skdebug(__func__, "fido_dev_info_manifest failed: %s", |
skdebug(__func__, "calloc sk failed"); |
|
return NULL; |
|
} |
|
if ((sk->path = strdup(path)) == NULL) { |
|
skdebug(__func__, "strdup path failed"); |
|
free(sk); |
|
return NULL; |
|
} |
|
if ((sk->dev = fido_dev_new()) == NULL) { |
|
skdebug(__func__, "fido_dev_new failed"); |
|
free(sk->path); |
|
free(sk); |
|
return NULL; |
|
} |
|
if ((r = fido_dev_open(sk->dev, sk->path)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_open %s failed: %s", sk->path, |
fido_strerr(r)); |
fido_strerr(r)); |
goto out; |
fido_dev_free(&sk->dev); |
|
free(sk->path); |
|
free(sk); |
|
return NULL; |
} |
} |
if (olen != 1) { |
return sk; |
skdebug(__func__, "fido_dev_info_manifest bad len %zu", olen); |
} |
goto out; |
|
|
static void |
|
sk_close(struct sk_usbhid *sk) |
|
{ |
|
if (sk == NULL) |
|
return; |
|
fido_dev_cancel(sk->dev); /* cancel any pending operation */ |
|
fido_dev_close(sk->dev); |
|
fido_dev_free(&sk->dev); |
|
free(sk->path); |
|
free(sk); |
|
} |
|
|
|
static struct sk_usbhid ** |
|
sk_openv(const fido_dev_info_t *devlist, size_t ndevs, size_t *nopen) |
|
{ |
|
const fido_dev_info_t *di; |
|
struct sk_usbhid **skv; |
|
size_t i; |
|
|
|
*nopen = 0; |
|
if ((skv = calloc(ndevs, sizeof(*skv))) == NULL) { |
|
skdebug(__func__, "calloc skv failed"); |
|
return NULL; |
} |
} |
di = fido_dev_info_ptr(devlist, 0); |
for (i = 0; i < ndevs; i++) { |
if ((ret = strdup(fido_dev_info_path(di))) == NULL) { |
if ((di = fido_dev_info_ptr(devlist, i)) == NULL) |
skdebug(__func__, "fido_dev_info_path failed"); |
skdebug(__func__, "fido_dev_info_ptr failed"); |
goto out; |
else if ((skv[*nopen] = sk_open(fido_dev_info_path(di))) == NULL) |
|
skdebug(__func__, "sk_open failed"); |
|
else |
|
(*nopen)++; |
} |
} |
out: |
if (*nopen == 0) { |
fido_dev_info_free(&devlist, 1); |
for (i = 0; i < ndevs; i++) |
return ret; |
sk_close(skv[i]); |
|
free(skv); |
|
skv = NULL; |
|
} |
|
|
|
return skv; |
} |
} |
|
|
/* Check if the specified key handle exists on a given device. */ |
static void |
|
sk_closev(struct sk_usbhid **skv, size_t nsk) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < nsk; i++) |
|
sk_close(skv[i]); |
|
free(skv); |
|
} |
|
|
static int |
static int |
try_device(fido_dev_t *dev, const uint8_t *message, size_t message_len, |
sk_touch_begin(struct sk_usbhid **skv, size_t nsk) |
const char *application, const uint8_t *key_handle, size_t key_handle_len, |
|
uint8_t flags, const char *pin) |
|
{ |
{ |
|
size_t i, ok = 0; |
|
int r; |
|
|
|
for (i = 0; i < nsk; i++) |
|
if ((r = fido_dev_get_touch_begin(skv[i]->dev)) != FIDO_OK) |
|
skdebug(__func__, "fido_dev_get_touch_begin %s failed:" |
|
" %s", skv[i]->path, fido_strerr(r)); |
|
else |
|
ok++; |
|
|
|
return ok ? 0 : -1; |
|
} |
|
|
|
static int |
|
sk_touch_poll(struct sk_usbhid **skv, size_t nsk, int *touch, size_t *idx) |
|
{ |
|
struct timespec ts_pause; |
|
size_t npoll, i; |
|
int r; |
|
|
|
ts_pause.tv_sec = 0; |
|
ts_pause.tv_nsec = POLL_SLEEP_NS; |
|
nanosleep(&ts_pause, NULL); |
|
npoll = nsk; |
|
for (i = 0; i < nsk; i++) { |
|
if (skv[i] == NULL) |
|
continue; /* device discarded */ |
|
skdebug(__func__, "polling %s", skv[i]->path); |
|
if ((r = fido_dev_get_touch_status(skv[i]->dev, touch, |
|
FIDO_POLL_MS)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_get_touch_status %s: %s", |
|
skv[i]->path, fido_strerr(r)); |
|
sk_close(skv[i]); /* discard device */ |
|
skv[i] = NULL; |
|
if (--npoll == 0) { |
|
skdebug(__func__, "no device left to poll"); |
|
return -1; |
|
} |
|
} else if (*touch) { |
|
*idx = i; |
|
return 0; |
|
} |
|
} |
|
*touch = 0; |
|
return 0; |
|
} |
|
|
|
/* Calculate SHA256(m) */ |
|
static int |
|
sha256_mem(const void *m, size_t mlen, u_char *d, size_t dlen) |
|
{ |
|
#ifdef WITH_OPENSSL |
|
u_int mdlen; |
|
#endif |
|
|
|
if (dlen != 32) |
|
return -1; |
|
#ifdef WITH_OPENSSL |
|
mdlen = dlen; |
|
if (!EVP_Digest(m, mlen, d, &mdlen, EVP_sha256(), NULL)) |
|
return -1; |
|
#else |
|
SHA256Data(m, mlen, d); |
|
#endif |
|
return 0; |
|
} |
|
|
|
/* Check if the specified key handle exists on a given sk. */ |
|
static int |
|
sk_try(const struct sk_usbhid *sk, const char *application, |
|
const uint8_t *key_handle, size_t key_handle_len) |
|
{ |
fido_assert_t *assert = NULL; |
fido_assert_t *assert = NULL; |
|
/* generate an invalid signature on FIDO2 tokens */ |
|
const char *data = ""; |
|
uint8_t message[32]; |
int r = FIDO_ERR_INTERNAL; |
int r = FIDO_ERR_INTERNAL; |
|
|
|
if (sha256_mem(data, strlen(data), message, sizeof(message)) != 0) { |
|
skdebug(__func__, "hash message failed"); |
|
goto out; |
|
} |
if ((assert = fido_assert_new()) == NULL) { |
if ((assert = fido_assert_new()) == NULL) { |
skdebug(__func__, "fido_assert_new failed"); |
skdebug(__func__, "fido_assert_new failed"); |
goto out; |
goto out; |
} |
} |
if ((r = fido_assert_set_clientdata_hash(assert, message, |
if ((r = fido_assert_set_clientdata_hash(assert, message, |
message_len)) != FIDO_OK) { |
sizeof(message))) != FIDO_OK) { |
skdebug(__func__, "fido_assert_set_clientdata_hash: %s", |
skdebug(__func__, "fido_assert_set_clientdata_hash: %s", |
fido_strerr(r)); |
fido_strerr(r)); |
goto out; |
goto out; |
|
|
skdebug(__func__, "fido_assert_up: %s", fido_strerr(r)); |
skdebug(__func__, "fido_assert_up: %s", fido_strerr(r)); |
goto out; |
goto out; |
} |
} |
r = fido_dev_get_assert(dev, assert, pin); |
r = fido_dev_get_assert(sk->dev, assert, NULL); |
skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); |
skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); |
if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) { |
if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) { |
/* U2F tokens may return this */ |
/* U2F tokens may return this */ |
|
|
return r != FIDO_OK ? -1 : 0; |
return r != FIDO_OK ? -1 : 0; |
} |
} |
|
|
/* Iterate over configured devices looking for a specific key handle */ |
static struct sk_usbhid * |
static fido_dev_t * |
sk_select_by_cred(const fido_dev_info_t *devlist, size_t ndevs, |
find_device(const char *path, const uint8_t *message, size_t message_len, |
const char *application, const uint8_t *key_handle, size_t key_handle_len) |
const char *application, const uint8_t *key_handle, size_t key_handle_len, |
|
uint8_t flags, const char *pin) |
|
{ |
{ |
fido_dev_info_t *devlist = NULL; |
struct sk_usbhid **skv, *sk; |
fido_dev_t *dev = NULL; |
size_t skvcnt, i; |
size_t devlist_len = 0, i; |
|
int r; |
|
|
|
if (path != NULL) { |
if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) { |
if ((dev = fido_dev_new()) == NULL) { |
skdebug(__func__, "sk_openv failed"); |
skdebug(__func__, "fido_dev_new failed"); |
return NULL; |
return NULL; |
|
} |
|
if ((r = fido_dev_open(dev, path)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_open failed"); |
|
fido_dev_free(&dev); |
|
return NULL; |
|
} |
|
return dev; |
|
} |
} |
|
sk = NULL; |
|
for (i = 0; i < skvcnt; i++) |
|
if (sk_try(skv[i], application, key_handle, |
|
key_handle_len) == 0) { |
|
sk = skv[i]; |
|
skv[i] = NULL; |
|
skdebug(__func__, "found key in %s", sk->path); |
|
break; |
|
} |
|
sk_closev(skv, skvcnt); |
|
return sk; |
|
} |
|
|
if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
static struct sk_usbhid * |
skdebug(__func__, "fido_dev_info_new failed"); |
sk_select_by_touch(const fido_dev_info_t *devlist, size_t ndevs) |
|
{ |
|
struct sk_usbhid **skv, *sk; |
|
struct timeval tv_start, tv_now, tv_delta; |
|
size_t skvcnt, idx; |
|
int touch, ms_remain; |
|
|
|
if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) { |
|
skdebug(__func__, "sk_openv failed"); |
|
return NULL; |
|
} |
|
sk = NULL; |
|
if (skvcnt < 2) { |
|
if (skvcnt == 1) { |
|
/* single candidate */ |
|
sk = skv[0]; |
|
skv[0] = NULL; |
|
} |
goto out; |
goto out; |
} |
} |
if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES, |
if (sk_touch_begin(skv, skvcnt) == -1) { |
&devlist_len)) != FIDO_OK) { |
skdebug(__func__, "sk_touch_begin failed"); |
skdebug(__func__, "fido_dev_info_manifest: %s", fido_strerr(r)); |
|
goto out; |
goto out; |
} |
} |
|
monotime_tv(&tv_start); |
skdebug(__func__, "found %zu device(s)", devlist_len); |
do { |
|
if (sk_touch_poll(skv, skvcnt, &touch, &idx) == -1) { |
for (i = 0; i < devlist_len; i++) { |
skdebug(__func__, "sk_touch_poll failed"); |
const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); |
goto out; |
|
|
if (di == NULL) { |
|
skdebug(__func__, "fido_dev_info_ptr %zu failed", i); |
|
continue; |
|
} |
} |
if ((path = fido_dev_info_path(di)) == NULL) { |
if (touch) { |
skdebug(__func__, "fido_dev_info_path %zu failed", i); |
sk = skv[idx]; |
continue; |
skv[idx] = NULL; |
|
goto out; |
} |
} |
skdebug(__func__, "trying device %zu: %s", i, path); |
monotime_tv(&tv_now); |
if ((dev = fido_dev_new()) == NULL) { |
timersub(&tv_now, &tv_start, &tv_delta); |
skdebug(__func__, "fido_dev_new failed"); |
ms_remain = SELECT_MS - tv_delta.tv_sec * 1000 - |
continue; |
tv_delta.tv_usec / 1000; |
} |
} while (ms_remain >= FIDO_POLL_MS); |
if ((r = fido_dev_open(dev, path)) != FIDO_OK) { |
skdebug(__func__, "timeout"); |
skdebug(__func__, "fido_dev_open failed"); |
out: |
fido_dev_free(&dev); |
sk_closev(skv, skvcnt); |
continue; |
return sk; |
} |
} |
if (try_device(dev, message, message_len, application, |
|
key_handle, key_handle_len, flags, pin) == 0) { |
|
skdebug(__func__, "found key"); |
|
break; |
|
} |
|
fido_dev_close(dev); |
|
fido_dev_free(&dev); |
|
} |
|
|
|
out: |
static struct sk_usbhid * |
if (devlist != NULL) |
sk_probe(const char *application, const uint8_t *key_handle, |
fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); |
size_t key_handle_len) |
|
{ |
|
struct sk_usbhid *sk; |
|
fido_dev_info_t *devlist; |
|
size_t ndevs; |
|
int r; |
|
|
return dev; |
if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
|
skdebug(__func__, "fido_dev_info_new failed"); |
|
return NULL; |
|
} |
|
if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES, |
|
&ndevs)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_info_manifest failed: %s", |
|
fido_strerr(r)); |
|
fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); |
|
return NULL; |
|
} |
|
skdebug(__func__, "%zu device(s) detected", ndevs); |
|
if (ndevs == 0) { |
|
sk = NULL; |
|
} else if (application != NULL && key_handle != NULL) { |
|
skdebug(__func__, "selecting sk by cred"); |
|
sk = sk_select_by_cred(devlist, ndevs, application, key_handle, |
|
key_handle_len); |
|
} else { |
|
skdebug(__func__, "selecting sk by touch"); |
|
sk = sk_select_by_touch(devlist, ndevs); |
|
} |
|
fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); |
|
return sk; |
} |
} |
|
|
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
|
|
return 0; |
return 0; |
} |
} |
|
|
static int |
|
check_sk_extensions(fido_dev_t *dev, const char *ext, int *ret) |
|
{ |
|
fido_cbor_info_t *info; |
|
char * const *ptr; |
|
size_t len, i; |
|
int r; |
|
|
|
*ret = 0; |
|
|
|
if (!fido_dev_is_fido2(dev)) { |
|
skdebug(__func__, "device is not fido2"); |
|
return 0; |
|
} |
|
if ((info = fido_cbor_info_new()) == NULL) { |
|
skdebug(__func__, "fido_cbor_info_new failed"); |
|
return -1; |
|
} |
|
if ((r = fido_dev_get_cbor_info(dev, info)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_get_cbor_info: %s", fido_strerr(r)); |
|
fido_cbor_info_free(&info); |
|
return -1; |
|
} |
|
ptr = fido_cbor_info_extensions_ptr(info); |
|
len = fido_cbor_info_extensions_len(info); |
|
for (i = 0; i < len; i++) { |
|
if (!strcmp(ptr[i], ext)) { |
|
*ret = 1; |
|
break; |
|
} |
|
} |
|
fido_cbor_info_free(&info); |
|
skdebug(__func__, "extension %s %s", ext, *ret ? "present" : "absent"); |
|
|
|
return 0; |
|
} |
|
|
|
int |
int |
sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len, |
sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len, |
const char *application, uint8_t flags, const char *pin, |
const char *application, uint8_t flags, const char *pin, |
struct sk_option **options, struct sk_enroll_response **enroll_response) |
struct sk_option **options, struct sk_enroll_response **enroll_response) |
{ |
{ |
fido_cred_t *cred = NULL; |
fido_cred_t *cred = NULL; |
fido_dev_t *dev = NULL; |
|
const uint8_t *ptr; |
const uint8_t *ptr; |
uint8_t user_id[32]; |
uint8_t user_id[32]; |
|
struct sk_usbhid *sk = NULL; |
struct sk_enroll_response *response = NULL; |
struct sk_enroll_response *response = NULL; |
size_t len; |
size_t len; |
int credprot; |
int credprot; |
|
|
skdebug(__func__, "enroll_response == NULL"); |
skdebug(__func__, "enroll_response == NULL"); |
goto out; |
goto out; |
} |
} |
|
*enroll_response = NULL; |
memset(user_id, 0, sizeof(user_id)); |
memset(user_id, 0, sizeof(user_id)); |
if (check_enroll_options(options, &device, |
if (check_enroll_options(options, &device, user_id, |
user_id, sizeof(user_id)) != 0) |
sizeof(user_id)) != 0) |
goto out; /* error already logged */ |
goto out; /* error already logged */ |
|
|
*enroll_response = NULL; |
|
switch(alg) { |
switch(alg) { |
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
case SSH_SK_ECDSA: |
case SSH_SK_ECDSA: |
|
|
skdebug(__func__, "unsupported key type %d", alg); |
skdebug(__func__, "unsupported key type %d", alg); |
goto out; |
goto out; |
} |
} |
if (device == NULL && (device = pick_first_device()) == NULL) { |
if (device != NULL) |
ret = SSH_SK_ERR_DEVICE_NOT_FOUND; |
sk = sk_open(device); |
skdebug(__func__, "pick_first_device failed"); |
else |
|
sk = sk_probe(NULL, NULL, 0); |
|
if (sk == NULL) { |
|
skdebug(__func__, "failed to find sk"); |
goto out; |
goto out; |
} |
} |
skdebug(__func__, "using device %s", device); |
skdebug(__func__, "using device %s", sk->path); |
if ((cred = fido_cred_new()) == NULL) { |
if ((cred = fido_cred_new()) == NULL) { |
skdebug(__func__, "fido_cred_new failed"); |
skdebug(__func__, "fido_cred_new failed"); |
goto out; |
goto out; |
|
|
skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r)); |
skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r)); |
goto out; |
goto out; |
} |
} |
if ((dev = fido_dev_new()) == NULL) { |
|
skdebug(__func__, "fido_dev_new failed"); |
|
goto out; |
|
} |
|
if ((r = fido_dev_open(dev, device)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_open: %s", fido_strerr(r)); |
|
goto out; |
|
} |
|
if ((flags & (SSH_SK_RESIDENT_KEY|SSH_SK_USER_VERIFICATION_REQD)) != 0) { |
if ((flags & (SSH_SK_RESIDENT_KEY|SSH_SK_USER_VERIFICATION_REQD)) != 0) { |
if (check_sk_extensions(dev, "credProtect", &credprot) < 0) { |
if (!fido_dev_supports_cred_prot(sk->dev)) { |
skdebug(__func__, "check_sk_extensions failed"); |
skdebug(__func__, "%s does not support credprot, " |
goto out; |
"refusing to create unprotected " |
} |
"resident/verify-required key", sk->path); |
if (credprot == 0) { |
|
skdebug(__func__, "refusing to create unprotected " |
|
"resident/verify-required key"); |
|
ret = SSH_SK_ERR_UNSUPPORTED; |
ret = SSH_SK_ERR_UNSUPPORTED; |
goto out; |
goto out; |
} |
} |
|
|
goto out; |
goto out; |
} |
} |
} |
} |
if ((r = fido_dev_make_cred(dev, cred, pin)) != FIDO_OK) { |
if ((r = fido_dev_make_cred(sk->dev, cred, pin)) != FIDO_OK) { |
skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r)); |
skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r)); |
ret = fidoerr_to_skerr(r); |
ret = fidoerr_to_skerr(r); |
goto out; |
goto out; |
|
|
free(response->attestation_cert); |
free(response->attestation_cert); |
free(response); |
free(response); |
} |
} |
if (dev != NULL) { |
sk_close(sk); |
fido_dev_close(dev); |
fido_cred_free(&cred); |
fido_dev_free(&dev); |
|
} |
|
if (cred != NULL) { |
|
fido_cred_free(&cred); |
|
} |
|
return ret; |
return ret; |
} |
} |
|
|
|
|
return 0; |
return 0; |
} |
} |
|
|
/* Calculate SHA256(m) */ |
|
static int |
|
sha256_mem(const void *m, size_t mlen, u_char *d, size_t dlen) |
|
{ |
|
#ifdef WITH_OPENSSL |
|
u_int mdlen; |
|
#endif |
|
|
|
if (dlen != 32) |
|
return -1; |
|
#ifdef WITH_OPENSSL |
|
mdlen = dlen; |
|
if (!EVP_Digest(m, mlen, d, &mdlen, EVP_sha256(), NULL)) |
|
return -1; |
|
#else |
|
SHA256Data(m, mlen, d); |
|
#endif |
|
return 0; |
|
} |
|
|
|
int |
int |
sk_sign(uint32_t alg, const uint8_t *data, size_t datalen, |
sk_sign(uint32_t alg, const uint8_t *data, size_t datalen, |
const char *application, |
const char *application, |
|
|
{ |
{ |
fido_assert_t *assert = NULL; |
fido_assert_t *assert = NULL; |
char *device = NULL; |
char *device = NULL; |
fido_dev_t *dev = NULL; |
struct sk_usbhid *sk = NULL; |
struct sk_sign_response *response = NULL; |
struct sk_sign_response *response = NULL; |
uint8_t message[32]; |
uint8_t message[32]; |
int ret = SSH_SK_ERR_GENERAL; |
int ret = SSH_SK_ERR_GENERAL; |
|
|
skdebug(__func__, "hash message failed"); |
skdebug(__func__, "hash message failed"); |
goto out; |
goto out; |
} |
} |
if ((dev = find_device(device, message, sizeof(message), |
if (device != NULL) |
application, key_handle, key_handle_len, flags, pin)) == NULL) { |
sk = sk_open(device); |
skdebug(__func__, "couldn't find device for key handle"); |
else if (pin != NULL || (flags & SSH_SK_USER_VERIFICATION_REQD)) |
|
sk = sk_probe(NULL, NULL, 0); |
|
else |
|
sk = sk_probe(application, key_handle, key_handle_len); |
|
if (sk == NULL) { |
|
skdebug(__func__, "failed to find sk"); |
goto out; |
goto out; |
} |
} |
if ((assert = fido_assert_new()) == NULL) { |
if ((assert = fido_assert_new()) == NULL) { |
|
|
ret = FIDO_ERR_PIN_REQUIRED; |
ret = FIDO_ERR_PIN_REQUIRED; |
goto out; |
goto out; |
} |
} |
if ((r = fido_dev_get_assert(dev, assert, pin)) != FIDO_OK) { |
if ((r = fido_dev_get_assert(sk->dev, assert, pin)) != FIDO_OK) { |
skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); |
skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); |
ret = fidoerr_to_skerr(r); |
ret = fidoerr_to_skerr(r); |
goto out; |
goto out; |
|
|
free(response->sig_s); |
free(response->sig_s); |
free(response); |
free(response); |
} |
} |
if (dev != NULL) { |
sk_close(sk); |
fido_dev_close(dev); |
fido_assert_free(&assert); |
fido_dev_free(&dev); |
|
} |
|
if (assert != NULL) { |
|
fido_assert_free(&assert); |
|
} |
|
return ret; |
return ret; |
} |
} |
|
|
static int |
static int |
read_rks(const char *devpath, const char *pin, |
read_rks(struct sk_usbhid *sk, const char *pin, |
struct sk_resident_key ***rksp, size_t *nrksp) |
struct sk_resident_key ***rksp, size_t *nrksp) |
{ |
{ |
int ret = SSH_SK_ERR_GENERAL, r = -1; |
int ret = SSH_SK_ERR_GENERAL, r = -1; |
fido_dev_t *dev = NULL; |
|
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; |
|
|
const fido_cred_t *cred; |
const fido_cred_t *cred; |
struct sk_resident_key *srk = NULL, **tmp; |
struct sk_resident_key *srk = NULL, **tmp; |
|
|
if ((dev = fido_dev_new()) == NULL) { |
if (pin == NULL) { |
skdebug(__func__, "fido_dev_new failed"); |
skdebug(__func__, "no PIN specified"); |
return ret; |
ret = SSH_SK_ERR_PIN_REQUIRED; |
|
goto out; |
} |
} |
if ((r = fido_dev_open(dev, devpath)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_open %s failed: %s", |
|
devpath, fido_strerr(r)); |
|
fido_dev_free(&dev); |
|
return ret; |
|
} |
|
if ((metadata = fido_credman_metadata_new()) == NULL) { |
if ((metadata = fido_credman_metadata_new()) == NULL) { |
skdebug(__func__, "alloc failed"); |
skdebug(__func__, "alloc failed"); |
goto out; |
goto out; |
} |
} |
|
|
if ((r = fido_credman_get_dev_metadata(dev, metadata, pin)) != 0) { |
if ((r = fido_credman_get_dev_metadata(sk->dev, metadata, pin)) != 0) { |
if (r == FIDO_ERR_INVALID_COMMAND) { |
if (r == FIDO_ERR_INVALID_COMMAND) { |
skdebug(__func__, "device %s does not support " |
skdebug(__func__, "device %s does not support " |
"resident keys", devpath); |
"resident keys", sk->path); |
ret = 0; |
ret = 0; |
goto out; |
goto out; |
} |
} |
skdebug(__func__, "get metadata for %s failed: %s", |
skdebug(__func__, "get metadata for %s failed: %s", |
devpath, fido_strerr(r)); |
sk->path, fido_strerr(r)); |
ret = fidoerr_to_skerr(r); |
ret = fidoerr_to_skerr(r); |
goto out; |
goto out; |
} |
} |
|
|
skdebug(__func__, "alloc rp failed"); |
skdebug(__func__, "alloc rp failed"); |
goto out; |
goto out; |
} |
} |
if ((r = fido_credman_get_dev_rp(dev, rp, pin)) != 0) { |
if ((r = fido_credman_get_dev_rp(sk->dev, rp, pin)) != 0) { |
skdebug(__func__, "get RPs for %s failed: %s", |
skdebug(__func__, "get RPs for %s failed: %s", |
devpath, fido_strerr(r)); |
sk->path, fido_strerr(r)); |
goto out; |
goto out; |
} |
} |
nrp = fido_credman_rp_count(rp); |
nrp = fido_credman_rp_count(rp); |
skdebug(__func__, "Device %s has resident keys for %zu RPs", |
skdebug(__func__, "Device %s has resident keys for %zu RPs", |
devpath, nrp); |
sk->path, nrp); |
|
|
/* 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++) { |
|
|
skdebug(__func__, "alloc rk failed"); |
skdebug(__func__, "alloc rk failed"); |
goto out; |
goto out; |
} |
} |
if ((r = fido_credman_get_dev_rk(dev, fido_credman_rp_id(rp, i), |
if ((r = fido_credman_get_dev_rk(sk->dev, |
rk, pin)) != 0) { |
fido_credman_rp_id(rp, i), rk, pin)) != 0) { |
skdebug(__func__, "get RKs for %s slot %zu failed: %s", |
skdebug(__func__, "get RKs for %s slot %zu failed: %s", |
devpath, i, fido_strerr(r)); |
sk->path, i, fido_strerr(r)); |
goto out; |
goto out; |
} |
} |
nrk = fido_credman_rk_count(rk); |
nrk = fido_credman_rk_count(rk); |
|
|
continue; |
continue; |
} |
} |
skdebug(__func__, "Device %s RP \"%s\" slot %zu: " |
skdebug(__func__, "Device %s RP \"%s\" slot %zu: " |
"type %d flags 0x%02x prot 0x%02x", devpath, |
"type %d flags 0x%02x prot 0x%02x", sk->path, |
fido_credman_rp_id(rp, i), j, fido_cred_type(cred), |
fido_credman_rp_id(rp, i), j, fido_cred_type(cred), |
fido_cred_flags(cred), fido_cred_prot(cred)); |
fido_cred_flags(cred), fido_cred_prot(cred)); |
|
|
|
|
} |
} |
fido_credman_rp_free(&rp); |
fido_credman_rp_free(&rp); |
fido_credman_rk_free(&rk); |
fido_credman_rk_free(&rk); |
fido_dev_close(dev); |
|
fido_dev_free(&dev); |
|
fido_credman_metadata_free(&metadata); |
fido_credman_metadata_free(&metadata); |
return ret; |
return ret; |
} |
} |
|
|
struct sk_resident_key ***rksp, size_t *nrksp) |
struct sk_resident_key ***rksp, size_t *nrksp) |
{ |
{ |
int ret = SSH_SK_ERR_GENERAL, r = -1; |
int ret = SSH_SK_ERR_GENERAL, r = -1; |
fido_dev_info_t *devlist = NULL; |
size_t i, nrks = 0; |
size_t i, ndev = 0, nrks = 0; |
|
const fido_dev_info_t *di; |
|
struct sk_resident_key **rks = NULL; |
struct sk_resident_key **rks = NULL; |
|
struct sk_usbhid *sk = NULL; |
char *device = NULL; |
char *device = NULL; |
|
|
*rksp = NULL; |
*rksp = NULL; |
*nrksp = 0; |
*nrksp = 0; |
|
|
|
|
|
|
if (check_sign_load_resident_options(options, &device) != 0) |
if (check_sign_load_resident_options(options, &device) != 0) |
goto out; /* error already logged */ |
goto out; /* error already logged */ |
if (device != NULL) { |
if (device != NULL) |
skdebug(__func__, "trying %s", device); |
sk = sk_open(device); |
if ((r = read_rks(device, pin, &rks, &nrks)) != 0) { |
else |
skdebug(__func__, "read_rks failed for %s", device); |
sk = sk_probe(NULL, NULL, 0); |
ret = r; |
if (sk == NULL) { |
goto out; |
skdebug(__func__, "failed to find sk"); |
} |
goto out; |
} else { |
|
/* Try all devices */ |
|
if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
|
skdebug(__func__, "fido_dev_info_new failed"); |
|
goto out; |
|
} |
|
if ((r = fido_dev_info_manifest(devlist, |
|
MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) { |
|
skdebug(__func__, "fido_dev_info_manifest failed: %s", |
|
fido_strerr(r)); |
|
goto out; |
|
} |
|
for (i = 0; i < ndev; i++) { |
|
if ((di = fido_dev_info_ptr(devlist, i)) == NULL) { |
|
skdebug(__func__, "no dev info at %zu", i); |
|
continue; |
|
} |
|
skdebug(__func__, "trying %s", fido_dev_info_path(di)); |
|
if ((r = read_rks(fido_dev_info_path(di), pin, |
|
&rks, &nrks)) != 0) { |
|
skdebug(__func__, "read_rks failed for %s", |
|
fido_dev_info_path(di)); |
|
/* remember last error */ |
|
ret = r; |
|
continue; |
|
} |
|
} |
|
} |
} |
|
skdebug(__func__, "trying %s", sk->path); |
|
if ((r = read_rks(sk, pin, &rks, &nrks)) != 0) { |
|
skdebug(__func__, "read_rks failed for %s", sk->path); |
|
ret = r; |
|
goto out; |
|
} |
/* success, unless we have no keys but a specific error */ |
/* success, unless we have no keys but a specific error */ |
if (nrks > 0 || ret == SSH_SK_ERR_GENERAL) |
if (nrks > 0 || ret == SSH_SK_ERR_GENERAL) |
ret = 0; |
ret = 0; |
|
|
rks = NULL; |
rks = NULL; |
nrks = 0; |
nrks = 0; |
out: |
out: |
free(device); |
sk_close(sk); |
for (i = 0; i < nrks; i++) { |
for (i = 0; i < nrks; i++) { |
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], sizeof(*rks[i])); |
freezero(rks[i], sizeof(*rks[i])); |
} |
} |
free(rks); |
free(rks); |
fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); |
|
return ret; |
return ret; |
} |
} |
|
|