diff --git a/CHANGELOG.md b/CHANGELOG.md index d3821ba1..452b660f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ - Add support for crossOrigin/topOrigin verification during credential registration and authentication. [#486](https://github.com/cedarcode/webauthn-ruby/pull/486) [@nicolastemciuc] - Add support for the `attestation_formats` option in `WebAuthn::Credential.options_for_create`. [#514](https://github.com/cedarcode/webauthn-ruby/pull/514) [@santiagorodriguez96] - Add support for the `hints` option in `WebAuthn::Credential.options_for_create` and `WebAuthn::Credential.options_for_get`. [#513](https://github.com/cedarcode/webauthn-ruby/pull/513) [@santiagorodriguez96] +- Reject authenticator data where the BS (backup state) flag is set but the BE (backup eligibility) flag is not, during credential registration and authentication. Raises `WebAuthn::BackupFlagsVerificationError`. [#516](https://github.com/cedarcode/webauthn-ruby/pull/516) [@santiagorodriguez96] ### Changed - Bump the default `credential_options_timeout` from `120_000` to `300_000` milliseconds (5 minutes). [#515](https://github.com/cedarcode/webauthn-ruby/pull/515) [@santiagorodriguez96] +- `WebAuthn::FakeClient#get` now defaults `backup_state` to `false`. The previous default (`backup_eligibility: false`, `backup_state: true`) produced authenticator data that now fails verification. [#516](https://github.com/cedarcode/webauthn-ruby/pull/516) [@santiagorodriguez96] ### Removed diff --git a/lib/webauthn/authenticator_response.rb b/lib/webauthn/authenticator_response.rb index 8f97c7d9..c5d02a47 100644 --- a/lib/webauthn/authenticator_response.rb +++ b/lib/webauthn/authenticator_response.rb @@ -10,6 +10,7 @@ module WebAuthn class VerificationError < Error; end class AuthenticatorDataVerificationError < VerificationError; end + class BackupFlagsVerificationError < VerificationError; end class ChallengeVerificationError < VerificationError; end class OriginVerificationError < VerificationError; end class RpIdVerificationError < VerificationError; end @@ -49,6 +50,8 @@ def verify(expected_challenge, expected_origin = nil, user_presence: nil, user_v verify_item(:user_verified) end + verify_item(:backup_flags) + true end @@ -116,6 +119,10 @@ def valid_user_verified? authenticator_data.user_verified? end + def valid_backup_flags? + authenticator_data.credential_backup_eligible? || !authenticator_data.credential_backed_up? + end + def rp_id_from_origin(expected_origin) URI.parse(expected_origin.first).host if expected_origin.size == 1 end diff --git a/lib/webauthn/fake_client.rb b/lib/webauthn/fake_client.rb index d02b7548..5e0027e0 100644 --- a/lib/webauthn/fake_client.rb +++ b/lib/webauthn/fake_client.rb @@ -83,7 +83,7 @@ def get(challenge: fake_challenge, user_present: true, user_verified: false, backup_eligibility: false, - backup_state: true, + backup_state: false, sign_count: nil, extensions: nil, user_handle: nil, diff --git a/spec/webauthn/authenticator_assertion_response_spec.rb b/spec/webauthn/authenticator_assertion_response_spec.rb index 93902ae4..cbb1ad92 100644 --- a/spec/webauthn/authenticator_assertion_response_spec.rb +++ b/spec/webauthn/authenticator_assertion_response_spec.rb @@ -223,6 +223,40 @@ end end + describe "backup flags validation" do + let(:assertion) do + client.get(challenge: original_challenge, backup_eligibility: backup_eligibility, backup_state: backup_state) + end + + context "when the credential is backup eligible and backed up" do + let(:backup_eligibility) { true } + let(:backup_state) { true } + + it_behaves_like "a valid assertion response" + end + + context "when the credential is backup eligible and not backed up" do + let(:backup_eligibility) { true } + let(:backup_state) { false } + + it_behaves_like "a valid assertion response" + end + + context "when the credential is not backup eligible and not backed up" do + let(:backup_eligibility) { false } + let(:backup_state) { false } + + it_behaves_like "a valid assertion response" + end + + context "when the credential is not backup eligible but backed up" do + let(:backup_eligibility) { false } + let(:backup_state) { true } + + it_behaves_like "an invalid assertion response that raises", WebAuthn::BackupFlagsVerificationError + end + end + describe "challenge validation" do context "if challenge doesn't match" do let(:challenge) { fake_challenge } diff --git a/spec/webauthn/authenticator_attestation_response_spec.rb b/spec/webauthn/authenticator_attestation_response_spec.rb index eb105df9..a88bd350 100644 --- a/spec/webauthn/authenticator_attestation_response_spec.rb +++ b/spec/webauthn/authenticator_attestation_response_spec.rb @@ -577,6 +577,44 @@ end end + describe "backup flags validation" do + let(:public_key_credential) do + client.create(challenge: original_challenge, backup_eligibility: backup_eligibility, backup_state: backup_state) + end + + before do + WebAuthn.configuration.allowed_origins = [origin] + end + + context "when the credential is backup eligible and backed up" do + let(:backup_eligibility) { true } + let(:backup_state) { true } + + it_behaves_like "a valid attestation response" + end + + context "when the credential is backup eligible and not backed up" do + let(:backup_eligibility) { true } + let(:backup_state) { false } + + it_behaves_like "a valid attestation response" + end + + context "when the credential is not backup eligible and not backed up" do + let(:backup_eligibility) { false } + let(:backup_state) { false } + + it_behaves_like "a valid attestation response" + end + + context "when the credential is not backup eligible but backed up" do + let(:backup_eligibility) { false } + let(:backup_state) { true } + + it_behaves_like "an invalid attestation response that raises", WebAuthn::BackupFlagsVerificationError + end + end + describe "rp_id validation" do let(:original_challenge) { fake_challenge }