diff --git a/lib/auth/webauthncli/fido2.go b/lib/auth/webauthncli/fido2.go index 2d45501e39f1e..9fed2377c9c0b 100644 --- a/lib/auth/webauthncli/fido2.go +++ b/lib/auth/webauthncli/fido2.go @@ -145,7 +145,7 @@ func fido2Login( // Does the device have a suitable credential? const pin = "" - actualRPID, err := discoverRPID(dev, pin, rpID, appID, allowedCreds) + actualRPID, err := discoverRPID(dev, info, pin, rpID, appID, allowedCreds) if err != nil { log.Debugf("FIDO2: Device %v: filtered due to lack of allowed credential", info.path) return false, nil @@ -237,7 +237,7 @@ func fido2Login( }, actualUser, nil } -func discoverRPID(dev FIDODevice, pin, rpID, appID string, allowedCreds [][]byte) (string, error) { +func discoverRPID(dev FIDODevice, info *deviceInfo, pin, rpID, appID string, allowedCreds [][]byte) (string, error) { // The actual hash is not necessary here. const cdh = "00000000000000000000000000000000" @@ -248,8 +248,15 @@ func discoverRPID(dev FIDODevice, pin, rpID, appID string, allowedCreds [][]byte if id == "" { continue } - if _, err := dev.Assertion(id, []byte(cdh), allowedCreds, pin, opts); err == nil { + switch _, err := dev.Assertion(id, []byte(cdh), allowedCreds, pin, opts); { + // Yubikey4 returns ErrUserPresenceRequired if the credential exists, + // despite the UP=false opts above. + case err == nil, errors.Is(err, libfido2.ErrUserPresenceRequired): return id, nil + case errors.Is(err, libfido2.ErrNoCredentials): + // Device not registered for RPID=id, keep trying. + default: + log.WithError(err).Debugf("FIDO2: Device %v: attempt RPID = %v", info.path, id) } } return "", libfido2.ErrNoCredentials diff --git a/lib/auth/webauthncli/fido2_test.go b/lib/auth/webauthncli/fido2_test.go index 5381493ae2475..4afb465249b1a 100644 --- a/lib/auth/webauthncli/fido2_test.go +++ b/lib/auth/webauthncli/fido2_test.go @@ -1967,6 +1967,11 @@ func (f *fakeFIDO2Device) Assertion( // "base" credential. Only add an assertion if explicitly requested. if _, ok := credIDs[string(f.key.KeyHandle)]; ok { + // Simulate Yubikey4 and require UP, even if UP==false is set. + if f.u2fOnly && opts.UP == libfido2.False { + return nil, libfido2.ErrUserPresenceRequired + } + assertions = append(assertions, &libfido2.Assertion{ AuthDataCBOR: assertionAuthDataCBOR, Sig: assertionSig,