webauthn 3.0.0.alpha2 → 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 -2
- data/.github/workflows/git.yml +1 -1
- data/CHANGELOG.md +26 -0
- data/README.md +4 -4
- data/docs/advanced_configuration.md +8 -8
- data/docs/u2f_migration.md +14 -21
- data/lib/webauthn/attestation_statement/base.rb +9 -0
- data/lib/webauthn/authenticator_data.rb +10 -2
- data/lib/webauthn/configuration.rb +3 -1
- data/lib/webauthn/credential.rb +0 -1
- 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 +10 -0
- data/lib/webauthn/public_key_credential/options.rb +5 -1
- data/lib/webauthn/public_key_credential/request_options.rb +10 -0
- data/lib/webauthn/public_key_credential.rb +33 -6
- data/lib/webauthn/public_key_credential_with_assertion.rb +14 -1
- data/lib/webauthn/relying_party.rb +5 -2
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +3 -2
- metadata +22 -13
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,6 +20,7 @@ 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'
|
@@ -23,7 +28,7 @@ jobs:
|
|
23
28
|
- '2.5'
|
24
29
|
- truffleruby
|
25
30
|
steps:
|
26
|
-
- uses: actions/checkout@
|
31
|
+
- uses: actions/checkout@v4
|
27
32
|
- uses: ruby/setup-ruby@v1
|
28
33
|
with:
|
29
34
|
ruby-version: ${{ matrix.ruby }}
|
data/.github/workflows/git.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,29 @@
|
|
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
|
+
|
3
27
|
## [v3.0.0.alpha2] - 2022-09-12
|
4
28
|
|
5
29
|
### Added
|
@@ -350,6 +374,8 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
350
374
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
351
375
|
- Works with ruby 2.5
|
352
376
|
|
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/
|
353
379
|
[v3.0.0.alpha2]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha2/
|
354
380
|
[v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v3.0.0.alpha1
|
355
381
|
[v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.1...v2.5.2/
|
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."
|
@@ -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`
|
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
|
-
```
|
@@ -102,6 +102,15 @@ module WebAuthn
|
|
102
102
|
end
|
103
103
|
|
104
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
|
+
|
105
114
|
attestation_root_certificates_store(
|
106
115
|
aaguid: aaguid,
|
107
116
|
attestation_certificate_key_id: attestation_certificate_key_id
|
@@ -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
|
data/lib/webauthn/credential.rb
CHANGED
@@ -4,7 +4,6 @@ require "webauthn/public_key_credential/creation_options"
|
|
4
4
|
require "webauthn/public_key_credential/request_options"
|
5
5
|
require "webauthn/public_key_credential_with_assertion"
|
6
6
|
require "webauthn/public_key_credential_with_attestation"
|
7
|
-
require "webauthn/relying_party"
|
8
7
|
|
9
8
|
module WebAuthn
|
10
9
|
module Credential
|
data/lib/webauthn/encoder.rb
CHANGED
@@ -20,9 +20,12 @@ module WebAuthn
|
|
20
20
|
def encode(data)
|
21
21
|
case encoding
|
22
22
|
when :base64
|
23
|
-
Base64.strict_encode64(data)
|
23
|
+
[data].pack("m0") # Base64.strict_encode64(data)
|
24
24
|
when :base64url
|
25
|
-
Base64.urlsafe_encode64(data, padding: false)
|
25
|
+
data = [data].pack("m0") # Base64.urlsafe_encode64(data, padding: false)
|
26
|
+
data.chomp!("==") or data.chomp!("=")
|
27
|
+
data.tr!("+/", "-_")
|
28
|
+
data
|
26
29
|
when nil, false
|
27
30
|
data
|
28
31
|
else
|
@@ -33,9 +36,15 @@ module WebAuthn
|
|
33
36
|
def decode(data)
|
34
37
|
case encoding
|
35
38
|
when :base64
|
36
|
-
Base64.strict_decode64(data)
|
39
|
+
data.unpack1("m0") # Base64.strict_decode64(data)
|
37
40
|
when :base64url
|
38
|
-
Base64.urlsafe_decode64(data)
|
41
|
+
if !data.end_with?("=") && data.length % 4 != 0 # Base64.urlsafe_decode64(data)
|
42
|
+
data = data.ljust((data.length + 3) & ~3, "=")
|
43
|
+
data.tr!("-_", "+/")
|
44
|
+
else
|
45
|
+
data = data.tr("-_", "+/")
|
46
|
+
end
|
47
|
+
data.unpack1("m0")
|
39
48
|
when nil, false
|
40
49
|
data
|
41
50
|
else
|
@@ -13,6 +13,8 @@ module WebAuthn
|
|
13
13
|
credential_key:,
|
14
14
|
user_present: true,
|
15
15
|
user_verified: false,
|
16
|
+
backup_eligibility: false,
|
17
|
+
backup_state: false,
|
16
18
|
attested_credential_data: true,
|
17
19
|
sign_count: 0,
|
18
20
|
extensions: nil
|
@@ -23,6 +25,8 @@ module WebAuthn
|
|
23
25
|
@credential_key = credential_key
|
24
26
|
@user_present = user_present
|
25
27
|
@user_verified = user_verified
|
28
|
+
@backup_eligibility = backup_eligibility
|
29
|
+
@backup_state = backup_state
|
26
30
|
@attested_credential_data = attested_credential_data
|
27
31
|
@sign_count = sign_count
|
28
32
|
@extensions = extensions
|
@@ -45,6 +49,8 @@ module WebAuthn
|
|
45
49
|
:credential_key,
|
46
50
|
:user_present,
|
47
51
|
:user_verified,
|
52
|
+
:backup_eligibility,
|
53
|
+
:backup_state,
|
48
54
|
:attested_credential_data,
|
49
55
|
:sign_count,
|
50
56
|
:extensions
|
@@ -63,6 +69,8 @@ module WebAuthn
|
|
63
69
|
credential: credential_data,
|
64
70
|
user_present: user_present,
|
65
71
|
user_verified: user_verified,
|
72
|
+
backup_eligibility: backup_eligibility,
|
73
|
+
backup_state: backup_state,
|
66
74
|
sign_count: 0,
|
67
75
|
extensions: extensions
|
68
76
|
)
|
@@ -20,6 +20,8 @@ module WebAuthn
|
|
20
20
|
sign_count: 0,
|
21
21
|
user_present: true,
|
22
22
|
user_verified: !user_present,
|
23
|
+
backup_eligibility: false,
|
24
|
+
backup_state: false,
|
23
25
|
aaguid: AAGUID,
|
24
26
|
extensions: { "fakeExtension" => "fakeExtensionValue" }
|
25
27
|
)
|
@@ -28,6 +30,8 @@ module WebAuthn
|
|
28
30
|
@sign_count = sign_count
|
29
31
|
@user_present = user_present
|
30
32
|
@user_verified = user_verified
|
33
|
+
@backup_eligibility = backup_eligibility
|
34
|
+
@backup_state = backup_state
|
31
35
|
@aaguid = aaguid
|
32
36
|
@extensions = extensions
|
33
37
|
end
|
@@ -38,7 +42,13 @@ module WebAuthn
|
|
38
42
|
|
39
43
|
private
|
40
44
|
|
41
|
-
attr_reader :rp_id_hash,
|
45
|
+
attr_reader :rp_id_hash,
|
46
|
+
:credential,
|
47
|
+
:user_present,
|
48
|
+
:user_verified,
|
49
|
+
:extensions,
|
50
|
+
:backup_eligibility,
|
51
|
+
:backup_state
|
42
52
|
|
43
53
|
def flags
|
44
54
|
[
|
@@ -46,8 +56,8 @@ module WebAuthn
|
|
46
56
|
bit(:user_present),
|
47
57
|
reserved_for_future_use_bit,
|
48
58
|
bit(:user_verified),
|
49
|
-
|
50
|
-
|
59
|
+
bit(:backup_eligibility),
|
60
|
+
bit(:backup_state),
|
51
61
|
reserved_for_future_use_bit,
|
52
62
|
attested_credential_data_included_bit,
|
53
63
|
extension_data_included_bit
|
@@ -108,7 +118,12 @@ module WebAuthn
|
|
108
118
|
end
|
109
119
|
|
110
120
|
def context
|
111
|
-
{
|
121
|
+
{
|
122
|
+
user_present: user_present,
|
123
|
+
user_verified: user_verified,
|
124
|
+
backup_eligibility: backup_eligibility,
|
125
|
+
backup_state: backup_state
|
126
|
+
}
|
112
127
|
end
|
113
128
|
|
114
129
|
def cose_credential_public_key
|
@@ -17,6 +17,8 @@ module WebAuthn
|
|
17
17
|
client_data_hash:,
|
18
18
|
user_present: true,
|
19
19
|
user_verified: false,
|
20
|
+
backup_eligibility: false,
|
21
|
+
backup_state: false,
|
20
22
|
attested_credential_data: true,
|
21
23
|
sign_count: nil,
|
22
24
|
extensions: nil
|
@@ -37,6 +39,8 @@ module WebAuthn
|
|
37
39
|
credential_key: credential_key,
|
38
40
|
user_present: user_present,
|
39
41
|
user_verified: user_verified,
|
42
|
+
backup_eligibility: backup_eligibility,
|
43
|
+
backup_state: backup_state,
|
40
44
|
attested_credential_data: attested_credential_data,
|
41
45
|
sign_count: sign_count,
|
42
46
|
extensions: extensions
|
@@ -48,6 +52,8 @@ module WebAuthn
|
|
48
52
|
client_data_hash:,
|
49
53
|
user_present: true,
|
50
54
|
user_verified: false,
|
55
|
+
backup_eligibility: false,
|
56
|
+
backup_state: false,
|
51
57
|
aaguid: AuthenticatorData::AAGUID,
|
52
58
|
sign_count: nil,
|
53
59
|
extensions: nil,
|
@@ -71,6 +77,8 @@ module WebAuthn
|
|
71
77
|
rp_id_hash: hashed(rp_id),
|
72
78
|
user_present: user_present,
|
73
79
|
user_verified: user_verified,
|
80
|
+
backup_eligibility: backup_eligibility,
|
81
|
+
backup_state: backup_state,
|
74
82
|
aaguid: aaguid,
|
75
83
|
credential: nil,
|
76
84
|
sign_count: sign_count || credential_sign_count,
|
data/lib/webauthn/fake_client.rb
CHANGED
@@ -29,6 +29,8 @@ module WebAuthn
|
|
29
29
|
rp_id: nil,
|
30
30
|
user_present: true,
|
31
31
|
user_verified: false,
|
32
|
+
backup_eligibility: false,
|
33
|
+
backup_state: false,
|
32
34
|
attested_credential_data: true,
|
33
35
|
extensions: nil
|
34
36
|
)
|
@@ -42,6 +44,8 @@ module WebAuthn
|
|
42
44
|
client_data_hash: client_data_hash,
|
43
45
|
user_present: user_present,
|
44
46
|
user_verified: user_verified,
|
47
|
+
backup_eligibility: backup_eligibility,
|
48
|
+
backup_state: backup_state,
|
45
49
|
attested_credential_data: attested_credential_data,
|
46
50
|
extensions: extensions
|
47
51
|
)
|
@@ -60,6 +64,7 @@ module WebAuthn
|
|
60
64
|
"type" => "public-key",
|
61
65
|
"id" => internal_encoder.encode(id),
|
62
66
|
"rawId" => encoder.encode(id),
|
67
|
+
"authenticatorAttachment" => 'platform',
|
63
68
|
"clientExtensionResults" => extensions,
|
64
69
|
"response" => {
|
65
70
|
"attestationObject" => encoder.encode(attestation_object),
|
@@ -72,6 +77,8 @@ module WebAuthn
|
|
72
77
|
rp_id: nil,
|
73
78
|
user_present: true,
|
74
79
|
user_verified: false,
|
80
|
+
backup_eligibility: false,
|
81
|
+
backup_state: true,
|
75
82
|
sign_count: nil,
|
76
83
|
extensions: nil,
|
77
84
|
user_handle: nil,
|
@@ -90,6 +97,8 @@ module WebAuthn
|
|
90
97
|
client_data_hash: client_data_hash,
|
91
98
|
user_present: user_present,
|
92
99
|
user_verified: user_verified,
|
100
|
+
backup_eligibility: backup_eligibility,
|
101
|
+
backup_state: backup_state,
|
93
102
|
sign_count: sign_count,
|
94
103
|
extensions: extensions,
|
95
104
|
allow_credentials: allow_credentials
|
@@ -100,6 +109,7 @@ module WebAuthn
|
|
100
109
|
"id" => internal_encoder.encode(assertion[:credential_id]),
|
101
110
|
"rawId" => encoder.encode(assertion[:credential_id]),
|
102
111
|
"clientExtensionResults" => extensions,
|
112
|
+
"authenticatorAttachment" => 'platform',
|
103
113
|
"response" => {
|
104
114
|
"clientDataJSON" => encoder.encode(client_data_json),
|
105
115
|
"authenticatorData" => encoder.encode(assertion[:authenticator_data]),
|
@@ -13,7 +13,7 @@ module WebAuthn
|
|
13
13
|
def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
|
14
14
|
@relying_party = relying_party
|
15
15
|
@timeout = timeout || default_timeout
|
16
|
-
@extensions = extensions
|
16
|
+
@extensions = default_extensions.merge(extensions || {})
|
17
17
|
end
|
18
18
|
|
19
19
|
def challenge
|
@@ -61,6 +61,10 @@ module WebAuthn
|
|
61
61
|
relying_party.credential_options_timeout
|
62
62
|
end
|
63
63
|
|
64
|
+
def default_extensions
|
65
|
+
{}
|
66
|
+
end
|
67
|
+
|
64
68
|
def as_public_key_descriptors(ids)
|
65
69
|
Array(ids).map { |id| { type: TYPE_PUBLIC_KEY, id: id } }
|
66
70
|
end
|
@@ -26,6 +26,16 @@ module WebAuthn
|
|
26
26
|
super.concat([:allow_credentials, :rp_id, :user_verification])
|
27
27
|
end
|
28
28
|
|
29
|
+
def default_extensions
|
30
|
+
extensions = super || {}
|
31
|
+
|
32
|
+
if relying_party.legacy_u2f_appid
|
33
|
+
extensions.merge!(appid: relying_party.legacy_u2f_appid)
|
34
|
+
end
|
35
|
+
|
36
|
+
extensions
|
37
|
+
end
|
38
|
+
|
29
39
|
def allow_credentials_from_allow
|
30
40
|
if allow
|
31
41
|
as_public_key_descriptors(allow)
|
@@ -4,7 +4,9 @@ require "webauthn/encoder"
|
|
4
4
|
|
5
5
|
module WebAuthn
|
6
6
|
class PublicKeyCredential
|
7
|
-
|
7
|
+
class InvalidChallengeError < Error; end
|
8
|
+
|
9
|
+
attr_reader :type, :id, :raw_id, :client_extension_outputs, :authenticator_attachment, :response
|
8
10
|
|
9
11
|
def self.from_client(credential, relying_party: WebAuthn.configuration.relying_party)
|
10
12
|
new(
|
@@ -12,8 +14,9 @@ module WebAuthn
|
|
12
14
|
id: credential["id"],
|
13
15
|
raw_id: relying_party.encoder.decode(credential["rawId"]),
|
14
16
|
client_extension_outputs: credential["clientExtensionResults"],
|
17
|
+
authenticator_attachment: credential["authenticatorAttachment"],
|
15
18
|
response: response_class.from_client(credential["response"], relying_party: relying_party),
|
16
|
-
|
19
|
+
relying_party: relying_party
|
17
20
|
)
|
18
21
|
end
|
19
22
|
|
@@ -22,18 +25,26 @@ module WebAuthn
|
|
22
25
|
id:,
|
23
26
|
raw_id:,
|
24
27
|
response:,
|
28
|
+
authenticator_attachment: nil,
|
25
29
|
client_extension_outputs: {},
|
26
|
-
|
30
|
+
relying_party: WebAuthn.configuration.relying_party
|
27
31
|
)
|
28
32
|
@type = type
|
29
33
|
@id = id
|
30
34
|
@raw_id = raw_id
|
31
35
|
@client_extension_outputs = client_extension_outputs
|
36
|
+
@authenticator_attachment = authenticator_attachment
|
32
37
|
@response = response
|
33
|
-
@
|
38
|
+
@relying_party = relying_party
|
34
39
|
end
|
35
40
|
|
36
|
-
def verify(*_args)
|
41
|
+
def verify(challenge, *_args)
|
42
|
+
unless valid_class?(challenge)
|
43
|
+
msg = "challenge must be a String. input challenge class: #{challenge.class}"
|
44
|
+
|
45
|
+
raise(InvalidChallengeError, msg)
|
46
|
+
end
|
47
|
+
|
37
48
|
valid_type? || raise("invalid type")
|
38
49
|
valid_id? || raise("invalid id")
|
39
50
|
|
@@ -48,9 +59,17 @@ module WebAuthn
|
|
48
59
|
authenticator_data.extension_data if authenticator_data&.extension_data_included?
|
49
60
|
end
|
50
61
|
|
62
|
+
def backup_eligible?
|
63
|
+
authenticator_data&.credential_backup_eligible?
|
64
|
+
end
|
65
|
+
|
66
|
+
def backed_up?
|
67
|
+
authenticator_data&.credential_backed_up?
|
68
|
+
end
|
69
|
+
|
51
70
|
private
|
52
71
|
|
53
|
-
attr_reader :
|
72
|
+
attr_reader :relying_party
|
54
73
|
|
55
74
|
def valid_type?
|
56
75
|
type == TYPE_PUBLIC_KEY
|
@@ -60,8 +79,16 @@ module WebAuthn
|
|
60
79
|
raw_id && id && raw_id == WebAuthn.standard_encoder.decode(id)
|
61
80
|
end
|
62
81
|
|
82
|
+
def valid_class?(challenge)
|
83
|
+
challenge.is_a?(String)
|
84
|
+
end
|
85
|
+
|
63
86
|
def authenticator_data
|
64
87
|
response&.authenticator_data
|
65
88
|
end
|
89
|
+
|
90
|
+
def encoder
|
91
|
+
relying_party.encoder
|
92
|
+
end
|
66
93
|
end
|
67
94
|
end
|
@@ -16,7 +16,8 @@ module WebAuthn
|
|
16
16
|
encoder.decode(challenge),
|
17
17
|
public_key: encoder.decode(public_key),
|
18
18
|
sign_count: sign_count,
|
19
|
-
user_verification: user_verification
|
19
|
+
user_verification: user_verification,
|
20
|
+
rp_id: appid_extension_output ? appid : nil
|
20
21
|
)
|
21
22
|
|
22
23
|
true
|
@@ -31,5 +32,17 @@ module WebAuthn
|
|
31
32
|
def raw_user_handle
|
32
33
|
response.user_handle
|
33
34
|
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def appid_extension_output
|
39
|
+
return if client_extension_outputs.nil?
|
40
|
+
|
41
|
+
client_extension_outputs['appid']
|
42
|
+
end
|
43
|
+
|
44
|
+
def appid
|
45
|
+
URI.parse(relying_party.legacy_u2f_appid || raise("Unspecified legacy U2F AppID")).to_s
|
46
|
+
end
|
34
47
|
end
|
35
48
|
end
|
@@ -25,7 +25,8 @@ module WebAuthn
|
|
25
25
|
credential_options_timeout: 120000,
|
26
26
|
silent_authentication: false,
|
27
27
|
acceptable_attestation_types: ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA', 'AnonCA'],
|
28
|
-
attestation_root_certificates_finders: []
|
28
|
+
attestation_root_certificates_finders: [],
|
29
|
+
legacy_u2f_appid: nil
|
29
30
|
)
|
30
31
|
@algorithms = algorithms
|
31
32
|
@encoding = encoding
|
@@ -36,6 +37,7 @@ module WebAuthn
|
|
36
37
|
@credential_options_timeout = credential_options_timeout
|
37
38
|
@silent_authentication = silent_authentication
|
38
39
|
@acceptable_attestation_types = acceptable_attestation_types
|
40
|
+
@legacy_u2f_appid = legacy_u2f_appid
|
39
41
|
self.attestation_root_certificates_finders = attestation_root_certificates_finders
|
40
42
|
end
|
41
43
|
|
@@ -47,7 +49,8 @@ module WebAuthn
|
|
47
49
|
:verify_attestation_statement,
|
48
50
|
:credential_options_timeout,
|
49
51
|
:silent_authentication,
|
50
|
-
:acceptable_attestation_types
|
52
|
+
:acceptable_attestation_types,
|
53
|
+
:legacy_u2f_appid
|
51
54
|
|
52
55
|
attr_reader :attestation_root_certificates_finders
|
53
56
|
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -38,10 +38,11 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_dependency "bindata", "~> 2.4"
|
39
39
|
spec.add_dependency "cbor", "~> 0.5.9"
|
40
40
|
spec.add_dependency "cose", "~> 1.1"
|
41
|
-
spec.add_dependency "openssl", ">= 2.2"
|
41
|
+
spec.add_dependency "openssl", ">= 2.2"
|
42
42
|
spec.add_dependency "safety_net_attestation", "~> 0.4.0"
|
43
|
-
spec.add_dependency "tpm-key_attestation", "~> 0.
|
43
|
+
spec.add_dependency "tpm-key_attestation", "~> 0.12.0"
|
44
44
|
|
45
|
+
spec.add_development_dependency "base64", ">= 0.1.0"
|
45
46
|
spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
|
46
47
|
spec.add_development_dependency "byebug", "~> 11.0"
|
47
48
|
spec.add_development_dependency "rake", "~> 13.0"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webauthn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gonzalo Rodriguez
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-12-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: android_key_attestation
|
@@ -88,9 +88,6 @@ dependencies:
|
|
88
88
|
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
90
|
version: '2.2'
|
91
|
-
- - "<"
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
version: '3.1'
|
94
91
|
type: :runtime
|
95
92
|
prerelease: false
|
96
93
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -98,9 +95,6 @@ dependencies:
|
|
98
95
|
- - ">="
|
99
96
|
- !ruby/object:Gem::Version
|
100
97
|
version: '2.2'
|
101
|
-
- - "<"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '3.1'
|
104
98
|
- !ruby/object:Gem::Dependency
|
105
99
|
name: safety_net_attestation
|
106
100
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,14 +115,28 @@ dependencies:
|
|
121
115
|
requirements:
|
122
116
|
- - "~>"
|
123
117
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
118
|
+
version: 0.12.0
|
125
119
|
type: :runtime
|
126
120
|
prerelease: false
|
127
121
|
version_requirements: !ruby/object:Gem::Requirement
|
128
122
|
requirements:
|
129
123
|
- - "~>"
|
130
124
|
- !ruby/object:Gem::Version
|
131
|
-
version: 0.
|
125
|
+
version: 0.12.0
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: base64
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 0.1.0
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 0.1.0
|
132
140
|
- !ruby/object:Gem::Dependency
|
133
141
|
name: bundler
|
134
142
|
requirement: !ruby/object:Gem::Requirement
|
@@ -243,6 +251,7 @@ executables: []
|
|
243
251
|
extensions: []
|
244
252
|
extra_rdoc_files: []
|
245
253
|
files:
|
254
|
+
- ".github/dependabot.yml"
|
246
255
|
- ".github/workflows/build.yml"
|
247
256
|
- ".github/workflows/git.yml"
|
248
257
|
- ".gitignore"
|
@@ -324,11 +333,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
324
333
|
version: '2.5'
|
325
334
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
326
335
|
requirements:
|
327
|
-
- - "
|
336
|
+
- - ">="
|
328
337
|
- !ruby/object:Gem::Version
|
329
|
-
version:
|
338
|
+
version: '0'
|
330
339
|
requirements: []
|
331
|
-
rubygems_version: 3.
|
340
|
+
rubygems_version: 3.4.10
|
332
341
|
signing_key:
|
333
342
|
specification_version: 4
|
334
343
|
summary: WebAuthn ruby server library
|