10afa8e06SEd Maste /*
2*60a517b6SEd Maste * Copyright (c) 2018-2023 Yubico AB. All rights reserved.
30afa8e06SEd Maste * Use of this source code is governed by a BSD-style
40afa8e06SEd Maste * license that can be found in the LICENSE file.
52ccfa855SEd Maste * SPDX-License-Identifier: BSD-2-Clause
60afa8e06SEd Maste */
70afa8e06SEd Maste
80afa8e06SEd Maste #include <errno.h>
90afa8e06SEd Maste #include <fido.h>
100afa8e06SEd Maste #include <stdbool.h>
110afa8e06SEd Maste #include <stdio.h>
120afa8e06SEd Maste #include <stdlib.h>
130afa8e06SEd Maste #include <string.h>
140afa8e06SEd Maste #ifdef HAVE_UNISTD_H
150afa8e06SEd Maste #include <unistd.h>
160afa8e06SEd Maste #endif
170afa8e06SEd Maste
180afa8e06SEd Maste #include "../openbsd-compat/openbsd-compat.h"
190afa8e06SEd Maste #include "extern.h"
200afa8e06SEd Maste
21f540a430SEd Maste static const unsigned char cd[32] = {
220afa8e06SEd Maste 0xf9, 0x64, 0x57, 0xe7, 0x2d, 0x97, 0xf6, 0xbb,
230afa8e06SEd Maste 0xdd, 0xd7, 0xfb, 0x06, 0x37, 0x62, 0xea, 0x26,
240afa8e06SEd Maste 0x20, 0x44, 0x8e, 0x69, 0x7c, 0x03, 0xf2, 0x31,
250afa8e06SEd Maste 0x2f, 0x99, 0xdc, 0xaf, 0x3e, 0x8a, 0x91, 0x6b,
260afa8e06SEd Maste };
270afa8e06SEd Maste
280afa8e06SEd Maste static const unsigned char user_id[32] = {
290afa8e06SEd Maste 0x78, 0x1c, 0x78, 0x60, 0xad, 0x88, 0xd2, 0x63,
300afa8e06SEd Maste 0x32, 0x62, 0x2a, 0xf1, 0x74, 0x5d, 0xed, 0xb2,
310afa8e06SEd Maste 0xe7, 0xa4, 0x2b, 0x44, 0x89, 0x29, 0x39, 0xc5,
320afa8e06SEd Maste 0x56, 0x64, 0x01, 0x27, 0x0d, 0xbb, 0xc4, 0x49,
330afa8e06SEd Maste };
340afa8e06SEd Maste
350afa8e06SEd Maste static void
usage(void)360afa8e06SEd Maste usage(void)
370afa8e06SEd Maste {
382ccfa855SEd Maste fprintf(stderr, "usage: cred [-t es256|es384|rs256|eddsa] [-k pubkey] "
39*60a517b6SEd Maste "[-ei cred_id] [-P pin] [-T seconds] [-b blobkey] [-c cred_protect] [-hruv] "
400afa8e06SEd Maste "<device>\n");
410afa8e06SEd Maste exit(EXIT_FAILURE);
420afa8e06SEd Maste }
430afa8e06SEd Maste
440afa8e06SEd Maste static void
verify_cred(int type,const char * fmt,const unsigned char * authdata_ptr,size_t authdata_len,const unsigned char * attstmt_ptr,size_t attstmt_len,bool rk,bool uv,int ext,int cred_protect,const char * key_out,const char * id_out)450afa8e06SEd Maste verify_cred(int type, const char *fmt, const unsigned char *authdata_ptr,
46f540a430SEd Maste size_t authdata_len, const unsigned char *attstmt_ptr, size_t attstmt_len,
47*60a517b6SEd Maste bool rk, bool uv, int ext, int cred_protect, const char *key_out,
48*60a517b6SEd Maste const char *id_out)
490afa8e06SEd Maste {
500afa8e06SEd Maste fido_cred_t *cred;
510afa8e06SEd Maste int r;
520afa8e06SEd Maste
530afa8e06SEd Maste if ((cred = fido_cred_new()) == NULL)
540afa8e06SEd Maste errx(1, "fido_cred_new");
550afa8e06SEd Maste
560afa8e06SEd Maste /* type */
570afa8e06SEd Maste r = fido_cred_set_type(cred, type);
580afa8e06SEd Maste if (r != FIDO_OK)
590afa8e06SEd Maste errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r);
600afa8e06SEd Maste
61f540a430SEd Maste /* client data */
62f540a430SEd Maste r = fido_cred_set_clientdata(cred, cd, sizeof(cd));
630afa8e06SEd Maste if (r != FIDO_OK)
64f540a430SEd Maste errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r);
650afa8e06SEd Maste
660afa8e06SEd Maste /* relying party */
670afa8e06SEd Maste r = fido_cred_set_rp(cred, "localhost", "sweet home localhost");
680afa8e06SEd Maste if (r != FIDO_OK)
690afa8e06SEd Maste errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r);
700afa8e06SEd Maste
710afa8e06SEd Maste /* authdata */
720afa8e06SEd Maste r = fido_cred_set_authdata(cred, authdata_ptr, authdata_len);
730afa8e06SEd Maste if (r != FIDO_OK)
740afa8e06SEd Maste errx(1, "fido_cred_set_authdata: %s (0x%x)", fido_strerr(r), r);
750afa8e06SEd Maste
760afa8e06SEd Maste /* extensions */
770afa8e06SEd Maste r = fido_cred_set_extensions(cred, ext);
780afa8e06SEd Maste if (r != FIDO_OK)
790afa8e06SEd Maste errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r);
800afa8e06SEd Maste
810afa8e06SEd Maste /* resident key */
820afa8e06SEd Maste if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
830afa8e06SEd Maste errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r);
840afa8e06SEd Maste
850afa8e06SEd Maste /* user verification */
860afa8e06SEd Maste if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
870afa8e06SEd Maste errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r);
880afa8e06SEd Maste
89*60a517b6SEd Maste /* credProt */
90*60a517b6SEd Maste if (cred_protect != 0 && (r = fido_cred_set_prot(cred,
91*60a517b6SEd Maste cred_protect)) != FIDO_OK)
92*60a517b6SEd Maste errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r);
93*60a517b6SEd Maste
940afa8e06SEd Maste /* fmt */
950afa8e06SEd Maste r = fido_cred_set_fmt(cred, fmt);
960afa8e06SEd Maste if (r != FIDO_OK)
970afa8e06SEd Maste errx(1, "fido_cred_set_fmt: %s (0x%x)", fido_strerr(r), r);
980afa8e06SEd Maste
990afa8e06SEd Maste if (!strcmp(fido_cred_fmt(cred), "none")) {
1000afa8e06SEd Maste warnx("no attestation data, skipping credential verification");
1010afa8e06SEd Maste goto out;
1020afa8e06SEd Maste }
1030afa8e06SEd Maste
104f540a430SEd Maste /* attestation statement */
105f540a430SEd Maste r = fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len);
1060afa8e06SEd Maste if (r != FIDO_OK)
107f540a430SEd Maste errx(1, "fido_cred_set_attstmt: %s (0x%x)", fido_strerr(r), r);
1080afa8e06SEd Maste
1090afa8e06SEd Maste r = fido_cred_verify(cred);
1100afa8e06SEd Maste if (r != FIDO_OK)
1110afa8e06SEd Maste errx(1, "fido_cred_verify: %s (0x%x)", fido_strerr(r), r);
1120afa8e06SEd Maste
1130afa8e06SEd Maste out:
1140afa8e06SEd Maste if (key_out != NULL) {
1150afa8e06SEd Maste /* extract the credential pubkey */
1160afa8e06SEd Maste if (type == COSE_ES256) {
1172ccfa855SEd Maste if (write_es256_pubkey(key_out,
1182ccfa855SEd Maste fido_cred_pubkey_ptr(cred),
1190afa8e06SEd Maste fido_cred_pubkey_len(cred)) < 0)
1202ccfa855SEd Maste errx(1, "write_es256_pubkey");
1212ccfa855SEd Maste } else if (type == COSE_ES384) {
1222ccfa855SEd Maste if (write_es384_pubkey(key_out,
1232ccfa855SEd Maste fido_cred_pubkey_ptr(cred),
1242ccfa855SEd Maste fido_cred_pubkey_len(cred)) < 0)
1252ccfa855SEd Maste errx(1, "write_es384_pubkey");
1260afa8e06SEd Maste } else if (type == COSE_RS256) {
1272ccfa855SEd Maste if (write_rs256_pubkey(key_out,
1282ccfa855SEd Maste fido_cred_pubkey_ptr(cred),
1290afa8e06SEd Maste fido_cred_pubkey_len(cred)) < 0)
1302ccfa855SEd Maste errx(1, "write_rs256_pubkey");
1310afa8e06SEd Maste } else if (type == COSE_EDDSA) {
1322ccfa855SEd Maste if (write_eddsa_pubkey(key_out,
1332ccfa855SEd Maste fido_cred_pubkey_ptr(cred),
1340afa8e06SEd Maste fido_cred_pubkey_len(cred)) < 0)
1350afa8e06SEd Maste errx(1, "write_eddsa_pubkey");
1360afa8e06SEd Maste }
1370afa8e06SEd Maste }
1380afa8e06SEd Maste
1390afa8e06SEd Maste if (id_out != NULL) {
1400afa8e06SEd Maste /* extract the credential id */
1410afa8e06SEd Maste if (write_blob(id_out, fido_cred_id_ptr(cred),
1420afa8e06SEd Maste fido_cred_id_len(cred)) < 0)
1430afa8e06SEd Maste errx(1, "write_blob");
1440afa8e06SEd Maste }
1450afa8e06SEd Maste
1460afa8e06SEd Maste fido_cred_free(&cred);
1470afa8e06SEd Maste }
1480afa8e06SEd Maste
1490afa8e06SEd Maste int
main(int argc,char ** argv)1500afa8e06SEd Maste main(int argc, char **argv)
1510afa8e06SEd Maste {
1520afa8e06SEd Maste bool rk = false;
1530afa8e06SEd Maste bool uv = false;
1540afa8e06SEd Maste bool u2f = false;
1550afa8e06SEd Maste fido_dev_t *dev;
1560afa8e06SEd Maste fido_cred_t *cred = NULL;
1570afa8e06SEd Maste const char *pin = NULL;
1580afa8e06SEd Maste const char *blobkey_out = NULL;
1590afa8e06SEd Maste const char *key_out = NULL;
1600afa8e06SEd Maste const char *id_out = NULL;
1610afa8e06SEd Maste unsigned char *body = NULL;
162f540a430SEd Maste long long ms = 0;
1630afa8e06SEd Maste size_t len;
1640afa8e06SEd Maste int type = COSE_ES256;
1650afa8e06SEd Maste int ext = 0;
1660afa8e06SEd Maste int ch;
1670afa8e06SEd Maste int r;
168*60a517b6SEd Maste long long cred_protect = 0;
1690afa8e06SEd Maste
1700afa8e06SEd Maste if ((cred = fido_cred_new()) == NULL)
1710afa8e06SEd Maste errx(1, "fido_cred_new");
1720afa8e06SEd Maste
173*60a517b6SEd Maste while ((ch = getopt(argc, argv, "P:T:b:e:hi:k:rt:uvc:")) != -1) {
1740afa8e06SEd Maste switch (ch) {
1750afa8e06SEd Maste case 'P':
1760afa8e06SEd Maste pin = optarg;
1770afa8e06SEd Maste break;
1780afa8e06SEd Maste case 'T':
179f540a430SEd Maste if (base10(optarg, &ms) < 0)
1800afa8e06SEd Maste errx(1, "base10: %s", optarg);
181f540a430SEd Maste if (ms <= 0 || ms > 30)
1820afa8e06SEd Maste errx(1, "-T: %s must be in (0,30]", optarg);
183f540a430SEd Maste ms *= 1000; /* seconds to milliseconds */
1840afa8e06SEd Maste break;
1850afa8e06SEd Maste case 'b':
1860afa8e06SEd Maste ext |= FIDO_EXT_LARGEBLOB_KEY;
1870afa8e06SEd Maste blobkey_out = optarg;
1880afa8e06SEd Maste break;
1890afa8e06SEd Maste case 'e':
1900afa8e06SEd Maste if (read_blob(optarg, &body, &len) < 0)
1910afa8e06SEd Maste errx(1, "read_blob: %s", optarg);
1920afa8e06SEd Maste r = fido_cred_exclude(cred, body, len);
1930afa8e06SEd Maste if (r != FIDO_OK)
1940afa8e06SEd Maste errx(1, "fido_cred_exclude: %s (0x%x)",
1950afa8e06SEd Maste fido_strerr(r), r);
1960afa8e06SEd Maste free(body);
1970afa8e06SEd Maste body = NULL;
1980afa8e06SEd Maste break;
1990afa8e06SEd Maste case 'h':
2000afa8e06SEd Maste ext |= FIDO_EXT_HMAC_SECRET;
2010afa8e06SEd Maste break;
202*60a517b6SEd Maste case 'c':
203*60a517b6SEd Maste if (base10(optarg, &cred_protect) < 0)
204*60a517b6SEd Maste errx(1, "base10: %s", optarg);
205*60a517b6SEd Maste if (cred_protect <= 0 || cred_protect > 3)
206*60a517b6SEd Maste errx(1, "-c: %s must be in (1,3)", optarg);
207*60a517b6SEd Maste ext |= FIDO_EXT_CRED_PROTECT;
208*60a517b6SEd Maste break;
2090afa8e06SEd Maste case 'i':
2100afa8e06SEd Maste id_out = optarg;
2110afa8e06SEd Maste break;
2120afa8e06SEd Maste case 'k':
2130afa8e06SEd Maste key_out = optarg;
2140afa8e06SEd Maste break;
2150afa8e06SEd Maste case 'r':
2160afa8e06SEd Maste rk = true;
2170afa8e06SEd Maste break;
2180afa8e06SEd Maste case 't':
2192ccfa855SEd Maste if (strcmp(optarg, "es256") == 0)
2200afa8e06SEd Maste type = COSE_ES256;
2212ccfa855SEd Maste else if (strcmp(optarg, "es384") == 0)
2222ccfa855SEd Maste type = COSE_ES384;
2232ccfa855SEd Maste else if (strcmp(optarg, "rs256") == 0)
2240afa8e06SEd Maste type = COSE_RS256;
2250afa8e06SEd Maste else if (strcmp(optarg, "eddsa") == 0)
2260afa8e06SEd Maste type = COSE_EDDSA;
2270afa8e06SEd Maste else
2280afa8e06SEd Maste errx(1, "unknown type %s", optarg);
2290afa8e06SEd Maste break;
2300afa8e06SEd Maste case 'u':
2310afa8e06SEd Maste u2f = true;
2320afa8e06SEd Maste break;
2330afa8e06SEd Maste case 'v':
2340afa8e06SEd Maste uv = true;
2350afa8e06SEd Maste break;
2360afa8e06SEd Maste default:
2370afa8e06SEd Maste usage();
2380afa8e06SEd Maste }
2390afa8e06SEd Maste }
2400afa8e06SEd Maste
2410afa8e06SEd Maste argc -= optind;
2420afa8e06SEd Maste argv += optind;
2430afa8e06SEd Maste
244f540a430SEd Maste if (argc != 1)
2450afa8e06SEd Maste usage();
2460afa8e06SEd Maste
247f540a430SEd Maste fido_init(0);
2480afa8e06SEd Maste
249f540a430SEd Maste if ((dev = fido_dev_new()) == NULL)
250f540a430SEd Maste errx(1, "fido_dev_new");
251f540a430SEd Maste
252f540a430SEd Maste r = fido_dev_open(dev, argv[0]);
253f540a430SEd Maste if (r != FIDO_OK)
254f540a430SEd Maste errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r);
2550afa8e06SEd Maste if (u2f)
2560afa8e06SEd Maste fido_dev_force_u2f(dev);
2570afa8e06SEd Maste
2580afa8e06SEd Maste /* type */
2590afa8e06SEd Maste r = fido_cred_set_type(cred, type);
2600afa8e06SEd Maste if (r != FIDO_OK)
2610afa8e06SEd Maste errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r);
2620afa8e06SEd Maste
263f540a430SEd Maste /* client data */
264f540a430SEd Maste r = fido_cred_set_clientdata(cred, cd, sizeof(cd));
2650afa8e06SEd Maste if (r != FIDO_OK)
266f540a430SEd Maste errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r);
2670afa8e06SEd Maste
2680afa8e06SEd Maste /* relying party */
2690afa8e06SEd Maste r = fido_cred_set_rp(cred, "localhost", "sweet home localhost");
2700afa8e06SEd Maste if (r != FIDO_OK)
2710afa8e06SEd Maste errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r);
2720afa8e06SEd Maste
2730afa8e06SEd Maste /* user */
2740afa8e06SEd Maste r = fido_cred_set_user(cred, user_id, sizeof(user_id), "john smith",
2750afa8e06SEd Maste "jsmith", NULL);
2760afa8e06SEd Maste if (r != FIDO_OK)
2770afa8e06SEd Maste errx(1, "fido_cred_set_user: %s (0x%x)", fido_strerr(r), r);
2780afa8e06SEd Maste
2790afa8e06SEd Maste /* extensions */
2800afa8e06SEd Maste r = fido_cred_set_extensions(cred, ext);
2810afa8e06SEd Maste if (r != FIDO_OK)
2820afa8e06SEd Maste errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r);
2830afa8e06SEd Maste
2840afa8e06SEd Maste /* resident key */
2850afa8e06SEd Maste if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
2860afa8e06SEd Maste errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r);
2870afa8e06SEd Maste
2880afa8e06SEd Maste /* user verification */
2890afa8e06SEd Maste if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
2900afa8e06SEd Maste errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r);
2910afa8e06SEd Maste
292*60a517b6SEd Maste /* credProt */
293*60a517b6SEd Maste if (cred_protect != 0 && (r = fido_cred_set_prot(cred,
294*60a517b6SEd Maste (int)cred_protect)) != FIDO_OK)
295*60a517b6SEd Maste errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r);
296*60a517b6SEd Maste
297f540a430SEd Maste /* timeout */
298f540a430SEd Maste if (ms != 0 && (r = fido_dev_set_timeout(dev, (int)ms)) != FIDO_OK)
299f540a430SEd Maste errx(1, "fido_dev_set_timeout: %s (0x%x)", fido_strerr(r), r);
3000afa8e06SEd Maste
301f540a430SEd Maste if ((r = fido_dev_make_cred(dev, cred, pin)) != FIDO_OK) {
3020afa8e06SEd Maste fido_dev_cancel(dev);
3030afa8e06SEd Maste errx(1, "fido_makecred: %s (0x%x)", fido_strerr(r), r);
3040afa8e06SEd Maste }
3050afa8e06SEd Maste
3060afa8e06SEd Maste r = fido_dev_close(dev);
3070afa8e06SEd Maste if (r != FIDO_OK)
3080afa8e06SEd Maste errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r);
3090afa8e06SEd Maste
3100afa8e06SEd Maste fido_dev_free(&dev);
3110afa8e06SEd Maste
3120afa8e06SEd Maste /* when verifying, pin implies uv */
3130afa8e06SEd Maste if (pin)
3140afa8e06SEd Maste uv = true;
3150afa8e06SEd Maste
3160afa8e06SEd Maste verify_cred(type, fido_cred_fmt(cred), fido_cred_authdata_ptr(cred),
317f540a430SEd Maste fido_cred_authdata_len(cred), fido_cred_attstmt_ptr(cred),
318*60a517b6SEd Maste fido_cred_attstmt_len(cred), rk, uv, ext, fido_cred_prot(cred),
319*60a517b6SEd Maste key_out, id_out);
3200afa8e06SEd Maste
3210afa8e06SEd Maste if (blobkey_out != NULL) {
3220afa8e06SEd Maste /* extract the "largeBlob" key */
3230afa8e06SEd Maste if (write_blob(blobkey_out, fido_cred_largeblob_key_ptr(cred),
3240afa8e06SEd Maste fido_cred_largeblob_key_len(cred)) < 0)
3250afa8e06SEd Maste errx(1, "write_blob");
3260afa8e06SEd Maste }
3270afa8e06SEd Maste
3280afa8e06SEd Maste fido_cred_free(&cred);
3290afa8e06SEd Maste
3300afa8e06SEd Maste exit(0);
3310afa8e06SEd Maste }
332