webauthn 2.5.2 → 3.0.0.alpha1

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 (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
@@ -18,12 +18,13 @@ module WebAuthn
18
18
  class AuthenticatorAttestationResponse < AuthenticatorResponse
19
19
  extend Forwardable
20
20
 
21
- def self.from_client(response)
22
- encoder = WebAuthn.configuration.encoder
21
+ def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
22
+ encoder = relying_party.encoder
23
23
 
24
24
  new(
25
25
  attestation_object: encoder.decode(response["attestationObject"]),
26
- client_data_json: encoder.decode(response["clientDataJSON"])
26
+ client_data_json: encoder.decode(response["clientDataJSON"]),
27
+ relying_party: relying_party
27
28
  )
28
29
  end
29
30
 
@@ -33,13 +34,14 @@ module WebAuthn
33
34
  super(**options)
34
35
 
35
36
  @attestation_object_bytes = attestation_object
37
+ @relying_party = relying_party
36
38
  end
37
39
 
38
40
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
39
41
  super
40
42
 
41
43
  verify_item(:attested_credential)
42
- if WebAuthn.configuration.verify_attestation_statement
44
+ if relying_party.verify_attestation_statement
43
45
  verify_item(:attestation_statement)
44
46
  end
45
47
 
@@ -47,7 +49,7 @@ module WebAuthn
47
49
  end
48
50
 
49
51
  def attestation_object
50
- @attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes)
52
+ @attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes, relying_party)
51
53
  end
52
54
 
53
55
  def_delegators(
@@ -63,14 +65,15 @@ module WebAuthn
63
65
 
64
66
  private
65
67
 
66
- attr_reader :attestation_object_bytes
68
+ attr_reader :attestation_object_bytes, :relying_party
67
69
 
68
70
  def type
69
71
  WebAuthn::TYPES[:create]
70
72
  end
71
73
 
72
74
  def valid_attested_credential?
73
- attestation_object.valid_attested_credential?
75
+ attestation_object.valid_attested_credential? &&
76
+ relying_party.algorithms.include?(authenticator_data.credential.algorithm)
74
77
  end
75
78
 
76
79
  def valid_attestation_statement?
@@ -24,7 +24,7 @@ module WebAuthn
24
24
 
25
25
  # TODO: use keyword_init when we dropped Ruby 2.4 support
26
26
  Credential =
27
- Struct.new(:id, :public_key) do
27
+ Struct.new(:id, :public_key, :algorithm) do
28
28
  def public_key_object
29
29
  COSE::Key.deserialize(public_key).to_pkey
30
30
  end
@@ -47,7 +47,7 @@ module WebAuthn
47
47
  def credential
48
48
  @credential ||=
49
49
  if valid?
50
- Credential.new(id, public_key)
50
+ Credential.new(id, public_key, algorithm)
51
51
  end
52
52
  end
53
53
 
@@ -59,10 +59,16 @@ module WebAuthn
59
59
 
60
60
  private
61
61
 
62
+ def algorithm
63
+ COSE::Algorithm.find(cose_key.alg).name
64
+ end
65
+
62
66
  def valid_credential_public_key?
63
- cose_key = COSE::Key.deserialize(public_key)
67
+ !!cose_key.alg
68
+ end
64
69
 
65
- !!cose_key.alg && WebAuthn.configuration.algorithms.include?(COSE::Algorithm.find(cose_key.alg).name)
70
+ def cose_key
71
+ @cose_key ||= COSE::Key.deserialize(public_key)
66
72
  end
67
73
 
68
74
  def public_key
@@ -3,6 +3,7 @@
3
3
  require "webauthn/authenticator_data"
4
4
  require "webauthn/client_data"
5
5
  require "webauthn/error"
6
+ require "webauthn/security_utils"
6
7
 
7
8
  module WebAuthn
8
9
  TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
@@ -19,13 +20,14 @@ module WebAuthn
19
20
  class UserVerifiedVerificationError < VerificationError; end
20
21
 
21
22
  class AuthenticatorResponse
22
- def initialize(client_data_json:)
23
+ def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
23
24
  @client_data_json = client_data_json
25
+ @relying_party = relying_party
24
26
  end
25
27
 
26
28
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
27
- expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
28
- rp_id ||= WebAuthn.configuration.rp_id
29
+ expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
30
+ rp_id ||= relying_party.id
29
31
 
30
32
  verify_item(:type)
31
33
  verify_item(:token_binding)
@@ -34,7 +36,7 @@ module WebAuthn
34
36
  verify_item(:authenticator_data)
35
37
  verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
36
38
 
37
- if !WebAuthn.configuration.silent_authentication
39
+ if !relying_party.silent_authentication
38
40
  verify_item(:user_presence)
39
41
  end
40
42
 
@@ -57,7 +59,7 @@ module WebAuthn
57
59
 
58
60
  private
59
61
 
60
- attr_reader :client_data_json
62
+ attr_reader :client_data_json, :relying_party
61
63
 
62
64
  def verify_item(item, *args)
63
65
  if send("valid_#{item}?", *args)
@@ -78,7 +80,7 @@ module WebAuthn
78
80
  end
79
81
 
80
82
  def valid_challenge?(expected_challenge)
81
- OpenSSL.secure_compare(client_data.challenge, expected_challenge)
83
+ WebAuthn::SecurityUtils.secure_compare(client_data.challenge, expected_challenge)
82
84
  end
83
85
 
84
86
  def valid_origin?(expected_origin)
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openssl"
4
- require "webauthn/encoder"
5
- require "webauthn/error"
3
+ require 'forwardable'
4
+ require 'webauthn/relying_party'
6
5
 
7
6
  module WebAuthn
8
7
  def self.configuration
@@ -13,50 +12,49 @@ module WebAuthn
13
12
  yield(configuration)
14
13
  end
15
14
 
16
- class RootCertificateFinderNotSupportedError < Error; end
17
-
18
15
  class Configuration
19
- DEFAULT_ALGORITHMS = ["ES256", "PS256", "RS256"].compact.freeze
20
-
21
- attr_accessor :algorithms
22
- attr_accessor :encoding
23
- attr_accessor :origin
24
- attr_accessor :rp_id
25
- attr_accessor :rp_name
26
- attr_accessor :verify_attestation_statement
27
- attr_accessor :credential_options_timeout
28
- attr_accessor :silent_authentication
29
- attr_accessor :acceptable_attestation_types
30
- attr_reader :attestation_root_certificates_finders
16
+ extend Forwardable
17
+
18
+ def_delegators :@relying_party,
19
+ :algorithms,
20
+ :algorithms=,
21
+ :encoding,
22
+ :encoding=,
23
+ :origin,
24
+ :origin=,
25
+ :verify_attestation_statement,
26
+ :verify_attestation_statement=,
27
+ :credential_options_timeout,
28
+ :credential_options_timeout=,
29
+ :silent_authentication,
30
+ :silent_authentication=,
31
+ :acceptable_attestation_types,
32
+ :acceptable_attestation_types=,
33
+ :attestation_root_certificates_finders,
34
+ :attestation_root_certificates_finders=,
35
+ :encoder,
36
+ :encoder=
37
+
38
+ attr_reader :relying_party
31
39
 
32
40
  def initialize
33
- @algorithms = DEFAULT_ALGORITHMS.dup
34
- @encoding = WebAuthn::Encoder::STANDARD_ENCODING
35
- @verify_attestation_statement = true
36
- @credential_options_timeout = 120000
37
- @silent_authentication = false
38
- @acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA', 'AnonCA']
39
- @attestation_root_certificates_finders = []
41
+ @relying_party = RelyingParty.new
40
42
  end
41
43
 
42
- # This is the user-data encoder.
43
- # Used to decode user input and to encode data provided to the user.
44
- def encoder
45
- @encoder ||= WebAuthn::Encoder.new(encoding)
44
+ def rp_name
45
+ relying_party.name
46
46
  end
47
47
 
48
- def attestation_root_certificates_finders=(finders)
49
- if !finders.respond_to?(:each)
50
- finders = [finders]
51
- end
48
+ def rp_name=(name)
49
+ relying_party.name = name
50
+ end
52
51
 
53
- finders.each do |finder|
54
- unless finder.respond_to?(:find)
55
- raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
56
- end
57
- end
52
+ def rp_id
53
+ relying_party.id
54
+ end
58
55
 
59
- @attestation_root_certificates_finders = finders
56
+ def rp_id=(id)
57
+ relying_party.id = id
60
58
  end
61
59
  end
62
60
  end
@@ -4,6 +4,7 @@ require "webauthn/public_key_credential/creation_options"
4
4
  require "webauthn/public_key_credential/request_options"
5
5
  require "webauthn/public_key_credential_with_assertion"
6
6
  require "webauthn/public_key_credential_with_attestation"
7
+ require "webauthn/relying_party"
7
8
 
8
9
  module WebAuthn
9
10
  module Credential
@@ -15,12 +16,12 @@ module WebAuthn
15
16
  WebAuthn::PublicKeyCredential::RequestOptions.new(**keyword_arguments)
16
17
  end
17
18
 
18
- def self.from_create(credential)
19
- WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential)
19
+ def self.from_create(credential, relying_party: WebAuthn.configuration.relying_party)
20
+ WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential, relying_party: relying_party)
20
21
  end
21
22
 
22
- def self.from_get(credential)
23
- WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential)
23
+ def self.from_get(credential, relying_party: WebAuthn.configuration.relying_party)
24
+ WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential, relying_party: relying_party)
24
25
  end
25
26
  end
26
27
  end
@@ -32,8 +32,6 @@ module WebAuthn
32
32
  user_display_name: nil,
33
33
  rp_name: nil
34
34
  )
35
- super()
36
-
37
35
  @attestation = attestation
38
36
  @authenticator_selection = authenticator_selection
39
37
  @exclude_credentials = exclude_credentials
@@ -16,8 +16,6 @@ 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
-
21
19
  @allow_credentials = allow_credentials
22
20
  @extensions = extensions
23
21
  @user_verification = user_verification
@@ -15,7 +15,7 @@ module WebAuthn
15
15
  rp_id_hash:,
16
16
  credential: {
17
17
  id: SecureRandom.random_bytes(16),
18
- public_key: OpenSSL::PKey::EC.generate("prime256v1").public_key
18
+ public_key: OpenSSL::PKey::EC.new("prime256v1").generate_key.public_key
19
19
  },
20
20
  sign_count: 0,
21
21
  user_present: true,
@@ -50,20 +50,12 @@ module WebAuthn
50
50
  user_verified: false,
51
51
  aaguid: AuthenticatorData::AAGUID,
52
52
  sign_count: nil,
53
- extensions: nil,
54
- allow_credentials: nil
53
+ extensions: nil
55
54
  )
56
55
  credential_options = credentials[rp_id]
57
56
 
58
57
  if credential_options
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]
58
+ credential_id, credential = credential_options.first
67
59
  credential_key = credential[:credential_key]
68
60
  credential_sign_count = credential[:sign_count]
69
61
 
@@ -95,7 +87,7 @@ module WebAuthn
95
87
  attr_reader :credentials
96
88
 
97
89
  def new_credential
98
- [SecureRandom.random_bytes(16), OpenSSL::PKey::EC.generate("prime256v1"), 0]
90
+ [SecureRandom.random_bytes(16), OpenSSL::PKey::EC.new("prime256v1").generate_key, 0]
99
91
  end
100
92
 
101
93
  def hashed(target)
@@ -10,7 +10,7 @@ module WebAuthn
10
10
  class FakeClient
11
11
  TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
12
12
 
13
- attr_reader :origin, :token_binding
13
+ attr_reader :origin, :token_binding, :encoding
14
14
 
15
15
  def initialize(
16
16
  origin = fake_origin,
@@ -73,26 +73,19 @@ module WebAuthn
73
73
  user_present: true,
74
74
  user_verified: false,
75
75
  sign_count: nil,
76
- extensions: nil,
77
- user_handle: nil,
78
- allow_credentials: nil)
76
+ extensions: nil)
79
77
  rp_id ||= URI.parse(origin).host
80
78
 
81
79
  client_data_json = data_json_for(:get, encoder.decode(challenge))
82
80
  client_data_hash = hashed(client_data_json)
83
81
 
84
- if allow_credentials
85
- allow_credentials = allow_credentials.map { |credential| encoder.decode(credential) }
86
- end
87
-
88
82
  assertion = authenticator.get_assertion(
89
83
  rp_id: rp_id,
90
84
  client_data_hash: client_data_hash,
91
85
  user_present: user_present,
92
86
  user_verified: user_verified,
93
87
  sign_count: sign_count,
94
- extensions: extensions,
95
- allow_credentials: allow_credentials
88
+ extensions: extensions
96
89
  )
97
90
 
98
91
  {
@@ -104,14 +97,14 @@ module WebAuthn
104
97
  "clientDataJSON" => encoder.encode(client_data_json),
105
98
  "authenticatorData" => encoder.encode(assertion[:authenticator_data]),
106
99
  "signature" => encoder.encode(assertion[:signature]),
107
- "userHandle" => user_handle ? encoder.encode(user_handle) : nil
100
+ "userHandle" => nil
108
101
  }
109
102
  }
110
103
  end
111
104
 
112
105
  private
113
106
 
114
- attr_reader :authenticator, :encoding
107
+ attr_reader :authenticator
115
108
 
116
109
  def data_json_for(method, challenge)
117
110
  data = {
@@ -39,8 +39,8 @@ module WebAuthn
39
39
 
40
40
  @rp =
41
41
  if rp.is_a?(Hash)
42
- rp[:name] ||= configuration.rp_name
43
- rp[:id] ||= configuration.rp_id
42
+ rp[:name] ||= relying_party.name
43
+ rp[:id] ||= relying_party.id
44
44
 
45
45
  RPEntity.new(**rp)
46
46
  else
@@ -76,7 +76,7 @@ module WebAuthn
76
76
  end
77
77
 
78
78
  def pub_key_cred_params_from_algs
79
- Array(algs || configuration.algorithms).map do |alg|
79
+ Array(algs || relying_party.algorithms).map do |alg|
80
80
  alg_id =
81
81
  if alg.is_a?(String) || alg.is_a?(Symbol)
82
82
  COSE::Algorithm.by_name(alg.to_s).id
@@ -5,10 +5,11 @@ require "awrence"
5
5
  module WebAuthn
6
6
  class PublicKeyCredential
7
7
  class Entity
8
- attr_reader :name
8
+ attr_reader :name, :icon
9
9
 
10
- def initialize(name:)
10
+ def initialize(name:, icon: nil)
11
11
  @name = name
12
+ @icon = icon
12
13
  end
13
14
 
14
15
  def as_json
@@ -36,7 +37,7 @@ module WebAuthn
36
37
  end
37
38
 
38
39
  def attributes
39
- [:name]
40
+ [:name, :icon]
40
41
  end
41
42
  end
42
43
  end
@@ -8,10 +8,11 @@ module WebAuthn
8
8
  class Options
9
9
  CHALLENGE_LENGTH = 32
10
10
 
11
- attr_reader :timeout, :extensions
11
+ attr_reader :timeout, :extensions, :relying_party
12
12
 
13
- def initialize(timeout: default_timeout, extensions: nil)
14
- @timeout = timeout
13
+ def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
14
+ @relying_party = relying_party
15
+ @timeout = timeout || default_timeout
15
16
  @extensions = extensions
16
17
  end
17
18
 
@@ -49,7 +50,7 @@ module WebAuthn
49
50
  end
50
51
 
51
52
  def encoder
52
- WebAuthn.configuration.encoder
53
+ relying_party.encoder
53
54
  end
54
55
 
55
56
  def raw_challenge
@@ -57,11 +58,7 @@ module WebAuthn
57
58
  end
58
59
 
59
60
  def default_timeout
60
- configuration.credential_options_timeout
61
- end
62
-
63
- def configuration
64
- WebAuthn.configuration
61
+ relying_party.credential_options_timeout
65
62
  end
66
63
 
67
64
  def as_public_key_descriptors(ids)
@@ -10,7 +10,7 @@ module WebAuthn
10
10
  def initialize(rp_id: nil, allow_credentials: nil, allow: nil, user_verification: nil, **keyword_arguments)
11
11
  super(**keyword_arguments)
12
12
 
13
- @rp_id = rp_id || configuration.rp_id
13
+ @rp_id = rp_id || relying_party.id
14
14
  @allow_credentials = allow_credentials
15
15
  @allow = allow
16
16
  @user_verification = user_verification
@@ -6,22 +6,31 @@ module WebAuthn
6
6
  class PublicKeyCredential
7
7
  attr_reader :type, :id, :raw_id, :client_extension_outputs, :response
8
8
 
9
- def self.from_client(credential)
9
+ def self.from_client(credential, relying_party: WebAuthn.configuration.relying_party)
10
10
  new(
11
11
  type: credential["type"],
12
12
  id: credential["id"],
13
- raw_id: WebAuthn.configuration.encoder.decode(credential["rawId"]),
13
+ raw_id: relying_party.encoder.decode(credential["rawId"]),
14
14
  client_extension_outputs: credential["clientExtensionResults"],
15
- response: response_class.from_client(credential["response"])
15
+ response: response_class.from_client(credential["response"], relying_party: relying_party),
16
+ encoder: relying_party.encoder
16
17
  )
17
18
  end
18
19
 
19
- def initialize(type:, id:, raw_id:, client_extension_outputs: {}, response:)
20
+ def initialize(
21
+ type:,
22
+ id:,
23
+ raw_id:,
24
+ response:,
25
+ client_extension_outputs: {},
26
+ encoder: WebAuthn.configuration.encoder
27
+ )
20
28
  @type = type
21
29
  @id = id
22
30
  @raw_id = raw_id
23
31
  @client_extension_outputs = client_extension_outputs
24
32
  @response = response
33
+ @encoder = encoder
25
34
  end
26
35
 
27
36
  def verify(*_args)
@@ -41,6 +50,8 @@ module WebAuthn
41
50
 
42
51
  private
43
52
 
53
+ attr_reader :encoder
54
+
44
55
  def valid_type?
45
56
  type == TYPE_PUBLIC_KEY
46
57
  end
@@ -52,9 +63,5 @@ module WebAuthn
52
63
  def authenticator_data
53
64
  response&.authenticator_data
54
65
  end
55
-
56
- def encoder
57
- WebAuthn.configuration.encoder
58
- end
59
66
  end
60
67
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "webauthn/credential"
5
+ require "webauthn/encoder"
6
+ require "webauthn/error"
7
+
8
+ module WebAuthn
9
+ class RootCertificateFinderNotSupportedError < Error; end
10
+
11
+ class RelyingParty
12
+ def self.if_pss_supported(algorithm)
13
+ OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
14
+ end
15
+
16
+ DEFAULT_ALGORITHMS = ["ES256", if_pss_supported("PS256"), "RS256"].compact.freeze
17
+
18
+ def initialize(
19
+ algorithms: DEFAULT_ALGORITHMS.dup,
20
+ encoding: WebAuthn::Encoder::STANDARD_ENCODING,
21
+ origin: nil,
22
+ id: nil,
23
+ name: nil,
24
+ verify_attestation_statement: true,
25
+ credential_options_timeout: 120000,
26
+ silent_authentication: false,
27
+ acceptable_attestation_types: ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA'],
28
+ attestation_root_certificates_finders: []
29
+ )
30
+ @algorithms = algorithms
31
+ @encoding = encoding
32
+ @origin = origin
33
+ @id = id
34
+ @name = name
35
+ @verify_attestation_statement = verify_attestation_statement
36
+ @credential_options_timeout = credential_options_timeout
37
+ @silent_authentication = silent_authentication
38
+ @acceptable_attestation_types = acceptable_attestation_types
39
+ self.attestation_root_certificates_finders = attestation_root_certificates_finders
40
+ end
41
+
42
+ attr_accessor :algorithms,
43
+ :encoding,
44
+ :origin,
45
+ :id,
46
+ :name,
47
+ :verify_attestation_statement,
48
+ :credential_options_timeout,
49
+ :silent_authentication,
50
+ :acceptable_attestation_types
51
+
52
+ attr_reader :attestation_root_certificates_finders
53
+
54
+ # This is the user-data encoder.
55
+ # Used to decode user input and to encode data provided to the user.
56
+ def encoder
57
+ @encoder ||= WebAuthn::Encoder.new(encoding)
58
+ end
59
+
60
+ def attestation_root_certificates_finders=(finders)
61
+ if !finders.respond_to?(:each)
62
+ finders = [finders]
63
+ end
64
+
65
+ finders.each do |finder|
66
+ unless finder.respond_to?(:find)
67
+ raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
68
+ end
69
+ end
70
+
71
+ @attestation_root_certificates_finders = finders
72
+ end
73
+
74
+ def options_for_registration(**keyword_arguments)
75
+ WebAuthn::Credential.options_for_create(
76
+ **keyword_arguments,
77
+ relying_party: self
78
+ )
79
+ end
80
+
81
+ def verify_registration(raw_credential, challenge, user_verification: nil)
82
+ webauthn_credential = WebAuthn::Credential.from_create(raw_credential, relying_party: self)
83
+
84
+ if webauthn_credential.verify(challenge, user_verification: user_verification)
85
+ webauthn_credential
86
+ end
87
+ end
88
+
89
+ def options_for_authentication(**keyword_arguments)
90
+ WebAuthn::Credential.options_for_get(
91
+ **keyword_arguments,
92
+ relying_party: self
93
+ )
94
+ end
95
+
96
+ def verify_authentication(
97
+ raw_credential,
98
+ challenge,
99
+ user_verification: nil,
100
+ public_key: nil,
101
+ sign_count: nil
102
+ )
103
+ webauthn_credential = WebAuthn::Credential.from_get(raw_credential, relying_party: self)
104
+
105
+ stored_credential = yield(webauthn_credential) if block_given?
106
+
107
+ if webauthn_credential.verify(
108
+ challenge,
109
+ public_key: public_key || stored_credential.public_key,
110
+ sign_count: sign_count || stored_credential.sign_count,
111
+ user_verification: user_verification
112
+ )
113
+ block_given? ? [webauthn_credential, stored_credential] : webauthn_credential
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securecompare"
4
+
5
+ module WebAuthn
6
+ module SecurityUtils
7
+ # Constant time string comparison, for variable length strings.
8
+ # This code was adapted from Rails ActiveSupport::SecurityUtils
9
+ #
10
+ # The values are first processed by SHA256, so that we don't leak length info
11
+ # via timing attacks.
12
+ def secure_compare(first_string, second_string)
13
+ first_string_sha256 = ::Digest::SHA256.digest(first_string)
14
+ second_string_sha256 = ::Digest::SHA256.digest(second_string)
15
+
16
+ SecureCompare.compare(first_string_sha256, second_string_sha256) && first_string == second_string
17
+ end
18
+ module_function :secure_compare
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebAuthn
4
- VERSION = "2.5.2"
4
+ VERSION = "3.0.0.alpha1"
5
5
  end