1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
# Migrating from U2F to WebAuthn
The Chromium team [recommends](https://groups.google.com/a/chromium.org/forum/#!msg/security-dev/BGWA1d7a6rI/W2avestmBAAJ)
application developers to switch from the U2F API to the WebAuthn API. This document describes how a Ruby application
using the [u2f gem by Castle](https://github.com/castle/ruby-u2f) can migrate existing credentials so that their users
do not experience interruption or need to re-register their security keys.
Note that the migration is one-way: credentials registered using WebAuthn cannot be made compatible with the U2F API.
It is recommended to successfully migrate authorization flows before migrating registration flows.
## Migrate registered U2F credentials
Assuming you have a registered credential per the u2f gem readme, base64 urlsafe encoded in a database:
```ruby
# This domain will be used in all code examples. It's a single-facet app but a multi-facet AppID
# (e.g. https://example.com/app-id.json) will work as well.
domain = URI("https://login.example.com")
u2f_registration = U2F::U2F.new(domain.to_s).register!(u2f_challenge, u2f_register_response)
# => #<U2F::Registration:0x00007fd62f43d688
# @certificate=
# "MIIBCzCBsgIBATAKBggqhkjOPQQDAjASMRAwDgYDVQQDDAdVMkZUZXN0MB4XDTE5MDUzMDE3MjIwM1oXDTIwMDUyOTE3MjIwM1owEjEQMA4GA1UEAwwHVTJGVGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBMKpejumzlH6NxWIx2Ol+EManS9oX5nguG4RT43rZNkyn/zGjdJEhXksN5zT34rLZgFheBgkDGJCdtTPlhVK10wCgYIKoZIzj0EAwIDSAAwRQIhALYxFcROCeifWpv5+wNZIiaO/bGQg8rFBHCw3aHgehdZAiBJ3xFmQh7+Gjxt6CeAcY/k/VVAYu2vP4sUqXnCQFgJUA==",
# @key_handle="mbVMRTgzST5xLumckGztJ9VFW6veObfNIYWSn3sTqIY",
# @public_key="BOJQXlFg+ZfZKm48FNq2Ye5vSwOscE1i7YsGRSIjIe3GI0OXrSBDADDn0dQlz2iDzZ7LvCwiHz72U1qhVas3vus=">
```
The `U2fMigrator` class quacks like `WebAuthn::AuthenticatorAttestationResponse` and can be used similarly as documented
in the [registration verification phase](https://github.com/cedarcode/webauthn-ruby/blob/master/README.md#verification-phase).
Of course a `verify` instance method is not implemented, as there is no real interaction with an authenticator.
The migrator can be used to convert credentials in real time during authentication while keeping them stored in the U2F
format, and in a backfill task to store credentials in the new format, depending on how you are approaching your
migration.
```ruby
require "webauthn/u2f_migrator"
migrated_credential = WebAuthn::U2fMigrator.new(
app_id: domain,
certificate: u2f_registration.certificate,
key_handle: u2f_registration.key_handle,
public_key: u2f_registration.public_key,
counter: u2f_registration.counter
)
migrated_credential.credential.id
# => "\x99\xB5LE83I>q.\xE9\x9C\x90l\xED'\xD5E[\xAB\xDE9\xB7\xCD!\x85\x92\x9F{\x13\xA8\x86"
migrated_credential.credential.public_key
# => "\xA5\x03& \x01!X \xE2P^Q`\xF9\x97\xD9*n<\x14\xDA\xB6a\xEEoK\x03\xACpMb\xED\x8B\x06E\"#!\xED\xC6\x01\x02\"X #C\x97\xAD C\x000\xE7\xD1\xD4%\xCFh\x83\xCD\x9E\xCB\xBC,\"\x1F>\xF6SZ\xA1U\xAB7\xBE\xEB"
migrated_credential.authenticator_data.sign_count
# => 41
```
## Authenticate migrated U2F credentials
Following the documentation on the [authentication initiation](https://github.com/cedarcode/webauthn-ruby/blob/master/README.md#authentication),
you need to specify the [FIDO AppID extension](https://www.w3.org/TR/webauthn/#sctn-appid-extension) for U2F migratedq
credentials. The WebAuthn standard explains:
> The FIDO APIs use an alternative identifier for Relying Parties called an _AppID_, and any credentials created using
> those APIs will be scoped to that identifier. Without this extension, they would need to be re-registered in order to
> be scoped to an RP ID.
For the earlier given example `domain` this means:
- FIDO AppID: `https://login.example.com`
- Valid RP IDs: `login.example.com` (default) and `example.com`
```ruby
credential_request_options = WebAuthn.credential_request_options
credential_request_options[:extensions] = { appid: domain.to_s }
```
On the frontend, in the resolved value from `navigator.credentials.get({ "publicKey": credentialRequestOptions })` add
a call to [getClientExtensionResults()](https://www.w3.org/TR/webauthn/#dom-publickeycredential-getclientextensionresults)
and send its result to your backend alongside the `id`/`rawId` and `response` values. If the authenticator used the AppID
extension, the returned value will contain `{ "appid": true }`. In the example below, we use `clientExtensionResults`.
During authentication verification phase, you must pass either the original AppID or the RP ID as the `rp_id` argument:
> If true, the AppID was used and thus, when verifying an assertion, the Relying Party MUST expect the `rpIdHash` to be
> the hash of the _AppID_, not the RP ID.
```ruby
assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
user_handle: params[:response][:userHandle],
authenticator_data: params[:response][:authenticatorData],
client_data_json: params[:response][:clientDataJSON],
signature: params[:response][:signature],
)
assertion_response.verify(
expected_challenge,
public_key: credential.public_key,
sign_count: credential.count,
rp_id: params[:clientExtensionResults][:appid] ? domain.to_s : domain.host,
)
```
|