-
Notifications
You must be signed in to change notification settings - Fork 556
Add WebAuthn documentation #870
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
emlun
wants to merge
6
commits into
syncthing:main
Choose a base branch
from
emlun:webauthn
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
bb9e4b5
Add WebAuthn documentation
emlun c559500
Address review comments
emlun 52a0f76
Add section on the 2FA setting for credentials
emlun b00ec9c
Fix typo
emlun 97f8023
Clarify unlimited credential storage
emlun 24d021d
Link to external resources on obtaining HTTPS cert
emlun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
<https://w3c.github.io/webauthn/#dictdef-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 | ||
<https://w3c.github.io/webauthn/#dictdef-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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
<https://w3c.github.io/webauthn/#dictdef-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 | ||
<https://w3c.github.io/webauthn/#dictdef-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 <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 <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 <rest-webauthn-credential_>`_. | ||
|
||
.. 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 <rest-webauthn-registration_>`_. | ||
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 <rest-webauthn-credential_>`_. | ||
|
||
Assuming that ``id: "VxT1FCv2..."`` | ||
was previously returned from `/rest/webauthn/register-finish <rest-webauthn-registration_>`_ | ||
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 <rest-webauthn-state_>`_ have the following attributes: | ||
|
||
- ``id`` | ||
|
||
The base64url-encoded `credential ID <https://www.w3.org/TR/webauthn/#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 <https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples>`_. | ||
This is created by the authenticator and cannot be changed. | ||
|
||
- ``signCount`` | ||
|
||
The `signature counter <https://www.w3.org/TR/webauthn/#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) <https://www.w3.org/TR/webauthn/#user-verification>`_, | ||
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. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.