webauthn 1.18.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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)