This commit adds a user message ("Detected security key tap") that is
printed at the earliest opportunity after `tsh` succesfully detects a
security key tap.
This solves a problem users have when there is significant latency in
their connection to their teleport proxy, where there may be zero
visible feedback to the user for upwards of 8 seconds after they tap
their security key.
This often causes users to think that the tap was not detected or
failed, and they attempt to tap again.
This can cause the user to accidentally paste a "yubisneeze" into their
terminal, which is not great from a security perspective.
As an API choice here I added a `TouchAcknowledger` returned by they
`PromptTouch` of the `LoginPrompt` interface.
It may seem like a bit of a weird API, but I did it this way so that all
callers of `PromptTouch` are forced to explicitly acknowledge the touch
or explicitly ignore that return value, they cannot simply forget to ack.
This applies to WebAuthn and Passwordless logins from `tsh`, no changes
are made to Connect.
Improves FIDO2 login/registration UX by letting users choose (almost) any
available key and then presenting a user-friendly error if the operation cannot
be done.
New devices are now polled for continuously, as we can't eagerly filter devices
anymore. All FIDO2 devices, regardless of their capabilities, are made to wait
for user interaction: once the user interacts with the device we either complete
the operation successfully or return a reason for failure.
U2F-only devices are still silently ignored, as before. They don't respond well
to FIDO2 APIs and proved to be unwieldy in practical tests. (Maybe we can tackle
those in a follow up.)
Examples of new UX:
```shell
# Attempting passwordless login on a non-capable device (lack of PIN)
$ tsh login --proxy=zarquon --user=ihaveitall --auth=passwordless
> Tap your security key
> ERROR: device not registered for passwordless
# Attempting passwordless registration on a non-capable device (lack of PIN)
$ tsh mfa add --type=WEBAUTHN --name=test --allow-passwordless
> Tap any *registered* security key
> Tap your *new* security key
> ERROR: device lacks PIN or user verification capabilities
```
Closes#15037.
U2F devices, like the Yubikey4, fail in a slightly unexpected way when
assertions set UP=false. Handle this edge case and also make sure that failures
when checking for excluded credentials won't break the entire ceremony.
Similar to #15975, but in the registration flow this time.
Repro steps:
1. Register a Yubikey 4
2. Attempt to register a different security key
3. Registration fails with a "user presence required" error
U2F devices, like Yubikey 4, don't seem to respect cancels, locking the program
if a different security key is chosen.
There isn't a clean way to cancel when Cancel() doesn't work, so I've added a
timeout to let the program complete. It's OK for the timeout to be short, it's
not tied to user interaction and by the time we get to it all cancels were
already issued.
Fixes an issue where libfido2 code wrongly interprets
`libfido2.ErrUserPresenceRequired` as `libfido2.ErrNoCredentials`.
Added some logging for good measure.
#14657
* Simulate Yubikey4 UP=false behavior
* Avoid wrongly filtering Yubikey4 devices
Improve the error message for libfido2 error 60 (UV blocked) and automatically
retry error 63 (UV invalid).
libfido2 error 63 happens with some frequency with Yubikey Bio. The biometric
sensor is a bit finicky, so if the finger is a bit off it tends to happen.
Fixes#13900.
* Simulate libfido2 errors 60 and 63
* Add better handling for common libfido2 errors
The `--allow-passwordless` flag was ignored when set, which makes tsh do
non-passwordless registrations. This causes all sorts of confusing behavior.
I'm taking the chance to also add some additional logging to webauthncli.
* Correctly use the value of --allow-passwordless
* Add additional logging to wancli
The FIDO1/2 device detection loop makes an incorrect [assumption about the
Info()][1] method, which breaks the loop for FIDO devices. That was in turn
compounded by a swallowed error, causing a rather confusing behavior.
We now correctly handle FIDO devices by assuming they have no FIDO2-like
capabilities and treat our errors more gracefully (or less silently).
Fixes#14657.
[1]: https://github.com/gravitational/go-libfido2/blob/45a8c53e4500d24b209a94b86dbc6cefeafaa649/fido2.go#L339
* Add a registration/login test for U2F devices
* Fix the device detection loop for U2F devices
Implement the Touch ID credential picker.
During passwordless authentication, when more than one login is present in
Enclave credentials, `tsh` now asks the end user to pick their desired login.
Credential picker terminal prompts are preceded by a system Touch ID prompt,
which is then reused for authentication, provided less than 10 seconds pass in
the meantime.
I've done a couple of refactors to make the `CredentialInfo` structs similar
between the `webauthncli` and `touchid` packages, so it's easier to trace
parallels between them.
#13901
* Refactor touchid.CredentialInfo
* Refactor wancli.CredentialInfo
* Move fido2_prompt*.go to prompt*.go
* Define the touchid credential picker API
* Add Touch ID credential picker tests
* Implement touchid credential picker
* configure golangci-lint misspell to check for anglicized spellings
* Americanize spellings
* fix aws constant value with british spelling 🇬🇧
* update api types with americanized spellings
* use american spellings .cloudbuild/scripts
Address the following FIDO2 issues:
1. Retry assertions without UV if UV=true if the PIN is set
2. Correctly handle single account responses from authenticators
1 happens in the wild and the fix is confirmed. The fix is based in the fact
that the authenticator will set the UV bit in the resulting assertion when the
PIN is present, regardless of us asking for it. We do need the UV bit to clear
server-side validation, that haven't changed.
2 was a misunderstanding on my part; the spec doesn't require authenticators to
return user names for single account responses, which they effectively don't. In
practical terms we are unable to guarantee the `--user` flag for single account
scenarios, but not failing seems better than failing. See the
[authenticatorGetAssertion response][1], user member.
[1]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorgetassertion-response-structure
* Add tests for UV failure and single account
* Correctly handle single account responses
* Retry without UV if a PIN is set
Add a credential picker to the tsh FIDO2/WebAuthn backend.
The PR pulls a recent patch in our go-libfido2 fork that makes it correctly
return multiple assertions from the authenticator. This allows us to implement
the credential picker for FIDO2, simplify our implementation and provide the
exact same UX that browsers use (always 1-touch for bio, touch->PIN->touch
otherwise).
I've dropped concepts like "optimistic assertions" and "eager PIN prompts" in
favor of a simple, uniform implementation.
Issue #13901.
* Prompt for credentials in LoginPrompt
* Update go-libfido2
* Implement FIDO2 credential picker
* Drop optimistic assertions, only set user if explicit
* Add license to fido2_prompt_test.go
Favor newer Touch ID credentials in the allowed set for MFA, or just the newer
credential for passwordless.
Fixes a capture-by-reference bug and adds coverage for it.
Issue #13340.
* Add tests for Touch ID credential-choosing logic
* Favor newer Touch ID credentials within the allowed set
* Warn about origin vs RPID mismatch
Avoid "input swallowing" bugs by disabling stdin hijacking by default.
Only `tsh login` is allowed to hijack stdin, as it is expected to exit right
after authentication. Any MFA authentication attempts resulting from
non-`tsh login` invocations default to the user's strongest auth method.
Defaulting to the strongest auth method can cause problems in constrained
environments for users that have both Webauthn and OTP registered. For example,
someone using `tsh` under WSL (Windows Subsystem for Linux) or a remote machine
could be locked into Webauthn MFA, which they can't use because their
environment lacks USB access or they don't have physical access to it. In order
to solve this problem I've slightly modified the meaning of the `--mfa-mode`
flag so `otp` can be specified.
The `TELEPORT_MFA_MODE` environment variable may be set to avoid constant flag
passing.
Fixes#12675 and #13021.
* Expand --mfa-mode and disable stdin hijack by default
* Use TELEPORT_ instead of TSH_ for FIDO2 env var
* Use t.Setenv in tests
U2F facets don't serve a purpose since Webauthn became the de-facto MFA
mechanism. This should make it easier for users to write v10-style
authentication configs.
The remaining U2F fields are still relevant for when we derive Webauthn settings
from U2F, so those are kept (and validated).
Note that we still keep the fileconf field, since we don't want configs to
needlessly break during version upgrades.
#10375
* Remove references to U2F.Facets
* Remove Facets from U2F proto
* Reinstate proto Facets field
U2F gets marshaled as JSON to storage, so we better keep the field to
avoid issue with resource configs.
* Add deprecation notice on comment
* Update generated protos
* Allow register passwordless on invite/reset
* Specify the type of device usage when registering new device
* Minor refactoring to reduce number of parameters to funcs
* Check if device register requested pwdless, response is pwdless capable
Add the --mfa-mode flag and wire touch ID into tsh authn/registration.
In the default MFA mode (auto), touch ID should be eager if there is a
credential present in the system. Users may change this behavior using other MFA
modes.
See https://github.com/gravitational/teleport/blob/master/rfd/0054-passwordless-macos.md.
#9160
* Add AuthenticatorAttachment/MFAMode to tsh
* Add support for touch ID in wancli.Login
* Warn users about passwordless in U2F mode
* Add support for touch ID in `tsh mfa add`
Fixes a rather exoteric race condition where a goroutine beats an
already-cancelled context in a select.
The select in question sits on selectDevice / fido2.go#L729. It is not an error
for the select to go either way, instead this is a failure of test setup. The
fix is simple: we use authenticators that do have resident credentials for the
test, so their Assertion operations block waiting for user interaction,
eliminating the race in the test in question.
Example flaky execution:
https://console.cloud.google.com/cloud-build/builds/ceaa0674-db49-444b-b059-a17e01b0328f?project=ci-account#9160
Document the different implementations, fallbacks, build tags, and provide some
install/use instructions.
#9160
* Document FIDO2 support in ./lib/auth/webauthncli
* Add license
Build `tsh` with static `libfido2`, `libcbor`,`libcrypto` and `libudev-zero`.
Dockerfiles for buildbox and Centos7 changed. FIPS and macOS to be addressed at
a later date.
Add the `tsh fido2 diag` hidden command for ease of testing.
#9160
* Update go-libfido2 and tidy modules
* Add a fido2 diagnostic command to tsh
* Add a few build artifacts to .gitignore
* Build tsh with static libfido2 in buildbox
* Build tsh with static libfido2 in centos7
* Add a few relevant cmake flags
* Use illiliti/libudev-zero
* Do multi-stage build on centos7, image tweaks
* Add `make enter/centos7`
* s/OFf/OFF/g
Allow users to disable FIDO2 integration in tsh binaries that do have it. This
is a safety toggle for cases where the libraries happen to cause problems, even
though the binary loads and runs.
#9160
Make sure we prompt the user if no devices are plugged, otherwise they get no
feedback.
#9160
* Prompt device for touch if no devices are plugged
* Better reuse knownPaths map
Introduce the concept of an "optimistic assertion", which allows us to skip
credential listing (saving a touch) and go directly for an authenticator
assertion.
The downside of an optimistic assertion is that the authenticator picks the
credential, meaning that we can't guarantee or choose the user. This should be
fine for most people, as they are unlikely to have multiple Teleport users in a
single cluster. If the --user flag is explicitly provided we'll honor it and do
the two-touch ceremony instead.
Optimistic assertions are only applied for biometric authenticators; we already
do single-touch for PINs if possible.
This is a bit of an experimental change. It should improve the experience in
most scenarios, but we may elect to rollback if the underlying assumption proves
itself to be poor.
Note that we now depend on gravitational/go-libfido2, as the upstream
go-libfido2 doesn't yet return the credential ID and user ID in assertions.
#9160
* Alias keys-pub/go-libfido2 to gravitational/go-libfido2
* Add LoginOpts to wancli.Login
* Allow optimistic assertions for bio devices
* Use optimistic assertions if the user is not provided
* Pull optimistic assertion check to deviceCallback
* Tweak OptimisticAssertion godoc
During passwordless logins, if the only candidate device is PIN protected, we
can ask for the PIN right away, saving a touch.
The timing for the the initial touch prompt is now dictated by
wancli.Login/wancli.Register, as the touch is not necessarily the first
operation to happen anymore.
#9160
* Move initial prompt to wancli callbacks
* Eagerly ask for PIN in certain situations
* Rename func to "shouldDoEagerPINPrompt"
A few FIDO2 scenarios are not respecting cancellation, causing problems in
certain `tsh` flows.
The PR fixes the issue above and sneaks a small fix in `tsh mfa add` as well.
#9160
* Add tests for various cancel conditions
* Respect context cancellation during select and PIN steps
* Do not ask for passwordless for non-webauthn devices
Passwordless login is enabled by the global `--pwdless` flag. Registration gets
a new prompt and an `--allow-passwordless` flag.
UX messages were tweaked to follow the descriptions on RFD 53: Passwordless
FIDO2[1].
Passwordless login requires two touches for all devices (both PIN and biometric).
I'd like to get it down to a single touch, at least for the most common
situations, but that'll be a follow up to this work.
Passwordless support requires `tsh` to be compiled with the `libfido2` tag, try
`go build -tags=libfido2 ./tool/tsh`.
#9160
[1] https://github.com/gravitational/teleport/blob/master/rfd/0053-passwordless-fido2.md#ux
* Allow reuse of devices for passwordless
* Implement passwordless registration in tsh
* Add better tracing to FIDO2 filters
* Implement passwordless logins in tsh
* Make --pwdless a global flag
* Fix lint errors
* Fix U2F tests
* Use initClient's URL as origin
* Distinguish whether --allow-passwordless is set or unset
Seamlessly change the public API of lib/auth/webauthncli to use the
libfido2-backed implementation, as long as the binary was compiled with the
libfido2 build tag.
A few adjustments are necessary to "wancli" methods to allow users to provide
prompt callbacks and to return the credential user (not applicable here, but
will be in following PRs).
Additional changes are made to tsh mfa add in order to avoid stdin hijacking by
ContextReader, since we now may require PIN reads for authenticators.
#9160
* Move U2F logic to u2f_* files
* Split U2F API from general l/a/webauthncli API
* Move FIDO2 public API to fido2_common.go, introduce IsFIDO2Available
* Introduce prompt.SyncReader
Sync reads allow prompt calls to be mixed with term.ReadPassword calls.
* Wire FIDO2 into MFA login
* Wire FIDO2 into MFA registration
Implements CLI login and registration using go-libfido2. Covers both MFA and
passwordless use cases.
The FIDO2 implementation is akin to the existing U2F Login / Registration logic,
including a similar "device detection" loop. A few notable differences are:
A filtered "device search" step that ends as soon a suitable device is found A
more explicit "device selection" step, which makes it easier to implement PIN
flows The MFA UX for end-users should remain mostly unaltered.
There are no separate methods for MFA and passwordless, as much of the logic
would be the same. Instead, the methods react to the assertion/credential
parameters accordingly.
At this moment this code is isolated from other callers, as well as from our
build processes via the libfido2 tag. This is to avoid impact to other
developers, as go-libfido2 has a few requirements before it can be downloaded or
executed.
#9160
* Import github.com/keys-pub/go-libfido2
* Implement FIDO2 login
* Add login tests
* Implement FIDO2 registration
* Add registration tests
I've opted to keep MFA and passwordless authorization interfaces separate, as
they have slight interface differences, storage needs, and allow for different
use-cases.
LoginFlow, the existing API, is now focused on MFA. A new type,
PasswordlessFlow, provides support for passwordless authorization. Both
implementations are backed by a private loginFlow type, refactored from the guts
of the old LoginFlow, which is a superset of MFA and Passwordless.
All registration use-cases are provided by the existing RegistrationFlow. In
this case the change is simpler, as a single passwordless parameter suffices.
Registration is always performed by a previously-authorized user, so it's
storage interactions remain uniform.
Finally, login methods won't create a WebAuthn ID anymore, instead they rely on
registration to do it. The simplification relies on the fact that user handles
are not mandatory. (They do matter for resident key registration, which does
provide them.)
#9160
* Add verification support to mocku2f
* Support passwordless login on lib/auth/webauthn
* Support passwordless registration on lib/auth/webauthn
* Refactor web ID creation
`tsh` doesn't support MFA logins on Windows; we make that explicit by warning
users when necessary, instead of directing them to a hopeless workflow.
Example attempt:
```shell
$ tsh login
> Enter password for Teleport user codingllama: <password>
> ERROR: hardware device MFA not supported by your platform, please register an OTP device
>
> exit status 1
```
The toy interface serves a few purposes:
Easy testing of Webauthn login and of interop between CLI and Web
Reference for the actual Web UI implementation
* Add a toy Webauthn web interface
* Use the device type to decide whether to use the App ID
* Small tweaks in server and Js
* Fix webauthncli TestLogin breakage
* Replace Printf with Println
Implement the device polling loop for Webauthn devices and apply it to
tsh login.
A separate package, lib/auth/webauthncli, is introduced to hold client-focused
Webauthn logic. The separation highlights the distinction between server- and
client-side code and isolates client-side dependencies from the existing
lib/auth/webauthn.
Includes improved test coverage for TeleportClient.Login (local logins only) and
refactoring of existing code to support Webauthn challenges (PromptMFAChallenge
logic has less branching and U2F device loop extracted and refactored into
lib/auth/webauthncli).
In terms of device compatibility, this is identical to the existing U2F
solution- we are using the same underlying libraries and adapting the CTAP1
responses to Webauthn. This is easy to do and easy to reason about. I'm planning
for a native Webauthn solution, such as libfido2, but that is larger change that
doesn't seem to get us all the way to where we want for now (looking at you,
Touch ID).
* Add transformations and validations to lib/auth/webauthn
* Implement client-side Webauthn login
* Use RunOnU2FDevices in the lib/auth/u2f package
This one is optional: it cuts a bit of the codebase today, but one could
argue that it is safer to keep U2F untouched (up to the point where it
gets removed).
* Use CollectedClientData definition in lib/auth/mocku2f
* Fix data race on auth.Server clock usage
* Add direct test for TeleportClient.Login
* Reply to Webauthn challenges on `tsh login`