Yubico/libfido2

fido_assert_user_name returns NULL if there is a single resident credential

codingllama opened this issue · 3 comments

What version of libfido2 are you using?
1.11.0

What operating system are you running?
macOS 12.4

What application are you using in conjunction with libfido2?
Reproducible using libfido2 directly, see attached toy program.

How does the problem manifest itself?
fido_assert_user_name returns NULL if there is a single statement in the assertion. If there is more than one statement user names are returned.

The same applies to other user information, such as fido_assert_user_display_name and fido_assert_user_icon. Based on the current documentation it's hard to say whether this is working as intended.

Note that the use case in question is passwordless, so there are no allowed credentials set.

Is the problem reproducible?
Yes

What are the steps that lead to the problem?

  1. Register a single resident credential for the RPID
  2. Run the toy program, output shows NULL
  3. Register an additional resident credential for the RPID
  4. Run the toy program, output now shows user names

Does the problem happen with different authenticators?

Seems consistent with Yubico authenticators (5ci and Bio).

Please include the output of fido2-token -L.

fido2-token -L
$ fido2-token -L
ioreg://4294969044: vendor=0x1050, product=0x0406 (Yubico YubiKey FIDO+CCID)
ioreg://4294977891: vendor=0x1050, product=0x0406 (Yubico YubiKey FIDO+CCID)

Please include the output of fido2-token -I.

fido2-token -I
$ fido2-token -I ioreg://4294977891
proto: 0x02
major: 0x05
minor: 0x04
build: 0x03
caps: 0x05 (wink, cbor, msg)
version strings: U2F_V2, FIDO_2_0, FIDO_2_1_PRE
extension strings: credProtect, hmac-secret
transport strings: usb, lightning
algorithms: es256 (public-key), eddsa (public-key)
aaguid: c5ef55ffad9a4b9fb580adebafe026d0
options: rk, up, noplat, clientPin, credentialMgmtPreview
maxmsgsiz: 1200
maxcredcntlst: 8
maxcredlen: 128
maxlargeblob: 0
fwversion: 0x50403
pin protocols: 2, 1
pin retries: 8
uv retries: undefined

Please include the output of FIDO_DEBUG=1.

FIDO_DEBUG=1
$ FIDO_DEBUG=1 ./pin
run_manifest: found 2 hid devices
Device #0: [ioreg://4294977891] [Yubico] [YubiKey FIDO+CCID]
Device #1: [ioreg://4294969044] [Yubico] [YubiKey FIDO+CCID]
Using device [ioreg://4294977891]
fido_tx: dev=0x6000038083f0, cmd=0x06
fido_tx: buf=0x6000038083f0, len=8
0000: 32 d1 0b 5d dd 71 5a f5
fido_rx: dev=0x6000038083f0, cmd=0x06, ms=-1
rx_preamble: buf=0x7ff7b71e6d50, len=64
0000: ff ff ff ff 86 00 11 32 d1 0b 5d dd 71 5a f5 2f
0016: b6 cd f1 02 05 04 03 05 00 00 00 00 00 00 00 00
0032: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0048: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
rx: payload_len=17
fido_rx: buf=0x6000038083f8, len=17
0000: 32 d1 0b 5d dd 71 5a f5 2f b6 cd f1 02 05 04 03
0016: 05
fido_dev_get_cbor_info_tx: dev=0x6000038083f0
fido_tx: dev=0x6000038083f0, cmd=0x10
fido_tx: buf=0x7ff7b71e6590, len=1
0000: 04
fido_dev_get_cbor_info_rx: dev=0x6000038083f0, ci=0x600003d04000, ms=-1
fido_rx: dev=0x6000038083f0, cmd=0x10, ms=-1
rx_preamble: buf=0x7ff7b71e6510, len=64
0000: 2f b6 cd f1 90 00 d2 00 ac 01 83 66 55 32 46 5f
0016: 56 32 68 46 49 44 4f 5f 32 5f 30 6c 46 49 44 4f
0032: 5f 32 5f 31 5f 50 52 45 02 82 6b 63 72 65 64 50
0048: 72 6f 74 65 63 74 6b 68 6d 61 63 2d 73 65 63 72
rx: payload_len=210
rx: buf=0x7ff7b71e6510, len=64
0000: 2f b6 cd f1 00 65 74 03 50 c5 ef 55 ff ad 9a 4b
0016: 9f b5 80 ad eb af e0 26 d0 04 a5 62 72 6b f5 62
0032: 75 70 f5 64 70 6c 61 74 f4 69 63 6c 69 65 6e 74
0048: 50 69 6e f5 75 63 72 65 64 65 6e 74 69 61 6c 4d
rx: buf=0x7ff7b71e6510, len=64
0000: 2f b6 cd f1 01 67 6d 74 50 72 65 76 69 65 77 f5
0016: 05 19 04 b0 06 82 02 01 07 08 08 18 80 09 82 63
0032: 75 73 62 69 6c 69 67 68 74 6e 69 6e 67 0a 82 a2
0048: 63 61 6c 67 26 64 74 79 70 65 6a 70 75 62 6c 69
rx: buf=0x7ff7b71e6510, len=64
0000: 2f b6 cd f1 02 63 2d 6b 65 79 a2 63 61 6c 67 27
0016: 64 74 79 70 65 6a 70 75 62 6c 69 63 2d 6b 65 79
0032: 0d 04 0e 1a 00 05 04 03 00 00 00 00 00 00 00 00
0048: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fido_rx: buf=0x7ff7b71e6590, len=210
0000: 00 ac 01 83 66 55 32 46 5f 56 32 68 46 49 44 4f
0016: 5f 32 5f 30 6c 46 49 44 4f 5f 32 5f 31 5f 50 52
0032: 45 02 82 6b 63 72 65 64 50 72 6f 74 65 63 74 6b
0048: 68 6d 61 63 2d 73 65 63 72 65 74 03 50 c5 ef 55
0064: ff ad 9a 4b 9f b5 80 ad eb af e0 26 d0 04 a5 62
0080: 72 6b f5 62 75 70 f5 64 70 6c 61 74 f4 69 63 6c
0096: 69 65 6e 74 50 69 6e f5 75 63 72 65 64 65 6e 74
0112: 69 61 6c 4d 67 6d 74 50 72 65 76 69 65 77 f5 05
0128: 19 04 b0 06 82 02 01 07 08 08 18 80 09 82 63 75
0144: 73 62 69 6c 69 67 68 74 6e 69 6e 67 0a 82 a2 63
0160: 61 6c 67 26 64 74 79 70 65 6a 70 75 62 6c 69 63
0176: 2d 6b 65 79 a2 63 61 6c 67 27 64 74 79 70 65 6a
0192: 70 75 62 6c 69 63 2d 6b 65 79 0d 04 0e 1a 00 05
0208: 04 03
parse_reply_element: cbor type
fido_dev_open_rx: FIDO_MAXMSG=2048, maxmsgsiz=1200
Running fake assertion for RPID zarquon
fido_dev_authkey_tx: dev=0x6000038083f0
fido_tx: dev=0x6000038083f0, cmd=0x10
fido_tx: buf=0x6000008140a0, len=6
0000: 06 a2 01 02 02 02
fido_dev_authkey_rx: dev=0x6000038083f0, authkey=0x600001f1c000, ms=4999
fido_rx: dev=0x6000038083f0, cmd=0x10, ms=4999
rx_preamble: buf=0x7ff7b71e5c60, len=64
0000: 2f b6 cd f1 90 00 51 00 a1 01 a5 01 02 03 38 18
0016: 20 01 21 58 20 e7 7e c6 e5 7c 77 f1 68 02 af ef
0032: c1 37 37 3e fa a2 26 d9 b4 89 df f7 1e 11 0b 68
0048: d8 a6 ef fb 82 22 58 20 69 27 42 9c 7e c0 85 1c
rx: payload_len=81
rx: buf=0x7ff7b71e5c60, len=64
0000: 2f b6 cd f1 00 04 19 c4 36 f5 eb 96 e4 61 f9 58
0016: 19 c6 a6 46 40 04 ef ad 64 9f e8 dd 92 00 00 00
0032: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0048: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fido_rx: buf=0x7ff7b71e5cf0, len=81
0000: 00 a1 01 a5 01 02 03 38 18 20 01 21 58 20 e7 7e
0016: c6 e5 7c 77 f1 68 02 af ef c1 37 37 3e fa a2 26
0032: d9 b4 89 df f7 1e 11 0b 68 d8 a6 ef fb 82 22 58
0048: 20 69 27 42 9c 7e c0 85 1c 04 19 c4 36 f5 eb 96
0064: e4 61 f9 58 19 c6 a6 46 40 04 ef ad 64 9f e8 dd
0080: 92
fido_tx: dev=0x6000038083f0, cmd=0x10
fido_tx: buf=0x600002400080, len=120
0000: 06 a4 01 02 02 05 03 a5 01 02 03 38 18 20 01 21
0016: 58 20 da cc be 95 d4 79 0b 34 fa e5 ae 4c 22 4b
0032: 9f 77 b2 a0 77 f7 45 77 2e a6 28 c7 a4 c1 f1 59
0048: a3 cb 22 58 20 cf 13 04 9f e5 a9 47 93 07 9b 29
0064: f5 c7 bf e4 95 9c 0e 3b 3d 46 73 35 6c ef d7 a1
0080: 6f 75 00 01 d5 06 58 20 29 7b 00 f3 8b ae 57 4e
0096: d0 ac eb 51 85 df b0 93 d3 8e f3 e7 41 7f 8f 4c
0112: 97 b5 a0 f3 db 73 cd 38
fido_rx: dev=0x6000038083f0, cmd=0x10, ms=4992
rx_preamble: buf=0x7ff7b71e5c40, len=64
0000: 2f b6 cd f1 90 00 35 00 a1 02 58 30 99 30 44 50
0016: 02 7e 38 e4 d4 da 4d 36 d8 24 3c a6 6d b1 e5 72
0032: e7 d9 fc 43 b5 a4 28 63 1b ac 97 bc 9c 0c b8 f7
0048: 18 0d 0a 5d 45 7c 33 f5 a6 60 ae 88 00 00 00 00
rx: payload_len=53
fido_rx: buf=0x7ff7b71e5d00, len=53
0000: 00 a1 02 58 30 99 30 44 50 02 7e 38 e4 d4 da 4d
0016: 36 d8 24 3c a6 6d b1 e5 72 e7 d9 fc 43 b5 a4 28
0032: 63 1b ac 97 bc 9c 0c b8 f7 18 0d 0a 5d 45 7c 33
0048: f5 a6 60 ae 88
fido_tx: dev=0x6000038083f0, cmd=0x10
fido_tx: buf=0x600002e04000, len=89
0000: 02 a5 01 67 7a 61 72 71 75 6f 6e 02 58 20 84 e0
0016: c0 ea fa a9 5a 34 c2 93 f2 78 ac 52 e4 5c e5 37
0032: ba b5 e7 52 a0 0e 69 59 a1 3a e1 03 b6 5a 05 a1
0048: 62 75 70 f5 06 58 20 19 5c 87 fc a8 7b 51 2b 49
0064: 29 0c f9 36 28 e7 a9 b4 54 de fa 5c eb 3c 75 ad
0080: 07 f2 86 d5 5f 3c 47 07 02
fido_rx: dev=0x6000038083f0, cmd=0x10, ms=4909
rx_preamble: buf=0x7ff7b71e6550, len=64
0000: 2f b6 cd f1 90 00 e7 00 a4 01 a2 62 69 64 58 30
0016: 09 86 28 be 01 fd 40 5b 96 5f 3a 1f c4 d6 df f9
0032: 2f f5 6e 98 2c bc f9 0d 15 02 35 a1 5a 60 f1 59
0048: 3d 78 ae 5d 53 7e 89 1a 9e af 82 e0 b0 80 b0 38
rx: payload_len=231
rx: buf=0x7ff7b71e6550, len=64
0000: 2f b6 cd f1 00 64 74 79 70 65 6a 70 75 62 6c 69
0016: 63 2d 6b 65 79 02 58 25 3a 57 e8 a8 fb 6b 38 ad
0032: 74 db 90 30 1b 94 1b 56 ab 07 a7 dc 5a 19 a1 41
0048: 27 29 39 0c f3 59 f7 06 05 00 00 02 92 03 58 48
rx: buf=0x7ff7b71e6550, len=64
0000: 2f b6 cd f1 01 30 46 02 21 00 89 ad 07 29 d3 ec
0016: 5b b3 b3 17 0c b0 d1 67 f1 10 2a 66 ae 42 3a 53
0032: a4 01 e8 0a 0a 30 22 ed 1d 13 02 21 00 a6 cf b3
0048: a7 b6 fd 47 fe 71 58 7c ed 22 26 ea d9 68 3e ed
rx: buf=0x7ff7b71e6550, len=64
0000: 2f b6 cd f1 02 f7 9f a3 46 91 9d ce 13 ce cf 61
0016: 82 ce 04 a1 62 69 64 58 24 32 61 30 65 36 30 63
0032: 34 2d 65 66 62 33 2d 34 61 34 32 2d 38 36 63 35
0048: 2d 34 37 34 33 62 63 39 37 63 32 33 63 00 00 00
fido_rx: buf=0x7ff7b71e6610, len=231
0000: 00 a4 01 a2 62 69 64 58 30 09 86 28 be 01 fd 40
0016: 5b 96 5f 3a 1f c4 d6 df f9 2f f5 6e 98 2c bc f9
0032: 0d 15 02 35 a1 5a 60 f1 59 3d 78 ae 5d 53 7e 89
0048: 1a 9e af 82 e0 b0 80 b0 38 64 74 79 70 65 6a 70
0064: 75 62 6c 69 63 2d 6b 65 79 02 58 25 3a 57 e8 a8
0080: fb 6b 38 ad 74 db 90 30 1b 94 1b 56 ab 07 a7 dc
0096: 5a 19 a1 41 27 29 39 0c f3 59 f7 06 05 00 00 02
0112: 92 03 58 48 30 46 02 21 00 89 ad 07 29 d3 ec 5b
0128: b3 b3 17 0c b0 d1 67 f1 10 2a 66 ae 42 3a 53 a4
0144: 01 e8 0a 0a 30 22 ed 1d 13 02 21 00 a6 cf b3 a7
0160: b6 fd 47 fe 71 58 7c ed 22 26 ea d9 68 3e ed f7
0176: 9f a3 46 91 9d ce 13 ce cf 61 82 ce 04 a1 62 69
0192: 64 58 24 32 61 30 65 36 30 63 34 2d 65 66 62 33
0208: 2d 34 61 34 32 2d 38 36 63 35 2d 34 37 34 33 62
0224: 63 39 37 63 32 33 63
adjust_assert_count: cbor_type
adjust_assert_count: cbor_type
adjust_assert_count: cbor_type
adjust_assert_count: cbor_type
cbor_decode_assert_authdata: buf=0x600000404180, len=37
fido_tx: dev=0x6000038083f0, cmd=0x11
fido_tx: buf=0x0, len=0
Got 1 assertions
	user = (null)

Repro program.

repro program
#include <stdio.h>
#include <string.h>

#include <fido.h>

const int kExitFailure = EXIT_FAILURE;

// NOTE: change consts below as appropriate.
const char *kDevPath = "ioreg://4294977891";
const char *rpid = "zarquon";
const char *pin = "12345";

int runOnDevice(const fido_dev_info_t *info) {
  if (info == NULL) {
    return kExitFailure;
  }

  printf("Using device [%s]\n", fido_dev_info_path(info));
  fido_dev_t *dev = fido_dev_new_with_info(info);
  if (dev == NULL) {
    return kExitFailure;
  }

  int r;
  if ((r = fido_dev_open_with_info(dev)) != FIDO_OK) {
    return r;
  }

  const unsigned char cdh[32] = "00000000000000000000000000000000";

  fido_assert_t *ass = fido_assert_new();
  if (r == FIDO_OK)
    r = fido_assert_set_clientdata(ass, cdh, 32);
  if (r == FIDO_OK)
    r = fido_assert_set_rp(ass, rpid);
  if (r == FIDO_OK)
    r = fido_assert_set_up(ass, FIDO_OPT_TRUE);
  if (r == FIDO_OK)
    r = fido_assert_set_uv(ass, FIDO_OPT_TRUE);
  if (r == FIDO_OK)
    r = fido_dev_set_timeout(dev, /* ms= */ 5000);
  if (r == FIDO_OK) {
    printf("Running fake assertion for RPID %s\n", rpid);
    r = fido_dev_get_assert(dev, ass, pin);
  }

  fido_dev_cancel(dev);
  fido_dev_close(dev);
  fido_dev_free(&dev);
  if (r != FIDO_OK) {
    fido_assert_free(&ass);
    return r;
  }

  size_t count = fido_assert_count(ass);
  printf("Got %zu assertions\n", count);
  for (size_t i = 0; i < count; i++) {
    printf("\tuser = %s\n", fido_assert_user_name(ass, i));
  }

  fido_assert_free(&ass);
  return 0;
}

int main() {
  fido_init(0);

  const size_t kMaxDevs = 64;
  fido_dev_info_t *infos = fido_dev_info_new(kMaxDevs);
  if (infos == NULL) {
    return kExitFailure;
  }

  size_t nInfos = 0;
  fido_dev_info_manifest(infos, kMaxDevs, &nInfos); // always returns FIDO_OK
  if (nInfos == 0) {
    return kExitFailure;
  }

  const fido_dev_info_t *tInfo = NULL;
  for (size_t i = 0; i < nInfos; i++) {
    const fido_dev_info_t *info = fido_dev_info_ptr(infos, i);
    if (info == NULL) {
      continue;
    }

    const char *path = fido_dev_info_path(info);
    const char *mstr = fido_dev_info_manufacturer_string(info);
    const char *pinfo = fido_dev_info_product_string(info);

    printf("Device #%zu: [%s] [%s] [%s]\n", i, path, mstr, pinfo);

    if (strcmp(kDevPath, path) == 0) {
      tInfo = info;
    }
  }

  int r = runOnDevice(tInfo);
  if (r != FIDO_OK) {
    fprintf(stderr, "libfido error %d: %s\n", r, fido_strerr(r));
  }

  fido_dev_info_free(&infos, nInfos);
  return r;
}

As said in the issue, not sure if this is WAI, but I appreciate the feedback nonetheless.

LDVG commented

Hi,

Sounds like this is working as intended. When an authenticator has a single discoverable credential for a specific relying party, it returns the user ID field only (retrievable using the fido_assert_user_id_{ptr,len}() functions). For the multiple accounts per RP case, it also returns the other fields (unless the authenticator has an internal mechanism for selecting which credential to use). For further details, please see the specification's authenticatorGetAssertion response structure [1], specifically the user (0x04) member.

[1] https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorgetassertion-response-structure

Thanks @LDVG for the quick reply and @martelletto for the man update.