webauthn 1.18.0 → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/.travis.yml +7 -3
  4. data/Appraisals +8 -0
  5. data/CHANGELOG.md +52 -0
  6. data/README.md +88 -80
  7. data/SECURITY.md +18 -0
  8. data/gemfiles/cose_head.gemfile +7 -0
  9. data/gemfiles/openssl_head.gemfile +7 -0
  10. data/lib/webauthn.rb +9 -1
  11. data/lib/webauthn/attestation_statement/android_safetynet.rb +4 -4
  12. data/lib/webauthn/attestation_statement/base.rb +4 -4
  13. data/lib/webauthn/attestation_statement/fido_u2f.rb +1 -2
  14. data/lib/webauthn/authenticator_assertion_response.rb +33 -35
  15. data/lib/webauthn/authenticator_attestation_response.rb +30 -0
  16. data/lib/webauthn/authenticator_data.rb +3 -1
  17. data/lib/webauthn/authenticator_data/attested_credential_data.rb +1 -0
  18. data/lib/webauthn/authenticator_response.rb +1 -2
  19. data/lib/webauthn/client_data.rb +2 -1
  20. data/lib/webauthn/configuration.rb +9 -0
  21. data/lib/webauthn/credential.rb +26 -0
  22. data/lib/webauthn/credential_creation_options.rb +5 -1
  23. data/lib/webauthn/credential_request_options.rb +5 -0
  24. data/lib/webauthn/encoder.rb +8 -1
  25. data/lib/webauthn/fake_authenticator.rb +1 -0
  26. data/lib/webauthn/fake_client.rb +26 -22
  27. data/lib/webauthn/public_key_credential.rb +10 -50
  28. data/lib/webauthn/public_key_credential/creation_options.rb +92 -0
  29. data/lib/webauthn/public_key_credential/entity.rb +44 -0
  30. data/lib/webauthn/public_key_credential/options.rb +72 -0
  31. data/lib/webauthn/public_key_credential/request_options.rb +36 -0
  32. data/lib/webauthn/public_key_credential/rp_entity.rb +23 -0
  33. data/lib/webauthn/public_key_credential/user_entity.rb +24 -0
  34. data/lib/webauthn/public_key_credential_with_assertion.rb +35 -0
  35. data/lib/webauthn/public_key_credential_with_attestation.rb +30 -0
  36. data/lib/webauthn/u2f_migrator.rb +1 -1
  37. data/lib/webauthn/version.rb +1 -1
  38. data/webauthn.gemspec +3 -2
  39. metadata +33 -8
  40. data/webauthn-ruby.png +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a6a15357ab32b764ce0694b5a6cc4950e9298e377c8a93f8b664befb67dffcb
4
- data.tar.gz: 3481eba4bbdd21676eda5015c9ebc9d11a92422ab0d57b6fed1da31b2ebc9d14
3
+ metadata.gz: 6181693e3c34f1ca289fb2056df36c35a5e144076288eef34e774e2dfc1794b5
4
+ data.tar.gz: 87883fa3fe7c7bd5da885025b7a571dfedfdfe1c27c6a5c8edeee54956bd98bc
5
5
  SHA512:
6
- metadata.gz: 78f3d1242e008dac5a7d1b66944f6e64dd2a11baced08a454ca587d5bb7b5a93b2e51052d044f8c34034355ab2536af96438b9178e9b6536e92366cd55fbe495
7
- data.tar.gz: 1e4ecf4a58bad0213e0893834f57f5564ce9b2d9d76ad214e35a85d7ede4d693d0d9f55a289fe998ac4ee4479279d7c521afef98fd7ddce1eec1f849d346948d
6
+ metadata.gz: a69f7870a89344a5d00b1c75b55dd6e25741719bf2fd79d03e83348530a964ef237f8330dea55edbb58b9fe5ee0522f2f41b5d9d46f7d975112b9dd8a05889bd
7
+ data.tar.gz: 19b32618b9e83e2618abe361aca398933030fe2963502c8a9054dc291b22678107e3257be5d7a711bdf0d826dc1d444e28d726282fa6306abf336f675e3794df
@@ -1,3 +1,7 @@
1
+ inherit_mode:
2
+ merge:
3
+ - AllowedNames
4
+
1
5
  AllCops:
2
6
  TargetRubyVersion: 2.3
3
7
  DisabledByDefault: true
@@ -24,6 +28,10 @@ Metrics/LineLength:
24
28
  Naming:
25
29
  Enabled: true
26
30
 
31
+ Naming/UncommunicativeMethodParamName:
32
+ AllowedNames:
33
+ - rp
34
+
27
35
  Security:
28
36
  Enabled: true
29
37
 
@@ -5,12 +5,14 @@ cache: bundler
5
5
  rvm:
6
6
  - ruby-head
7
7
  - 2.7.0-preview1
8
- - 2.6.3
9
- - 2.5.5
10
- - 2.4.6
8
+ - 2.6.4
9
+ - 2.5.6
10
+ - 2.4.7
11
11
  - 2.3.8
12
12
 
13
13
  gemfile:
14
+ - gemfiles/cose_head.gemfile
15
+ - gemfiles/openssl_head.gemfile
14
16
  - gemfiles/openssl_2_1.gemfile
15
17
  - gemfiles/openssl_2_0.gemfile
16
18
 
@@ -19,6 +21,8 @@ matrix:
19
21
  allow_failures:
20
22
  - rvm: ruby-head
21
23
  - rvm: 2.7.0-preview1
24
+ - gemfile: gemfiles/cose_head.gemfile
25
+ - gemfile: gemfiles/openssl_head.gemfile
22
26
 
23
27
  before_install:
24
28
  - wget http://archive.ubuntu.com/ubuntu/pool/universe/f/faketime/libfaketime_0.9.7-3_amd64.deb
data/Appraisals CHANGED
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ appraise "cose_head" do
4
+ gem "cose", git: "https://github.com/cedarcode/cose-ruby"
5
+ end
6
+
7
+ appraise "openssl_head" do
8
+ gem "openssl", git: "https://github.com/ruby/openssl"
9
+ end
10
+
3
11
  appraise "openssl_2_1" do
4
12
  gem "openssl", "~> 2.1.0"
5
13
  end
@@ -1,5 +1,56 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.0.0.beta1] - 2019-09-16
4
+
5
+ ### Added
6
+
7
+ - Smarter new public API methods:
8
+ - `WebAuthn.generate_user_id`
9
+ - `WebAuthn::Credential.options_for_create`
10
+ - `WebAuthn::Credential.options_for_get`
11
+ - `WebAuthn::Credential.from_create`
12
+ - `WebAuthn::Credential.from_get`
13
+ - All the above automatically handle encoding/decoding for necessary values. The specific encoding scheme can
14
+ be set (or even turned off) in `WebAutnn.configuration.encoding=`. Defaults to `:base64url`.
15
+ - `WebAuthn::FakeClient#get` better fakes a real client by including `userHandle` in the returned hash.
16
+ - Expose AAGUID and attestationCertificateKey for MDS lookup during attestation (@bdwater)
17
+
18
+ ### Changed
19
+
20
+ - `WebAuthn::AuthenticatorAssertionResponse#verify` no longer accepts `allowed_credentials:` keyword argument.
21
+ Please replace with `public_key:` and `sign_count:` keyword arguments. If you're not performing sign count
22
+ verification, signal opt-out with `sign_count: false`.
23
+
24
+ - `WebAuthn::FakeClient#create` and `WebAuthn::FakeClient#get` better fakes a real client by using camelBack string
25
+ keys instead of snake_case symbol keys in the returned hash.
26
+ - `WebAuthn::FakeClient#create` and `WebAuthn::FakeClient#get` better fakes a real client by not padding the
27
+ returned base64url-encoded `id` value.
28
+
29
+ ### Deprecated
30
+
31
+ - `WebAuthn.credential_creation_options` method. Please consider using `WebAuthn::Credential.options_for_create`.
32
+ - `WebAuthn.credential_request_options` method. Please consider using `WebAuthn::Credential.options_for_get`.
33
+
34
+ ### Removed
35
+
36
+ - `WebAuthn::AuthenticatorAssertionResponse.new` no longer accepts `credential_id`. No replacement needed, just don't
37
+ pass it.
38
+
39
+ ### BREAKING CHANGES
40
+
41
+ - `WebAuthn::AuthenticatorAssertionResponse.new` no longer accepts `credential_id`. No replacement needed, just don't
42
+ pass it.
43
+
44
+ - `WebAuthn::AuthenticatorAssertionResponse#verify` no longer accepts `allowed_credentials:` keyword argument.
45
+ Please replace with `public_key:` and `sign_count:` keyword arguments. If you're not performing sign count
46
+ verification, signal opt-out with `sign_count: false`.
47
+
48
+ - `WebAuthn::FakeClient#create` and `WebAuthn::FakeClient#get` better fakes a real client by using camelBack string
49
+ keys instead of snake_case symbol keys in the returned hash.
50
+
51
+ - `WebAuthn::FakeClient#create` and `WebAuthn::FakeClient#get` better fakes a real client by not padding the
52
+ returned base64url-encoded `id` value.
53
+
3
54
  ## [v1.18.0] - 2019-07-27
4
55
 
5
56
  ### Added
@@ -209,6 +260,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
209
260
  - `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
210
261
  - Works with ruby 2.5
211
262
 
263
+ [v2.0.0.beta1]: https://github.com/cedarcode/webauthn-ruby/compare/v1.18.0...v2.0.0.beta1/
212
264
  [v1.18.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.17.0...v1.18.0/
213
265
  [v1.17.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.16.0...v1.17.0/
214
266
  [v1.16.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.15.0...v1.16.0/
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
+ __Note__: You are viewing the README for the development version of webauthn-ruby.
2
+ For the current release version see https://github.com/cedarcode/webauthn-ruby/blob/1-stable/README.md.
3
+
1
4
  # webauthn-ruby
2
5
 
3
- ![banner](webauthn-ruby.png)
6
+ ![banner](assets/webauthn-ruby.png)
4
7
 
5
8
  [![Gem](https://img.shields.io/gem/v/webauthn.svg?style=flat-square)](https://rubygems.org/gems/webauthn)
6
9
  [![Travis](https://img.shields.io/travis/cedarcode/webauthn-ruby/master.svg?style=flat-square)](https://travis-ci.org/cedarcode/webauthn-ruby)
@@ -30,7 +33,9 @@ a user [credential](https://www.w3.org/TR/webauthn/#public-key-credential), incl
30
33
 
31
34
  ## Security
32
35
 
33
- If you have discovered a security bug, please send an email to security@cedarcode.com instead of posting to the GitHub issue tracker.
36
+ Please report security vulnerabilities to security@cedarcode.com.
37
+
38
+ _More_: [SECURITY](SECURITY.md)
34
39
 
35
40
  ## Background
36
41
 
@@ -117,126 +122,129 @@ WebAuthn.configure do |config|
117
122
  # the suffix "example.com"
118
123
  #
119
124
  # config.rp_id = "example.com"
125
+
126
+ # Configure preferred binary-to-text encoding scheme. This should match the encoding scheme
127
+ # used in your client-side (user agent) code before sending the credential to the server.
128
+ # Supported values: `:base64url` (default), `:base64` or `false` to disable all encoding.
129
+ #
130
+ # config.encoding = false
120
131
  end
121
132
  ```
122
133
 
123
- ### Registration
134
+ ### Credential Registration
135
+
136
+ > The ceremony where a user, a Relying Party, and the user’s client (containing at least one authenticator) work in concert to create a public key credential and associate it with the user’s Relying Party account. Note that this includes employing a test of user presence or user verification.
137
+ > [[source](https://www.w3.org/TR/webauthn-2/#registration-ceremony)]
124
138
 
125
139
  #### Initiation phase
126
140
 
127
141
  ```ruby
128
- credential_creation_options = WebAuthn.credential_creation_options
142
+ # Generate and store the WebAuthn User ID the first time the user registers a credential
143
+ if !user.webauthn_id
144
+ user.update!(webauthn_id: WebAuthn.generate_user_id)
145
+ end
146
+
147
+ options = WebAuthn::Credential.options_for_create(
148
+ user: { id: user.webauthn_id, name: user.name }
149
+ exclude: user.credentials.map { |c| c.webauthn_id }
150
+ )
129
151
 
130
152
  # Store the newly generated challenge somewhere so you can have it
131
153
  # for the verification phase.
154
+ session[:creation_challenge] = options.challenge
155
+
156
+ # Send `options` back to the browser, so that they can be used
157
+ # to call `navigator.credentials.create({ "publicKey": options })`
132
158
  #
133
- # You can read it from the resulting options:
134
- credential_creation_options[:challenge]
159
+ # You can call `options.as_json` to get a ruby hash with a JSON representation if needed.
135
160
 
136
- # Send `credential_creation_options` to the browser, so that they can be used
137
- # to call `navigator.credentials.create({ "publicKey": credentialCreationOptions })`
161
+ # If inside a Rails controller, `render json: options` will just work.
162
+ # I.e. it will encode and convert the options to JSON automatically.
163
+
164
+ # For your frontend code, you might find @github/webauthn-json npm package useful.
165
+ # Especially for handling the necessary decoding of the options, and sending the
166
+ # `PublicKeyCredential` object back to the server.
138
167
  ```
139
168
 
140
169
  #### Verification phase
141
170
 
142
171
  ```ruby
143
- # These should be ruby `String`s encoded as binary data, e.g. `Encoding:ASCII-8BIT`.
144
- #
145
- # If the user-agent is a web browser, you would use some encoding algorithm to send what
146
- # `navigator.credentials.create` returned through the wire.
147
- #
148
- # Then you need to decode that data before passing it to the `#verify` method.
149
- #
150
- # E.g. in https://github.com/cedarcode/webauthn-rails-demo-app we use `Base64.strict_decode64`
151
- # on the user-agent encoded data before calling `#verify`
152
- attestation_object = "..."
153
- client_data_json = "..."
154
-
155
- attestation_response = WebAuthn::AuthenticatorAttestationResponse.new(
156
- attestation_object: attestation_object,
157
- client_data_json: client_data_json
158
- )
159
-
172
+ # Assuming you're using @github/webauthn-json package to send the `PublicKeyCredential` object back
173
+ # in params[:publicKeyCredential]:
174
+ webauthn_credential = WebAuthn::Credential.from_create(params[:publicKeyCredential])
160
175
 
161
176
  begin
162
- attestation_response.verify(expected_challenge)
163
-
164
- # 1. Register the new user and
165
- # 2. Keep Credential ID, Credential Public Key and Sign Count under storage
166
- # for future authentications
167
- # Access by invoking:
168
- # `attestation_response.credential.id`
169
- # `attestation_response.credential.public_key`
170
- # `attestation_response.authenticator_data.sign_count`
171
- rescue WebAuthn::VerificationError => e
177
+ webauthn_credential.verify(session[:creation_challenge])
178
+
179
+ # Store Credential ID, Credential Public Key and Sign Count for future authentications
180
+ user.credentials.create!(
181
+ webauthn_id: webauthn_credential.id,
182
+ public_key: webauthn_credential.public_key,
183
+ sign_count: webauthn_credential.sign_count
184
+ )
185
+ rescue WebAuthn::Error => e
172
186
  # Handle error
173
187
  end
174
188
  ```
175
189
 
176
- ### Authentication
190
+ ### Credential Authentication
177
191
 
178
- #### Initiation phase
192
+ > The ceremony where a user, and the user’s client (containing at least one authenticator) work in concert to cryptographically prove to a Relying Party that the user controls the credential private key associated with a previously-registered public key credential (see Registration). Note that this includes a test of user presence or user verification. [[source](https://www.w3.org/TR/webauthn-2/#authentication-ceremony)]
179
193
 
180
- Assuming you have the previously stored Credential ID, now in variable `credential_id`
194
+ #### Initiation phase
181
195
 
182
196
  ```ruby
183
- credential_request_options = WebAuthn.credential_request_options
184
- credential_request_options[:allowCredentials] << { id: credential_id, type: "public-key" }
197
+ options = WebAuthn::Credential.options_for_get(allow: user.credentials.map { |c| c.webauthn_id })
185
198
 
186
199
  # Store the newly generated challenge somewhere so you can have it
187
200
  # for the verification phase.
188
- #
189
- # You can read it from the resulting options:
190
- credential_request_options[:challenge]
201
+ session[:authentication_challenge] = options.challenge
202
+
203
+ # Send `options` back to the browser, so that they can be used
204
+ # to call `navigator.credentials.get({ "publicKey": options })`
205
+
206
+ # You can call `options.as_json` to get a ruby hash with a JSON representation if needed.
207
+
208
+ # If inside a Rails controller, `render json: options` will just work.
209
+ # I.e. it will encode and convert the options to JSON automatically.
191
210
 
192
- # Send `credential_request_options` to the browser, so that they can be used
193
- # to call `navigator.credentials.get({ "publicKey": credentialRequestOptions })`
211
+ # For your frontend code, you might find @github/webauthn-json npm package useful.
212
+ # Especially for handling the necessary decoding of the options, and sending the
213
+ # `PublicKeyCredential` object back to the server.
194
214
  ```
195
215
 
196
216
  #### Verification phase
197
217
 
198
- Assuming you have the previously stored Credential Public Key, now in variable `credential_public_key`
218
+ You need to look up the stored credential for a user by matching the `id` attribute from the PublicKeyCredential
219
+ interface returned by the browser to the stored `credential_id`. The corresponding `public_key` and `sign_count`
220
+ attributes must be passed as keyword arguments to the `verify` method call.
199
221
 
200
222
  ```ruby
201
- # These should be ruby `String`s encoded as binary data, e.g. `Encoding:ASCII-8BIT`.
202
- #
203
- # If the user-agent is a web browser, you would use some encoding algorithm to send what
204
- # `navigator.credentials.get` returned through the wire.
205
- #
206
- # Then you need to decode that data before passing it to the `#verify` method.
207
- #
208
- # E.g. in https://github.com/cedarcode/webauthn-rails-demo-app we use `Base64.strict_decode64`
209
- # on the user-agent encoded data before calling `#verify`
210
- selected_credential_id = "..."
211
- authenticator_data = "..."
212
- client_data_json = "..."
213
- signature = "..."
214
-
215
- assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
216
- credential_id: selected_credential_id,
217
- authenticator_data: authenticator_data,
218
- client_data_json: client_data_json,
219
- signature: signature
220
- )
223
+ # Assuming you're using @github/webauthn-json package to send the `PublicKeyCredential` object back
224
+ # in params[:publicKeyCredential]:
225
+ webauthn_credential = WebAuthn::Credential.from_get(params[:publicKeyCredential])
221
226
 
222
- # This hash must have the id and its corresponding public key of the
223
- # previously stored credential for the user that is attempting to sign in.
224
- allowed_credential = {
225
- id: credential_id,
226
- public_key: credential_public_key,
227
- sign_count: sign_count,
228
- }
227
+ stored_credential = user.credentials.find_by(webauthn_id: webauthn_credential.id)
229
228
 
230
229
  begin
231
- assertion_response.verify(expected_challenge, allowed_credentials: [allowed_credential])
232
-
233
- # Sign in the user
234
- rescue WebAuthn::VerificationError => e
230
+ webauthn_credential.verify(
231
+ session[:authentication_challenge],
232
+ public_key: stored_credential.public_key,
233
+ sign_count: stored_credential.sign_count
234
+ )
235
+
236
+ # Update the stored credential sign count with the value from `webauthn_credential.sign_count`
237
+ stored_credential.update!(sign_count: webauthn_credential.sign_count)
238
+
239
+ # Continue with successful sign in or 2FA verification...
240
+
241
+ rescue WebAuthn::SignCountVerificationError => e
242
+ # Cryptographic verification of the authenticator data succeeded, but the signature counter was less then or equal
243
+ # to the stored value. This can have several reasons and depending on your risk tolerance you can choose to fail or
244
+ # pass authentication. For more information see https://www.w3.org/TR/webauthn/#sign-counter
245
+ rescue WebAuthn::Error => e
235
246
  # Handle error
236
247
  end
237
-
238
- # Find the selected credential in your data storage using `selected_credential_id`
239
- # Update the stored sign count with the value from `assertion_response.authenticator_data.sign_count`
240
248
  ```
241
249
 
242
250
  ## API
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 1.18.z | :white_check_mark: |
8
+ | 1.17.z | :white_check_mark: |
9
+ | 1.16.z | :white_check_mark: |
10
+ | 1.15.z | :white_check_mark: |
11
+ | < 1.15 | :x: |
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ If you have discovered a security bug, please send an email to security@cedarcode.com
16
+ instead of posting to the GitHub issue tracker.
17
+
18
+ Thank you!
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "cose", git: "https://github.com/cedarcode/cose-ruby"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "openssl", git: "https://github.com/ruby/openssl"
6
+
7
+ gemspec path: "../"
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "webauthn/configuration"
4
+ require "webauthn/credential"
4
5
  require "webauthn/credential_creation_options"
5
6
  require "webauthn/credential_request_options"
6
- require "webauthn/public_key_credential"
7
7
  require "webauthn/version"
8
+
9
+ module WebAuthn
10
+ TYPE_PUBLIC_KEY = "public-key"
11
+
12
+ def self.generate_user_id
13
+ configuration.encoder.encode(SecureRandom.random_bytes(64))
14
+ end
15
+ end
@@ -20,6 +20,10 @@ module WebAuthn
20
20
  [WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC, attestation_certificate]
21
21
  end
22
22
 
23
+ def attestation_certificate
24
+ attestation_response.certificate_chain[0]
25
+ end
26
+
23
27
  private
24
28
 
25
29
  # FIXME: This should be a responsibility of AndroidSafetynet::AttestationResponse#verify
@@ -46,10 +50,6 @@ module WebAuthn
46
50
  attestation_response.cts_profile_match?
47
51
  end
48
52
 
49
- def attestation_certificate
50
- attestation_response.certificate_chain[0]
51
- end
52
-
53
53
  def signing_certificates
54
54
  attestation_response.certificate_chain[1..-1]
55
55
  end
@@ -26,6 +26,10 @@ module WebAuthn
26
26
  raise NotImpelementedError
27
27
  end
28
28
 
29
+ def attestation_certificate
30
+ attestation_certificate_chain&.first
31
+ end
32
+
29
33
  private
30
34
 
31
35
  attr_reader :statement
@@ -42,10 +46,6 @@ module WebAuthn
42
46
  end
43
47
  end
44
48
 
45
- def attestation_certificate
46
- attestation_certificate_chain&.first
47
- end
48
-
49
49
  def attestation_certificate_chain
50
50
  @attestation_certificate_chain ||= raw_attestation_certificates&.map do |raw_certificate|
51
51
  OpenSSL::X509::Certificate.new(raw_certificate)