Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions lib/auth/webauthncli/fido2.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,19 @@ func fido2Register(
}); {
case errors.Is(err, libfido2.ErrNoCredentials):
return true, nil
case err == nil:
case errors.Is(err, libfido2.ErrUserPresenceRequired):
// Yubikey4 does this when the credential exists.
return false, nil
case err != nil:
// Swallow unexpected errors: a double registration is better than
// aborting the ceremony.
log.Debugf(
"FIDO2: Device %v: excluded credential assertion failed, letting device through: err=%q",
info.path, err)
return true, nil
default:
log.Debugf("FIDO2: Device %v: filtered due to presence of excluded credential", info.path)
return false, nil
default: // unexpected error
return false, trace.Wrap(err)
}
}

Expand Down Expand Up @@ -650,6 +658,7 @@ func findSuitableDevices(filter deviceFilterFunc, knownPaths map[string]struct{}
}

var info *libfido2.DeviceInfo
var u2f bool
const infoAttempts = 3
for i := 0; i < infoAttempts; i++ {
info, err = dev.Info()
Expand All @@ -659,6 +668,7 @@ func findSuitableDevices(filter deviceFilterFunc, knownPaths map[string]struct{}
// A FIDO/U2F device has no capabilities beyond MFA
// registrations/assertions.
info = &libfido2.DeviceInfo{}
u2f = true
case errors.Is(err, libfido2.ErrTX):
// Happens occasionally, give the device a short grace period and retry.
time.Sleep(1 * time.Millisecond)
Expand All @@ -673,7 +683,7 @@ func findSuitableDevices(filter deviceFilterFunc, knownPaths map[string]struct{}
}
log.Debugf("FIDO2: Info for device %v: %#v", path, info)

di := makeDevInfo(path, info)
di := makeDevInfo(path, info, u2f)
switch ok, err := filter(dev, di); {
case err != nil:
return nil, trace.Wrap(err, "device %v: filter", path)
Expand Down Expand Up @@ -838,6 +848,7 @@ func selectDevice(
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetInfo.
type deviceInfo struct {
path string
u2f bool
plat bool
rk bool
clientPinCapable, clientPinSet bool
Expand All @@ -850,8 +861,17 @@ func (di *deviceInfo) uvCapable() bool {
return di.uv || di.clientPinSet
}

func makeDevInfo(path string, info *libfido2.DeviceInfo) *deviceInfo {
di := &deviceInfo{path: path}
func makeDevInfo(path string, info *libfido2.DeviceInfo, u2f bool) *deviceInfo {
di := &deviceInfo{
path: path,
u2f: u2f,
}

// U2F devices don't respond to dev.Info().
if u2f {
return di
}

for _, opt := range info.Options {
// See
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetInfo.
Expand Down
49 changes: 49 additions & 0 deletions lib/auth/webauthncli/fido2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1659,6 +1659,55 @@ func TestFIDO2Register_errors(t *testing.T) {
}
}

func TestFIDO2Register_u2fExcludedCredentials(t *testing.T) {
resetFIDO2AfterTests(t)

u2fDev := mustNewFIDO2Device("/u2f", "" /* pin */, nil /* info */)
u2fDev.u2fOnly = true

// otherDev is FIDO2 in this test, but it could be any non-registered device.
otherDev := mustNewFIDO2Device("/fido2", "" /* pin */, &libfido2.DeviceInfo{
Options: authOpts,
})

f2 := newFakeFIDO2(u2fDev, otherDev).withNonMeteredLocations()
f2.setCallbacks()

const origin = "https://example.com"
cc := &wanlib.CredentialCreation{
Response: protocol.PublicKeyCredentialCreationOptions{
Challenge: make([]byte, 32),
RelyingParty: protocol.RelyingPartyEntity{
ID: "example.com",
},
Parameters: []protocol.CredentialParameter{
{Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgES256},
},
AuthenticatorSelection: protocol.AuthenticatorSelection{
UserVerification: protocol.VerificationDiscouraged,
},
Attestation: protocol.PreferNoAttestation,
},
}

ctx := context.Background()

// Setup: register the U2F device.
resp, err := wancli.FIDO2Register(ctx, origin, cc, u2fDev)
require.NoError(t, err, "FIDO2Register errored")

// Setup: mark the registered credential as excluded.
cc.Response.CredentialExcludeList = append(cc.Response.CredentialExcludeList, protocol.CredentialDescriptor{
Type: protocol.PublicKeyCredentialType,
CredentialID: resp.GetWebauthn().GetRawId(),
})

// Register a new device, making sure a failed excluded credential assertion
// won't break the ceremony.
_, err = wancli.FIDO2Register(ctx, origin, cc, otherDev)
require.NoError(t, err, "FIDO2Register errored, expected a successful registration")
}

func resetFIDO2AfterTests(t *testing.T) {
pollInterval := wancli.FIDO2PollInterval
devLocations := wancli.FIDODeviceLocations
Expand Down