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 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 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 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