From bb9e4b5aaf349de8a5ba0f868e9bee5f7140717a Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sun, 21 Jul 2024 16:43:22 +0200 Subject: [PATCH 1/6] Add WebAuthn documentation --- dev/rest.rst | 9 ++ rest/noauth-webauthn.rst | 83 +++++++++++ rest/webauthn.rst | 292 +++++++++++++++++++++++++++++++++++++++ users/config.rst | 42 ++++++ users/index.rst | 1 + users/webauthn.rst | 39 ++++++ 6 files changed, 466 insertions(+) create mode 100644 rest/noauth-webauthn.rst create mode 100644 rest/webauthn.rst create mode 100644 users/webauthn.rst diff --git a/dev/rest.rst b/dev/rest.rst index 6a986f8b6..94c7fb964 100644 --- a/dev/rest.rst +++ b/dev/rest.rst @@ -118,6 +118,15 @@ Debug Endpoints /rest/debug/... <../rest/debug.rst> +WebAuthn Endpoints +---------------- + +.. toctree:: + :maxdepth: 1 + :glob: + + ../rest/webauthn* + Noauth Endpoints ---------------- diff --git a/rest/noauth-webauthn.rst b/rest/noauth-webauthn.rst new file mode 100644 index 000000000..68d165559 --- /dev/null +++ b/rest/noauth-webauthn.rst @@ -0,0 +1,83 @@ +POST /rest/noauth/auth/webauthn-start +===================================== + +.. versionadded:: TODO 1.28.0 + +Begins a WebAuthn authentication ceremony. + +No parameters. The response is a JSON object +suitable as the parameter to ``navigator.credentials.get()`` +after base64url decoding binary values - i.e., +it contains a single attribute ``publicKey`` with the shape of +`PublicKeyCredentialRequestOptionsJSON +`_. + +Example response: + +.. code-block:: json + + { + "publicKey": { + "challenge": "tlGvyFeTIOEPWVJLWZuiRCBEl2dVnC0ZvWt4Epmk-rk", + "timeout": 120000, + "rpId": "localhost", + "allowCredentials": [ + { + "type": "public-key", + "id": "XW6tWsMNphd3rbESk4n9HEtd-h2MUdkHWQV6k2vuAzz8F9UoDTAVj3D-DWF_0z6q4R03mRJbtUPMDdNVr2Km-A", + "transports": ["usb"] + } + ], + "userVerification": "discouraged" + } + } + + +POST /rest/noauth/auth/webauthn-finish +====================================== + +.. versionadded:: TODO 1.28.0 + +Finishes a WebAuthn authentication ceremony, logging the user into the GUI if successful. + +The request body is a JSON object containing two attributes: +a required ``credential`` attribute with the shape of +`AuthenticationResponseJSON +`_ - i.e., +a ``PublicKeyCredential`` object +(the result of calling ``navigator.credentials.get()``) +with base64url encoded binary values, +and an optional Boolean ``stayLoggedIn`` attribute. +If ``stayLoggedIn`` is ``false`` or absent, the returned session cookie will expire with the browser session, +if ``true`` the cookie will persist for a time after the browser session ends. + +The response on success is status 204 (No content) with no response body +and a ``Set-Cookie`` header containing the session cookie. + +Example request: + +.. code-block:: json + + { + "credential": { + "type": "public-key", + "id": "XW6tWsMNphd3rbESk4n9HEtd-h2MUdkHWQV6k2vuAzz8F9UoDTAVj3D-DWF_0z6q4R03mRJbtUPMDdNVr2Km-A", + "rawId": "XW6tWsMNphd3rbESk4n9HEtd-h2MUdkHWQV6k2vuAzz8F9UoDTAVj3D-DWF_0z6q4R03mRJbtUPMDdNVr2Km-A", + "authenticatorAttachment": "cross-platform", + "response": { + "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJ0bEd2eUZlVElPRVBXVkpMV1p1aVJDQkVsMmRWbkMwWnZXdDRFcG1rLXJrIiwib3JpZ2luIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODM4NCIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ", + "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAABPA", + "signature": "MEUCIQDjTizDIioXQFPrMih8UaAAo9R6sdYCMedrBxpSeYkd2wIgIMI-5h_CHJHa04EFN4HPsFO4nLCW8XR3iu5cRu5X4-w", + "userHandle": null + }, + "clientExtensionResults": {} + }, + "stayLoggedIn": true + } + +Example response headers (other headers omitted): + +.. code-block:: + + HTTP/1.1 204 No Content + Set-Cookie: sessionid-STLYOU4=banm5zZNHRAzJXmHUwWLjZmoJ9p4huCGscVSxnbXjgSR6CLuES3vQr2u5uX3Zt43; Path=/; Max-Age=604800; Secure diff --git a/rest/webauthn.rst b/rest/webauthn.rst new file mode 100644 index 000000000..17664f94f --- /dev/null +++ b/rest/webauthn.rst @@ -0,0 +1,292 @@ +.. _rest-webauthn-registration: + +POST /rest/webauthn/register-start +---------------------------------- + +.. versionadded:: TODO 1.28.0 + +``POST .../register-start`` begins a WebAuthn registration ceremony +and ``POST .../register-finish`` finshes it, +adding the newly created credential to a list of pending credentials. +Pending credentials may be persisted +by including them in a request to ``POST /rest/webauthn/state``. + +``POST .../register-start`` takes no parameters and returns a JSON object +suitable as the parameter to ``navigator.credentials.create()`` +after base64url decoding binary values - i.e., +it contains a single attribute ``publicKey`` with the shape of +`PublicKeyCredentialCreationOptionsJSON +`_. + +Example response: + +.. code-block:: json + + { + "publicKey": { + "rp": { + "name": "Syncthing @ DEVICENAME", + "id": "localhost" + }, + "user": { + "name": "asdf", + "displayName": "asdf", + "id": "4_KyxKWr6x2KvB3GGHYLkjmn1M6xTip5ITZQUgaUzJW5e023M0j4NBOkgR-4aQarM7RRCv7TGkmOD53kQBPhLQ" + }, + "challenge": "VopAfwRL52Jc1E_H0yi-kEmb59s4IfJ1UN2zSjY_5CA", + "pubKeyCredParams": [ + { "type": "public-key", "alg": -7 }, + { "type": "public-key", "alg": -35 }, + { "type": "public-key", "alg": -36 }, + { "type": "public-key", "alg": -257 }, + { "type": "public-key", "alg": -258 }, + { "type": "public-key", "alg": -259 }, + { "type": "public-key", "alg": -37 }, + { "type": "public-key", "alg": -38 }, + { "type": "public-key", "alg": -39 }, + { "type": "public-key", "alg": -8 } + ], + "timeout": 300000, + "authenticatorSelection": { + "requireResidentKey": false, + "userVerification": "preferred" + } + } + } + + +POST /rest/webauthn/register-finish +----------------------------------- + +.. versionadded:: TODO 1.28.0 + +``POST .../register-start`` begins a WebAuthn registration ceremony +and ``POST .../register-finish`` finshes it, +adding the newly created credential to a list of pending credentials. +Pending credentials may be persisted +by including them in a request to ``POST /rest/webauthn/state``. + +``POST .../register-finish`` takes a request body with the shape of +`RegistrationResponseJSON +`_ - i.e., +a ``PublicKeyCredential`` object +(the result of calling ``navigator.credentials.create()``) +with base64url encoded binary values. +It returns a JSON representation of the pending registered credential, +which can be added as an element of the ``credentials`` array +in the JSON body of a `/rest/webauthn/state `_ request. + + +.. note:: + WebAuthn credentials are "config-like" + and are managed in the "GUI" section of the settings GUI. + In order to follow the convention of changes being pending + until the user presses the "save" button, + this API call does not yet permanently save the returned credential. + To permanently save the credential and activate it as a login option, + the returned object must be saved by appending it to the ``credentials`` list + in the JSON body of a `/rest/webauthn/state `_ request. + + +Example request: + +.. code-block:: json + + { + "type": "public-key", + "id": "VxT1FCv2nrNwCTGmOnNDoUAY3p6RJyvBzF7y-dsD5Ll73Mve76m9okIX7C5cDf2elKxtBRRmcnMUuVnPk3TUuA", + "rawId": "VxT1FCv2nrNwCTGmOnNDoUAY3p6RJyvBzF7y-dsD5Ll73Mve76m9okIX7C5cDf2elKxtBRRmcnMUuVnPk3TUuA", + "authenticatorAttachment": "cross-platform", + "response": { + "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJWb3BBZndSTDUySmMxRV9IMHlpLWtFbWI1OXM0SWZKMVVOMnpTallfNUNBIiwib3JpZ2luIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODM4NCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ", + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAABAAAAAAAAAAAAAAAAAAAAAAAQFcU9RQr9p6zcAkxpjpzQ6FAGN6ekScrwcxe8vnbA-S5e9zL3u-pvaJCF-wuXA39npSsbQUUZnJzFLlZz5N01LilAQIDJiABIVgg1ZEbVe7_o93_XuuRl98qhHa-cmsJrpL_Rw5wrpEqgqIiWCCpp0NlSL-xBR9lDc5Th5Y1WsGLs0vS5jgjxh_kS1D_0Q", + "transports": ["nfc", "usb"] + }, + "clientExtensionResults": {} + } + +Example response: + +.. code-block:: json + + { + "id": "VxT1FCv2nrNwCTGmOnNDoUAY3p6RJyvBzF7y-dsD5Ll73Mve76m9okIX7C5cDf2elKxtBRRmcnMUuVnPk3TUuA==", + "rpId": "localhost", + "nickname": "", + "publicKeyCose": "pQECAyYgASFYINWRG1Xu_6Pd_17rkZffKoR2vnJrCa6S_0cOcK6RKoKiIlggqadDZUi_sQUfZQ3OU4eWNVrBi7NL0uY4I8Yf5EtQ_9E=", + "signCount": 4, + "transports": ["nfc", "usb"], + "requireUv": false, + "createTime": "2024-07-21T15:24:01+02:00", + "lastUseTime": "2024-07-21T15:24:01+02:00" + } + + +.. _rest-webauthn-state: + +GET /rest/webauthn/state +------------------------ + +Returns the state of currently registered WebAuthn credentials. +The credential data model is described `below `_. + +.. versionadded:: TODO 1.28.0 + +Example ``GET`` response: + +.. code-block:: json + + { + "credentials": [ + { + "id": "cTVm-CWdvbMOX7v4QdUxJgPZ5TWpFuliLDWNcI9chOw02DBJcZjmvHDOwpGEwxS6Lk6H8eikYbystBghaJuq-g==", + "rpId": "localhost", + "nickname": "Security key", + "publicKeyCose": "pQECAyYgASFYIC9CP0p82dtJiRKYfUGSYeVaccOsNAmYgIz-EAl1GzbyIlggtcbhDVA8bUpjK_GH3QpGL9i_y9GfoTM1pg0jyEBf88M=", + "signCount": 644, + "transports": ["nfc", "usb"], + "requireUv": true, + "createTime": "2024-07-13T13:58:07Z", + "lastUseTime": "2024-07-21T12:55:43Z" + }, + { + "id": "4gvuaMwVUnv6a0cNRzUm4hkbeTgVsf7HUBbgXBoSB9A57AagRbZvWCUMaBjroYhnWBubRq_29uo4CGFtfWwpdg==", + "rpId": "localhost", + "nickname": "", + "publicKeyCose": "pQECAyYgASFYIGxYkCaHAkelm7Mu5JGtaQFdcAPqlWlhOFuGah4eom7KIlggtvPzU9tMFtxElKqr3zXO2YZAlIKAbUOvbTA93tx39Rc=", + "signCount": 115, + "transports": ["nfc", "usb"], + "requireUv": false, + "createTime": "2024-07-13T14:07:20Z", + "lastUseTime": "2024-07-13T15:36:44Z" + } + ] + } + + +POST /rest/webauthn/state +------------------------- + +.. versionadded:: TODO 1.28.0 + +Updates the WebAuthn state to match the body, +except the following rules are applied to the new ``credentials`` value: + +- Each item must have an ``id`` that already exists in the currently stored ``credentials`` value, + or in the list of pending credentials stored by `/rest/webauthn/register-finish `_. + Items with any other ``id`` are ignored. +- For each already existing item, all attributes except ``nickname`` and ``requireUv`` are ignored. + +The credential data model is described in the `next section `_. + +Assuming that ``id: "VxT1FCv2..."`` +was previously returned from `/rest/webauthn/register-finish `_ +as in the example above, +this example request would change the nickname of "Security key" to "My security key", +delete the credential with ``id: "4gvuaMwV..."`` +and persist the pending credential with the nickname "New security key": + +.. code-block:: json + + { + "credentials": [ + { + "id": "cTVm-CWdvbMOX7v4QdUxJgPZ5TWpFuliLDWNcI9chOw02DBJcZjmvHDOwpGEwxS6Lk6H8eikYbystBghaJuq-g==", + "rpId": "localhost", + "nickname": "My security key", + "publicKeyCose": "pQECAyYgASFYIC9CP0p82dtJiRKYfUGSYeVaccOsNAmYgIz-EAl1GzbyIlggtcbhDVA8bUpjK_GH3QpGL9i_y9GfoTM1pg0jyEBf88M=", + "signCount": 644, + "transports": ["nfc", "usb"], + "requireUv": true, + "createTime": "2024-07-13T13:58:07Z", + "lastUseTime": "2024-07-21T12:55:43Z" + }, + { + "id": "VxT1FCv2nrNwCTGmOnNDoUAY3p6RJyvBzF7y-dsD5Ll73Mve76m9okIX7C5cDf2elKxtBRRmcnMUuVnPk3TUuA==", + "rpId": "localhost", + "nickname": "New security key", + "publicKeyCose": "pQECAyYgASFYINWRG1Xu_6Pd_17rkZffKoR2vnJrCa6S_0cOcK6RKoKiIlggqadDZUi_sQUfZQ3OU4eWNVrBi7NL0uY4I8Yf5EtQ_9E=", + "signCount": 4, + "transports": ["nfc", "usb"], + "requireUv": false, + "createTime": "2024-07-21T15:24:01+02:00", + "lastUseTime": "2024-07-21T15:24:01+02:00" + } + ] + } + + +.. _rest-webauthn-credential: + +The credential data model +------------------------- + +Items in the ``credentials`` field of the `WebAuthn state `_ have the following attributes: + +- ``id`` + + The base64url-encoded `credential ID `_ of the credential. + This is created by the authenticator and cannot be changed. + +- ``rpId`` + + The value of the :stconf:opt:`gui.webauthnRpId` setting in effect at the time this credential was created. + This is set automatically and cannot be changed. + + If :stconf:opt:`gui.webauthnRpId` is changed after creating a credential, + the credential can no longer be used unless the :stconf:opt:`gui.webauthnRpId` value is restored. + This attribute is used in the settings GUI to highlight credentials that cannot currently be used + and show what :stconf:opt:`gui.webauthnRpId` to restore to in order to make them usable again. + +- ``publicKeyCose`` + + The base64url-encoded public key of the credential, in `COSE_Key format `_. + This is created by the authenticator and cannot be changed. + +- ``signCount`` + + The `signature counter `_ of the credential. + A decrease in the signature counter may indicate that the credential has been cloned. + Syncthing displays a warning if this happens, but does not otherwise act on it. + +- ``nickname`` + + A user-chosen nickname for the credential. + If empty or not set, the GUI will use the abbreviated credential ID (``id``) as the name of the credential. + This can be edited in the settings GUI. + +- ``requireUv`` + + If set to ``true``, this credential requires `User Verification (UV) `_, + for example a PIN or a biometric. + This means that logging in with this credential is two-factor authentication (2FA): + something you have (the credential private key) + combined with something you know (a PIN) or something you are (a biometric). + + This can be enabled or disabled in the settings GUI. + + .. note:: + + The PIN or biometric is not sent to the server - + Syncthing does **not** collect or store biometric information. + Instead, the PIN or biometric is only verified locally by your authenticator + (for example, a USB security key or a smartphone) before unlocking the passkey for login. + +- ``transports`` + + A list of hints the browser may use to determine how to communicate with the authenticator + that holds the private key for this credential - + for example, this may be ``["nfc", "usb"]`` if the credential is stored on a USB security key + or ``["hybrid", "internal"]`` if the credential is stored on a smartphone or laptop. + + This is set automatically and cannot not be changed. + Changing it could make the credential unusable, + since the browser might conclude it has no way to communicate with the authenticator + if none of the transports listed here is available on the platform. + If this happens, you can attempt to make the credential usable again by deleting the attribute. + +- ``createTime`` and ``lastUseTime`` + + Timestamps recording when this credential was created and when it was last used to log in to the GUI. + Used only to help the user identify and distinguish credentials in the GUI; + not used for any security decisions. diff --git a/users/config.rst b/users/config.rst index 1d0c7d8fa..fde675813 100644 --- a/users/config.rst +++ b/users/config.rst @@ -144,6 +144,9 @@ The following shows an example of a default configuration file (IDs will differ)
127.0.0.1:8384
k1dnz1Dd0rzTBjjFFh7CXPnrF12C49B1 default + 4_KyxKWr6x2KvB3GGHYLkjmn1M6xTip5ITZQUgaUzJW5e023M0j4NBOkgR-4aQarM7RRCv7TGkmOD53kQBPhLQ== + localhost + https://localhost:8384 @@ -842,6 +845,8 @@ set on the ``gui`` element: If not ``true``, the GUI and API will not be started. +.. _gui-tls: + .. option:: gui.tls :aliases: gui.useTLS @@ -943,6 +948,43 @@ The following child elements may be present: won't see browser popups prompting for username and password. +.. option:: gui.webauthnUserId + + .. versionadded:: TODO 1.28.0 + + The base64url-encoded `user handle `_ + to use when registering WebAuthn credentials (passkeys). + This is automatically set and should usually never need to be changed. + Authenticators may use this to overwrite existing credentials + with the same combination of user handle and RP ID when creating a new credential. + +.. option:: gui.webauthnRpId + + .. versionadded:: TODO 1.28.0 + + The `RP ID `_ + to use for WebAuthn (passkey) registration and authentication. + If not set, this defaults to ``localhost``. + + The RP ID is a domain name and must be the same as or a parent domain of the + domain where the Syncthing GUI is hosted. The RP ID cannot be a raw IP address. + + When you register a new WebAuthn credential (passkey), it gets tied to this RP ID. + If you change the RP ID, any existing keys tied to the previous RP ID will stop working. + +.. option:: gui.webauthnOrigin + + .. versionadded:: TODO 1.28.0 + + The scheme, host and port of the address where WebAuthn logins will take place. + If not set, this defaults to ``https://localhost:8384``. + WebAuthn registration and login will only work if the GUI is hosted at exactly this host address + (excluding the path, query string and hash fragment). + + In general, this should be set to ``https://:
``, + omitting the port if it is ``443``. + + LDAP Element ------------ diff --git a/users/index.rst b/users/index.rst index 4e7f0814f..4c888c91c 100644 --- a/users/index.rst +++ b/users/index.rst @@ -14,6 +14,7 @@ Usage introducer guilisten ldap + webauthn tuning metrics diff --git a/users/webauthn.rst b/users/webauthn.rst new file mode 100644 index 000000000..b2ae92bfb --- /dev/null +++ b/users/webauthn.rst @@ -0,0 +1,39 @@ +WebAuthn (Passkey) Authentication +================================= + +Syncthing can be configured to allow GUI authentication using `WebAuthn `_ (passkeys) +as an alternative to a password. + +To enable WebAuthn, the GUI must use HTTPS (see config :ref:`gui.tls `) +and must be served at exactly the address ``https://localhost:8384``. + +The GUI address can be customized via the advanced GUI settings +:stconf:opt:`gui.webauthnRpId` and :stconf:opt:`gui.webauthnOrigin`. + +If you access the GUI at some other address than ``https://localhost:``, +you'll need to set the ``webauthnRpId`` setting to the domain name or a parent domain name of that address +and ``webauthnOrigin`` to the full address including scheme and port (except the default port), but not path. +For example, if you serve the GUI at the address ``https://syncthing.mydomain.org:8443/syncthing/gui``, +set ``webauthnRpId`` to one of ``mydomain.org`` or ``syncthing.mydomain.org`` +and set ``webauthnOrigin`` to ``https://syncthing.mydomain.org:8443``. + +WebAuthn authentication will be enabled if you have at least one `credential` registered. +A credential is a public-private key pair stored either on an external security key, +or a `platform credential` stored on your computer or phone. +Some platforms might sync platform credentials between devices signed into the same cloud account. + +Use the settings GUI to register a new credential. + +.. note:: + We use the term "passkey" more inclusively here than usual. + For technical reasons, the term "passkey" usually means + a credential that consumes storage space on the authenticator device. + Some external security keys have limited storage capacity + and therefore also have a limited capacity for storing passkeys. + + However, because Syncthing has only a single user account per installation, + we can enable the same use cases as passkeys + but with credentials that do not need to consume storage space. + We therefore sometimes refer to WebAuthn credentials in Syncthing as "passkeys", + because they enable most of the same UI flows as passkeys, + even though they do not consume storage space on external security keys like passkeys usually do. From c5595001fc23d413ad3f8d2698cc0ea96fa2b5db Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sat, 27 Jul 2024 14:35:14 +0200 Subject: [PATCH 2/6] Address review comments --- users/config.rst | 4 ++-- users/webauthn.rst | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/users/config.rst b/users/config.rst index fde675813..5f915581e 100644 --- a/users/config.rst +++ b/users/config.rst @@ -954,7 +954,7 @@ The following child elements may be present: The base64url-encoded `user handle `_ to use when registering WebAuthn credentials (passkeys). - This is automatically set and should usually never need to be changed. + This is automatically set and should usually not need to be changed. Authenticators may use this to overwrite existing credentials with the same combination of user handle and RP ID when creating a new credential. @@ -966,7 +966,7 @@ The following child elements may be present: to use for WebAuthn (passkey) registration and authentication. If not set, this defaults to ``localhost``. - The RP ID is a domain name and must be the same as or a parent domain of the + The RP ID is a domain name and must be the same as, or a parent domain of, the domain where the Syncthing GUI is hosted. The RP ID cannot be a raw IP address. When you register a new WebAuthn credential (passkey), it gets tied to this RP ID. diff --git a/users/webauthn.rst b/users/webauthn.rst index b2ae92bfb..273671f94 100644 --- a/users/webauthn.rst +++ b/users/webauthn.rst @@ -5,7 +5,14 @@ Syncthing can be configured to allow GUI authentication using `WebAuthn `) -and must be served at exactly the address ``https://localhost:8384``. +and must be served at exactly the address ``https://localhost:8384``, +unless configured otherwise as described in :ref:`webauthn-custom-gui-address`. + + +.. _webauthn-custom-gui-address: + +Customizing the GUI address +--------------------------- The GUI address can be customized via the advanced GUI settings :stconf:opt:`gui.webauthnRpId` and :stconf:opt:`gui.webauthnOrigin`. From 52a0f76d79d2607b707e34058ad4dc1e2b89030f Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sat, 27 Jul 2024 15:02:55 +0200 Subject: [PATCH 3/6] Add section on the 2FA setting for credentials --- rest/webauthn.rst | 9 +----- users/webauthn.rst | 75 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/rest/webauthn.rst b/rest/webauthn.rst index 17664f94f..0ea279d59 100644 --- a/rest/webauthn.rst +++ b/rest/webauthn.rst @@ -263,14 +263,7 @@ Items in the ``credentials`` field of the `WebAuthn state something you have (the credential private key) combined with something you know (a PIN) or something you are (a biometric). - This can be enabled or disabled in the settings GUI. - - .. note:: - - The PIN or biometric is not sent to the server - - Syncthing does **not** collect or store biometric information. - Instead, the PIN or biometric is only verified locally by your authenticator - (for example, a USB security key or a smartphone) before unlocking the passkey for login. + This can be enabled or disabled in the settings GUI, see :ref:`webauthn-require2fa`. - ``transports`` diff --git a/users/webauthn.rst b/users/webauthn.rst index 273671f94..d73ffa211 100644 --- a/users/webauthn.rst +++ b/users/webauthn.rst @@ -3,28 +3,13 @@ WebAuthn (Passkey) Authentication Syncthing can be configured to allow GUI authentication using `WebAuthn `_ (passkeys) as an alternative to a password. +WebAuthn offers a passwordless login experience that some users may find preferable. To enable WebAuthn, the GUI must use HTTPS (see config :ref:`gui.tls `) and must be served at exactly the address ``https://localhost:8384``, unless configured otherwise as described in :ref:`webauthn-custom-gui-address`. - -.. _webauthn-custom-gui-address: - -Customizing the GUI address ---------------------------- - -The GUI address can be customized via the advanced GUI settings -:stconf:opt:`gui.webauthnRpId` and :stconf:opt:`gui.webauthnOrigin`. - -If you access the GUI at some other address than ``https://localhost:``, -you'll need to set the ``webauthnRpId`` setting to the domain name or a parent domain name of that address -and ``webauthnOrigin`` to the full address including scheme and port (except the default port), but not path. -For example, if you serve the GUI at the address ``https://syncthing.mydomain.org:8443/syncthing/gui``, -set ``webauthnRpId`` to one of ``mydomain.org`` or ``syncthing.mydomain.org`` -and set ``webauthnOrigin`` to ``https://syncthing.mydomain.org:8443``. - -WebAuthn authentication will be enabled if you have at least one `credential` registered. +WebAuthn authentication will be enabled if you have at least one `credential`, also called a `passkey`, registered. A credential is a public-private key pair stored either on an external security key, or a `platform credential` stored on your computer or phone. Some platforms might sync platform credentials between devices signed into the same cloud account. @@ -44,3 +29,59 @@ Use the settings GUI to register a new credential. We therefore sometimes refer to WebAuthn credentials in Syncthing as "passkeys", because they enable most of the same UI flows as passkeys, even though they do not consume storage space on external security keys like passkeys usually do. + + +.. _webauthn-require2fa: + +The 2FA setting +--------------- + +Each credential (passkey) has a checkbox setting labeled "2FA" in the GUI. +When checked, Syncthing will enforce that this credential uses +`two-factor authentication (2FA) `_. +The technical name for this is `User Verification (UV) `_. + +For example: + +- If the credential is stored on a smartphone, + the phone may prompt for screen unlock to authenticate you to the phone before unlocking the passkey. + This could be a PIN, swipe pattern, fingerprint or face recognition, according to the phone's settings. + + Smartphones typically always require 2FA, + so this setting may not make a noticeable difference for smartphone-based credentials. + +- An external security key may prompt for a PIN configured on the security key, + or use a built-in fingerprint reader. + With the 2FA setting disabled, you would only need to plug in the security key + and usually press a button on it, + but would need no additional factor beyond possessing the security key. + + Some older models of security keys do not support 2FA. + +.. note:: + + No biometrics, PIN or other data is sent to the server - + Syncthing does **not** collect or store biometric information. + Instead, the second factor is only verified locally by your authenticator + (for example, a USB security key or a smartphone) before unlocking the passkey for login. + +If you have some credentials with 2FA enabled and some with 2FA disabled, +you might get prompted for 2FA even when using a credential that doesn't require it. +This is because Syncthing doesn't know beforehand which credential you're going to use, +so it needs to pessimistically request 2FA in case it is required for the credential you choose. + + +.. _webauthn-custom-gui-address: + +Customizing the GUI address +--------------------------- + +The GUI address can be customized via the advanced GUI settings +:stconf:opt:`gui.webauthnRpId` and :stconf:opt:`gui.webauthnOrigin`. + +If you access the GUI at some other address than ``https://localhost:``, +you'll need to set the ``webauthnRpId`` setting to the domain name or a parent domain name of that address +and ``webauthnOrigin`` to the full address including scheme and port (except the default port), but not path. +For example, if you serve the GUI at the address ``https://syncthing.mydomain.org:8443/syncthing/gui``, +set ``webauthnRpId`` to one of ``mydomain.org`` or ``syncthing.mydomain.org`` +and set ``webauthnOrigin`` to ``https://syncthing.mydomain.org:8443``. From b00ec9c2c7f1d077a6b4a89d15a48bcf7551bec9 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Thu, 1 Aug 2024 21:21:09 +0200 Subject: [PATCH 4/6] Fix typo Co-authored-by: Simon Frei --- rest/webauthn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/webauthn.rst b/rest/webauthn.rst index 0ea279d59..bed75c070 100644 --- a/rest/webauthn.rst +++ b/rest/webauthn.rst @@ -6,7 +6,7 @@ POST /rest/webauthn/register-start .. versionadded:: TODO 1.28.0 ``POST .../register-start`` begins a WebAuthn registration ceremony -and ``POST .../register-finish`` finshes it, +and ``POST .../register-finish`` finishes it, adding the newly created credential to a list of pending credentials. Pending credentials may be persisted by including them in a request to ``POST /rest/webauthn/state``. From 97f8023aa6edc38713f2ac190ada59c98dfb39e9 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sun, 11 Aug 2024 21:31:45 +0200 Subject: [PATCH 5/6] Clarify unlimited credential storage --- users/webauthn.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/users/webauthn.rst b/users/webauthn.rst index d73ffa211..7cdf0025e 100644 --- a/users/webauthn.rst +++ b/users/webauthn.rst @@ -10,25 +10,24 @@ and must be served at exactly the address ``https://localhost:8384``, unless configured otherwise as described in :ref:`webauthn-custom-gui-address`. WebAuthn authentication will be enabled if you have at least one `credential`, also called a `passkey`, registered. -A credential is a public-private key pair stored either on an external security key, -or a `platform credential` stored on your computer or phone. +A credential is a public-private key pair that is stored on an `authenticator`, +which could be an external security key, a smartphone, or built into your computer. Some platforms might sync platform credentials between devices signed into the same cloud account. Use the settings GUI to register a new credential. .. note:: We use the term "passkey" more inclusively here than usual. - For technical reasons, the term "passkey" usually means - a credential that consumes storage space on the authenticator device. - Some external security keys have limited storage capacity - and therefore also have a limited capacity for storing passkeys. - - However, because Syncthing has only a single user account per installation, - we can enable the same use cases as passkeys - but with credentials that do not need to consume storage space. + A "passkey" is a credential that enables "username-less login", + which identifies the user automatically without needing them to enter a username first. + For technical reasons, this is incompatible with a cryptographic trick commonly used by external security keys + to support an unlimited number of credentials without consuming storage space. + Therefore, a "passkey" generally must consume storage space on the authenticator. + + However, because a Syncthing instance has only a single user account, + we can enable "username-less login" without preventing the unlimited storage trick. We therefore sometimes refer to WebAuthn credentials in Syncthing as "passkeys", - because they enable most of the same UI flows as passkeys, - even though they do not consume storage space on external security keys like passkeys usually do. + even though they do not consume storage space on external security keys like passkeys generally do. .. _webauthn-require2fa: @@ -45,7 +44,8 @@ For example: - If the credential is stored on a smartphone, the phone may prompt for screen unlock to authenticate you to the phone before unlocking the passkey. - This could be a PIN, swipe pattern, fingerprint or face recognition, according to the phone's settings. + This could be a PIN, swipe pattern, fingerprint, face recognition + or something else, according to the phone's settings. Smartphones typically always require 2FA, so this setting may not make a noticeable difference for smartphone-based credentials. From 24d021d75ee3b8527a172a7dfeb589c18dfb1823 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sun, 11 Aug 2024 21:32:11 +0200 Subject: [PATCH 6/6] Link to external resources on obtaining HTTPS cert --- users/webauthn.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/users/webauthn.rst b/users/webauthn.rst index 7cdf0025e..9de0f3504 100644 --- a/users/webauthn.rst +++ b/users/webauthn.rst @@ -85,3 +85,8 @@ and ``webauthnOrigin`` to the full address including scheme and port (except the For example, if you serve the GUI at the address ``https://syncthing.mydomain.org:8443/syncthing/gui``, set ``webauthnRpId`` to one of ``mydomain.org`` or ``syncthing.mydomain.org`` and set ``webauthnOrigin`` to ``https://syncthing.mydomain.org:8443``. + +For hostnames other than ``localhost`` you will also need an HTTPS certificate your browser considers valid. +For guidance on how to create or obtain one, see for example +`OpenSSL Cookbook `_ +or `Let's Encrypt `_.