webauthn 2.5.2 → 3.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -56
  3. data/.travis.yml +39 -0
  4. data/Appraisals +21 -0
  5. data/CHANGELOG.md +0 -51
  6. data/README.md +2 -5
  7. data/SECURITY.md +3 -6
  8. data/docs/u2f_migration.md +2 -3
  9. data/gemfiles/cose_head.gemfile +7 -0
  10. data/gemfiles/openssl_2_0.gemfile +7 -0
  11. data/gemfiles/openssl_2_1.gemfile +7 -0
  12. data/gemfiles/openssl_2_2.gemfile +7 -0
  13. data/gemfiles/openssl_head.gemfile +7 -0
  14. data/lib/cose/rsapkcs1_algorithm.rb +0 -7
  15. data/lib/webauthn/attestation_object.rb +9 -5
  16. data/lib/webauthn/attestation_statement/android_key.rb +4 -0
  17. data/lib/webauthn/attestation_statement/android_safetynet.rb +5 -1
  18. data/lib/webauthn/attestation_statement/base.rb +29 -21
  19. data/lib/webauthn/attestation_statement/none.rb +1 -7
  20. data/lib/webauthn/attestation_statement/packed.rb +1 -1
  21. data/lib/webauthn/attestation_statement/tpm.rb +2 -2
  22. data/lib/webauthn/attestation_statement.rb +3 -6
  23. data/lib/webauthn/authenticator_assertion_response.rb +4 -3
  24. data/lib/webauthn/authenticator_attestation_response.rb +10 -7
  25. data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -4
  26. data/lib/webauthn/authenticator_response.rb +8 -6
  27. data/lib/webauthn/configuration.rb +36 -38
  28. data/lib/webauthn/credential.rb +5 -4
  29. data/lib/webauthn/credential_creation_options.rb +0 -2
  30. data/lib/webauthn/credential_request_options.rb +0 -2
  31. data/lib/webauthn/fake_authenticator/authenticator_data.rb +1 -1
  32. data/lib/webauthn/fake_authenticator.rb +3 -11
  33. data/lib/webauthn/fake_client.rb +5 -12
  34. data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
  35. data/lib/webauthn/public_key_credential/entity.rb +4 -3
  36. data/lib/webauthn/public_key_credential/options.rb +6 -9
  37. data/lib/webauthn/public_key_credential/request_options.rb +1 -1
  38. data/lib/webauthn/public_key_credential.rb +15 -8
  39. data/lib/webauthn/relying_party.rb +117 -0
  40. data/lib/webauthn/security_utils.rb +20 -0
  41. data/lib/webauthn/version.rb +1 -1
  42. data/script/ci/install-openssl +7 -0
  43. data/script/ci/install-ruby +13 -0
  44. data/webauthn.gemspec +7 -6
  45. metadata +61 -46
  46. data/.github/workflows/build.yml +0 -32
  47. data/.github/workflows/git.yml +0 -21
  48. data/docs/advanced_configuration.md +0 -174
  49. data/lib/webauthn/attestation_statement/apple.rb +0 -65
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f07d44a7778995c6bd2531b26f61e8974ba6ccc8dcf5ff1f21886f353423d598
4
- data.tar.gz: ecb287679447b3074065f2b06cb0f38b20c9a94114ed2b592d93e99874dfda1a
3
+ metadata.gz: 1f418eb52a085d8e7c03bd1c3d11b88ed8fe467f4ec01d3178836689d470f436
4
+ data.tar.gz: 4ab67e8804cbd7d785e29b94760af6db9d6b1e52de1a58bafc74aed19b5b7e21
5
5
  SHA512:
6
- metadata.gz: 98811fdbdb15cd80defb95a75de555a3775864bb1c1f1ac94af4a605af2fa3acf7908d91a120b5c3e214af890469d5f2194e5e037a9f8a40b03935159be6a1f1
7
- data.tar.gz: 8d90a9d37fb83a0ecf5e0c29e8f5154a0ee2170da994238b55fcc4042cb84f6b7fb4e09386e704a2d83f363ef0864fb1fa60c913eea3ae2754e5189435302570
6
+ metadata.gz: ccdcd22e494079eb67c122c03e3061166f91de50dd0c2bf8662748c976483a9d472fa9f8d6b67db9abf484e8eac182195b5f1e1cca8aaaf88146d831919f1c93
7
+ data.tar.gz: b2506b530c796ee57e5d037e7dd3692b1e359408fd9757141b6c4f042c43d57344baa09dc63d17ace549927eeea50abaa8c0771a0ab6da0ece5d880362d890db
data/.rubocop.yml CHANGED
@@ -1,6 +1,5 @@
1
1
  require:
2
2
  - rubocop-rspec
3
- - rubocop-rake
4
3
 
5
4
  inherit_mode:
6
5
  merge:
@@ -9,7 +8,6 @@ inherit_mode:
9
8
  AllCops:
10
9
  TargetRubyVersion: 2.4
11
10
  DisabledByDefault: true
12
- NewCops: disable
13
11
  Exclude:
14
12
  - "gemfiles/**/*"
15
13
  - "vendor/**/*"
@@ -26,12 +24,6 @@ Layout:
26
24
  Layout/ClassStructure:
27
25
  Enabled: true
28
26
 
29
- Layout/EmptyLineBetweenDefs:
30
- AllowAdjacentOneLineDefs: true
31
-
32
- Layout/EmptyLinesAroundAttributeAccessor:
33
- Enabled: true
34
-
35
27
  Layout/FirstMethodArgumentLineBreak:
36
28
  Enabled: true
37
29
 
@@ -46,60 +38,12 @@ Layout/MultilineAssignmentLayout:
46
38
  Layout/MultilineMethodArgumentLineBreaks:
47
39
  Enabled: true
48
40
 
49
- Layout/SpaceAroundMethodCallOperator:
50
- Enabled: true
51
-
52
41
  Lint:
53
42
  Enabled: true
54
43
 
55
- Lint/DeprecatedOpenSSLConstant:
56
- Enabled: true
57
-
58
- Lint/MixedRegexpCaptureTypes:
59
- Enabled: true
60
-
61
- Lint/RaiseException:
62
- Enabled: true
63
-
64
- Lint/StructNewOverride:
65
- Enabled: true
66
-
67
- Lint/BinaryOperatorWithIdenticalOperands:
68
- Enabled: true
69
-
70
- Lint/DuplicateElsifCondition:
71
- Enabled: true
72
-
73
- Lint/DuplicateRescueException:
74
- Enabled: true
75
-
76
- Lint/EmptyConditionalBody:
77
- Enabled: true
78
-
79
- Lint/FloatComparison:
80
- Enabled: true
81
-
82
- Lint/MissingSuper:
83
- Enabled: true
84
-
85
- Lint/OutOfRangeRegexpRef:
86
- Enabled: true
87
-
88
- Lint/SelfAssignment:
89
- Enabled: true
90
-
91
- Lint/TopLevelReturnWithArgument:
92
- Enabled: true
93
-
94
- Lint/UnreachableLoop:
95
- Enabled: true
96
-
97
44
  Naming:
98
45
  Enabled: true
99
46
 
100
- Naming/VariableNumber:
101
- Enabled: false
102
-
103
47
  RSpec/Be:
104
48
  Enabled: true
105
49
 
data/.travis.yml ADDED
@@ -0,0 +1,39 @@
1
+ dist: bionic
2
+ language: ruby
3
+
4
+ cache:
5
+ bundler: true
6
+ directories:
7
+ - /home/travis/.rvm/
8
+
9
+ env:
10
+ - LIBSSL=1.1 RB=2.7.1
11
+ - LIBSSL=1.1 RB=2.6.6
12
+ - LIBSSL=1.1 RB=2.5.8
13
+ - LIBSSL=1.1 RB=2.4.10
14
+ - LIBSSL=1.1 RB=ruby-head
15
+ - LIBSSL=1.0 RB=2.7.1
16
+ - LIBSSL=1.0 RB=2.6.6
17
+ - LIBSSL=1.0 RB=2.5.8
18
+ - LIBSSL=1.0 RB=2.4.10
19
+ - LIBSSL=1.0 RB=ruby-head
20
+
21
+ gemfile:
22
+ - gemfiles/cose_head.gemfile
23
+ - gemfiles/openssl_head.gemfile
24
+ - gemfiles/openssl_2_2.gemfile
25
+ - gemfiles/openssl_2_1.gemfile
26
+ - gemfiles/openssl_2_0.gemfile
27
+
28
+ matrix:
29
+ fast_finish: true
30
+ allow_failures:
31
+ - env: LIBSSL=1.1 RB=ruby-head
32
+ - env: LIBSSL=1.0 RB=ruby-head
33
+ - gemfile: gemfiles/cose_head.gemfile
34
+ - gemfile: gemfiles/openssl_head.gemfile
35
+
36
+ before_install:
37
+ - ./script/ci/install-openssl
38
+ - ./script/ci/install-ruby
39
+ - gem install bundler -v "~> 2.0"
data/Appraisals ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
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
+
11
+ appraise "openssl_2_2" do
12
+ gem "openssl", "~> 2.2.0"
13
+ end
14
+
15
+ appraise "openssl_2_1" do
16
+ gem "openssl", "~> 2.1.0"
17
+ end
18
+
19
+ appraise "openssl_2_0" do
20
+ gem "openssl", "~> 2.0.0"
21
+ end
data/CHANGELOG.md CHANGED
@@ -6,46 +6,6 @@
6
6
 
7
7
  - Ability to define multiple relying parties with the introduction of the `WebAuthn::RelyingParty` class ([@padulafacundo], [@brauliomartinezlm])
8
8
 
9
- ## [v2.5.1] - 2022-07-13
10
-
11
- ### Added
12
-
13
- - Updated dependencies to make the gem compatible with openssl-3 [@ClearlyClaire]
14
-
15
- ## [v2.5.1] - 2022-03-20
16
-
17
- ### Added
18
-
19
- - Updated openssl support to be ~>2.2 [@bdewater]
20
-
21
- ### Removed
22
-
23
- - Removed dependency [secure_compare dependency] (https://rubygems.org/gems/secure_compare/versions/0.0.1) and use OpenSSL#secure_compare instead [@bdewater]
24
-
25
- ## [v2.5.0] - 2021-03-14
26
-
27
- ### Added
28
-
29
- - Support 'apple' attestation statement format ([#343](https://github.com/cedarcode/webauthn-ruby/pull/343) / [@juanarias93], [@santiagorodriguez96])
30
- - Allow specifying an array of ids as `allow_credentials:` for `FakeClient#get` method ([#335](https://github.com/cedarcode/webauthn-ruby/pull/335) / [@kingjan1999])
31
-
32
- ### Removed
33
-
34
- - No longer accept "removed from the WebAuthn spec" options `rp: { icon: }` and `user: { icon: }` for `WebAuthn::Credential.options_for_create` method ([#326](https://github.com/cedarcode/webauthn-ruby/pull/326) / [@santiagorodriguez96])
35
-
36
- ## [v2.4.1] - 2021-02-15
37
-
38
- ### Fixed
39
-
40
- - Fix verification of new credential if no attestation provided and 'None' type is not among configured `acceptable_attestation_types`. I.e. reject it instead of letting it go through.
41
-
42
- ## [v2.4.0] - 2020-09-03
43
-
44
- ### Added
45
-
46
- - Support for ES256K credentials
47
- - `FakeClient#get` accepts `user_handle:` keyword argument ([@lgarron])
48
-
49
9
  ## [v2.3.0] - 2020-06-27
50
10
 
51
11
  ### Added
@@ -341,11 +301,6 @@ Note: Both additions should help making it compatible with Chrome for Android 70
341
301
  - Works with ruby 2.5
342
302
 
343
303
  [v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha1/
344
- [v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.2/
345
- [v2.5.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.1/
346
- [v2.5.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.1...v2.5.0/
347
- [v2.4.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.0...v2.4.1/
348
- [v2.4.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v2.4.0/
349
304
  [v2.3.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.1...v2.3.0/
350
305
  [v2.2.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.0...v2.2.1/
351
306
  [v2.2.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.1.0...v2.2.0/
@@ -382,9 +337,3 @@ Note: Both additions should help making it compatible with Chrome for Android 70
382
337
  [@ssuttner]: https://github.com/ssuttner
383
338
  [@padulafacundo]: https://github.com/padulafacundo
384
339
  [@santiagorodriguez96]: https://github.com/santiagorodriguez96
385
- [@lgarron]: https://github.com/lgarron
386
- [@juanarias93]: https://github.com/juanarias93
387
- [@kingjan1999]: https://github.com/@kingjan1999
388
- [@jdongelmans]: https://github.com/jdongelmans
389
- [@petergoldstein]: https://github.com/petergoldstein
390
- [@ClearlyClaire]: https://github.com/ClearlyClaire
data/README.md CHANGED
@@ -6,7 +6,7 @@ For the current release version see https://github.com/cedarcode/webauthn-ruby/b
6
6
  ![banner](assets/webauthn-ruby.png)
7
7
 
8
8
  [![Gem](https://img.shields.io/gem/v/webauthn.svg?style=flat-square)](https://rubygems.org/gems/webauthn)
9
- [![Travis](https://img.shields.io/travis/cedarcode/webauthn-ruby/master.svg?style=flat-square)](https://travis-ci.com/cedarcode/webauthn-ruby)
9
+ [![Travis](https://img.shields.io/travis/cedarcode/webauthn-ruby/master.svg?style=flat-square)](https://travis-ci.org/cedarcode/webauthn-ruby)
10
10
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-informational.svg?style=flat-square)](https://conventionalcommits.org)
11
11
  [![Join the chat at https://gitter.im/cedarcode/webauthn-ruby](https://badges.gitter.im/cedarcode/webauthn-ruby.svg)](https://gitter.im/cedarcode/webauthn-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
12
12
 
@@ -96,8 +96,6 @@ If you are migrating an existing application from the legacy FIDO U2F JavaScript
96
96
 
97
97
  ### Configuration
98
98
 
99
- If you have a multi-tenant application or just need to configure WebAuthn differently for separate parts of your application (e.g. if your users authenticate to different subdomains in the same application), we strongly recommend you look at this [Advanced Configuration](docs/advanced_configuration.md) section instead of this.
100
-
101
99
  For a Rails application this would go in `config/initializers/webauthn.rb`.
102
100
 
103
101
  ```ruby
@@ -410,7 +408,7 @@ credential.authenticator_extension_outputs
410
408
 
411
409
  ## Attestation
412
410
 
413
- ### Attestation Statement Formats
411
+ ### Attestation Statement Format
414
412
 
415
413
  | Attestation Statement Format | Supported? |
416
414
  | -------- | :--------: |
@@ -419,7 +417,6 @@ credential.authenticator_extension_outputs
419
417
  | tpm (x5c attestation) | Yes |
420
418
  | android-key | Yes |
421
419
  | android-safetynet | Yes |
422
- | apple | Yes |
423
420
  | fido-u2f | Yes |
424
421
  | none | Yes |
425
422
 
data/SECURITY.md CHANGED
@@ -4,12 +4,9 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------- | ------------------ |
7
- | 2.5.z | :white_check_mark: |
8
- | 2.4.z | :white_check_mark: |
9
- | 2.3.z | :white_check_mark: |
10
- | 2.2.z | :x: |
11
- | 2.1.z | :x: |
12
- | 2.0.z | :x: |
7
+ | 2.2.z | :white_check_mark: |
8
+ | 2.1.z | :white_check_mark: |
9
+ | 2.0.z | :white_check_mark: |
13
10
  | 1.18.z | :white_check_mark: |
14
11
  | < 1.18 | :x: |
15
12
 
@@ -82,7 +82,7 @@ During authentication verification phase, you must pass either the original AppI
82
82
 
83
83
  ```ruby
84
84
  assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
85
- user_handle: params[:response][:userHandle],
85
+ credential_id: params[:id],
86
86
  authenticator_data: params[:response][:authenticatorData],
87
87
  client_data_json: params[:response][:clientDataJSON],
88
88
  signature: params[:response][:signature],
@@ -90,8 +90,7 @@ assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
90
90
 
91
91
  assertion_response.verify(
92
92
  expected_challenge,
93
- public_key: credential.public_key,
94
- sign_count: credential.count,
93
+ allowed_credentials: [credential],
95
94
  rp_id: params[:clientExtensionResults][:appid] ? domain.to_s : domain.host,
96
95
  )
97
96
  ```
@@ -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", "~> 2.0.0"
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", "~> 2.1.0"
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", "~> 2.2.0"
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: "../"
@@ -40,11 +40,4 @@ end
40
40
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-257, "RS256", hash_function: "SHA256"))
41
41
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-258, "RS384", hash_function: "SHA384"))
42
42
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-259, "RS512", hash_function: "SHA512"))
43
-
44
- # Patch openssl-signature_algorithm gem to support discouraged/deprecated RSA-PKCS#1 with SHA-1
45
- # (RS1 in JOSE/COSE terminology) algorithm needed for WebAuthn.
46
- OpenSSL::SignatureAlgorithm::RSAPKCS1.const_set(
47
- :ACCEPTED_HASH_FUNCTIONS,
48
- OpenSSL::SignatureAlgorithm::RSAPKCS1::ACCEPTED_HASH_FUNCTIONS + ["SHA1"]
49
- )
50
43
  COSE::Algorithm.register(RSAPKCS1Algorithm.new(-65535, "RS1", hash_function: "SHA1"))
@@ -10,18 +10,22 @@ module WebAuthn
10
10
  class AttestationObject
11
11
  extend Forwardable
12
12
 
13
- def self.deserialize(attestation_object)
14
- from_map(CBOR.decode(attestation_object))
13
+ def self.deserialize(attestation_object, relying_party)
14
+ from_map(CBOR.decode(attestation_object), relying_party)
15
15
  end
16
16
 
17
- def self.from_map(map)
17
+ def self.from_map(map, relying_party)
18
18
  new(
19
19
  authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
20
- attestation_statement: WebAuthn::AttestationStatement.from(map["fmt"], map["attStmt"])
20
+ attestation_statement: WebAuthn::AttestationStatement.from(
21
+ map["fmt"],
22
+ map["attStmt"],
23
+ relying_party: relying_party
24
+ )
21
25
  )
22
26
  end
23
27
 
24
- attr_reader :authenticator_data, :attestation_statement
28
+ attr_reader :authenticator_data, :attestation_statement, :relying_party
25
29
 
26
30
  def initialize(authenticator_data:, attestation_statement:)
27
31
  @authenticator_data = authenticator_data
@@ -20,6 +20,10 @@ module WebAuthn
20
20
 
21
21
  private
22
22
 
23
+ def matching_public_key?(authenticator_data)
24
+ attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
25
+ end
26
+
23
27
  def valid_attestation_challenge?(client_data_hash)
24
28
  android_key_attestation.verify_challenge(client_data_hash)
25
29
  rescue AndroidKeyAttestation::ChallengeMismatchError
@@ -16,6 +16,10 @@ module WebAuthn
16
16
  [attestation_type, attestation_trust_path]
17
17
  end
18
18
 
19
+ def attestation_certificate
20
+ attestation_trust_path.first
21
+ end
22
+
19
23
  private
20
24
 
21
25
  def valid_response?(authenticator_data, client_data_hash)
@@ -48,7 +52,7 @@ module WebAuthn
48
52
  end
49
53
 
50
54
  # SafetyNetAttestation returns full chain including root, WebAuthn expects only the x5c certificates
51
- def certificates
55
+ def attestation_trust_path
52
56
  attestation_response.certificate_chain[0..-2]
53
57
  end
54
58
 
@@ -16,20 +16,19 @@ module WebAuthn
16
16
  ATTESTATION_TYPE_SELF = "Self"
17
17
  ATTESTATION_TYPE_ATTCA = "AttCA"
18
18
  ATTESTATION_TYPE_BASIC_OR_ATTCA = "Basic_or_AttCA"
19
- ATTESTATION_TYPE_ANONCA = "AnonCA"
20
19
 
21
20
  ATTESTATION_TYPES_WITH_ROOT = [
22
21
  ATTESTATION_TYPE_BASIC,
23
22
  ATTESTATION_TYPE_BASIC_OR_ATTCA,
24
- ATTESTATION_TYPE_ATTCA,
25
- ATTESTATION_TYPE_ANONCA
23
+ ATTESTATION_TYPE_ATTCA
26
24
  ].freeze
27
25
 
28
26
  class Base
29
27
  AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
30
28
 
31
- def initialize(statement)
29
+ def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
32
30
  @statement = statement
31
+ @relying_party = relying_party
33
32
  end
34
33
 
35
34
  def valid?(_authenticator_data, _client_data_hash)
@@ -44,28 +43,32 @@ module WebAuthn
44
43
  certificates&.first
45
44
  end
46
45
 
46
+ def certificate_chain
47
+ if certificates
48
+ certificates[1..-1]
49
+ end
50
+ end
51
+
47
52
  def attestation_certificate_key_id
48
- attestation_certificate.subject_key_identifier&.unpack("H*")&.[](0)
53
+ raw_subject_key_identifier&.unpack("H*")&.[](0)
49
54
  end
50
55
 
51
56
  private
52
57
 
53
- attr_reader :statement
58
+ attr_reader :statement, :relying_party
54
59
 
55
60
  def matching_aaguid?(attested_credential_data_aaguid)
56
- extension = attestation_certificate&.find_extension(AAGUID_EXTENSION_OID)
61
+ extension = attestation_certificate&.extensions&.detect { |ext| ext.oid == AAGUID_EXTENSION_OID }
57
62
  if extension
58
- aaguid_value = OpenSSL::ASN1.decode(extension.value_der).value
59
- aaguid_value == attested_credential_data_aaguid
63
+ # `extension.value` mangles data into ASCII, so we must manually compare bytes
64
+ # see https://github.com/ruby/openssl/pull/234
65
+ extension.to_der[-WebAuthn::AuthenticatorData::AttestedCredentialData::AAGUID_LENGTH..-1] ==
66
+ attested_credential_data_aaguid
60
67
  else
61
68
  true
62
69
  end
63
70
  end
64
71
 
65
- def matching_public_key?(authenticator_data)
66
- attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
67
- end
68
-
69
72
  def certificates
70
73
  @certificates ||=
71
74
  raw_certificates&.map do |raw_certificate|
@@ -93,10 +96,10 @@ module WebAuthn
93
96
 
94
97
  def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
95
98
  if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
96
- configuration.acceptable_attestation_types.include?(attestation_type) &&
99
+ relying_party.acceptable_attestation_types.include?(attestation_type) &&
97
100
  valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
98
101
  else
99
- configuration.acceptable_attestation_types.include?(attestation_type)
102
+ relying_party.acceptable_attestation_types.include?(attestation_type)
100
103
  end
101
104
  end
102
105
 
@@ -120,7 +123,7 @@ module WebAuthn
120
123
 
121
124
  def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
122
125
  root_certificates =
123
- configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
126
+ relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
124
127
  if certs.empty?
125
128
  finder.find(
126
129
  attestation_format: format,
@@ -139,6 +142,15 @@ module WebAuthn
139
142
  end
140
143
  end
141
144
 
145
+ def raw_subject_key_identifier
146
+ extension = attestation_certificate.extensions.detect { |ext| ext.oid == "subjectKeyIdentifier" }
147
+ return unless extension
148
+
149
+ ext_asn1 = OpenSSL::ASN1.decode(extension.to_der)
150
+ ext_value = ext_asn1.value.last
151
+ OpenSSL::ASN1.decode(ext_value.value).value
152
+ end
153
+
142
154
  def valid_signature?(authenticator_data, client_data_hash, public_key = attestation_certificate.public_key)
143
155
  raise("Incompatible algorithm and key") unless cose_algorithm.compatible_key?(public_key)
144
156
 
@@ -158,14 +170,10 @@ module WebAuthn
158
170
  def cose_algorithm
159
171
  @cose_algorithm ||=
160
172
  COSE::Algorithm.find(algorithm).tap do |alg|
161
- alg && configuration.algorithms.include?(alg.name) ||
173
+ alg && relying_party.algorithms.include?(alg.name) ||
162
174
  raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
163
175
  end
164
176
  end
165
-
166
- def configuration
167
- WebAuthn.configuration
168
- end
169
177
  end
170
178
  end
171
179
  end
@@ -6,18 +6,12 @@ module WebAuthn
6
6
  module AttestationStatement
7
7
  class None < Base
8
8
  def valid?(*_args)
9
- if statement == {} && trustworthy?
9
+ if statement == {}
10
10
  [WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE, nil]
11
11
  else
12
12
  false
13
13
  end
14
14
  end
15
-
16
- private
17
-
18
- def attestation_type
19
- WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE
20
- end
21
15
  end
22
16
  end
23
17
  end
@@ -46,7 +46,7 @@ module WebAuthn
46
46
 
47
47
  attestation_certificate.version == 2 &&
48
48
  subject.assoc('OU')&.at(1) == "Authenticator Attestation" &&
49
- attestation_certificate.find_extension('basicConstraints')&.value == 'CA:FALSE'
49
+ attestation_certificate.extensions.find { |ext| ext.oid == 'basicConstraints' }&.value == 'CA:FALSE'
50
50
  else
51
51
  true
52
52
  end
@@ -42,7 +42,7 @@ module WebAuthn
42
42
  OpenSSL::Digest.digest(cose_algorithm.hash_function, certified_extra_data),
43
43
  signature_algorithm: tpm_algorithm[:signature],
44
44
  hash_algorithm: tpm_algorithm[:hash],
45
- trusted_certificates: root_certificates(aaguid: aaguid)
45
+ root_certificates: root_certificates(aaguid: aaguid)
46
46
  )
47
47
 
48
48
  key_attestation.valid? && key_attestation.key && key_attestation.key.to_pem == key.to_pem
@@ -54,7 +54,7 @@ module WebAuthn
54
54
  end
55
55
 
56
56
  def default_root_certificates
57
- ::TPM::KeyAttestation::TRUSTED_CERTIFICATES
57
+ ::TPM::KeyAttestation::ROOT_CERTIFICATES
58
58
  end
59
59
 
60
60
  def tpm_algorithm
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "webauthn/attestation_statement/android_key"
4
4
  require "webauthn/attestation_statement/android_safetynet"
5
- require "webauthn/attestation_statement/apple"
6
5
  require "webauthn/attestation_statement/fido_u2f"
7
6
  require "webauthn/attestation_statement/none"
8
7
  require "webauthn/attestation_statement/packed"
@@ -19,7 +18,6 @@ module WebAuthn
19
18
  ATTESTATION_FORMAT_ANDROID_SAFETYNET = "android-safetynet"
20
19
  ATTESTATION_FORMAT_ANDROID_KEY = "android-key"
21
20
  ATTESTATION_FORMAT_TPM = "tpm"
22
- ATTESTATION_FORMAT_APPLE = "apple"
23
21
 
24
22
  FORMAT_TO_CLASS = {
25
23
  ATTESTATION_FORMAT_NONE => WebAuthn::AttestationStatement::None,
@@ -27,15 +25,14 @@ module WebAuthn
27
25
  ATTESTATION_FORMAT_PACKED => WebAuthn::AttestationStatement::Packed,
28
26
  ATTESTATION_FORMAT_ANDROID_SAFETYNET => WebAuthn::AttestationStatement::AndroidSafetynet,
29
27
  ATTESTATION_FORMAT_ANDROID_KEY => WebAuthn::AttestationStatement::AndroidKey,
30
- ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM,
31
- ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
28
+ ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
32
29
  }.freeze
33
30
 
34
- def self.from(format, statement)
31
+ def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
35
32
  klass = FORMAT_TO_CLASS[format]
36
33
 
37
34
  if klass
38
- klass.new(statement)
35
+ klass.new(statement, relying_party)
39
36
  else
40
37
  raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
41
38
  end
@@ -10,8 +10,8 @@ module WebAuthn
10
10
  class SignCountVerificationError < VerificationError; end
11
11
 
12
12
  class AuthenticatorAssertionResponse < AuthenticatorResponse
13
- def self.from_client(response)
14
- encoder = WebAuthn.configuration.encoder
13
+ def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
14
+ encoder = relying_party.encoder
15
15
 
16
16
  user_handle =
17
17
  if response["userHandle"]
@@ -22,7 +22,8 @@ module WebAuthn
22
22
  authenticator_data: encoder.decode(response["authenticatorData"]),
23
23
  client_data_json: encoder.decode(response["clientDataJSON"]),
24
24
  signature: encoder.decode(response["signature"]),
25
- user_handle: user_handle
25
+ user_handle: user_handle,
26
+ relying_party: relying_party
26
27
  )
27
28
  end
28
29