Yubico/libfido2

assert without -p still triggers userPresence check on authenticator

restena-sw opened this issue · 3 comments

What version of libfido2 are you using?

master (cf85116)

What operating system are you running?

Debian 11

What application are you using in conjunction with libfido2?

examples/cred & examples/assert

How does the problem manifest itself?

After successful registration of a credential, subsequent assert for the credential always triggers the blink&wait on the authenticator even if the the "-p" command-line option is NOT set.

Is the problem reproducible?

Yes.

What are the steps that lead to the problem?

Example:

./cred -t es256 -k 12345 -r /dev/hidraw6
./assert 12345 /dev/hidraw6

Does the problem happen with different authenticators?

On two different Yubico products at least (Yubikey 5C, no PIN set; Yubikey Bio, PIN set + fingerprint enrolled).

Please include the output of fido2-token -L.

fido2-token -L
# fido2-token -L
/dev/hidraw4: vendor=0x1050, product=0x0402 (Yubico YubiKey FIDO)
/dev/hidraw6: vendor=0x1050, product=0x0407 (Yubico YubiKey OTP+FIDO+CCID)

Please include the output of fido2-token -I.

fido2-token -I
# fido2-token -I /dev/hidraw4
proto: 0x02
major: 0x05
minor: 0x05
build: 0x06
caps: 0x05 (wink, cbor, msg)
version strings: U2F_V2, FIDO_2_0, FIDO_2_1_PRE, FIDO_2_1
extension strings: credProtect, hmac-secret, largeBlobKey, credBlob, minPinLength
aaguid: d8522d9f575b486688a9ba99fa02f35b
options: rk, up, uv, noplat, uvToken, alwaysUv, credMgmt, authnrCfg, bioEnroll, clientPin, largeBlobs, pinUvAuthToken, setMinPINLength, nomakeCredUvNotRqd, credentialMgmtPreview, userVerificationMgmtPreview
maxmsgsiz: 1200
maxcredcntlst: 8
maxcredlen: 128
fwversion: 0x50506
pin protocols: 2, 1
pin retries: 8
uv retries: 3
sensor type: 1 (touch)
max samples: 16
# fido2-token -I /dev/hidraw6
proto: 0x02
major: 0x05
minor: 0x01
build: 0x02
caps: 0x05 (wink, cbor, msg)
version strings: U2F_V2, FIDO_2_0
extension strings: hmac-secret
aaguid: cb69481e8ff7403993ec0a2729a154a8
options: rk, up, noplat, noclientPin
maxmsgsiz: 1200
maxcredcntlst: 0
maxcredlen: 0
fwversion: 0x0
pin protocols: 1
pin retries: undefined
uv retries: undefined

Please include the output of FIDO_DEBUG=1.

FIDO_DEBUG=1
# FIDO_DEBUG=1 ./examples/assert 12345 /dev/hidraw6
fido_tx: dev=0x55d3159c7320, cmd=0x06
fido_tx: buf=0x55d3159c7320, len=8
0000: a4 ae 30 1e bc c2 fb 0e
fido_rx: dev=0x55d3159c7320, cmd=0x06, ms=-1
rx_preamble: buf=0x7ffe9d2c7e40, len=64
0000: ff ff ff ff 86 00 11 a4 ae 30 1e bc c2 fb 0e 01
0016: 21 00 0a 02 05 01 02 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=0x55d3159c7328, len=17
0000: a4 ae 30 1e bc c2 fb 0e 01 21 00 0a 02 05 01 02
0016: 05
fido_dev_get_cbor_info_tx: dev=0x55d3159c7320
fido_tx: dev=0x55d3159c7320, cmd=0x10
fido_tx: buf=0x7ffe9d2c7e97, len=1
0000: 04
fido_dev_get_cbor_info_rx: dev=0x55d3159c7320, ci=0x55d3159c7470, ms=-1
fido_rx: dev=0x55d3159c7320, cmd=0x10, ms=-1
rx_preamble: buf=0x7ffe9d2c7db0, len=64
0000: 01 21 00 0a 90 00 56 00 a6 01 82 66 55 32 46 5f
0016: 56 32 68 46 49 44 4f 5f 32 5f 30 02 81 6b 68 6d
0032: 61 63 2d 73 65 63 72 65 74 03 50 cb 69 48 1e 8f
0048: f7 40 39 93 ec 0a 27 29 a1 54 a8 04 a4 62 72 6b
rx: payload_len=86
rx: buf=0x7ffe9d2c7db0, len=64
0000: 01 21 00 0a 00 f5 62 75 70 f5 64 70 6c 61 74 f4
0016: 69 63 6c 69 65 6e 74 50 69 6e f4 05 19 04 b0 06
0032: 81 01 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=0x55d3159c7570, len=86
0000: 00 a6 01 82 66 55 32 46 5f 56 32 68 46 49 44 4f
0016: 5f 32 5f 30 02 81 6b 68 6d 61 63 2d 73 65 63 72
0032: 65 74 03 50 cb 69 48 1e 8f f7 40 39 93 ec 0a 27
0048: 29 a1 54 a8 04 a4 62 72 6b f5 62 75 70 f5 64 70
0064: 6c 61 74 f4 69 63 6c 69 65 6e 74 50 69 6e f4 05
0080: 19 04 b0 06 81 01
fido_dev_open_rx: FIDO_MAXMSG=2048, maxmsgsiz=1200
fido_tx: dev=0x55d3159c7320, cmd=0x10
fido_tx: buf=0x55d3159c7f30, len=48
0000: 02 a2 01 69 6c 6f 63 61 6c 68 6f 73 74 02 58 20
0016: a2 fe ee a3 57 46 c3 8f 60 3e 6e 0d 6b 75 30 d9
0032: c9 43 88 22 5a 69 60 1a 19 9d eb 0a 05 59 4b 36
fido_rx: dev=0x55d3159c7320, cmd=0x10, ms=-1
rx_preamble: buf=0x7ffe9d2c7e00, len=64
0000: 01 21 00 0a 90 00 c2 00 a4 01 a2 62 69 64 50 9b
0016: 8b 5f 1d 1b c8 91 14 d6 8b 9b 2a 3d e2 7c 19 64
0032: 74 79 70 65 6a 70 75 62 6c 69 63 2d 6b 65 79 02
0048: 58 25 49 96 0d e5 88 0e 8c 68 74 34 17 0f 64 76
rx: payload_len=194
rx: buf=0x7ffe9d2c7e00, len=64
0000: 01 21 00 0a 00 60 5b 8f e4 ae b9 a2 86 32 c7 99
0016: 5c f3 ba 83 1d 97 63 01 00 00 03 d0 03 58 48 30
0032: 46 02 21 00 bd 9b fa a6 bb 3a 6e 99 12 c4 ee 27
0048: 57 60 23 ef 0c 9d c9 1a 25 45 62 62 c3 32 4e 93
rx: buf=0x7ffe9d2c7e00, len=64
0000: 01 21 00 0a 01 88 3a 00 a3 02 21 00 b7 20 5d 1e
0016: f3 3e fe a3 98 80 bb 2a 57 ac 02 c7 96 61 01 db
0032: cd 3b 7c 0c ed 99 01 d8 5f 94 76 8d 04 a1 62 69
0048: 64 58 20 78 1c 78 60 ad 88 d2 63 32 62 2a f1 74
rx: buf=0x7ffe9d2c7e00, len=64
0000: 01 21 00 0a 02 5d ed b2 e7 a4 2b 44 89 29 39 c5
0016: 56 64 01 27 0d bb c4 49 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
fido_rx: buf=0x55d3159c86b0, len=194
0000: 00 a4 01 a2 62 69 64 50 9b 8b 5f 1d 1b c8 91 14
0016: d6 8b 9b 2a 3d e2 7c 19 64 74 79 70 65 6a 70 75
0032: 62 6c 69 63 2d 6b 65 79 02 58 25 49 96 0d e5 88
0048: 0e 8c 68 74 34 17 0f 64 76 60 5b 8f e4 ae b9 a2
0064: 86 32 c7 99 5c f3 ba 83 1d 97 63 01 00 00 03 d0
0080: 03 58 48 30 46 02 21 00 bd 9b fa a6 bb 3a 6e 99
0096: 12 c4 ee 27 57 60 23 ef 0c 9d c9 1a 25 45 62 62
0112: c3 32 4e 93 88 3a 00 a3 02 21 00 b7 20 5d 1e f3
0128: 3e fe a3 98 80 bb 2a 57 ac 02 c7 96 61 01 db cd
0144: 3b 7c 0c ed 99 01 d8 5f 94 76 8d 04 a1 62 69 64
0160: 58 20 78 1c 78 60 ad 88 d2 63 32 62 2a f1 74 5d
0176: ed b2 e7 a4 2b 44 89 29 39 c5 56 64 01 27 0d bb
0192: c4 49
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=0x55d3159c81a0, len=37
assert: fopen: No such file or directory
assert: read_ec_pubkey

Remark

Inspecting the code, I realised that options.up simply remains unset when the "-p" parameter is not supplied to assert - which then may explain the observed behaviour because options.up is spec'ed as true by default in the CTAP2 spec.

In code, up is initialised to false, is not changed during parameter parsing if -p is unset, and setting the actual CTAP2 parameter happens inside a if (up && conditional, which is skipped when the parameter is unset.

I changed the source to always set fido_assert_set_up() like this in assert.c line 151:

if ((r = fido_assert_set_up(assert, up ? FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK)

to make options.up always explicitly set; but that did not change the observed behaviour. (authenticators still wait for user interaction)
So, I suspect, in addition to the code issue of falling back to spec defaults rather than explicitly setting to false, there might be an actual issue with Yubikey firmwares in that they do not honour the request not to execute userPresence checks.

LDVG commented

Hi,

examples/assert.c:151 controls the behavior of the assertion verification, not the request itself. You have the corresponding code for the request sent to the authenticator at examples/assert.c:297. Making your modifications there should get you your desired behavior. We've deliberately not included UP=false in the example; the -p flag simply enforces that the user presence flag is set in the returned authenticator data.

For more control over the various options, you may want to experiment with fido2-assert instead. For example:

$ fido2-assert -G -i assert_param -t up=false /dev/hidraw7 | fido2-assert -V pubkey es256
$ echo $?
0

Ah, that's very helpful indeed!

  1. It's more of a documentation bug in the examples/ then. The README states for "assert": The -p option requests that the user be present. It would be more correct to state `The -p option verifies that the authenticator asserted that the user was present."

  2. I've modified line 297, and yes: it does yield the desired behaviour (immediate auth transaction without user interaction). There is only one oddity I noticed when running with FIDO_DEBUG=1:

# FIDO_DEBUG=1 ./assert 12345 /dev/hidraw2
fido_tx: dev=0x5585d47bf320, cmd=0x06
fido_tx: buf=0x5585d47bf320, len=8
0000: d3 7b 9e ee d9 3c e7 07
fido_rx: dev=0x5585d47bf320, cmd=0x06, ms=-1
rx_preamble: buf=0x7ffd5290f870, len=64
0000: ff ff ff ff 86 00 11 d3 7b 9e ee d9 3c e7 07 01
0016: 22 00 04 02 05 01 02 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=0x5585d47bf328, len=17
0000: d3 7b 9e ee d9 3c e7 07 01 22 00 04 02 05 01 02
0016: 05
fido_dev_get_cbor_info_tx: dev=0x5585d47bf320
fido_tx: dev=0x5585d47bf320, cmd=0x10
fido_tx: buf=0x7ffd5290f8c7, len=1
0000: 04
fido_dev_get_cbor_info_rx: dev=0x5585d47bf320, ci=0x5585d47bf470, ms=-1
fido_rx: dev=0x5585d47bf320, cmd=0x10, ms=-1
rx_preamble: buf=0x7ffd5290f7e0, len=64
0000: 01 22 00 04 90 00 56 00 a6 01 82 66 55 32 46 5f
0016: 56 32 68 46 49 44 4f 5f 32 5f 30 02 81 6b 68 6d
0032: 61 63 2d 73 65 63 72 65 74 03 50 cb 69 48 1e 8f
0048: f7 40 39 93 ec 0a 27 29 a1 54 a8 04 a4 62 72 6b
rx: payload_len=86
rx: buf=0x7ffd5290f7e0, len=64
0000: 01 22 00 04 00 f5 62 75 70 f5 64 70 6c 61 74 f4
0016: 69 63 6c 69 65 6e 74 50 69 6e f4 05 19 04 b0 06
0032: 81 01 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=0x5585d47bf570, len=86
0000: 00 a6 01 82 66 55 32 46 5f 56 32 68 46 49 44 4f
0016: 5f 32 5f 30 02 81 6b 68 6d 61 63 2d 73 65 63 72
0032: 65 74 03 50 cb 69 48 1e 8f f7 40 39 93 ec 0a 27
0048: 29 a1 54 a8 04 a4 62 72 6b f5 62 75 70 f5 64 70
0064: 6c 61 74 f4 69 63 6c 69 65 6e 74 50 69 6e f4 05
0080: 19 04 b0 06 81 01
fido_dev_open_rx: FIDO_MAXMSG=2048, maxmsgsiz=1200
fido_tx: dev=0x5585d47bf320, cmd=0x10
fido_tx: buf=0x5585d47c0190, len=54
0000: 02 a3 01 69 6c 6f 63 61 6c 68 6f 73 74 02 58 20
0016: a2 fe ee a3 57 46 c3 8f 60 3e 6e 0d 6b 75 30 d9
0032: c9 43 88 22 5a 69 60 1a 19 9d eb 0a 05 59 4b 36
0048: 05 a1 62 75 70 f4
fido_rx: dev=0x5585d47bf320, cmd=0x10, ms=-1
rx_preamble: buf=0x7ffd5290f830, len=64
0000: 01 22 00 04 90 00 c1 00 a4 01 a2 62 69 64 50 87
0016: 9c c5 20 6d a3 db ef 96 d5 13 e7 8d 8d b1 80 64
0032: 74 79 70 65 6a 70 75 62 6c 69 63 2d 6b 65 79 02
0048: 58 25 49 96 0d e5 88 0e 8c 68 74 34 17 0f 64 76
rx: payload_len=193
rx: buf=0x7ffd5290f830, len=64
0000: 01 22 00 04 00 60 5b 8f e4 ae b9 a2 86 32 c7 99
0016: 5c f3 ba 83 1d 97 63 00 00 00 03 d5 03 58 47 30
0032: 45 02 21 00 de 70 a5 ff 4d a4 d8 5e 35 d8 34 7e
0048: 3b 8f 9d f3 f6 32 01 7e cd 0c 08 5e 51 7e a1 62
rx: buf=0x7ffd5290f830, len=64
0000: 01 22 00 04 01 17 a9 9d 2d 02 20 69 c6 8b 5c bf
0016: b7 b0 ca b9 82 a1 c8 b6 3a 4c d1 ec 5b 69 29 07
0032: 36 fa 45 f9 af 7f f0 3f 4e ea 01 04 a1 62 69 64
0048: 58 20 78 1c 78 60 ad 88 d2 63 32 62 2a f1 74 5d
rx: buf=0x7ffd5290f830, len=64
0000: 01 22 00 04 02 ed b2 e7 a4 2b 44 89 29 39 c5 56
0016: 64 01 27 0d bb c4 49 00 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
fido_rx: buf=0x5585d47c06b0, len=193
0000: 00 a4 01 a2 62 69 64 50 87 9c c5 20 6d a3 db ef
0016: 96 d5 13 e7 8d 8d b1 80 64 74 79 70 65 6a 70 75
0032: 62 6c 69 63 2d 6b 65 79 02 58 25 49 96 0d e5 88
0048: 0e 8c 68 74 34 17 0f 64 76 60 5b 8f e4 ae b9 a2
0064: 86 32 c7 99 5c f3 ba 83 1d 97 63 00 00 00 03 d5
0080: 03 58 47 30 45 02 21 00 de 70 a5 ff 4d a4 d8 5e
0096: 35 d8 34 7e 3b 8f 9d f3 f6 32 01 7e cd 0c 08 5e
0112: 51 7e a1 62 17 a9 9d 2d 02 20 69 c6 8b 5c bf b7
0128: b0 ca b9 82 a1 c8 b6 3a 4c d1 ec 5b 69 29 07 36
0144: fa 45 f9 af 7f f0 3f 4e ea 01 04 a1 62 69 64 58
0160: 20 78 1c 78 60 ad 88 d2 63 32 62 2a f1 74 5d ed
0176: b2 e7 a4 2b 44 89 29 39 c5 56 64 01 27 0d bb c4
0192: 49
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=0x5585d47bf5a0, len=37
cbor_decode_assert_authdata: buf=0x5585d47d9550, len=37
fido_check_flags: flags=00
fido_check_flags: up=1, uv=0
fido_get_signed_hash: cose_alg=-7

Note the two lines

fido_check_flags: flags=00
fido_check_flags: up=1, uv=0

The assertion did correctly not set userPresence, but the lib function fido_check_flags was called with up=1 anyway? And then doesn't fail?

LDVG commented

It's more of a documentation bug in the examples/ then. The README states for "assert": The -p option requests that the user be present. It would be more correct to state `The -p option verifies that the authenticator asserted that the user was present."

We'll give it a look and see if we can improve it. Thank you!

Note the two lines

fido_check_flags: flags=00
fido_check_flags: up=1, uv=0

The assertion did correctly not set userPresence, but the lib function fido_check_flags was called with up=1 anyway? And then doesn't fail?

up=1 is FIDO_OPT_FALSE. To verify that that the user presence flag has been set by the authenticator, fido_assert_set_up() must be called with FIDO_OPT_TRUE before fido_assert_verify() is called.

$ export FIDO_DEBUG=1
$ fido2-assert -G -i assert_param -t up=false /dev/hidraw7 | fido2-assert -V -p pubkey es256
...
fido_check_flags: flags=00
fido_check_flags: up=2, uv=0
fido_check_flags: CTAP_AUTHDATA_USER_PRESENT
fido_assert_verify: fido_check_flags
fido2-assert: fido_assert_verify: FIDO_ERR_INVALID_PARAM
$ echo $?
1