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