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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f9f14328571fd39b5e19c9a6ec5b427cfd4dea9fc2ab4774135f4453e14b676
4
- data.tar.gz: 1e33b7564ea1b3b8aeb32864782a0dad1e0207f1c70c037eb80140db2272ae01
3
+ metadata.gz: 3b23698bdd722a0cda45be7ee9d5ba528f966eb98304a50069242f0a25dcd259
4
+ data.tar.gz: 4d7bf8874268cbbbfa75136006af9dc5a824b25d3f0522c5f2046092261bae5d
5
5
  SHA512:
6
- metadata.gz: b1e22bb07b1ebced7851c592a2a4e8b0e6772e502f402bd841383f8071963e3b0df8c75da66db5623840a32f14799439d09a52c8dabcdd4e5edbef8a0a419a00
7
- data.tar.gz: 1a1b1c647a06398edad9d1da94ca8de53202e184f8930b2fe001f82e17d8416516cbaf4db4fd012fd44d1ac5da32c6d8d32c971507584bf00ef9d74b15d535b5
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,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@v2
31
+ - uses: actions/checkout@v4
27
32
  - uses: ruby/setup-ruby@v1
28
33
  with:
29
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/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
- [![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."
@@ -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`
@@ -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
- ```
@@ -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
@@ -33,7 +33,9 @@ module WebAuthn
33
33
  :attestation_root_certificates_finders,
34
34
  :attestation_root_certificates_finders=,
35
35
  :encoder,
36
- :encoder=
36
+ :encoder=,
37
+ :legacy_u2f_appid,
38
+ :legacy_u2f_appid=
37
39
 
38
40
  attr_reader :relying_party
39
41
 
@@ -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
@@ -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, :credential, :user_present, :user_verified, :extensions
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
- reserved_for_future_use_bit,
50
- reserved_for_future_use_bit,
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
- { user_present: user_present, user_verified: user_verified }
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,
@@ -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
- attr_reader :type, :id, :raw_id, :client_extension_outputs, :response
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
- encoder: relying_party.encoder
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
- encoder: WebAuthn.configuration.encoder
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
- @encoder = encoder
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 :encoder
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "3.0.0.alpha2"
4
+ VERSION = "3.1.0"
5
5
  end
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", "< 3.1"
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.11.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.0.0.alpha2
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: 2022-09-15 00:00:00.000000000 Z
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.11.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.11.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: 1.3.1
338
+ version: '0'
330
339
  requirements: []
331
- rubygems_version: 3.2.32
340
+ rubygems_version: 3.4.10
332
341
  signing_key:
333
342
  specification_version: 4
334
343
  summary: WebAuthn ruby server library