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
@@ -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