version 1.11, 2019/12/30 09:24:45 |
version 1.12, 2020/01/06 02:00:46 |
|
|
} while (0) |
} while (0) |
#endif |
#endif |
|
|
#define SK_VERSION_MAJOR 0x00030000 /* current API version */ |
#define SK_VERSION_MAJOR 0x00040000 /* current API version */ |
|
|
/* Flags */ |
/* Flags */ |
#define SK_USER_PRESENCE_REQD 0x01 |
#define SK_USER_PRESENCE_REQD 0x01 |
|
|
}; |
}; |
|
|
struct sk_resident_key { |
struct sk_resident_key { |
uint8_t alg; |
uint32_t alg; |
size_t slot; |
size_t slot; |
char *application; |
char *application; |
struct sk_enroll_response key; |
struct sk_enroll_response key; |
}; |
}; |
|
|
|
struct sk_option { |
|
char *name; |
|
char *value; |
|
uint8_t required; |
|
}; |
|
|
/* If building as part of OpenSSH, then rename exported functions */ |
/* If building as part of OpenSSH, then rename exported functions */ |
#if !defined(SK_STANDALONE) |
#if !defined(SK_STANDALONE) |
#define sk_api_version ssh_sk_api_version |
#define sk_api_version ssh_sk_api_version |
|
|
uint32_t sk_api_version(void); |
uint32_t sk_api_version(void); |
|
|
/* Enroll a U2F key (private key generation) */ |
/* Enroll a U2F key (private key generation) */ |
int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, |
int 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_enroll_response **enroll_response); |
struct sk_option **options, struct sk_enroll_response **enroll_response); |
|
|
/* Sign a challenge */ |
/* Sign a challenge */ |
int sk_sign(int alg, const uint8_t *message, size_t message_len, |
int sk_sign(uint32_t alg, 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, struct sk_sign_response **sign_response); |
uint8_t flags, const char *pin, struct sk_option **options, |
|
struct sk_sign_response **sign_response); |
|
|
/* Load resident keys */ |
/* Load resident keys */ |
int sk_load_resident_keys(const char *pin, |
int sk_load_resident_keys(const char *pin, struct sk_option **options, |
struct sk_resident_key ***rks, size_t *nrks); |
struct sk_resident_key ***rks, size_t *nrks); |
|
|
static void skdebug(const char *func, const char *fmt, ...) |
static void skdebug(const char *func, const char *fmt, ...) |
|
|
|
|
/* Iterate over configured devices looking for a specific key handle */ |
/* Iterate over configured devices looking for a specific key handle */ |
static fido_dev_t * |
static fido_dev_t * |
find_device(const uint8_t *message, size_t message_len, const char *application, |
find_device(const char *path, const uint8_t *message, size_t message_len, |
const uint8_t *key_handle, size_t key_handle_len) |
const char *application, const uint8_t *key_handle, size_t key_handle_len) |
{ |
{ |
fido_dev_info_t *devlist = NULL; |
fido_dev_info_t *devlist = NULL; |
fido_dev_t *dev = NULL; |
fido_dev_t *dev = NULL; |
size_t devlist_len = 0, i; |
size_t devlist_len = 0, i; |
const char *path; |
|
int r; |
int r; |
|
|
|
if (path != NULL) { |
|
if ((dev = fido_dev_new()) == NULL) { |
|
skdebug(__func__, "fido_dev_new failed"); |
|
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; |
|
} |
|
|
if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
skdebug(__func__, "fido_dev_info_new failed"); |
skdebug(__func__, "fido_dev_info_new failed"); |
goto out; |
goto out; |
|
|
} |
} |
|
|
static int |
static int |
pack_public_key(int alg, const fido_cred_t *cred, |
pack_public_key(uint32_t alg, const fido_cred_t *cred, |
struct sk_enroll_response *response) |
struct sk_enroll_response *response) |
{ |
{ |
switch(alg) { |
switch(alg) { |
|
|
} |
} |
} |
} |
|
|
|
static int |
|
check_enroll_options(struct sk_option **options, char **devicep, |
|
uint8_t *user_id, size_t user_id_len) |
|
{ |
|
size_t i; |
|
|
|
if (options == NULL) |
|
return 0; |
|
for (i = 0; options[i] != NULL; i++) { |
|
if (strcmp(options[i]->name, "device") == 0) { |
|
if ((*devicep = strdup(options[i]->value)) == NULL) { |
|
skdebug(__func__, "strdup device failed"); |
|
return -1; |
|
} |
|
skdebug(__func__, "requested device %s", *devicep); |
|
} if (strcmp(options[i]->name, "user") == 0) { |
|
if (strlcpy(user_id, options[i]->value, user_id_len) >= |
|
user_id_len) { |
|
skdebug(__func__, "user too long"); |
|
return -1; |
|
} |
|
skdebug(__func__, "requested user %s", |
|
(char *)user_id); |
|
} else { |
|
skdebug(__func__, "requested unsupported option %s", |
|
options[i]->name); |
|
if (options[i]->required) { |
|
skdebug(__func__, "unknown required option"); |
|
return -1; |
|
} |
|
} |
|
} |
|
return 0; |
|
} |
|
|
int |
int |
sk_enroll(int 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_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; |
fido_dev_t *dev = NULL; |
|
|
skdebug(__func__, "enroll_response == NULL"); |
skdebug(__func__, "enroll_response == NULL"); |
goto out; |
goto out; |
} |
} |
|
memset(user_id, 0, sizeof(user_id)); |
|
if (check_enroll_options(options, &device, |
|
user_id, sizeof(user_id)) != 0) |
|
goto out; /* error already logged */ |
|
|
*enroll_response = NULL; |
*enroll_response = NULL; |
switch(alg) { |
switch(alg) { |
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
|
|
skdebug(__func__, "unsupported key type %d", alg); |
skdebug(__func__, "unsupported key type %d", alg); |
goto out; |
goto out; |
} |
} |
if ((device = pick_first_device()) == NULL) { |
if (device == NULL && (device = pick_first_device()) == NULL) { |
skdebug(__func__, "pick_first_device failed"); |
skdebug(__func__, "pick_first_device failed"); |
goto out; |
goto out; |
} |
} |
|
|
skdebug(__func__, "fido_cred_new failed"); |
skdebug(__func__, "fido_cred_new failed"); |
goto out; |
goto out; |
} |
} |
memset(user_id, 0, sizeof(user_id)); |
|
if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { |
if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { |
skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); |
skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); |
goto out; |
goto out; |
|
|
} |
} |
|
|
static int |
static int |
pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response) |
pack_sig(uint32_t alg, fido_assert_t *assert, |
|
struct sk_sign_response *response) |
{ |
{ |
switch(alg) { |
switch(alg) { |
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
|
|
} |
} |
} |
} |
|
|
|
/* Checks sk_options for sk_sign() and sk_load_resident_keys() */ |
|
static int |
|
check_sign_load_resident_options(struct sk_option **options, char **devicep) |
|
{ |
|
size_t i; |
|
|
|
if (options == NULL) |
|
return 0; |
|
for (i = 0; options[i] != NULL; i++) { |
|
if (strcmp(options[i]->name, "device") == 0) { |
|
if ((*devicep = strdup(options[i]->value)) == NULL) { |
|
skdebug(__func__, "strdup device failed"); |
|
return -1; |
|
} |
|
skdebug(__func__, "requested device %s", *devicep); |
|
} else { |
|
skdebug(__func__, "requested unsupported option %s", |
|
options[i]->name); |
|
if (options[i]->required) { |
|
skdebug(__func__, "unknown required option"); |
|
return -1; |
|
} |
|
} |
|
} |
|
return 0; |
|
} |
|
|
int |
int |
sk_sign(int alg, const uint8_t *message, size_t message_len, |
sk_sign(uint32_t alg, const uint8_t *message, size_t message_len, |
const char *application, |
const char *application, |
const uint8_t *key_handle, size_t key_handle_len, |
const uint8_t *key_handle, size_t key_handle_len, |
uint8_t flags, const char *pin, struct sk_sign_response **sign_response) |
uint8_t flags, const char *pin, struct sk_option **options, |
|
struct sk_sign_response **sign_response) |
{ |
{ |
fido_assert_t *assert = NULL; |
fido_assert_t *assert = NULL; |
|
char *device = NULL; |
fido_dev_t *dev = NULL; |
fido_dev_t *dev = NULL; |
struct sk_sign_response *response = NULL; |
struct sk_sign_response *response = NULL; |
int ret = SSH_SK_ERR_GENERAL; |
int ret = SSH_SK_ERR_GENERAL; |
|
|
goto out; |
goto out; |
} |
} |
*sign_response = NULL; |
*sign_response = NULL; |
if ((dev = find_device(message, message_len, application, key_handle, |
if (check_sign_load_resident_options(options, &device) != 0) |
key_handle_len)) == NULL) { |
goto out; /* error already logged */ |
|
if ((dev = find_device(device, message, message_len, |
|
application, key_handle, key_handle_len)) == NULL) { |
skdebug(__func__, "couldn't find device for key handle"); |
skdebug(__func__, "couldn't find device for key handle"); |
goto out; |
goto out; |
} |
} |
|
|
response = NULL; |
response = NULL; |
ret = 0; |
ret = 0; |
out: |
out: |
|
free(device); |
if (response != NULL) { |
if (response != NULL) { |
free(response->sig_r); |
free(response->sig_r); |
free(response->sig_s); |
free(response->sig_s); |
|
|
} |
} |
skdebug(__func__, "get metadata for %s failed: %s", |
skdebug(__func__, "get metadata for %s failed: %s", |
devpath, fido_strerr(r)); |
devpath, fido_strerr(r)); |
|
ret = fidoerr_to_skerr(r); |
goto out; |
goto out; |
} |
} |
skdebug(__func__, "existing %llu, remaining %llu", |
skdebug(__func__, "existing %llu, remaining %llu", |
|
|
} |
} |
|
|
int |
int |
sk_load_resident_keys(const char *pin, |
sk_load_resident_keys(const char *pin, struct sk_option **options, |
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; |
|
|
size_t i, ndev = 0, nrks = 0; |
size_t i, ndev = 0, nrks = 0; |
const fido_dev_info_t *di; |
const fido_dev_info_t *di; |
struct sk_resident_key **rks = NULL; |
struct sk_resident_key **rks = NULL; |
|
char *device = NULL; |
*rksp = NULL; |
*rksp = NULL; |
*nrksp = 0; |
*nrksp = 0; |
|
|
if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
if (check_sign_load_resident_options(options, &device) != 0) |
skdebug(__func__, "fido_dev_info_new failed"); |
goto out; /* error already logged */ |
goto out; |
if (device != NULL) { |
} |
skdebug(__func__, "trying %s", device); |
if ((r = fido_dev_info_manifest(devlist, |
if ((r = read_rks(device, pin, &rks, &nrks)) != 0) { |
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", |
skdebug(__func__, "read_rks failed for %s", |
fido_dev_info_path(di)); |
fido_dev_info_path(di)); |
continue; |
ret = r; |
|
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; |
|
} |
|
} |
} |
} |
/* success */ |
/* success, unless we have no keys but a specific error */ |
ret = 0; |
if (nrks > 0 || ret == SSH_SK_ERR_GENERAL) |
|
ret = 0; |
*rksp = rks; |
*rksp = rks; |
*nrksp = nrks; |
*nrksp = nrks; |
rks = NULL; |
rks = NULL; |
nrks = 0; |
nrks = 0; |
out: |
out: |
|
free(device); |
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); |