webauthn 2.5.2 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/build.yml +7 -3
- data/.github/workflows/git.yml +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +40 -3
- data/README.md +4 -4
- data/docs/advanced_configuration.md +10 -10
- data/docs/u2f_migration.md +14 -21
- data/lib/webauthn/attestation_object.rb +9 -5
- data/lib/webauthn/attestation_statement/base.rb +16 -10
- data/lib/webauthn/attestation_statement.rb +2 -2
- data/lib/webauthn/authenticator_assertion_response.rb +4 -3
- data/lib/webauthn/authenticator_attestation_response.rb +10 -7
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -5
- data/lib/webauthn/authenticator_data.rb +10 -2
- data/lib/webauthn/authenticator_response.rb +6 -5
- data/lib/webauthn/configuration.rb +38 -38
- data/lib/webauthn/credential.rb +4 -4
- data/lib/webauthn/encoder.rb +13 -4
- data/lib/webauthn/fake_authenticator/attestation_object.rb +8 -0
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +19 -4
- data/lib/webauthn/fake_authenticator.rb +8 -0
- data/lib/webauthn/fake_client.rb +12 -2
- data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
- data/lib/webauthn/public_key_credential/options.rb +9 -8
- data/lib/webauthn/public_key_credential/request_options.rb +11 -1
- data/lib/webauthn/public_key_credential.rb +41 -7
- data/lib/webauthn/public_key_credential_with_assertion.rb +14 -1
- data/lib/webauthn/relying_party.rb +120 -0
- data/lib/webauthn/u2f_migrator.rb +4 -1
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +4 -3
- metadata +22 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b23698bdd722a0cda45be7ee9d5ba528f966eb98304a50069242f0a25dcd259
|
4
|
+
data.tar.gz: 4d7bf8874268cbbbfa75136006af9dc5a824b25d3f0522c5f2046092261bae5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf2d6697ee0c34cccbb1f5f9765e70efe1a8062f73b35cb900894c59f512d81157a836046e8006c9c6d5b0a0b3040f9c76a83417928d0ff9fbaba2db1893aaef
|
7
|
+
data.tar.gz: 2952c2573b030ffbba7837f2ba299e73c7a4e09a3a46b4a7a19c7875b4ea5394a8af00059dda69ae4c3dc34b1531b3aad3d1d1445dc4000260b3cfc4e5e23a98
|
data/.github/workflows/build.yml
CHANGED
@@ -7,7 +7,11 @@
|
|
7
7
|
|
8
8
|
name: build
|
9
9
|
|
10
|
-
on:
|
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@
|
31
|
+
- uses: actions/checkout@v4
|
28
32
|
- uses: ruby/setup-ruby@v1
|
29
33
|
with:
|
30
34
|
ruby-version: ${{ matrix.ruby }}
|
data/.github/workflows/git.yml
CHANGED
data/.rubocop.yml
CHANGED
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.
|
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.
|
344
|
-
[
|
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
|
-
[![
|
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
|
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
|
-
#
|
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::
|
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.
|
74
|
+
options = relying_party.options_for_registration(
|
75
75
|
user: { id: user.webauthn_id, name: user.name },
|
76
|
-
exclude: user.credentials.map { |c| c.
|
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
|
-
|
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.
|
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(
|
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`.
|
data/docs/u2f_migration.md
CHANGED
@@ -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#
|
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
|
-
|
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 }`.
|
85
|
+
extension, the returned value will contain `{ "appid": true }`.
|
77
86
|
|
78
|
-
During authentication verification phase, you
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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 &&
|
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 =
|
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 =
|
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
|
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
|
66
|
+
!!cose_key.alg
|
67
|
+
end
|
64
68
|
|
65
|
-
|
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 ||=
|
28
|
-
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 !
|
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)
|