webauthn 2.1.0 → 2.4.1

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