webauthn 2.2.0 → 2.5.0

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +36 -0
  3. data/.rubocop.yml +60 -0
  4. data/Appraisals +2 -10
  5. data/CHANGELOG.md +53 -0
  6. data/README.md +71 -9
  7. data/SECURITY.md +6 -3
  8. data/gemfiles/{openssl_2_0.gemfile → openssl_2_2.gemfile} +1 -1
  9. data/lib/cose/rsapkcs1_algorithm.rb +11 -0
  10. data/lib/webauthn/attestation_object.rb +2 -2
  11. data/lib/webauthn/attestation_statement.rb +4 -1
  12. data/lib/webauthn/attestation_statement/android_key.rb +0 -11
  13. data/lib/webauthn/attestation_statement/android_safetynet.rb +1 -5
  14. data/lib/webauthn/attestation_statement/apple.rb +65 -0
  15. data/lib/webauthn/attestation_statement/base.rb +36 -14
  16. data/lib/webauthn/attestation_statement/fido_u2f.rb +2 -5
  17. data/lib/webauthn/attestation_statement/none.rb +7 -1
  18. data/lib/webauthn/attestation_statement/packed.rb +10 -23
  19. data/lib/webauthn/attestation_statement/tpm.rb +10 -20
  20. data/lib/webauthn/authenticator_assertion_response.rb +1 -4
  21. data/lib/webauthn/authenticator_attestation_response.rb +2 -2
  22. data/lib/webauthn/configuration.rb +2 -6
  23. data/lib/webauthn/credential_creation_options.rb +2 -0
  24. data/lib/webauthn/credential_request_options.rb +2 -0
  25. data/lib/webauthn/fake_authenticator.rb +16 -4
  26. data/lib/webauthn/fake_authenticator/attestation_object.rb +7 -3
  27. data/lib/webauthn/fake_client.rb +21 -4
  28. data/lib/webauthn/public_key.rb +21 -2
  29. data/lib/webauthn/public_key_credential.rb +13 -3
  30. data/lib/webauthn/public_key_credential/entity.rb +3 -4
  31. data/lib/webauthn/version.rb +1 -1
  32. data/webauthn.gemspec +7 -6
  33. metadata +34 -22
  34. data/.travis.yml +0 -26
  35. data/gemfiles/cose_head.gemfile +0 -7
  36. data/gemfiles/openssl_head.gemfile +0 -7
  37. data/lib/webauthn/signature_verifier.rb +0 -52
@@ -16,6 +16,8 @@ module WebAuthn
16
16
  attr_accessor :allow_credentials, :extensions, :user_verification
17
17
 
18
18
  def initialize(allow_credentials: [], extensions: nil, user_verification: nil)
19
+ super()
20
+
19
21
  @allow_credentials = allow_credentials
20
22
  @extensions = extensions
21
23
  @user_verification = user_verification
@@ -18,7 +18,8 @@ module WebAuthn
18
18
  user_present: true,
19
19
  user_verified: false,
20
20
  attested_credential_data: true,
21
- sign_count: nil
21
+ sign_count: nil,
22
+ extensions: nil
22
23
  )
23
24
  credential_id, credential_key, credential_sign_count = new_credential
24
25
  sign_count ||= credential_sign_count
@@ -37,7 +38,8 @@ module WebAuthn
37
38
  user_present: user_present,
38
39
  user_verified: user_verified,
39
40
  attested_credential_data: attested_credential_data,
40
- sign_count: sign_count
41
+ sign_count: sign_count,
42
+ extensions: extensions
41
43
  ).serialize
42
44
  end
43
45
 
@@ -47,12 +49,21 @@ module WebAuthn
47
49
  user_present: true,
48
50
  user_verified: false,
49
51
  aaguid: AuthenticatorData::AAGUID,
50
- sign_count: nil
52
+ sign_count: nil,
53
+ extensions: nil,
54
+ allow_credentials: nil
51
55
  )
52
56
  credential_options = credentials[rp_id]
53
57
 
54
58
  if credential_options
55
- credential_id, credential = credential_options.first
59
+ allow_credentials ||= credential_options.keys
60
+ credential_id = (credential_options.keys & allow_credentials).first
61
+ unless credential_id
62
+ raise "No matching credentials (allowed=#{allow_credentials}) " \
63
+ "found for RP #{rp_id} among credentials=#{credential_options}"
64
+ end
65
+
66
+ credential = credential_options[credential_id]
56
67
  credential_key = credential[:credential_key]
57
68
  credential_sign_count = credential[:sign_count]
58
69
 
@@ -63,6 +74,7 @@ module WebAuthn
63
74
  aaguid: aaguid,
64
75
  credential: nil,
65
76
  sign_count: sign_count || credential_sign_count,
77
+ extensions: extensions
66
78
  ).serialize
67
79
 
68
80
  signature = credential_key.sign("SHA256", authenticator_data + client_data_hash)
@@ -14,7 +14,8 @@ module WebAuthn
14
14
  user_present: true,
15
15
  user_verified: false,
16
16
  attested_credential_data: true,
17
- sign_count: 0
17
+ sign_count: 0,
18
+ extensions: nil
18
19
  )
19
20
  @client_data_hash = client_data_hash
20
21
  @rp_id_hash = rp_id_hash
@@ -24,6 +25,7 @@ module WebAuthn
24
25
  @user_verified = user_verified
25
26
  @attested_credential_data = attested_credential_data
26
27
  @sign_count = sign_count
28
+ @extensions = extensions
27
29
  end
28
30
 
29
31
  def serialize
@@ -44,7 +46,8 @@ module WebAuthn
44
46
  :user_present,
45
47
  :user_verified,
46
48
  :attested_credential_data,
47
- :sign_count
49
+ :sign_count,
50
+ :extensions
48
51
  )
49
52
 
50
53
  def authenticator_data
@@ -60,7 +63,8 @@ module WebAuthn
60
63
  credential: credential_data,
61
64
  user_present: user_present,
62
65
  user_verified: user_verified,
63
- sign_count: 0
66
+ sign_count: 0,
67
+ extensions: extensions
64
68
  )
65
69
  end
66
70
  end
@@ -29,7 +29,8 @@ module WebAuthn
29
29
  rp_id: nil,
30
30
  user_present: true,
31
31
  user_verified: false,
32
- attested_credential_data: true
32
+ attested_credential_data: true,
33
+ extensions: nil
33
34
  )
34
35
  rp_id ||= URI.parse(origin).host
35
36
 
@@ -41,7 +42,8 @@ module WebAuthn
41
42
  client_data_hash: client_data_hash,
42
43
  user_present: user_present,
43
44
  user_verified: user_verified,
44
- attested_credential_data: attested_credential_data
45
+ attested_credential_data: attested_credential_data,
46
+ extensions: extensions
45
47
  )
46
48
 
47
49
  id =
@@ -58,6 +60,7 @@ module WebAuthn
58
60
  "type" => "public-key",
59
61
  "id" => internal_encoder.encode(id),
60
62
  "rawId" => encoder.encode(id),
63
+ "clientExtensionResults" => extensions,
61
64
  "response" => {
62
65
  "attestationObject" => encoder.encode(attestation_object),
63
66
  "clientDataJSON" => encoder.encode(client_data_json)
@@ -65,29 +68,43 @@ module WebAuthn
65
68
  }
66
69
  end
67
70
 
68
- def get(challenge: fake_challenge, rp_id: nil, user_present: true, user_verified: false, sign_count: nil)
71
+ def get(challenge: fake_challenge,
72
+ rp_id: nil,
73
+ user_present: true,
74
+ user_verified: false,
75
+ sign_count: nil,
76
+ extensions: nil,
77
+ user_handle: nil,
78
+ allow_credentials: nil)
69
79
  rp_id ||= URI.parse(origin).host
70
80
 
71
81
  client_data_json = data_json_for(:get, encoder.decode(challenge))
72
82
  client_data_hash = hashed(client_data_json)
73
83
 
84
+ if allow_credentials
85
+ allow_credentials = allow_credentials.map { |credential| encoder.decode(credential) }
86
+ end
87
+
74
88
  assertion = authenticator.get_assertion(
75
89
  rp_id: rp_id,
76
90
  client_data_hash: client_data_hash,
77
91
  user_present: user_present,
78
92
  user_verified: user_verified,
79
93
  sign_count: sign_count,
94
+ extensions: extensions,
95
+ allow_credentials: allow_credentials
80
96
  )
81
97
 
82
98
  {
83
99
  "type" => "public-key",
84
100
  "id" => internal_encoder.encode(assertion[:credential_id]),
85
101
  "rawId" => encoder.encode(assertion[:credential_id]),
102
+ "clientExtensionResults" => extensions,
86
103
  "response" => {
87
104
  "clientDataJSON" => encoder.encode(client_data_json),
88
105
  "authenticatorData" => encoder.encode(assertion[:authenticator_data]),
89
106
  "signature" => encoder.encode(assertion[:signature]),
90
- "userHandle" => nil
107
+ "userHandle" => user_handle ? encoder.encode(user_handle) : nil
91
108
  }
92
109
  }
93
110
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "webauthn/attestation_statement/fido_u2f/public_key"
4
- require "cose/key"
5
3
  require "cose/algorithm"
4
+ require "cose/error"
5
+ require "cose/key"
6
+ require "cose/rsapkcs1_algorithm"
7
+ require "webauthn/attestation_statement/fido_u2f/public_key"
6
8
 
7
9
  module WebAuthn
8
10
  class PublicKey
11
+ class UnsupportedAlgorithm < Error; end
12
+
9
13
  def self.deserialize(public_key)
10
14
  cose_key =
11
15
  if WebAuthn::AttestationStatement::FidoU2f::PublicKey.uncompressed_point?(public_key)
@@ -45,5 +49,20 @@ module WebAuthn
45
49
  def alg
46
50
  @cose_key.alg
47
51
  end
52
+
53
+ def verify(signature, verification_data)
54
+ cose_algorithm.verify(pkey, signature, verification_data)
55
+ rescue COSE::Error
56
+ false
57
+ end
58
+
59
+ private
60
+
61
+ def cose_algorithm
62
+ @cose_algorithm ||= COSE::Algorithm.find(alg) || raise(
63
+ UnsupportedAlgorithm,
64
+ "The public key algorithm #{alg} is not among the available COSE algorithms"
65
+ )
66
+ end
48
67
  end
49
68
  end
@@ -4,21 +4,23 @@ require "webauthn/encoder"
4
4
 
5
5
  module WebAuthn
6
6
  class PublicKeyCredential
7
- attr_reader :type, :id, :raw_id, :response
7
+ attr_reader :type, :id, :raw_id, :client_extension_outputs, :response
8
8
 
9
9
  def self.from_client(credential)
10
10
  new(
11
11
  type: credential["type"],
12
12
  id: credential["id"],
13
13
  raw_id: WebAuthn.configuration.encoder.decode(credential["rawId"]),
14
+ client_extension_outputs: credential["clientExtensionResults"],
14
15
  response: response_class.from_client(credential["response"])
15
16
  )
16
17
  end
17
18
 
18
- def initialize(type:, id:, raw_id:, response:)
19
+ def initialize(type:, id:, raw_id:, client_extension_outputs: {}, response:)
19
20
  @type = type
20
21
  @id = id
21
22
  @raw_id = raw_id
23
+ @client_extension_outputs = client_extension_outputs
22
24
  @response = response
23
25
  end
24
26
 
@@ -30,7 +32,11 @@ module WebAuthn
30
32
  end
31
33
 
32
34
  def sign_count
33
- response&.authenticator_data&.sign_count
35
+ authenticator_data&.sign_count
36
+ end
37
+
38
+ def authenticator_extension_outputs
39
+ authenticator_data.extension_data if authenticator_data&.extension_data_included?
34
40
  end
35
41
 
36
42
  private
@@ -43,6 +49,10 @@ module WebAuthn
43
49
  raw_id && id && raw_id == WebAuthn.standard_encoder.decode(id)
44
50
  end
45
51
 
52
+ def authenticator_data
53
+ response&.authenticator_data
54
+ end
55
+
46
56
  def encoder
47
57
  WebAuthn.configuration.encoder
48
58
  end
@@ -5,11 +5,10 @@ require "awrence"
5
5
  module WebAuthn
6
6
  class PublicKeyCredential
7
7
  class Entity
8
- attr_reader :name, :icon
8
+ attr_reader :name
9
9
 
10
- def initialize(name:, icon: nil)
10
+ def initialize(name:)
11
11
  @name = name
12
- @icon = icon
13
12
  end
14
13
 
15
14
  def as_json
@@ -37,7 +36,7 @@ module WebAuthn
37
36
  end
38
37
 
39
38
  def attributes
40
- [:name, :icon]
39
+ [:name]
41
40
  end
42
41
  end
43
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "2.2.0"
4
+ VERSION = "2.5.0"
5
5
  end
data/webauthn.gemspec CHANGED
@@ -37,17 +37,18 @@ Gem::Specification.new do |spec|
37
37
  spec.add_dependency "awrence", "~> 1.1"
38
38
  spec.add_dependency "bindata", "~> 2.4"
39
39
  spec.add_dependency "cbor", "~> 0.5.9"
40
- spec.add_dependency "cose", "~> 0.11.0"
41
- spec.add_dependency "openssl", "~> 2.0"
40
+ spec.add_dependency "cose", "~> 1.1"
41
+ spec.add_dependency "openssl", "~> 2.1"
42
42
  spec.add_dependency "safety_net_attestation", "~> 0.4.0"
43
43
  spec.add_dependency "securecompare", "~> 1.0"
44
- spec.add_dependency "tpm-key_attestation", "~> 0.7.0"
44
+ spec.add_dependency "tpm-key_attestation", "~> 0.10.0"
45
45
 
46
- spec.add_development_dependency "appraisal", "~> 2.2.0"
46
+ spec.add_development_dependency "appraisal", "~> 2.4"
47
47
  spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
48
48
  spec.add_development_dependency "byebug", "~> 11.0"
49
49
  spec.add_development_dependency "rake", "~> 13.0"
50
50
  spec.add_development_dependency "rspec", "~> 3.8"
51
- spec.add_development_dependency "rubocop", "0.80.1"
52
- spec.add_development_dependency "rubocop-rspec", "~> 1.38.1"
51
+ spec.add_development_dependency "rubocop", "~> 1.9.1"
52
+ spec.add_development_dependency "rubocop-rake", "~> 0.5.1"
53
+ spec.add_development_dependency "rubocop-rspec", "~> 2.2.0"
53
54
  end
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: 2.2.0
4
+ version: 2.5.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: 2020-03-14 00:00:00.000000000 Z
12
+ date: 2021-03-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: android_key_attestation
@@ -73,28 +73,28 @@ dependencies:
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: 0.11.0
76
+ version: '1.1'
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
- version: 0.11.0
83
+ version: '1.1'
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: openssl
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - "~>"
89
89
  - !ruby/object:Gem::Version
90
- version: '2.0'
90
+ version: '2.1'
91
91
  type: :runtime
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - "~>"
96
96
  - !ruby/object:Gem::Version
97
- version: '2.0'
97
+ version: '2.1'
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: safety_net_attestation
100
100
  requirement: !ruby/object:Gem::Requirement
@@ -129,28 +129,28 @@ dependencies:
129
129
  requirements:
130
130
  - - "~>"
131
131
  - !ruby/object:Gem::Version
132
- version: 0.7.0
132
+ version: 0.10.0
133
133
  type: :runtime
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
- version: 0.7.0
139
+ version: 0.10.0
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: appraisal
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
144
  - - "~>"
145
145
  - !ruby/object:Gem::Version
146
- version: 2.2.0
146
+ version: '2.4'
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
- version: 2.2.0
153
+ version: '2.4'
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: bundler
156
156
  requirement: !ruby/object:Gem::Requirement
@@ -217,30 +217,44 @@ dependencies:
217
217
  name: rubocop
218
218
  requirement: !ruby/object:Gem::Requirement
219
219
  requirements:
220
- - - '='
220
+ - - "~>"
221
221
  - !ruby/object:Gem::Version
222
- version: 0.80.1
222
+ version: 1.9.1
223
223
  type: :development
224
224
  prerelease: false
225
225
  version_requirements: !ruby/object:Gem::Requirement
226
226
  requirements:
227
- - - '='
227
+ - - "~>"
228
228
  - !ruby/object:Gem::Version
229
- version: 0.80.1
229
+ version: 1.9.1
230
+ - !ruby/object:Gem::Dependency
231
+ name: rubocop-rake
232
+ requirement: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: 0.5.1
237
+ type: :development
238
+ prerelease: false
239
+ version_requirements: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: 0.5.1
230
244
  - !ruby/object:Gem::Dependency
231
245
  name: rubocop-rspec
232
246
  requirement: !ruby/object:Gem::Requirement
233
247
  requirements:
234
248
  - - "~>"
235
249
  - !ruby/object:Gem::Version
236
- version: 1.38.1
250
+ version: 2.2.0
237
251
  type: :development
238
252
  prerelease: false
239
253
  version_requirements: !ruby/object:Gem::Requirement
240
254
  requirements:
241
255
  - - "~>"
242
256
  - !ruby/object:Gem::Version
243
- version: 1.38.1
257
+ version: 2.2.0
244
258
  description: |-
245
259
  WebAuthn ruby server library ― Make your application a W3C Web Authentication conformant
246
260
  Relying Party and allow your users to authenticate with U2F and FIDO2 authenticators.
@@ -251,10 +265,10 @@ executables: []
251
265
  extensions: []
252
266
  extra_rdoc_files: []
253
267
  files:
268
+ - ".github/workflows/build.yml"
254
269
  - ".gitignore"
255
270
  - ".rspec"
256
271
  - ".rubocop.yml"
257
- - ".travis.yml"
258
272
  - Appraisals
259
273
  - CHANGELOG.md
260
274
  - CONTRIBUTING.md
@@ -266,16 +280,15 @@ files:
266
280
  - bin/console
267
281
  - bin/setup
268
282
  - docs/u2f_migration.md
269
- - gemfiles/cose_head.gemfile
270
- - gemfiles/openssl_2_0.gemfile
271
283
  - gemfiles/openssl_2_1.gemfile
272
- - gemfiles/openssl_head.gemfile
284
+ - gemfiles/openssl_2_2.gemfile
273
285
  - lib/cose/rsapkcs1_algorithm.rb
274
286
  - lib/webauthn.rb
275
287
  - lib/webauthn/attestation_object.rb
276
288
  - lib/webauthn/attestation_statement.rb
277
289
  - lib/webauthn/attestation_statement/android_key.rb
278
290
  - lib/webauthn/attestation_statement/android_safetynet.rb
291
+ - lib/webauthn/attestation_statement/apple.rb
279
292
  - lib/webauthn/attestation_statement/base.rb
280
293
  - lib/webauthn/attestation_statement/fido_u2f.rb
281
294
  - lib/webauthn/attestation_statement/fido_u2f/public_key.rb
@@ -313,7 +326,6 @@ files:
313
326
  - lib/webauthn/public_key_credential_with_assertion.rb
314
327
  - lib/webauthn/public_key_credential_with_attestation.rb
315
328
  - lib/webauthn/security_utils.rb
316
- - lib/webauthn/signature_verifier.rb
317
329
  - lib/webauthn/u2f_migrator.rb
318
330
  - lib/webauthn/version.rb
319
331
  - webauthn.gemspec
@@ -339,7 +351,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
339
351
  - !ruby/object:Gem::Version
340
352
  version: '0'
341
353
  requirements: []
342
- rubygems_version: 3.1.2
354
+ rubygems_version: 3.2.14
343
355
  signing_key:
344
356
  specification_version: 4
345
357
  summary: WebAuthn ruby server library