webauthn 2.1.0 → 2.4.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +113 -13
  3. data/.travis.yml +21 -18
  4. data/Appraisals +4 -0
  5. data/CHANGELOG.md +41 -0
  6. data/CONTRIBUTING.md +0 -5
  7. data/README.md +70 -8
  8. data/SECURITY.md +6 -4
  9. data/gemfiles/openssl_2_2.gemfile +7 -0
  10. data/lib/cose/rsapkcs1_algorithm.rb +50 -0
  11. data/lib/webauthn/attestation_object.rb +43 -0
  12. data/lib/webauthn/attestation_statement.rb +20 -20
  13. data/lib/webauthn/attestation_statement/android_key.rb +28 -30
  14. data/lib/webauthn/attestation_statement/android_safetynet.rb +27 -7
  15. data/lib/webauthn/attestation_statement/base.rb +108 -10
  16. data/lib/webauthn/attestation_statement/fido_u2f.rb +8 -6
  17. data/lib/webauthn/attestation_statement/none.rb +7 -1
  18. data/lib/webauthn/attestation_statement/packed.rb +13 -41
  19. data/lib/webauthn/attestation_statement/tpm.rb +38 -75
  20. data/lib/webauthn/authenticator_assertion_response.rb +3 -7
  21. data/lib/webauthn/authenticator_attestation_response.rb +19 -84
  22. data/lib/webauthn/authenticator_data.rb +51 -51
  23. data/lib/webauthn/authenticator_data/attested_credential_data.rb +29 -50
  24. data/lib/webauthn/authenticator_response.rb +3 -0
  25. data/lib/webauthn/credential_creation_options.rb +2 -0
  26. data/lib/webauthn/credential_request_options.rb +2 -0
  27. data/lib/webauthn/fake_authenticator.rb +7 -3
  28. data/lib/webauthn/fake_authenticator/attestation_object.rb +7 -3
  29. data/lib/webauthn/fake_authenticator/authenticator_data.rb +2 -4
  30. data/lib/webauthn/fake_client.rb +19 -5
  31. data/lib/webauthn/public_key.rb +21 -2
  32. data/lib/webauthn/public_key_credential.rb +13 -3
  33. data/lib/webauthn/u2f_migrator.rb +5 -4
  34. data/lib/webauthn/version.rb +1 -1
  35. data/script/ci/install-openssl +7 -0
  36. data/script/ci/install-ruby +13 -0
  37. data/webauthn.gemspec +13 -9
  38. metadata +54 -41
  39. data/lib/android_safetynet/attestation_response.rb +0 -116
  40. data/lib/cose/rsassa_algorithm.rb +0 -10
  41. data/lib/tpm/constants.rb +0 -44
  42. data/lib/tpm/s_attest.rb +0 -26
  43. data/lib/tpm/s_attest/s_certify_info.rb +0 -14
  44. data/lib/tpm/sized_buffer.rb +0 -13
  45. data/lib/tpm/t_public.rb +0 -32
  46. data/lib/tpm/t_public/s_ecc_parms.rb +0 -17
  47. data/lib/tpm/t_public/s_rsa_parms.rb +0 -17
  48. data/lib/webauthn/attestation_statement/android_key/authorization_list.rb +0 -39
  49. data/lib/webauthn/attestation_statement/android_key/key_description.rb +0 -37
  50. data/lib/webauthn/attestation_statement/tpm/cert_info.rb +0 -44
  51. data/lib/webauthn/attestation_statement/tpm/pub_area.rb +0 -85
  52. data/lib/webauthn/signature_verifier.rb +0 -77
@@ -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
@@ -28,10 +28,11 @@ module WebAuthn
28
28
  end
29
29
 
30
30
  def credential
31
- @credential ||= begin
32
- hash = authenticator_data.send(:credential)
33
- WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(hash[:id], hash[:public_key].serialize)
34
- end
31
+ @credential ||=
32
+ begin
33
+ hash = authenticator_data.send(:credential)
34
+ WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(hash[:id], hash[:public_key].serialize)
35
+ end
35
36
  end
36
37
 
37
38
  def attestation_type
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "2.1.0"
4
+ VERSION = "2.4.1"
5
5
  end
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ if [[ "$LIBSSL" == "1.0" ]]; then
6
+ sudo apt-get install libssl1.0-dev
7
+ fi
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ source "$HOME/.rvm/scripts/rvm"
6
+
7
+ if [[ "$LIBSSL" == "1.0" ]]; then
8
+ rvm use --install $RB --autolibs=read-only --disable-binary
9
+ elif [[ "$LIBSSL" == "1.1" ]]; then
10
+ rvm use --install $RB --binary --fuzzy
11
+ fi
12
+
13
+ [[ "`ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'`" =~ "OpenSSL $LIBSSL" ]] || { echo "Wrong libssl version"; exit 1; }
data/webauthn.gemspec CHANGED
@@ -22,28 +22,32 @@ Gem::Specification.new do |spec|
22
22
  "source_code_uri" => "https://github.com/cedarcode/webauthn-ruby"
23
23
  }
24
24
 
25
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
- f.match(%r{^(test|spec|features|assets)/})
27
- end
25
+ spec.files =
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features|assets)/})
28
+ end
29
+
28
30
  spec.bindir = "exe"
29
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
32
  spec.require_paths = ["lib"]
31
33
 
32
- spec.required_ruby_version = ">= 2.3"
34
+ spec.required_ruby_version = ">= 2.4"
33
35
 
36
+ spec.add_dependency "android_key_attestation", "~> 0.3.0"
34
37
  spec.add_dependency "awrence", "~> 1.1"
35
38
  spec.add_dependency "bindata", "~> 2.4"
36
39
  spec.add_dependency "cbor", "~> 0.5.9"
37
- spec.add_dependency "cose", "~> 0.10.0"
38
- spec.add_dependency "jwt", [">= 1.5", "< 3.0"]
40
+ spec.add_dependency "cose", "~> 1.1"
39
41
  spec.add_dependency "openssl", "~> 2.0"
42
+ spec.add_dependency "safety_net_attestation", "~> 0.4.0"
40
43
  spec.add_dependency "securecompare", "~> 1.0"
44
+ spec.add_dependency "tpm-key_attestation", "~> 0.10.0"
41
45
 
42
- spec.add_development_dependency "appraisal", "~> 2.2.0"
46
+ spec.add_development_dependency "appraisal", "~> 2.3.0"
43
47
  spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
44
48
  spec.add_development_dependency "byebug", "~> 11.0"
45
49
  spec.add_development_dependency "rake", "~> 13.0"
46
50
  spec.add_development_dependency "rspec", "~> 3.8"
47
- spec.add_development_dependency "rubocop", "0.75.0"
48
- spec.add_development_dependency "timecop", "~> 0.9.1"
51
+ spec.add_development_dependency "rubocop", "0.89"
52
+ spec.add_development_dependency "rubocop-rspec", "~> 1.38.1"
49
53
  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.1.0
4
+ version: 2.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gonzalo Rodriguez
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-12-30 00:00:00.000000000 Z
12
+ date: 2021-02-15 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: android_key_attestation
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.3.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.3.0
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: awrence
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -59,48 +73,42 @@ dependencies:
59
73
  requirements:
60
74
  - - "~>"
61
75
  - !ruby/object:Gem::Version
62
- version: 0.10.0
76
+ version: '1.1'
63
77
  type: :runtime
64
78
  prerelease: false
65
79
  version_requirements: !ruby/object:Gem::Requirement
66
80
  requirements:
67
81
  - - "~>"
68
82
  - !ruby/object:Gem::Version
69
- version: 0.10.0
83
+ version: '1.1'
70
84
  - !ruby/object:Gem::Dependency
71
- name: jwt
85
+ name: openssl
72
86
  requirement: !ruby/object:Gem::Requirement
73
87
  requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '1.5'
77
- - - "<"
88
+ - - "~>"
78
89
  - !ruby/object:Gem::Version
79
- version: '3.0'
90
+ version: '2.0'
80
91
  type: :runtime
81
92
  prerelease: false
82
93
  version_requirements: !ruby/object:Gem::Requirement
83
94
  requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: '1.5'
87
- - - "<"
95
+ - - "~>"
88
96
  - !ruby/object:Gem::Version
89
- version: '3.0'
97
+ version: '2.0'
90
98
  - !ruby/object:Gem::Dependency
91
- name: openssl
99
+ name: safety_net_attestation
92
100
  requirement: !ruby/object:Gem::Requirement
93
101
  requirements:
94
102
  - - "~>"
95
103
  - !ruby/object:Gem::Version
96
- version: '2.0'
104
+ version: 0.4.0
97
105
  type: :runtime
98
106
  prerelease: false
99
107
  version_requirements: !ruby/object:Gem::Requirement
100
108
  requirements:
101
109
  - - "~>"
102
110
  - !ruby/object:Gem::Version
103
- version: '2.0'
111
+ version: 0.4.0
104
112
  - !ruby/object:Gem::Dependency
105
113
  name: securecompare
106
114
  requirement: !ruby/object:Gem::Requirement
@@ -115,20 +123,34 @@ dependencies:
115
123
  - - "~>"
116
124
  - !ruby/object:Gem::Version
117
125
  version: '1.0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: tpm-key_attestation
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: 0.10.0
133
+ type: :runtime
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: 0.10.0
118
140
  - !ruby/object:Gem::Dependency
119
141
  name: appraisal
120
142
  requirement: !ruby/object:Gem::Requirement
121
143
  requirements:
122
144
  - - "~>"
123
145
  - !ruby/object:Gem::Version
124
- version: 2.2.0
146
+ version: 2.3.0
125
147
  type: :development
126
148
  prerelease: false
127
149
  version_requirements: !ruby/object:Gem::Requirement
128
150
  requirements:
129
151
  - - "~>"
130
152
  - !ruby/object:Gem::Version
131
- version: 2.2.0
153
+ version: 2.3.0
132
154
  - !ruby/object:Gem::Dependency
133
155
  name: bundler
134
156
  requirement: !ruby/object:Gem::Requirement
@@ -197,28 +219,28 @@ dependencies:
197
219
  requirements:
198
220
  - - '='
199
221
  - !ruby/object:Gem::Version
200
- version: 0.75.0
222
+ version: '0.89'
201
223
  type: :development
202
224
  prerelease: false
203
225
  version_requirements: !ruby/object:Gem::Requirement
204
226
  requirements:
205
227
  - - '='
206
228
  - !ruby/object:Gem::Version
207
- version: 0.75.0
229
+ version: '0.89'
208
230
  - !ruby/object:Gem::Dependency
209
- name: timecop
231
+ name: rubocop-rspec
210
232
  requirement: !ruby/object:Gem::Requirement
211
233
  requirements:
212
234
  - - "~>"
213
235
  - !ruby/object:Gem::Version
214
- version: 0.9.1
236
+ version: 1.38.1
215
237
  type: :development
216
238
  prerelease: false
217
239
  version_requirements: !ruby/object:Gem::Requirement
218
240
  requirements:
219
241
  - - "~>"
220
242
  - !ruby/object:Gem::Version
221
- version: 0.9.1
243
+ version: 1.38.1
222
244
  description: |-
223
245
  WebAuthn ruby server library ― Make your application a W3C Web Authentication conformant
224
246
  Relying Party and allow your users to authenticate with U2F and FIDO2 authenticators.
@@ -247,21 +269,13 @@ files:
247
269
  - gemfiles/cose_head.gemfile
248
270
  - gemfiles/openssl_2_0.gemfile
249
271
  - gemfiles/openssl_2_1.gemfile
272
+ - gemfiles/openssl_2_2.gemfile
250
273
  - gemfiles/openssl_head.gemfile
251
- - lib/android_safetynet/attestation_response.rb
252
- - lib/cose/rsassa_algorithm.rb
253
- - lib/tpm/constants.rb
254
- - lib/tpm/s_attest.rb
255
- - lib/tpm/s_attest/s_certify_info.rb
256
- - lib/tpm/sized_buffer.rb
257
- - lib/tpm/t_public.rb
258
- - lib/tpm/t_public/s_ecc_parms.rb
259
- - lib/tpm/t_public/s_rsa_parms.rb
274
+ - lib/cose/rsapkcs1_algorithm.rb
260
275
  - lib/webauthn.rb
276
+ - lib/webauthn/attestation_object.rb
261
277
  - lib/webauthn/attestation_statement.rb
262
278
  - lib/webauthn/attestation_statement/android_key.rb
263
- - lib/webauthn/attestation_statement/android_key/authorization_list.rb
264
- - lib/webauthn/attestation_statement/android_key/key_description.rb
265
279
  - lib/webauthn/attestation_statement/android_safetynet.rb
266
280
  - lib/webauthn/attestation_statement/base.rb
267
281
  - lib/webauthn/attestation_statement/fido_u2f.rb
@@ -269,8 +283,6 @@ files:
269
283
  - lib/webauthn/attestation_statement/none.rb
270
284
  - lib/webauthn/attestation_statement/packed.rb
271
285
  - lib/webauthn/attestation_statement/tpm.rb
272
- - lib/webauthn/attestation_statement/tpm/cert_info.rb
273
- - lib/webauthn/attestation_statement/tpm/pub_area.rb
274
286
  - lib/webauthn/authenticator_assertion_response.rb
275
287
  - lib/webauthn/authenticator_attestation_response.rb
276
288
  - lib/webauthn/authenticator_data.rb
@@ -302,9 +314,10 @@ files:
302
314
  - lib/webauthn/public_key_credential_with_assertion.rb
303
315
  - lib/webauthn/public_key_credential_with_attestation.rb
304
316
  - lib/webauthn/security_utils.rb
305
- - lib/webauthn/signature_verifier.rb
306
317
  - lib/webauthn/u2f_migrator.rb
307
318
  - lib/webauthn/version.rb
319
+ - script/ci/install-openssl
320
+ - script/ci/install-ruby
308
321
  - webauthn.gemspec
309
322
  homepage: https://github.com/cedarcode/webauthn-ruby
310
323
  licenses:
@@ -321,14 +334,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
321
334
  requirements:
322
335
  - - ">="
323
336
  - !ruby/object:Gem::Version
324
- version: '2.3'
337
+ version: '2.4'
325
338
  required_rubygems_version: !ruby/object:Gem::Requirement
326
339
  requirements:
327
340
  - - ">="
328
341
  - !ruby/object:Gem::Version
329
342
  version: '0'
330
343
  requirements: []
331
- rubygems_version: 3.1.2
344
+ rubygems_version: 3.2.8
332
345
  signing_key:
333
346
  specification_version: 4
334
347
  summary: WebAuthn ruby server library
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "base64"
4
- require "jwt"
5
- require "webauthn/security_utils"
6
-
7
- module AndroidSafetynet
8
- # Decoupled from WebAuthn, candidate for extraction
9
- # Reference: https://developer.android.com/training/safetynet/attestation.html
10
- class AttestationResponse
11
- class VerificationError < StandardError; end
12
- class LeafCertificateSubjectError < VerificationError; end
13
- class NonceMismatchError < VerificationError; end
14
- class SignatureError < VerificationError; end
15
- class ResponseMissingError < VerificationError; end
16
- class TimestampError < VerificationError; end
17
- class TrustworthinessError < VerificationError; end
18
-
19
- CERTIRICATE_CHAIN_HEADER = "x5c"
20
- VALID_SUBJECT_HOSTNAME = "attest.android.com"
21
- HEADERS_POSITION = 1
22
- PAYLOAD_POSITION = 0
23
- LEEWAY = 60
24
-
25
- # FIXME: Should probably be limited to only roots published by Google
26
- # See https://github.com/cedarcode/webauthn-ruby/issues/160#issuecomment-487941201
27
- @trust_store = OpenSSL::X509::Store.new.tap { |trust_store| trust_store.set_default_paths }
28
-
29
- class << self
30
- attr_accessor :trust_store
31
- end
32
-
33
- attr_reader :response
34
-
35
- def initialize(response)
36
- @response = response
37
- end
38
-
39
- def verify(nonce, trustworthiness: true)
40
- if response
41
- valid_nonce?(nonce) || raise(NonceMismatchError)
42
- valid_attestation_domain? || raise(LeafCertificateSubjectError)
43
- valid_signature? || raise(SignatureError)
44
- valid_timestamp? || raise(TimestampError)
45
- trustworthy? || raise(TrustworthinessError) if trustworthiness
46
-
47
- true
48
- else
49
- raise(ResponseMissingError)
50
- end
51
- end
52
-
53
- def cts_profile_match?
54
- payload["ctsProfileMatch"]
55
- end
56
-
57
- def leaf_certificate
58
- certificate_chain[0]
59
- end
60
-
61
- def certificate_chain
62
- @certificate_chain ||= headers[CERTIRICATE_CHAIN_HEADER].map do |cert|
63
- OpenSSL::X509::Certificate.new(Base64.strict_decode64(cert))
64
- end
65
- end
66
-
67
- def valid_timestamp?
68
- now = Time.now
69
- Time.at((payload["timestampMs"] / 1000.0).round).between?(now - LEEWAY, now)
70
- end
71
-
72
- private
73
-
74
- def valid_nonce?(nonce)
75
- WebAuthn::SecurityUtils.secure_compare(payload["nonce"], nonce)
76
- end
77
-
78
- def valid_attestation_domain?
79
- common_name = leaf_certificate&.subject&.to_a&.assoc('CN')
80
-
81
- if common_name
82
- common_name[1] == VALID_SUBJECT_HOSTNAME
83
- end
84
- end
85
-
86
- def valid_signature?
87
- JWT.decode(response, leaf_certificate.public_key, true, algorithms: ["ES256", "RS256"])
88
- rescue JWT::VerificationError
89
- false
90
- end
91
-
92
- def trustworthy?
93
- !trust_store || trust_store.verify(leaf_certificate, signing_certificates)
94
- end
95
-
96
- def trust_store
97
- self.class.trust_store
98
- end
99
-
100
- def signing_certificates
101
- certificate_chain[1..-1]
102
- end
103
-
104
- def headers
105
- jws_parts[HEADERS_POSITION]
106
- end
107
-
108
- def payload
109
- jws_parts[PAYLOAD_POSITION]
110
- end
111
-
112
- def jws_parts
113
- @jws_parts ||= JWT.decode(response, nil, false)
114
- end
115
- end
116
- end