1/*
2 * Copyright (c) 2018-2023 Yubico AB. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <errno.h>
9#include <fido.h>
10#include <stdbool.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#ifdef HAVE_UNISTD_H
15#include <unistd.h>
16#endif
17
18#include "../openbsd-compat/openbsd-compat.h"
19#include "extern.h"
20
21static const unsigned char cd[32] = {
22	0xf9, 0x64, 0x57, 0xe7, 0x2d, 0x97, 0xf6, 0xbb,
23	0xdd, 0xd7, 0xfb, 0x06, 0x37, 0x62, 0xea, 0x26,
24	0x20, 0x44, 0x8e, 0x69, 0x7c, 0x03, 0xf2, 0x31,
25	0x2f, 0x99, 0xdc, 0xaf, 0x3e, 0x8a, 0x91, 0x6b,
26};
27
28static const unsigned char user_id[32] = {
29	0x78, 0x1c, 0x78, 0x60, 0xad, 0x88, 0xd2, 0x63,
30	0x32, 0x62, 0x2a, 0xf1, 0x74, 0x5d, 0xed, 0xb2,
31	0xe7, 0xa4, 0x2b, 0x44, 0x89, 0x29, 0x39, 0xc5,
32	0x56, 0x64, 0x01, 0x27, 0x0d, 0xbb, 0xc4, 0x49,
33};
34
35static void
36usage(void)
37{
38	fprintf(stderr, "usage: cred [-t es256|es384|rs256|eddsa] [-k pubkey] "
39	    "[-ei cred_id] [-P pin] [-T seconds] [-b blobkey] [-c cred_protect] [-hruv] "
40	    "<device>\n");
41	exit(EXIT_FAILURE);
42}
43
44static void
45verify_cred(int type, const char *fmt, const unsigned char *authdata_ptr,
46    size_t authdata_len, const unsigned char *attstmt_ptr, size_t attstmt_len,
47    bool rk, bool uv, int ext, int cred_protect, const char *key_out,
48    const char *id_out)
49{
50	fido_cred_t	*cred;
51	int		 r;
52
53	if ((cred = fido_cred_new()) == NULL)
54		errx(1, "fido_cred_new");
55
56	/* type */
57	r = fido_cred_set_type(cred, type);
58	if (r != FIDO_OK)
59		errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r);
60
61	/* client data */
62	r = fido_cred_set_clientdata(cred, cd, sizeof(cd));
63	if (r != FIDO_OK)
64		errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r);
65
66	/* relying party */
67	r = fido_cred_set_rp(cred, "localhost", "sweet home localhost");
68	if (r != FIDO_OK)
69		errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r);
70
71	/* authdata */
72	r = fido_cred_set_authdata(cred, authdata_ptr, authdata_len);
73	if (r != FIDO_OK)
74		errx(1, "fido_cred_set_authdata: %s (0x%x)", fido_strerr(r), r);
75
76	/* extensions */
77	r = fido_cred_set_extensions(cred, ext);
78	if (r != FIDO_OK)
79		errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r);
80
81	/* resident key */
82	if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
83		errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r);
84
85	/* user verification */
86	if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
87		errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r);
88
89	/* credProt */
90	if (cred_protect != 0 && (r = fido_cred_set_prot(cred,
91	    cred_protect)) != FIDO_OK)
92		errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r);
93
94	/* fmt */
95	r = fido_cred_set_fmt(cred, fmt);
96	if (r != FIDO_OK)
97		errx(1, "fido_cred_set_fmt: %s (0x%x)", fido_strerr(r), r);
98
99	if (!strcmp(fido_cred_fmt(cred), "none")) {
100		warnx("no attestation data, skipping credential verification");
101		goto out;
102	}
103
104	/* attestation statement */
105	r = fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len);
106	if (r != FIDO_OK)
107		errx(1, "fido_cred_set_attstmt: %s (0x%x)", fido_strerr(r), r);
108
109	r = fido_cred_verify(cred);
110	if (r != FIDO_OK)
111		errx(1, "fido_cred_verify: %s (0x%x)", fido_strerr(r), r);
112
113out:
114	if (key_out != NULL) {
115		/* extract the credential pubkey */
116		if (type == COSE_ES256) {
117			if (write_es256_pubkey(key_out,
118			    fido_cred_pubkey_ptr(cred),
119			    fido_cred_pubkey_len(cred)) < 0)
120				errx(1, "write_es256_pubkey");
121		} else if (type == COSE_ES384) {
122			if (write_es384_pubkey(key_out,
123			    fido_cred_pubkey_ptr(cred),
124			    fido_cred_pubkey_len(cred)) < 0)
125				errx(1, "write_es384_pubkey");
126		} else if (type == COSE_RS256) {
127			if (write_rs256_pubkey(key_out,
128			    fido_cred_pubkey_ptr(cred),
129			    fido_cred_pubkey_len(cred)) < 0)
130				errx(1, "write_rs256_pubkey");
131		} else if (type == COSE_EDDSA) {
132			if (write_eddsa_pubkey(key_out,
133			    fido_cred_pubkey_ptr(cred),
134			    fido_cred_pubkey_len(cred)) < 0)
135				errx(1, "write_eddsa_pubkey");
136		}
137	}
138
139	if (id_out != NULL) {
140		/* extract the credential id */
141		if (write_blob(id_out, fido_cred_id_ptr(cred),
142		    fido_cred_id_len(cred)) < 0)
143			errx(1, "write_blob");
144	}
145
146	fido_cred_free(&cred);
147}
148
149int
150main(int argc, char **argv)
151{
152	bool		 rk = false;
153	bool		 uv = false;
154	bool		 u2f = false;
155	fido_dev_t	*dev;
156	fido_cred_t	*cred = NULL;
157	const char	*pin = NULL;
158	const char	*blobkey_out = NULL;
159	const char	*key_out = NULL;
160	const char	*id_out = NULL;
161	unsigned char	*body = NULL;
162	long long	 ms = 0;
163	size_t		 len;
164	int		 type = COSE_ES256;
165	int		 ext = 0;
166	int		 ch;
167	int		 r;
168	long long cred_protect = 0;
169
170	if ((cred = fido_cred_new()) == NULL)
171		errx(1, "fido_cred_new");
172
173	while ((ch = getopt(argc, argv, "P:T:b:e:hi:k:rt:uvc:")) != -1) {
174		switch (ch) {
175		case 'P':
176			pin = optarg;
177			break;
178		case 'T':
179			if (base10(optarg, &ms) < 0)
180				errx(1, "base10: %s", optarg);
181			if (ms <= 0 || ms > 30)
182				errx(1, "-T: %s must be in (0,30]", optarg);
183			ms *= 1000; /* seconds to milliseconds */
184			break;
185		case 'b':
186			ext |= FIDO_EXT_LARGEBLOB_KEY;
187			blobkey_out = optarg;
188			break;
189		case 'e':
190			if (read_blob(optarg, &body, &len) < 0)
191				errx(1, "read_blob: %s", optarg);
192			r = fido_cred_exclude(cred, body, len);
193			if (r != FIDO_OK)
194				errx(1, "fido_cred_exclude: %s (0x%x)",
195				    fido_strerr(r), r);
196			free(body);
197			body = NULL;
198			break;
199		case 'h':
200			ext |= FIDO_EXT_HMAC_SECRET;
201			break;
202		case 'c':
203			if (base10(optarg, &cred_protect) < 0)
204				errx(1, "base10: %s", optarg);
205			if (cred_protect <= 0 || cred_protect > 3)
206				errx(1, "-c: %s must be in (1,3)", optarg);
207			ext |= FIDO_EXT_CRED_PROTECT;
208			break;
209		case 'i':
210			id_out = optarg;
211			break;
212		case 'k':
213			key_out = optarg;
214			break;
215		case 'r':
216			rk = true;
217			break;
218		case 't':
219			if (strcmp(optarg, "es256") == 0)
220				type = COSE_ES256;
221			else if (strcmp(optarg, "es384") == 0)
222				type = COSE_ES384;
223			else if (strcmp(optarg, "rs256") == 0)
224				type = COSE_RS256;
225			else if (strcmp(optarg, "eddsa") == 0)
226				type = COSE_EDDSA;
227			else
228				errx(1, "unknown type %s", optarg);
229			break;
230		case 'u':
231			u2f = true;
232			break;
233		case 'v':
234			uv = true;
235			break;
236		default:
237			usage();
238		}
239	}
240
241	argc -= optind;
242	argv += optind;
243
244	if (argc != 1)
245		usage();
246
247	fido_init(0);
248
249	if ((dev = fido_dev_new()) == NULL)
250		errx(1, "fido_dev_new");
251
252	r = fido_dev_open(dev, argv[0]);
253	if (r != FIDO_OK)
254		errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r);
255	if (u2f)
256		fido_dev_force_u2f(dev);
257
258	/* type */
259	r = fido_cred_set_type(cred, type);
260	if (r != FIDO_OK)
261		errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r);
262
263	/* client data */
264	r = fido_cred_set_clientdata(cred, cd, sizeof(cd));
265	if (r != FIDO_OK)
266		errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r);
267
268	/* relying party */
269	r = fido_cred_set_rp(cred, "localhost", "sweet home localhost");
270	if (r != FIDO_OK)
271		errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r);
272
273	/* user */
274	r = fido_cred_set_user(cred, user_id, sizeof(user_id), "john smith",
275	    "jsmith", NULL);
276	if (r != FIDO_OK)
277		errx(1, "fido_cred_set_user: %s (0x%x)", fido_strerr(r), r);
278
279	/* extensions */
280	r = fido_cred_set_extensions(cred, ext);
281	if (r != FIDO_OK)
282		errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r);
283
284	/* resident key */
285	if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
286		errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r);
287
288	/* user verification */
289	if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
290		errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r);
291
292	/* credProt */
293	if (cred_protect != 0 && (r = fido_cred_set_prot(cred,
294	    (int)cred_protect)) != FIDO_OK)
295		errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r);
296
297	/* timeout */
298	if (ms != 0 && (r = fido_dev_set_timeout(dev, (int)ms)) != FIDO_OK)
299		errx(1, "fido_dev_set_timeout: %s (0x%x)", fido_strerr(r), r);
300
301	if ((r = fido_dev_make_cred(dev, cred, pin)) != FIDO_OK) {
302		fido_dev_cancel(dev);
303		errx(1, "fido_makecred: %s (0x%x)", fido_strerr(r), r);
304	}
305
306	r = fido_dev_close(dev);
307	if (r != FIDO_OK)
308		errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r);
309
310	fido_dev_free(&dev);
311
312	/* when verifying, pin implies uv */
313	if (pin)
314		uv = true;
315
316	verify_cred(type, fido_cred_fmt(cred), fido_cred_authdata_ptr(cred),
317	    fido_cred_authdata_len(cred), fido_cred_attstmt_ptr(cred),
318	    fido_cred_attstmt_len(cred), rk, uv, ext, fido_cred_prot(cred),
319	    key_out, id_out);
320
321	if (blobkey_out != NULL) {
322		/* extract the "largeBlob" key */
323		if (write_blob(blobkey_out, fido_cred_largeblob_key_ptr(cred),
324		    fido_cred_largeblob_key_len(cred)) < 0)
325			errx(1, "write_blob");
326	}
327
328	fido_cred_free(&cred);
329
330	exit(0);
331}
332