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
prepare_fido_cred(fido_cred_t * cred,int credtype,const char * attfmt,const char * rp_id,struct sshbuf * b,const struct sshbuf * challenge,struct sshbuf ** attestation_certp)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 *
get_pubkey_from_cred_ecdsa(const fido_cred_t * cred,size_t * pubkey_len)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
cred_matches_key_ecdsa(const fido_cred_t * cred,const struct sshkey * k)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
cred_matches_key_ed25519(const fido_cred_t * cred,const struct sshkey * k)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
cred_matches_key(const fido_cred_t * cred,const struct sshkey * k)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
main(int argc,char ** argv)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