webauthn 2.5.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/build.yml +7 -3
  4. data/.github/workflows/git.yml +1 -1
  5. data/.rubocop.yml +1 -1
  6. data/CHANGELOG.md +40 -3
  7. data/README.md +4 -4
  8. data/docs/advanced_configuration.md +10 -10
  9. data/docs/u2f_migration.md +14 -21
  10. data/lib/webauthn/attestation_object.rb +9 -5
  11. data/lib/webauthn/attestation_statement/base.rb +16 -10
  12. data/lib/webauthn/attestation_statement.rb +2 -2
  13. data/lib/webauthn/authenticator_assertion_response.rb +4 -3
  14. data/lib/webauthn/authenticator_attestation_response.rb +10 -7
  15. data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -5
  16. data/lib/webauthn/authenticator_data.rb +10 -2
  17. data/lib/webauthn/authenticator_response.rb +6 -5
  18. data/lib/webauthn/configuration.rb +38 -38
  19. data/lib/webauthn/credential.rb +4 -4
  20. data/lib/webauthn/encoder.rb +13 -4
  21. data/lib/webauthn/fake_authenticator/attestation_object.rb +8 -0
  22. data/lib/webauthn/fake_authenticator/authenticator_data.rb +19 -4
  23. data/lib/webauthn/fake_authenticator.rb +8 -0
  24. data/lib/webauthn/fake_client.rb +12 -2
  25. data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
  26. data/lib/webauthn/public_key_credential/options.rb +9 -8
  27. data/lib/webauthn/public_key_credential/request_options.rb +11 -1
  28. data/lib/webauthn/public_key_credential.rb +41 -7
  29. data/lib/webauthn/public_key_credential_with_assertion.rb +14 -1
  30. data/lib/webauthn/relying_party.rb +120 -0
  31. data/lib/webauthn/u2f_migrator.rb +4 -1
  32. data/lib/webauthn/version.rb +1 -1
  33. data/webauthn.gemspec +4 -3
  34. metadata +22 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f07d44a7778995c6bd2531b26f61e8974ba6ccc8dcf5ff1f21886f353423d598
4
- data.tar.gz: ecb287679447b3074065f2b06cb0f38b20c9a94114ed2b592d93e99874dfda1a
3
+ metadata.gz: 3b23698bdd722a0cda45be7ee9d5ba528f966eb98304a50069242f0a25dcd259
4
+ data.tar.gz: 4d7bf8874268cbbbfa75136006af9dc5a824b25d3f0522c5f2046092261bae5d
5
5
  SHA512:
6
- metadata.gz: 98811fdbdb15cd80defb95a75de555a3775864bb1c1f1ac94af4a605af2fa3acf7908d91a120b5c3e214af890469d5f2194e5e037a9f8a40b03935159be6a1f1
7
- data.tar.gz: 8d90a9d37fb83a0ecf5e0c29e8f5154a0ee2170da994238b55fcc4042cb84f6b7fb4e09386e704a2d83f363ef0864fb1fa60c913eea3ae2754e5189435302570
6
+ metadata.gz: bf2d6697ee0c34cccbb1f5f9765e70efe1a8062f73b35cb900894c59f512d81157a836046e8006c9c6d5b0a0b3040f9c76a83417928d0ff9fbaba2db1893aaef
7
+ data.tar.gz: 2952c2573b030ffbba7837f2ba299e73c7a4e09a3a46b4a7a19c7875b4ea5394a8af00059dda69ae4c3dc34b1531b3aad3d1d1445dc4000260b3cfc4e5e23a98
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
@@ -7,7 +7,11 @@
7
7
 
8
8
  name: build
9
9
 
10
- on: push
10
+ on:
11
+ push:
12
+ branches: [master]
13
+ pull_request:
14
+ types: [opened, synchronize]
11
15
 
12
16
  jobs:
13
17
  test:
@@ -16,15 +20,15 @@ jobs:
16
20
  fail-fast: false
17
21
  matrix:
18
22
  ruby:
23
+ - '3.2'
19
24
  - '3.1'
20
25
  - '3.0'
21
26
  - '2.7'
22
27
  - '2.6'
23
28
  - '2.5'
24
- - '2.4'
25
29
  - truffleruby
26
30
  steps:
27
- - uses: actions/checkout@v2
31
+ - uses: actions/checkout@v4
28
32
  - uses: ruby/setup-ruby@v1
29
33
  with:
30
34
  ruby-version: ${{ matrix.ruby }}
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v2.0.0
17
+ - uses: actions/checkout@v4
18
18
  - name: Block autosquash commits
19
19
  uses: xt0rted/block-autosquash-commits-action@v2
20
20
  with:
data/.rubocop.yml CHANGED
@@ -7,7 +7,7 @@ inherit_mode:
7
7
  - AllowedNames
8
8
 
9
9
  AllCops:
10
- TargetRubyVersion: 2.4
10
+ TargetRubyVersion: 2.5
11
11
  DisabledByDefault: true
12
12
  NewCops: disable
13
13
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,12 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.1.0] - 2023-12-26
4
+
5
+ ### Added
6
+
7
+ - Add support for optional `authenticator_attachment` in `PublicKeyCredential`. #370 [@8ma10s]
8
+
9
+ ### Fixed
10
+
11
+ - Fix circular require warning between `webauthn/relying_party` and `webauthn/credential`. #389 [@bdewater]
12
+ - Correctly verify attestation that contains just a batch certificate that is present in the attestation root certificates. #406 [@santiagorodriguez96]
13
+
14
+ ### Changed
15
+
16
+ - Inlined `base64` implementation. #402 [@olleolleolle]
17
+ - Raise a more descriptive error if input `challenge` is `nil` when verifying the `PublicKeyCredential`. #413 [@soartec-lab]
18
+
19
+ ## [v3.0.0] - 2023-02-15
20
+
21
+ ### Added
22
+
23
+ - Add the capability of handling appid extension #319 [@santiagorodriguez96]
24
+ - Add support for credential backup flags #378 [@santiagorodriguez96]
25
+ - Update dependencies to make gem compatible with OpenSSL 3.1 ([@bdewater],[@santiagorodriguez96])
26
+
27
+ ## [v3.0.0.alpha2] - 2022-09-12
28
+
29
+ ### Added
30
+
31
+ - Rebased support for multiple relying parties from v3.0.0.alpha1 on top of v2.5.2, the previous alpha version was based on v2.3.0 ([@bdewater])
32
+
33
+ ### BREAKING CHANGES
34
+
35
+ - Bumped minimum required Ruby version to 2.5 ([@bdewater])
36
+
3
37
  ## [v3.0.0.alpha1] - 2020-06-27
4
38
 
5
39
  ### Added
6
40
 
7
41
  - Ability to define multiple relying parties with the introduction of the `WebAuthn::RelyingParty` class ([@padulafacundo], [@brauliomartinezlm])
8
42
 
9
- ## [v2.5.1] - 2022-07-13
43
+ ## [v2.5.2] - 2022-07-13
10
44
 
11
45
  ### Added
12
46
 
@@ -340,8 +374,11 @@ Note: Both additions should help making it compatible with Chrome for Android 70
340
374
  - `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
341
375
  - Works with ruby 2.5
342
376
 
343
- [v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha1/
344
- [v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.2/
377
+ [v3.1.0]: https://github.com/cedarcode/webauthn-ruby/compare/v3.0.0...v3.1.0/
378
+ [v3.0.0]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0/
379
+ [v3.0.0.alpha2]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha2/
380
+ [v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v3.0.0.alpha1
381
+ [v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.1...v2.5.2/
345
382
  [v2.5.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.1/
346
383
  [v2.5.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.1...v2.5.0/
347
384
  [v2.4.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.0...v2.4.1/
data/README.md CHANGED
@@ -6,7 +6,7 @@ For the current release version see https://github.com/cedarcode/webauthn-ruby/b
6
6
  ![banner](assets/webauthn-ruby.png)
7
7
 
8
8
  [![Gem](https://img.shields.io/gem/v/webauthn.svg?style=flat-square)](https://rubygems.org/gems/webauthn)
9
- [![Travis](https://img.shields.io/travis/cedarcode/webauthn-ruby/master.svg?style=flat-square)](https://travis-ci.com/cedarcode/webauthn-ruby)
9
+ [![Build](https://github.com/cedarcode/webauthn-ruby/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/cedarcode/webauthn-ruby/actions/workflows/build.yml)
10
10
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-informational.svg?style=flat-square)](https://conventionalcommits.org)
11
11
  [![Join the chat at https://gitter.im/cedarcode/webauthn-ruby](https://badges.gitter.im/cedarcode/webauthn-ruby.svg)](https://gitter.im/cedarcode/webauthn-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
12
12
 
@@ -16,7 +16,7 @@ Makes your Ruby/Rails web server become a functional [WebAuthn Relying Party](ht
16
16
 
17
17
  Takes care of the [server-side operations](https://www.w3.org/TR/webauthn/#rp-operations) needed to
18
18
  [register](https://www.w3.org/TR/webauthn/#registration) or [authenticate](https://www.w3.org/TR/webauthn/#authentication)
19
- a user [credential](https://www.w3.org/TR/webauthn/#public-key-credential), including the necessary cryptographic checks.
19
+ a user's [public key credential](https://www.w3.org/TR/webauthn/#public-key-credential) (also called a "passkey"), including the necessary cryptographic checks.
20
20
 
21
21
  ## Table of Contents
22
22
 
@@ -52,7 +52,7 @@ WebAuthn (Web Authentication) is a W3C standard for secure public-key authentica
52
52
 
53
53
  - WebAuthn [W3C Recommendation](https://www.w3.org/TR/webauthn/) (i.e. "The Standard")
54
54
  - [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) in MDN
55
- - How to use [WebAuthn in Android apps](https://developers.google.com/identity/fido/android/native-apps)
55
+ - How to use WebAuthn in native [Android](https://developers.google.com/identity/fido/android/native-apps) or [macOS/iOS/iPadOS](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication) apps.
56
56
  - [Security Benefits for WebAuthn Servers (a.k.a Relying Parties)](https://www.w3.org/TR/webauthn/#sctn-rp-benefits)
57
57
 
58
58
  ## Prerequisites
@@ -89,7 +89,7 @@ Or install it yourself as:
89
89
 
90
90
  ## Usage
91
91
 
92
- You can find a working example on how to use this gem in a __Rails__ app in [webauthn-rails-demo-app](https://github.com/cedarcode/webauthn-rails-demo-app).
92
+ You can find a working example on how to use this gem in a pasword-less login in a __Rails__ app in [webauthn-rails-demo-app](https://github.com/cedarcode/webauthn-rails-demo-app). If you want to see an example on how to use this gem as a second factor authenticator in a __Rails__ application instead, you can check it in [webauthn-2fa-rails-demo](https://github.com/cedarcode/webauthn-2fa-rails-demo).
93
93
 
94
94
  If you are migrating an existing application from the legacy FIDO U2F JavaScript API to WebAuthn, also refer to
95
95
  [`docs/u2f_migration.md`](docs/u2f_migration.md).
@@ -21,7 +21,7 @@ Intead of the [Global Configuration](../README.md#configuration) you place in `c
21
21
  relying_party = WebAuthn::RelyingParty.new(
22
22
  # This value needs to match `window.location.origin` evaluated by
23
23
  # the User Agent during registration and authentication ceremonies.
24
- origin: "https://admin.example.com"
24
+ origin: "https://admin.example.com",
25
25
 
26
26
  # Relying Party name for display purposes
27
27
  name: "Admin Site for Example Inc."
@@ -40,7 +40,7 @@ Intead of the [Global Configuration](../README.md#configuration) you place in `c
40
40
  # In this case the default would be "admin.example.com", but you can set it to
41
41
  # the suffix "example.com"
42
42
  #
43
- # rp_id: "example.com"
43
+ # id: "example.com"
44
44
 
45
45
  # Configure preferred binary-to-text encoding scheme. This should match the encoding scheme
46
46
  # used in your client-side (user agent) code before sending the credential to the server.
@@ -59,7 +59,7 @@ Intead of the [Global Configuration](../README.md#configuration) you place in `c
59
59
 
60
60
  **DISCLAIMER: This API was released on version 3.0.0.alpha1 and is still under evaluation. Although it has been throughly tested and it is fully functional it might be changed until the final release of version 3.0.0.**
61
61
 
62
- The explanation for each ceremony can be found in depth in [Credential Registration](../README.md#credential-registration) and [Credential Authentication](../README.md#credential-authentication) but if you choose this instance based approach to define your WebAuthn configurations and assuming `relying_party` is the result of an instance you get through `WebAuthn::RelytingParty.new(...)` the code in those explanations needs to be updated to:
62
+ The explanation for each ceremony can be found in depth in [Credential Registration](../README.md#credential-registration) and [Credential Authentication](../README.md#credential-authentication) but if you choose this instance based approach to define your WebAuthn configurations and assuming `relying_party` is the result of an instance you get through `WebAuthn::RelyingParty.new(...)` the code in those explanations needs to be updated to:
63
63
 
64
64
  ### Credential Registration
65
65
 
@@ -71,9 +71,9 @@ if !user.webauthn_id
71
71
  user.update!(webauthn_id: WebAuthn.generate_user_id)
72
72
  end
73
73
 
74
- options = relying_party.options_for_create(
74
+ options = relying_party.options_for_registration(
75
75
  user: { id: user.webauthn_id, name: user.name },
76
- exclude: user.credentials.map { |c| c.webauthn_id }
76
+ exclude: user.credentials.map { |c| c.external_id }
77
77
  )
78
78
 
79
79
  # Store the newly generated challenge somewhere so you can have it
@@ -106,7 +106,7 @@ begin
106
106
 
107
107
  # Store Credential ID, Credential Public Key and Sign Count for future authentications
108
108
  user.credentials.create!(
109
- webauthn_id: webauthn_credential.id,
109
+ external_id: webauthn_credential.id,
110
110
  public_key: webauthn_credential.public_key,
111
111
  sign_count: webauthn_credential.sign_count
112
112
  )
@@ -120,7 +120,7 @@ end
120
120
  #### Initiation phase
121
121
 
122
122
  ```ruby
123
- options = relying_party.options_for_get(allow: user.credentials.map { |c| c.webauthn_id })
123
+ options = relying_party.options_for_authentication(allow: user.credentials.map { |c| c.webauthn_id })
124
124
 
125
125
  # Store the newly generated challenge somewhere so you can have it
126
126
  # for the verification phase.
@@ -148,9 +148,9 @@ begin
148
148
  webauthn_credential, stored_credential = relying_party.verify_authentication(
149
149
  params[:publicKeyCredential],
150
150
  session[:authentication_challenge]
151
- ) do
151
+ ) do |webauthn_credential|
152
152
  # the returned object needs to respond to #public_key and #sign_count
153
- user.credentials.find_by(webauthn_id: webauthn_credential.id)
153
+ user.credentials.find_by(external_id: webauthn_credential.id)
154
154
  end
155
155
 
156
156
  # Update the stored credential sign count with the value from `webauthn_credential.sign_count`
@@ -171,4 +171,4 @@ end
171
171
 
172
172
  Adding a configuration for a new instance does not mean you need to get rid of your Global configuration. They can co-exist in your application and be both available for the different usages you might have. `WebAuthn.configuration.relying_party` will always return the global one while `WebAuthn::RelyingParty.new`, executed anywhere in your codebase, will allow you to create a different instance as you see the need. They will not collide and instead operate in isolation without any shared state.
173
173
 
174
- The gem API described in the current [Usage](../README.md#usage) section for the [Global Configuration](../README.md#configuration) approach will still valid but the [Instance Based API](#instance-based-api) also works with the global `relying_party` that is maintain globally at `WebAuthn.configuration.relying_party`.
174
+ The gem API described in the current [Usage](../README.md#usage) section for the [Global Configuration](../README.md#configuration) approach will still valid but the [Instance Based API](#instance-based-api) also works with the global `relying_party` that is maintain globally at `WebAuthn.configuration.relying_party`.
@@ -53,7 +53,7 @@ migrated_credential.authenticator_data.sign_count
53
53
 
54
54
  ## Authenticate migrated U2F credentials
55
55
 
56
- Following the documentation on the [authentication initiation](https://github.com/cedarcode/webauthn-ruby/blob/master/README.md#authentication),
56
+ Following the documentation on the [authentication initiation](https://github.com/cedarcode/webauthn-ruby/blob/master/README.md#initiation-phase-1),
57
57
  you need to specify the [FIDO AppID extension](https://www.w3.org/TR/webauthn/#sctn-appid-extension) for U2F migratedq
58
58
  credentials. The WebAuthn standard explains:
59
59
 
@@ -65,33 +65,26 @@ For the earlier given example `domain` this means:
65
65
  - FIDO AppID: `https://login.example.com`
66
66
  - Valid RP IDs: `login.example.com` (default) and `example.com`
67
67
 
68
+ You can request the use of the `appid` extension by setting the AppID in the configuration, like this:
69
+
70
+ ```ruby
71
+ WebAuthn.configure do |config|
72
+ config.legacy_u2f_appid = "https://login.example.com"
73
+ end
74
+ ```
75
+
76
+ By doing this, the `appid` extension will be automatically requested when generating the options for get:
77
+
68
78
  ```ruby
69
- credential_request_options = WebAuthn.credential_request_options
70
- credential_request_options[:extensions] = { appid: domain.to_s }
79
+ options = WebAuthn::Credential.options_for_get
71
80
  ```
72
81
 
73
82
  On the frontend, in the resolved value from `navigator.credentials.get({ "publicKey": credentialRequestOptions })` add
74
83
  a call to [getClientExtensionResults()](https://www.w3.org/TR/webauthn/#dom-publickeycredential-getclientextensionresults)
75
84
  and send its result to your backend alongside the `id`/`rawId` and `response` values. If the authenticator used the AppID
76
- extension, the returned value will contain `{ "appid": true }`. In the example below, we use `clientExtensionResults`.
85
+ extension, the returned value will contain `{ "appid": true }`.
77
86
 
78
- During authentication verification phase, you must pass either the original AppID or the RP ID as the `rp_id` argument:
87
+ During authentication verification phase, if you followed the [verification phase documentation](https://github.com/cedarcode/webauthn-ruby#verification-phase-1) and have set the AppID in the config, the method `PublicKeyCredentialWithAssertion#verify` will be smart enough to determine if it should use the AppID or the RP ID to verify the WebAuthn credential, depending on the output of the `appid` client extension:
79
88
 
80
89
  > If true, the AppID was used and thus, when verifying an assertion, the Relying Party MUST expect the `rpIdHash` to be
81
90
  > the hash of the _AppID_, not the RP ID.
82
-
83
- ```ruby
84
- assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
85
- user_handle: params[:response][:userHandle],
86
- authenticator_data: params[:response][:authenticatorData],
87
- client_data_json: params[:response][:clientDataJSON],
88
- signature: params[:response][:signature],
89
- )
90
-
91
- assertion_response.verify(
92
- expected_challenge,
93
- public_key: credential.public_key,
94
- sign_count: credential.count,
95
- rp_id: params[:clientExtensionResults][:appid] ? domain.to_s : domain.host,
96
- )
97
- ```
@@ -10,18 +10,22 @@ module WebAuthn
10
10
  class AttestationObject
11
11
  extend Forwardable
12
12
 
13
- def self.deserialize(attestation_object)
14
- from_map(CBOR.decode(attestation_object))
13
+ def self.deserialize(attestation_object, relying_party)
14
+ from_map(CBOR.decode(attestation_object), relying_party)
15
15
  end
16
16
 
17
- def self.from_map(map)
17
+ def self.from_map(map, relying_party)
18
18
  new(
19
19
  authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
20
- attestation_statement: WebAuthn::AttestationStatement.from(map["fmt"], map["attStmt"])
20
+ attestation_statement: WebAuthn::AttestationStatement.from(
21
+ map["fmt"],
22
+ map["attStmt"],
23
+ relying_party: relying_party
24
+ )
21
25
  )
22
26
  end
23
27
 
24
- attr_reader :authenticator_data, :attestation_statement
28
+ attr_reader :authenticator_data, :attestation_statement, :relying_party
25
29
 
26
30
  def initialize(authenticator_data:, attestation_statement:)
27
31
  @authenticator_data = authenticator_data
@@ -28,8 +28,9 @@ module WebAuthn
28
28
  class Base
29
29
  AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
30
30
 
31
- def initialize(statement)
31
+ def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
32
32
  @statement = statement
33
+ @relying_party = relying_party
33
34
  end
34
35
 
35
36
  def valid?(_authenticator_data, _client_data_hash)
@@ -50,7 +51,7 @@ module WebAuthn
50
51
 
51
52
  private
52
53
 
53
- attr_reader :statement
54
+ attr_reader :statement, :relying_party
54
55
 
55
56
  def matching_aaguid?(attested_credential_data_aaguid)
56
57
  extension = attestation_certificate&.find_extension(AAGUID_EXTENSION_OID)
@@ -93,14 +94,23 @@ module WebAuthn
93
94
 
94
95
  def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
95
96
  if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
96
- configuration.acceptable_attestation_types.include?(attestation_type) &&
97
+ relying_party.acceptable_attestation_types.include?(attestation_type) &&
97
98
  valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
98
99
  else
99
- configuration.acceptable_attestation_types.include?(attestation_type)
100
+ relying_party.acceptable_attestation_types.include?(attestation_type)
100
101
  end
101
102
  end
102
103
 
103
104
  def valid_certificate_chain?(aaguid: nil, attestation_certificate_key_id: nil)
105
+ root_certificates = root_certificates(
106
+ aaguid: aaguid,
107
+ attestation_certificate_key_id: attestation_certificate_key_id
108
+ )
109
+
110
+ if certificates&.one? && root_certificates.include?(attestation_certificate)
111
+ return true
112
+ end
113
+
104
114
  attestation_root_certificates_store(
105
115
  aaguid: aaguid,
106
116
  attestation_certificate_key_id: attestation_certificate_key_id
@@ -120,7 +130,7 @@ module WebAuthn
120
130
 
121
131
  def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
122
132
  root_certificates =
123
- configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
133
+ relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
124
134
  if certs.empty?
125
135
  finder.find(
126
136
  attestation_format: format,
@@ -158,14 +168,10 @@ module WebAuthn
158
168
  def cose_algorithm
159
169
  @cose_algorithm ||=
160
170
  COSE::Algorithm.find(algorithm).tap do |alg|
161
- alg && configuration.algorithms.include?(alg.name) ||
171
+ alg && relying_party.algorithms.include?(alg.name) ||
162
172
  raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
163
173
  end
164
174
  end
165
-
166
- def configuration
167
- WebAuthn.configuration
168
- end
169
175
  end
170
176
  end
171
177
  end
@@ -31,11 +31,11 @@ module WebAuthn
31
31
  ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
32
32
  }.freeze
33
33
 
34
- def self.from(format, statement)
34
+ def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
35
35
  klass = FORMAT_TO_CLASS[format]
36
36
 
37
37
  if klass
38
- klass.new(statement)
38
+ klass.new(statement, relying_party)
39
39
  else
40
40
  raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
41
41
  end
@@ -10,8 +10,8 @@ module WebAuthn
10
10
  class SignCountVerificationError < VerificationError; end
11
11
 
12
12
  class AuthenticatorAssertionResponse < AuthenticatorResponse
13
- def self.from_client(response)
14
- encoder = WebAuthn.configuration.encoder
13
+ def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
14
+ encoder = relying_party.encoder
15
15
 
16
16
  user_handle =
17
17
  if response["userHandle"]
@@ -22,7 +22,8 @@ module WebAuthn
22
22
  authenticator_data: encoder.decode(response["authenticatorData"]),
23
23
  client_data_json: encoder.decode(response["clientDataJSON"]),
24
24
  signature: encoder.decode(response["signature"]),
25
- user_handle: user_handle
25
+ user_handle: user_handle,
26
+ relying_party: relying_party
26
27
  )
27
28
  end
28
29
 
@@ -18,12 +18,13 @@ module WebAuthn
18
18
  class AuthenticatorAttestationResponse < AuthenticatorResponse
19
19
  extend Forwardable
20
20
 
21
- def self.from_client(response)
22
- encoder = WebAuthn.configuration.encoder
21
+ def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
22
+ encoder = relying_party.encoder
23
23
 
24
24
  new(
25
25
  attestation_object: encoder.decode(response["attestationObject"]),
26
- client_data_json: encoder.decode(response["clientDataJSON"])
26
+ client_data_json: encoder.decode(response["clientDataJSON"]),
27
+ relying_party: relying_party
27
28
  )
28
29
  end
29
30
 
@@ -33,13 +34,14 @@ module WebAuthn
33
34
  super(**options)
34
35
 
35
36
  @attestation_object_bytes = attestation_object
37
+ @relying_party = relying_party
36
38
  end
37
39
 
38
40
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
39
41
  super
40
42
 
41
43
  verify_item(:attested_credential)
42
- if WebAuthn.configuration.verify_attestation_statement
44
+ if relying_party.verify_attestation_statement
43
45
  verify_item(:attestation_statement)
44
46
  end
45
47
 
@@ -47,7 +49,7 @@ module WebAuthn
47
49
  end
48
50
 
49
51
  def attestation_object
50
- @attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes)
52
+ @attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes, relying_party)
51
53
  end
52
54
 
53
55
  def_delegators(
@@ -63,14 +65,15 @@ module WebAuthn
63
65
 
64
66
  private
65
67
 
66
- attr_reader :attestation_object_bytes
68
+ attr_reader :attestation_object_bytes, :relying_party
67
69
 
68
70
  def type
69
71
  WebAuthn::TYPES[:create]
70
72
  end
71
73
 
72
74
  def valid_attested_credential?
73
- attestation_object.valid_attested_credential?
75
+ attestation_object.valid_attested_credential? &&
76
+ relying_party.algorithms.include?(authenticator_data.credential.algorithm)
74
77
  end
75
78
 
76
79
  def valid_attestation_statement?
@@ -22,9 +22,8 @@ module WebAuthn
22
22
  count_bytes_remaining :trailing_bytes_length
23
23
  string :trailing_bytes, length: :trailing_bytes_length
24
24
 
25
- # TODO: use keyword_init when we dropped Ruby 2.4 support
26
25
  Credential =
27
- Struct.new(:id, :public_key) do
26
+ Struct.new(:id, :public_key, :algorithm, keyword_init: true) do
28
27
  def public_key_object
29
28
  COSE::Key.deserialize(public_key).to_pkey
30
29
  end
@@ -47,7 +46,7 @@ module WebAuthn
47
46
  def credential
48
47
  @credential ||=
49
48
  if valid?
50
- Credential.new(id, public_key)
49
+ Credential.new(id: id, public_key: public_key, algorithm: algorithm)
51
50
  end
52
51
  end
53
52
 
@@ -59,10 +58,16 @@ module WebAuthn
59
58
 
60
59
  private
61
60
 
61
+ def algorithm
62
+ COSE::Algorithm.find(cose_key.alg).name
63
+ end
64
+
62
65
  def valid_credential_public_key?
63
- cose_key = COSE::Key.deserialize(public_key)
66
+ !!cose_key.alg
67
+ end
64
68
 
65
- !!cose_key.alg && WebAuthn.configuration.algorithms.include?(COSE::Algorithm.find(cose_key.alg).name)
69
+ def cose_key
70
+ @cose_key ||= COSE::Key.deserialize(public_key)
66
71
  end
67
72
 
68
73
  def public_key
@@ -19,9 +19,9 @@ module WebAuthn
19
19
  struct :flags do
20
20
  bit1 :extension_data_included
21
21
  bit1 :attested_credential_data_included
22
- bit1 :reserved_for_future_use_4
23
- bit1 :reserved_for_future_use_3
24
22
  bit1 :reserved_for_future_use_2
23
+ bit1 :backup_state
24
+ bit1 :backup_eligibility
25
25
  bit1 :user_verified
26
26
  bit1 :reserved_for_future_use_1
27
27
  bit1 :user_present
@@ -58,6 +58,14 @@ module WebAuthn
58
58
  flags.user_verified == 1
59
59
  end
60
60
 
61
+ def credential_backup_eligible?
62
+ flags.backup_eligibility == 1
63
+ end
64
+
65
+ def credential_backed_up?
66
+ flags.backup_state == 1
67
+ end
68
+
61
69
  def attested_credential_data_included?
62
70
  flags.attested_credential_data_included == 1
63
71
  end
@@ -19,13 +19,14 @@ module WebAuthn
19
19
  class UserVerifiedVerificationError < VerificationError; end
20
20
 
21
21
  class AuthenticatorResponse
22
- def initialize(client_data_json:)
22
+ def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
23
23
  @client_data_json = client_data_json
24
+ @relying_party = relying_party
24
25
  end
25
26
 
26
27
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
27
- expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
28
- rp_id ||= WebAuthn.configuration.rp_id
28
+ expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
29
+ rp_id ||= relying_party.id
29
30
 
30
31
  verify_item(:type)
31
32
  verify_item(:token_binding)
@@ -34,7 +35,7 @@ module WebAuthn
34
35
  verify_item(:authenticator_data)
35
36
  verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
36
37
 
37
- if !WebAuthn.configuration.silent_authentication
38
+ if !relying_party.silent_authentication
38
39
  verify_item(:user_presence)
39
40
  end
40
41
 
@@ -57,7 +58,7 @@ module WebAuthn
57
58
 
58
59
  private
59
60
 
60
- attr_reader :client_data_json
61
+ attr_reader :client_data_json, :relying_party
61
62
 
62
63
  def verify_item(item, *args)
63
64
  if send("valid_#{item}?", *args)