1*eb7387f6Sdjm /* $OpenBSD: ssh-verify-attestation.c,v 1.2 2024/12/06 10:37:42 djm Exp $ */ 2a8b9d729Sdjm /* 3a8b9d729Sdjm * Copyright (c) 2022-2024 Damien Miller 4a8b9d729Sdjm * 5a8b9d729Sdjm * Permission to use, copy, modify, and distribute this software for any 6a8b9d729Sdjm * purpose with or without fee is hereby granted, provided that the above 7a8b9d729Sdjm * copyright notice and this permission notice appear in all copies. 8a8b9d729Sdjm * 9a8b9d729Sdjm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10a8b9d729Sdjm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11a8b9d729Sdjm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12a8b9d729Sdjm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13a8b9d729Sdjm * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14a8b9d729Sdjm * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15a8b9d729Sdjm * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16a8b9d729Sdjm */ 17a8b9d729Sdjm 18a8b9d729Sdjm /* 19a8b9d729Sdjm * This is a small program to verify FIDO attestation objects that 20a8b9d729Sdjm * ssh-keygen(1) can record when enrolling a FIDO key. It requires that 21a8b9d729Sdjm * the attestation object and challenge used when creating the key be 22a8b9d729Sdjm * recorded. 23a8b9d729Sdjm * 24a8b9d729Sdjm * Example usage: 25a8b9d729Sdjm * 26a8b9d729Sdjm * $ # Generate a random challenge. 27a8b9d729Sdjm * $ dd if=/dev/urandom of=key_ecdsa_sk.challenge bs=32 count=1 28a8b9d729Sdjm * $ # Generate a key, record the attestation blob. 29a8b9d729Sdjm * $ ssh-keygen -f key_ecdsa_sk -t ecdsa-sk \ 30a8b9d729Sdjm * -Ochallenge=key_ecdsa_sk.challenge \ 31a8b9d729Sdjm * -Owrite-attestation=key_ecdsa_sk.attest -N '' 32a8b9d729Sdjm * $ # Validate the challenge (-A = print attestation CA cert) 33a8b9d729Sdjm * $ ./obj/ssh-verify-attestation -A key_ecdsa_sk key_ecdsa_sk.challenge \ 34a8b9d729Sdjm * key_ecdsa_sk.attest 35a8b9d729Sdjm * 36a8b9d729Sdjm * Limitations/TODO: 37a8b9d729Sdjm * 38a8b9d729Sdjm * 1) It doesn't automatically detect the attestation statement format. It 39a8b9d729Sdjm * assumes the "packed" format used by FIDO2 keys. If that doesn't work, 40a8b9d729Sdjm * then try using the -U option to select the "fido-u2f" format. 41*eb7387f6Sdjm * 2) It makes assumptions about RK, UV, etc status of the key/cred. 42a8b9d729Sdjm * 3) Probably bugs. 43a8b9d729Sdjm * 44*eb7387f6Sdjm * Thanks to Markus Friedl and Pedro Martelletto for help getting this 45*eb7387f6Sdjm * working. 46a8b9d729Sdjm */ 47a8b9d729Sdjm 48a8b9d729Sdjm #include <stdint.h> 49a8b9d729Sdjm #include <inttypes.h> 50a8b9d729Sdjm #include <stdlib.h> 51a8b9d729Sdjm #include <stdio.h> 52a8b9d729Sdjm #include <unistd.h> 53a8b9d729Sdjm #include <stdarg.h> 54a8b9d729Sdjm 55a8b9d729Sdjm #include "xmalloc.h" 56a8b9d729Sdjm #include "log.h" 57a8b9d729Sdjm #include "sshbuf.h" 58a8b9d729Sdjm #include "sshkey.h" 59a8b9d729Sdjm #include "authfile.h" 60a8b9d729Sdjm #include "ssherr.h" 61a8b9d729Sdjm #include "misc.h" 62a8b9d729Sdjm #include "digest.h" 63*eb7387f6Sdjm #include "crypto_api.h" 64a8b9d729Sdjm 65a8b9d729Sdjm #include <fido.h> 66a8b9d729Sdjm #include <openssl/x509.h> 67a8b9d729Sdjm #include <openssl/x509v3.h> 68a8b9d729Sdjm #include <openssl/bio.h> 69a8b9d729Sdjm #include <openssl/err.h> 70a8b9d729Sdjm #include <openssl/pem.h> 71a8b9d729Sdjm 72a8b9d729Sdjm extern char *__progname; 73a8b9d729Sdjm 74a8b9d729Sdjm #define ATTEST_MAGIC "ssh-sk-attest-v01" 75a8b9d729Sdjm 76a8b9d729Sdjm static int 77a8b9d729Sdjm prepare_fido_cred(fido_cred_t *cred, int credtype, const char *attfmt, 78a8b9d729Sdjm const char *rp_id, struct sshbuf *b, const struct sshbuf *challenge, 79a8b9d729Sdjm struct sshbuf **attestation_certp) 80a8b9d729Sdjm { 81a8b9d729Sdjm struct sshbuf *attestation_cert = NULL, *sig = NULL, *authdata = NULL; 82a8b9d729Sdjm char *magic = NULL; 83a8b9d729Sdjm int r = SSH_ERR_INTERNAL_ERROR; 84a8b9d729Sdjm 85a8b9d729Sdjm *attestation_certp = NULL; 86a8b9d729Sdjm 87a8b9d729Sdjm /* Make sure it's the format we're expecting */ 88a8b9d729Sdjm if ((r = sshbuf_get_cstring(b, &magic, NULL)) != 0) { 89a8b9d729Sdjm error_fr(r, "parse header"); 90a8b9d729Sdjm goto out; 91a8b9d729Sdjm } 92a8b9d729Sdjm if (strcmp(magic, ATTEST_MAGIC) != 0) { 93a8b9d729Sdjm error_f("unsupported format"); 94a8b9d729Sdjm r = SSH_ERR_INVALID_FORMAT; 95a8b9d729Sdjm goto out; 96a8b9d729Sdjm } 97a8b9d729Sdjm /* Parse the remaining fields */ 98a8b9d729Sdjm if ((r = sshbuf_froms(b, &attestation_cert)) != 0 || 99a8b9d729Sdjm (r = sshbuf_froms(b, &sig)) != 0 || 100a8b9d729Sdjm (r = sshbuf_froms(b, &authdata)) != 0 || 101a8b9d729Sdjm (r = sshbuf_get_u32(b, NULL)) != 0 || /* reserved flags */ 102a8b9d729Sdjm (r = sshbuf_get_string_direct(b, NULL, NULL)) != 0) { /* reserved */ 103a8b9d729Sdjm error_fr(r, "parse body"); 104a8b9d729Sdjm goto out; 105a8b9d729Sdjm } 106*eb7387f6Sdjm debug3_f("attestation cert len=%zu, sig len=%zu, " 107a8b9d729Sdjm "authdata len=%zu challenge len=%zu", sshbuf_len(attestation_cert), 108*eb7387f6Sdjm sshbuf_len(sig), sshbuf_len(authdata), sshbuf_len(challenge)); 109a8b9d729Sdjm 110*eb7387f6Sdjm fido_cred_set_type(cred, credtype); 111a8b9d729Sdjm fido_cred_set_fmt(cred, attfmt); 112a8b9d729Sdjm fido_cred_set_clientdata(cred, sshbuf_ptr(challenge), 113a8b9d729Sdjm sshbuf_len(challenge)); 114a8b9d729Sdjm fido_cred_set_rp(cred, rp_id, NULL); 115a8b9d729Sdjm fido_cred_set_authdata(cred, sshbuf_ptr(authdata), 116a8b9d729Sdjm sshbuf_len(authdata)); 117a8b9d729Sdjm /* XXX set_extensions, set_rk, set_uv */ 118a8b9d729Sdjm fido_cred_set_x509(cred, sshbuf_ptr(attestation_cert), 119a8b9d729Sdjm sshbuf_len(attestation_cert)); 120a8b9d729Sdjm fido_cred_set_sig(cred, sshbuf_ptr(sig), sshbuf_len(sig)); 121a8b9d729Sdjm 122a8b9d729Sdjm /* success */ 123a8b9d729Sdjm *attestation_certp = attestation_cert; 124a8b9d729Sdjm attestation_cert = NULL; 125a8b9d729Sdjm r = 0; 126a8b9d729Sdjm out: 127a8b9d729Sdjm free(magic); 128a8b9d729Sdjm sshbuf_free(attestation_cert); 129a8b9d729Sdjm sshbuf_free(sig); 130a8b9d729Sdjm sshbuf_free(authdata); 131a8b9d729Sdjm return r; 132a8b9d729Sdjm } 133a8b9d729Sdjm 134a8b9d729Sdjm static uint8_t * 135a8b9d729Sdjm get_pubkey_from_cred_ecdsa(const fido_cred_t *cred, size_t *pubkey_len) 136a8b9d729Sdjm { 137a8b9d729Sdjm const uint8_t *ptr; 138a8b9d729Sdjm uint8_t *pubkey = NULL, *ret = NULL; 139a8b9d729Sdjm BIGNUM *x = NULL, *y = NULL; 140a8b9d729Sdjm EC_POINT *q = NULL; 141a8b9d729Sdjm EC_GROUP *g = NULL; 142a8b9d729Sdjm 143a8b9d729Sdjm if ((x = BN_new()) == NULL || 144a8b9d729Sdjm (y = BN_new()) == NULL || 145a8b9d729Sdjm (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL || 146a8b9d729Sdjm (q = EC_POINT_new(g)) == NULL) { 147a8b9d729Sdjm error_f("libcrypto setup failed"); 148a8b9d729Sdjm goto out; 149a8b9d729Sdjm } 150a8b9d729Sdjm if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { 151a8b9d729Sdjm error_f("fido_cred_pubkey_ptr failed"); 152a8b9d729Sdjm goto out; 153a8b9d729Sdjm } 154a8b9d729Sdjm if (fido_cred_pubkey_len(cred) != 64) { 155a8b9d729Sdjm error_f("bad fido_cred_pubkey_len %zu", 156a8b9d729Sdjm fido_cred_pubkey_len(cred)); 157a8b9d729Sdjm goto out; 158a8b9d729Sdjm } 159a8b9d729Sdjm 160a8b9d729Sdjm if (BN_bin2bn(ptr, 32, x) == NULL || 161a8b9d729Sdjm BN_bin2bn(ptr + 32, 32, y) == NULL) { 162a8b9d729Sdjm error_f("BN_bin2bn failed"); 163a8b9d729Sdjm goto out; 164a8b9d729Sdjm } 165a8b9d729Sdjm if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) { 166a8b9d729Sdjm error_f("EC_POINT_set_affine_coordinates_GFp failed"); 167a8b9d729Sdjm goto out; 168a8b9d729Sdjm } 169a8b9d729Sdjm *pubkey_len = EC_POINT_point2oct(g, q, 170a8b9d729Sdjm POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); 171a8b9d729Sdjm if (*pubkey_len == 0 || *pubkey_len > 2048) { 172a8b9d729Sdjm error_f("bad pubkey length %zu", *pubkey_len); 173a8b9d729Sdjm goto out; 174a8b9d729Sdjm } 175a8b9d729Sdjm if ((pubkey = malloc(*pubkey_len)) == NULL) { 176a8b9d729Sdjm error_f("malloc pubkey failed"); 177a8b9d729Sdjm goto out; 178a8b9d729Sdjm } 179a8b9d729Sdjm if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED, 180a8b9d729Sdjm pubkey, *pubkey_len, NULL) == 0) { 181a8b9d729Sdjm error_f("EC_POINT_point2oct failed"); 182a8b9d729Sdjm goto out; 183a8b9d729Sdjm } 184a8b9d729Sdjm /* success */ 185a8b9d729Sdjm ret = pubkey; 186a8b9d729Sdjm pubkey = NULL; 187a8b9d729Sdjm out: 188a8b9d729Sdjm free(pubkey); 189a8b9d729Sdjm EC_POINT_free(q); 190a8b9d729Sdjm EC_GROUP_free(g); 191a8b9d729Sdjm BN_clear_free(x); 192a8b9d729Sdjm BN_clear_free(y); 193a8b9d729Sdjm return ret; 194a8b9d729Sdjm } 195a8b9d729Sdjm 196a8b9d729Sdjm /* copied from sshsk_ecdsa_assemble() */ 197a8b9d729Sdjm static int 198a8b9d729Sdjm cred_matches_key_ecdsa(const fido_cred_t *cred, const struct sshkey *k) 199a8b9d729Sdjm { 200a8b9d729Sdjm struct sshkey *key = NULL; 201a8b9d729Sdjm struct sshbuf *b = NULL; 202a8b9d729Sdjm EC_KEY *ec = NULL; 203a8b9d729Sdjm uint8_t *pubkey = NULL; 204a8b9d729Sdjm size_t pubkey_len; 205a8b9d729Sdjm int r; 206a8b9d729Sdjm 207a8b9d729Sdjm if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) { 208a8b9d729Sdjm error_f("sshkey_new failed"); 209a8b9d729Sdjm r = SSH_ERR_ALLOC_FAIL; 210a8b9d729Sdjm goto out; 211a8b9d729Sdjm } 212a8b9d729Sdjm key->ecdsa_nid = NID_X9_62_prime256v1; 213a8b9d729Sdjm if ((key->pkey = EVP_PKEY_new()) == NULL || 214a8b9d729Sdjm (ec = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL || 215a8b9d729Sdjm (b = sshbuf_new()) == NULL) { 216a8b9d729Sdjm error_f("allocation failed"); 217a8b9d729Sdjm r = SSH_ERR_ALLOC_FAIL; 218a8b9d729Sdjm goto out; 219a8b9d729Sdjm } 220a8b9d729Sdjm if ((pubkey = get_pubkey_from_cred_ecdsa(cred, &pubkey_len)) == NULL) { 221a8b9d729Sdjm error_f("get_pubkey_from_cred_ecdsa failed"); 222a8b9d729Sdjm r = SSH_ERR_INVALID_FORMAT; 223a8b9d729Sdjm goto out; 224a8b9d729Sdjm } 225a8b9d729Sdjm if ((r = sshbuf_put_string(b, pubkey, pubkey_len)) != 0) { 226a8b9d729Sdjm error_fr(r, "sshbuf_put_string"); 227a8b9d729Sdjm goto out; 228a8b9d729Sdjm } 229a8b9d729Sdjm if ((r = sshbuf_get_eckey(b, ec)) != 0) { 230a8b9d729Sdjm error_fr(r, "parse"); 231a8b9d729Sdjm r = SSH_ERR_INVALID_FORMAT; 232a8b9d729Sdjm goto out; 233a8b9d729Sdjm } 234a8b9d729Sdjm if (sshkey_ec_validate_public(EC_KEY_get0_group(ec), 235a8b9d729Sdjm EC_KEY_get0_public_key(ec)) != 0) { 236a8b9d729Sdjm error("Authenticator returned invalid ECDSA key"); 237a8b9d729Sdjm r = SSH_ERR_KEY_INVALID_EC_VALUE; 238a8b9d729Sdjm goto out; 239a8b9d729Sdjm } 240a8b9d729Sdjm if (EVP_PKEY_set1_EC_KEY(key->pkey, ec) != 1) { 241a8b9d729Sdjm /* XXX assume it is a allocation error */ 242a8b9d729Sdjm error_f("allocation failed"); 243a8b9d729Sdjm r = SSH_ERR_ALLOC_FAIL; 244a8b9d729Sdjm goto out; 245a8b9d729Sdjm } 246a8b9d729Sdjm key->sk_application = xstrdup(k->sk_application); /* XXX */ 247a8b9d729Sdjm if (!sshkey_equal_public(key, k)) { 248a8b9d729Sdjm error("sshkey_equal_public failed"); 249a8b9d729Sdjm r = SSH_ERR_INVALID_ARGUMENT; 250a8b9d729Sdjm goto out; 251a8b9d729Sdjm } 252a8b9d729Sdjm r = 0; /* success */ 253a8b9d729Sdjm out: 254a8b9d729Sdjm EC_KEY_free(ec); 255a8b9d729Sdjm free(pubkey); 256a8b9d729Sdjm sshkey_free(key); 257a8b9d729Sdjm sshbuf_free(b); 258a8b9d729Sdjm return r; 259a8b9d729Sdjm } 260a8b9d729Sdjm 261*eb7387f6Sdjm 262*eb7387f6Sdjm /* copied from sshsk_ed25519_assemble() */ 263*eb7387f6Sdjm static int 264*eb7387f6Sdjm cred_matches_key_ed25519(const fido_cred_t *cred, const struct sshkey *k) 265*eb7387f6Sdjm { 266*eb7387f6Sdjm struct sshkey *key = NULL; 267*eb7387f6Sdjm const uint8_t *ptr; 268*eb7387f6Sdjm int r = -1; 269*eb7387f6Sdjm 270*eb7387f6Sdjm if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { 271*eb7387f6Sdjm error_f("fido_cred_pubkey_ptr failed"); 272*eb7387f6Sdjm goto out; 273*eb7387f6Sdjm } 274*eb7387f6Sdjm if (fido_cred_pubkey_len(cred) != ED25519_PK_SZ) { 275*eb7387f6Sdjm error_f("bad fido_cred_pubkey_len %zu", 276*eb7387f6Sdjm fido_cred_pubkey_len(cred)); 277*eb7387f6Sdjm goto out; 278*eb7387f6Sdjm } 279*eb7387f6Sdjm 280*eb7387f6Sdjm if ((key = sshkey_new(KEY_ED25519_SK)) == NULL) { 281*eb7387f6Sdjm error_f("sshkey_new failed"); 282*eb7387f6Sdjm r = SSH_ERR_ALLOC_FAIL; 283*eb7387f6Sdjm goto out; 284*eb7387f6Sdjm } 285*eb7387f6Sdjm if ((key->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL) { 286*eb7387f6Sdjm error_f("malloc failed"); 287*eb7387f6Sdjm r = SSH_ERR_ALLOC_FAIL; 288*eb7387f6Sdjm goto out; 289*eb7387f6Sdjm } 290*eb7387f6Sdjm memcpy(key->ed25519_pk, ptr, ED25519_PK_SZ); 291*eb7387f6Sdjm key->sk_application = xstrdup(k->sk_application); /* XXX */ 292*eb7387f6Sdjm if (!sshkey_equal_public(key, k)) { 293*eb7387f6Sdjm error("sshkey_equal_public failed"); 294*eb7387f6Sdjm r = SSH_ERR_INVALID_ARGUMENT; 295*eb7387f6Sdjm goto out; 296*eb7387f6Sdjm } 297*eb7387f6Sdjm r = 0; /* success */ 298*eb7387f6Sdjm out: 299*eb7387f6Sdjm sshkey_free(key); 300*eb7387f6Sdjm return r; 301*eb7387f6Sdjm } 302*eb7387f6Sdjm 303a8b9d729Sdjm static int 304a8b9d729Sdjm cred_matches_key(const fido_cred_t *cred, const struct sshkey *k) 305a8b9d729Sdjm { 306a8b9d729Sdjm switch (sshkey_type_plain(k->type)) { 307a8b9d729Sdjm case KEY_ECDSA_SK: 308*eb7387f6Sdjm switch (k->ecdsa_nid) { 309*eb7387f6Sdjm case NID_X9_62_prime256v1: 310a8b9d729Sdjm return cred_matches_key_ecdsa(cred, k); 311*eb7387f6Sdjm break; 312*eb7387f6Sdjm default: 313*eb7387f6Sdjm fatal("Unsupported ECDSA key size"); 314*eb7387f6Sdjm } 315*eb7387f6Sdjm break; 316*eb7387f6Sdjm case KEY_ED25519_SK: 317*eb7387f6Sdjm return cred_matches_key_ed25519(cred, k); 318a8b9d729Sdjm default: 319a8b9d729Sdjm error_f("key type %s not supported", sshkey_type(k)); 320a8b9d729Sdjm return -1; 321a8b9d729Sdjm } 322a8b9d729Sdjm } 323a8b9d729Sdjm 324a8b9d729Sdjm int 325a8b9d729Sdjm main(int argc, char **argv) 326a8b9d729Sdjm { 327a8b9d729Sdjm LogLevel log_level = SYSLOG_LEVEL_INFO; 328a8b9d729Sdjm int r, ch, credtype = -1; 329a8b9d729Sdjm struct sshkey *k = NULL; 330a8b9d729Sdjm struct sshbuf *attestation = NULL, *challenge = NULL; 331a8b9d729Sdjm struct sshbuf *attestation_cert = NULL; 332a8b9d729Sdjm char *fp; 333*eb7387f6Sdjm const char *attfmt = "packed", *style = NULL; 334a8b9d729Sdjm fido_cred_t *cred = NULL; 335a8b9d729Sdjm int write_attestation_cert = 0; 336a8b9d729Sdjm extern int optind; 337a8b9d729Sdjm /* extern char *optarg; */ 338a8b9d729Sdjm 339a8b9d729Sdjm ERR_load_crypto_strings(); 340a8b9d729Sdjm 341a8b9d729Sdjm sanitise_stdfd(); 342a8b9d729Sdjm log_init(__progname, log_level, SYSLOG_FACILITY_AUTH, 1); 343a8b9d729Sdjm 344a8b9d729Sdjm while ((ch = getopt(argc, argv, "UAv")) != -1) { 345a8b9d729Sdjm switch (ch) { 346a8b9d729Sdjm case 'U': 347a8b9d729Sdjm attfmt = "fido-u2f"; 348a8b9d729Sdjm break; 349a8b9d729Sdjm case 'A': 350a8b9d729Sdjm write_attestation_cert = 1; 351a8b9d729Sdjm break; 352a8b9d729Sdjm case 'v': 353a8b9d729Sdjm if (log_level == SYSLOG_LEVEL_ERROR) 354a8b9d729Sdjm log_level = SYSLOG_LEVEL_DEBUG1; 355a8b9d729Sdjm else if (log_level < SYSLOG_LEVEL_DEBUG3) 356a8b9d729Sdjm log_level++; 357a8b9d729Sdjm break; 358a8b9d729Sdjm default: 359a8b9d729Sdjm goto usage; 360a8b9d729Sdjm } 361a8b9d729Sdjm } 362a8b9d729Sdjm log_init(__progname, log_level, SYSLOG_FACILITY_AUTH, 1); 363a8b9d729Sdjm argv += optind; 364a8b9d729Sdjm argc -= optind; 365a8b9d729Sdjm 366a8b9d729Sdjm if (argc < 3) { 367a8b9d729Sdjm usage: 368a8b9d729Sdjm fprintf(stderr, "usage: %s [-vAU] " 369a8b9d729Sdjm "pubkey challenge attestation-blob\n", __progname); 370a8b9d729Sdjm exit(1); 371a8b9d729Sdjm } 372a8b9d729Sdjm if ((r = sshkey_load_public(argv[0], &k, NULL)) != 0) 373a8b9d729Sdjm fatal_r(r, "load key %s", argv[0]); 374a8b9d729Sdjm if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, 375a8b9d729Sdjm SSH_FP_DEFAULT)) == NULL) 376a8b9d729Sdjm fatal("sshkey_fingerprint failed"); 377a8b9d729Sdjm debug2("key %s: %s %s", argv[2], sshkey_type(k), fp); 378a8b9d729Sdjm free(fp); 379a8b9d729Sdjm if ((r = sshbuf_load_file(argv[1], &challenge)) != 0) 380a8b9d729Sdjm fatal_r(r, "load challenge %s", argv[1]); 381a8b9d729Sdjm if ((r = sshbuf_load_file(argv[2], &attestation)) != 0) 382a8b9d729Sdjm fatal_r(r, "load attestation %s", argv[2]); 383a8b9d729Sdjm if ((cred = fido_cred_new()) == NULL) 384a8b9d729Sdjm fatal("fido_cred_new failed"); 385a8b9d729Sdjm 386a8b9d729Sdjm switch (sshkey_type_plain(k->type)) { 387a8b9d729Sdjm case KEY_ECDSA_SK: 388*eb7387f6Sdjm switch (k->ecdsa_nid) { 389*eb7387f6Sdjm case NID_X9_62_prime256v1: 390a8b9d729Sdjm credtype = COSE_ES256; 391a8b9d729Sdjm break; 392a8b9d729Sdjm default: 393*eb7387f6Sdjm fatal("Unsupported ECDSA key size"); 394*eb7387f6Sdjm } 395*eb7387f6Sdjm break; 396*eb7387f6Sdjm case KEY_ED25519_SK: 397*eb7387f6Sdjm credtype = COSE_EDDSA; 398*eb7387f6Sdjm break; 399*eb7387f6Sdjm default: 400a8b9d729Sdjm fatal("unsupported key type %s", sshkey_type(k)); 401a8b9d729Sdjm } 402a8b9d729Sdjm 403a8b9d729Sdjm if ((r = prepare_fido_cred(cred, credtype, attfmt, k->sk_application, 404a8b9d729Sdjm attestation, challenge, &attestation_cert)) != 0) 405a8b9d729Sdjm fatal_r(r, "prepare_fido_cred %s", argv[2]); 406a8b9d729Sdjm if (fido_cred_x5c_ptr(cred) != NULL) { 407a8b9d729Sdjm debug("basic attestation"); 408*eb7387f6Sdjm if ((r = fido_cred_verify(cred)) != FIDO_OK) 409*eb7387f6Sdjm fatal("basic attestation failed"); 410*eb7387f6Sdjm style = "basic"; 411a8b9d729Sdjm } else { 412a8b9d729Sdjm debug("self attestation"); 413*eb7387f6Sdjm if ((r = fido_cred_verify_self(cred)) != FIDO_OK) 414*eb7387f6Sdjm fatal("self attestation failed"); 415*eb7387f6Sdjm style = "self"; 416a8b9d729Sdjm } 417a8b9d729Sdjm if (cred_matches_key(cred, k) != 0) 418a8b9d729Sdjm fatal("cred authdata does not match key"); 419a8b9d729Sdjm 420a8b9d729Sdjm fido_cred_free(&cred); 421a8b9d729Sdjm 422a8b9d729Sdjm if (write_attestation_cert) { 423a8b9d729Sdjm PEM_write(stdout, "CERTIFICATE", NULL, 424a8b9d729Sdjm sshbuf_ptr(attestation_cert), sshbuf_len(attestation_cert)); 425a8b9d729Sdjm } 426a8b9d729Sdjm sshbuf_free(attestation_cert); 427a8b9d729Sdjm 428*eb7387f6Sdjm logit("%s: verified %s attestation", argv[2], style); 429a8b9d729Sdjm 430a8b9d729Sdjm return (0); 431a8b9d729Sdjm } 432