webauthn 2.2.0 → 2.5.0

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