webauthn 2.5.2 → 3.1.0

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/build.yml +7 -3
  4. data/.github/workflows/git.yml +1 -1
  5. data/.rubocop.yml +1 -1
  6. data/CHANGELOG.md +40 -3
  7. data/README.md +4 -4
  8. data/docs/advanced_configuration.md +10 -10
  9. data/docs/u2f_migration.md +14 -21
  10. data/lib/webauthn/attestation_object.rb +9 -5
  11. data/lib/webauthn/attestation_statement/base.rb +16 -10
  12. data/lib/webauthn/attestation_statement.rb +2 -2
  13. data/lib/webauthn/authenticator_assertion_response.rb +4 -3
  14. data/lib/webauthn/authenticator_attestation_response.rb +10 -7
  15. data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -5
  16. data/lib/webauthn/authenticator_data.rb +10 -2
  17. data/lib/webauthn/authenticator_response.rb +6 -5
  18. data/lib/webauthn/configuration.rb +38 -38
  19. data/lib/webauthn/credential.rb +4 -4
  20. data/lib/webauthn/encoder.rb +13 -4
  21. data/lib/webauthn/fake_authenticator/attestation_object.rb +8 -0
  22. data/lib/webauthn/fake_authenticator/authenticator_data.rb +19 -4
  23. data/lib/webauthn/fake_authenticator.rb +8 -0
  24. data/lib/webauthn/fake_client.rb +12 -2
  25. data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
  26. data/lib/webauthn/public_key_credential/options.rb +9 -8
  27. data/lib/webauthn/public_key_credential/request_options.rb +11 -1
  28. data/lib/webauthn/public_key_credential.rb +41 -7
  29. data/lib/webauthn/public_key_credential_with_assertion.rb +14 -1
  30. data/lib/webauthn/relying_party.rb +120 -0
  31. data/lib/webauthn/u2f_migrator.rb +4 -1
  32. data/lib/webauthn/version.rb +1 -1
  33. data/webauthn.gemspec +4 -3
  34. metadata +22 -12
@@ -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,51 @@ 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
+ :legacy_u2f_appid,
38
+ :legacy_u2f_appid=
39
+
40
+ attr_reader :relying_party
31
41
 
32
42
  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 = []
43
+ @relying_party = RelyingParty.new
40
44
  end
41
45
 
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)
46
+ def rp_name
47
+ relying_party.name
46
48
  end
47
49
 
48
- def attestation_root_certificates_finders=(finders)
49
- if !finders.respond_to?(:each)
50
- finders = [finders]
51
- end
50
+ def rp_name=(name)
51
+ relying_party.name = name
52
+ end
52
53
 
53
- finders.each do |finder|
54
- unless finder.respond_to?(:find)
55
- raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
56
- end
57
- end
54
+ def rp_id
55
+ relying_party.id
56
+ end
58
57
 
59
- @attestation_root_certificates_finders = finders
58
+ def rp_id=(id)
59
+ relying_party.id = id
60
60
  end
61
61
  end
62
62
  end
@@ -15,12 +15,12 @@ module WebAuthn
15
15
  WebAuthn::PublicKeyCredential::RequestOptions.new(**keyword_arguments)
16
16
  end
17
17
 
18
- def self.from_create(credential)
19
- WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential)
18
+ def self.from_create(credential, relying_party: WebAuthn.configuration.relying_party)
19
+ WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential, relying_party: relying_party)
20
20
  end
21
21
 
22
- def self.from_get(credential)
23
- WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential)
22
+ def self.from_get(credential, relying_party: WebAuthn.configuration.relying_party)
23
+ WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential, relying_party: relying_party)
24
24
  end
25
25
  end
26
26
  end
@@ -20,9 +20,12 @@ module WebAuthn
20
20
  def encode(data)
21
21
  case encoding
22
22
  when :base64
23
- Base64.strict_encode64(data)
23
+ [data].pack("m0") # Base64.strict_encode64(data)
24
24
  when :base64url
25
- Base64.urlsafe_encode64(data, padding: false)
25
+ data = [data].pack("m0") # Base64.urlsafe_encode64(data, padding: false)
26
+ data.chomp!("==") or data.chomp!("=")
27
+ data.tr!("+/", "-_")
28
+ data
26
29
  when nil, false
27
30
  data
28
31
  else
@@ -33,9 +36,15 @@ module WebAuthn
33
36
  def decode(data)
34
37
  case encoding
35
38
  when :base64
36
- Base64.strict_decode64(data)
39
+ data.unpack1("m0") # Base64.strict_decode64(data)
37
40
  when :base64url
38
- Base64.urlsafe_decode64(data)
41
+ if !data.end_with?("=") && data.length % 4 != 0 # Base64.urlsafe_decode64(data)
42
+ data = data.ljust((data.length + 3) & ~3, "=")
43
+ data.tr!("-_", "+/")
44
+ else
45
+ data = data.tr("-_", "+/")
46
+ end
47
+ data.unpack1("m0")
39
48
  when nil, false
40
49
  data
41
50
  else
@@ -13,6 +13,8 @@ module WebAuthn
13
13
  credential_key:,
14
14
  user_present: true,
15
15
  user_verified: false,
16
+ backup_eligibility: false,
17
+ backup_state: false,
16
18
  attested_credential_data: true,
17
19
  sign_count: 0,
18
20
  extensions: nil
@@ -23,6 +25,8 @@ module WebAuthn
23
25
  @credential_key = credential_key
24
26
  @user_present = user_present
25
27
  @user_verified = user_verified
28
+ @backup_eligibility = backup_eligibility
29
+ @backup_state = backup_state
26
30
  @attested_credential_data = attested_credential_data
27
31
  @sign_count = sign_count
28
32
  @extensions = extensions
@@ -45,6 +49,8 @@ module WebAuthn
45
49
  :credential_key,
46
50
  :user_present,
47
51
  :user_verified,
52
+ :backup_eligibility,
53
+ :backup_state,
48
54
  :attested_credential_data,
49
55
  :sign_count,
50
56
  :extensions
@@ -63,6 +69,8 @@ module WebAuthn
63
69
  credential: credential_data,
64
70
  user_present: user_present,
65
71
  user_verified: user_verified,
72
+ backup_eligibility: backup_eligibility,
73
+ backup_state: backup_state,
66
74
  sign_count: 0,
67
75
  extensions: extensions
68
76
  )
@@ -20,6 +20,8 @@ module WebAuthn
20
20
  sign_count: 0,
21
21
  user_present: true,
22
22
  user_verified: !user_present,
23
+ backup_eligibility: false,
24
+ backup_state: false,
23
25
  aaguid: AAGUID,
24
26
  extensions: { "fakeExtension" => "fakeExtensionValue" }
25
27
  )
@@ -28,6 +30,8 @@ module WebAuthn
28
30
  @sign_count = sign_count
29
31
  @user_present = user_present
30
32
  @user_verified = user_verified
33
+ @backup_eligibility = backup_eligibility
34
+ @backup_state = backup_state
31
35
  @aaguid = aaguid
32
36
  @extensions = extensions
33
37
  end
@@ -38,7 +42,13 @@ module WebAuthn
38
42
 
39
43
  private
40
44
 
41
- attr_reader :rp_id_hash, :credential, :user_present, :user_verified, :extensions
45
+ attr_reader :rp_id_hash,
46
+ :credential,
47
+ :user_present,
48
+ :user_verified,
49
+ :extensions,
50
+ :backup_eligibility,
51
+ :backup_state
42
52
 
43
53
  def flags
44
54
  [
@@ -46,8 +56,8 @@ module WebAuthn
46
56
  bit(:user_present),
47
57
  reserved_for_future_use_bit,
48
58
  bit(:user_verified),
49
- reserved_for_future_use_bit,
50
- reserved_for_future_use_bit,
59
+ bit(:backup_eligibility),
60
+ bit(:backup_state),
51
61
  reserved_for_future_use_bit,
52
62
  attested_credential_data_included_bit,
53
63
  extension_data_included_bit
@@ -108,7 +118,12 @@ module WebAuthn
108
118
  end
109
119
 
110
120
  def context
111
- { user_present: user_present, user_verified: user_verified }
121
+ {
122
+ user_present: user_present,
123
+ user_verified: user_verified,
124
+ backup_eligibility: backup_eligibility,
125
+ backup_state: backup_state
126
+ }
112
127
  end
113
128
 
114
129
  def cose_credential_public_key
@@ -17,6 +17,8 @@ module WebAuthn
17
17
  client_data_hash:,
18
18
  user_present: true,
19
19
  user_verified: false,
20
+ backup_eligibility: false,
21
+ backup_state: false,
20
22
  attested_credential_data: true,
21
23
  sign_count: nil,
22
24
  extensions: nil
@@ -37,6 +39,8 @@ module WebAuthn
37
39
  credential_key: credential_key,
38
40
  user_present: user_present,
39
41
  user_verified: user_verified,
42
+ backup_eligibility: backup_eligibility,
43
+ backup_state: backup_state,
40
44
  attested_credential_data: attested_credential_data,
41
45
  sign_count: sign_count,
42
46
  extensions: extensions
@@ -48,6 +52,8 @@ module WebAuthn
48
52
  client_data_hash:,
49
53
  user_present: true,
50
54
  user_verified: false,
55
+ backup_eligibility: false,
56
+ backup_state: false,
51
57
  aaguid: AuthenticatorData::AAGUID,
52
58
  sign_count: nil,
53
59
  extensions: nil,
@@ -71,6 +77,8 @@ module WebAuthn
71
77
  rp_id_hash: hashed(rp_id),
72
78
  user_present: user_present,
73
79
  user_verified: user_verified,
80
+ backup_eligibility: backup_eligibility,
81
+ backup_state: backup_state,
74
82
  aaguid: aaguid,
75
83
  credential: nil,
76
84
  sign_count: sign_count || credential_sign_count,
@@ -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,
@@ -29,6 +29,8 @@ module WebAuthn
29
29
  rp_id: nil,
30
30
  user_present: true,
31
31
  user_verified: false,
32
+ backup_eligibility: false,
33
+ backup_state: false,
32
34
  attested_credential_data: true,
33
35
  extensions: nil
34
36
  )
@@ -42,6 +44,8 @@ module WebAuthn
42
44
  client_data_hash: client_data_hash,
43
45
  user_present: user_present,
44
46
  user_verified: user_verified,
47
+ backup_eligibility: backup_eligibility,
48
+ backup_state: backup_state,
45
49
  attested_credential_data: attested_credential_data,
46
50
  extensions: extensions
47
51
  )
@@ -60,6 +64,7 @@ module WebAuthn
60
64
  "type" => "public-key",
61
65
  "id" => internal_encoder.encode(id),
62
66
  "rawId" => encoder.encode(id),
67
+ "authenticatorAttachment" => 'platform',
63
68
  "clientExtensionResults" => extensions,
64
69
  "response" => {
65
70
  "attestationObject" => encoder.encode(attestation_object),
@@ -72,6 +77,8 @@ module WebAuthn
72
77
  rp_id: nil,
73
78
  user_present: true,
74
79
  user_verified: false,
80
+ backup_eligibility: false,
81
+ backup_state: true,
75
82
  sign_count: nil,
76
83
  extensions: nil,
77
84
  user_handle: nil,
@@ -90,6 +97,8 @@ module WebAuthn
90
97
  client_data_hash: client_data_hash,
91
98
  user_present: user_present,
92
99
  user_verified: user_verified,
100
+ backup_eligibility: backup_eligibility,
101
+ backup_state: backup_state,
93
102
  sign_count: sign_count,
94
103
  extensions: extensions,
95
104
  allow_credentials: allow_credentials
@@ -100,6 +109,7 @@ module WebAuthn
100
109
  "id" => internal_encoder.encode(assertion[:credential_id]),
101
110
  "rawId" => encoder.encode(assertion[:credential_id]),
102
111
  "clientExtensionResults" => extensions,
112
+ "authenticatorAttachment" => 'platform',
103
113
  "response" => {
104
114
  "clientDataJSON" => encoder.encode(client_data_json),
105
115
  "authenticatorData" => encoder.encode(assertion[:authenticator_data]),
@@ -111,7 +121,7 @@ module WebAuthn
111
121
 
112
122
  private
113
123
 
114
- attr_reader :authenticator, :encoding
124
+ attr_reader :authenticator
115
125
 
116
126
  def data_json_for(method, challenge)
117
127
  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
@@ -8,11 +8,12 @@ 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
15
- @extensions = extensions
13
+ def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
14
+ @relying_party = relying_party
15
+ @timeout = timeout || default_timeout
16
+ @extensions = default_extensions.merge(extensions || {})
16
17
  end
17
18
 
18
19
  def challenge
@@ -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,11 @@ module WebAuthn
57
58
  end
58
59
 
59
60
  def default_timeout
60
- configuration.credential_options_timeout
61
+ relying_party.credential_options_timeout
61
62
  end
62
63
 
63
- def configuration
64
- WebAuthn.configuration
64
+ def default_extensions
65
+ {}
65
66
  end
66
67
 
67
68
  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
@@ -26,6 +26,16 @@ module WebAuthn
26
26
  super.concat([:allow_credentials, :rp_id, :user_verification])
27
27
  end
28
28
 
29
+ def default_extensions
30
+ extensions = super || {}
31
+
32
+ if relying_party.legacy_u2f_appid
33
+ extensions.merge!(appid: relying_party.legacy_u2f_appid)
34
+ end
35
+
36
+ extensions
37
+ end
38
+
29
39
  def allow_credentials_from_allow
30
40
  if allow
31
41
  as_public_key_descriptors(allow)
@@ -4,27 +4,47 @@ require "webauthn/encoder"
4
4
 
5
5
  module WebAuthn
6
6
  class PublicKeyCredential
7
- attr_reader :type, :id, :raw_id, :client_extension_outputs, :response
7
+ class InvalidChallengeError < Error; end
8
8
 
9
- def self.from_client(credential)
9
+ attr_reader :type, :id, :raw_id, :client_extension_outputs, :authenticator_attachment, :response
10
+
11
+ def self.from_client(credential, relying_party: WebAuthn.configuration.relying_party)
10
12
  new(
11
13
  type: credential["type"],
12
14
  id: credential["id"],
13
- raw_id: WebAuthn.configuration.encoder.decode(credential["rawId"]),
15
+ raw_id: relying_party.encoder.decode(credential["rawId"]),
14
16
  client_extension_outputs: credential["clientExtensionResults"],
15
- response: response_class.from_client(credential["response"])
17
+ authenticator_attachment: credential["authenticatorAttachment"],
18
+ response: response_class.from_client(credential["response"], relying_party: relying_party),
19
+ relying_party: relying_party
16
20
  )
17
21
  end
18
22
 
19
- def initialize(type:, id:, raw_id:, client_extension_outputs: {}, response:)
23
+ def initialize(
24
+ type:,
25
+ id:,
26
+ raw_id:,
27
+ response:,
28
+ authenticator_attachment: nil,
29
+ client_extension_outputs: {},
30
+ relying_party: WebAuthn.configuration.relying_party
31
+ )
20
32
  @type = type
21
33
  @id = id
22
34
  @raw_id = raw_id
23
35
  @client_extension_outputs = client_extension_outputs
36
+ @authenticator_attachment = authenticator_attachment
24
37
  @response = response
38
+ @relying_party = relying_party
25
39
  end
26
40
 
27
- def verify(*_args)
41
+ def verify(challenge, *_args)
42
+ unless valid_class?(challenge)
43
+ msg = "challenge must be a String. input challenge class: #{challenge.class}"
44
+
45
+ raise(InvalidChallengeError, msg)
46
+ end
47
+
28
48
  valid_type? || raise("invalid type")
29
49
  valid_id? || raise("invalid id")
30
50
 
@@ -39,8 +59,18 @@ module WebAuthn
39
59
  authenticator_data.extension_data if authenticator_data&.extension_data_included?
40
60
  end
41
61
 
62
+ def backup_eligible?
63
+ authenticator_data&.credential_backup_eligible?
64
+ end
65
+
66
+ def backed_up?
67
+ authenticator_data&.credential_backed_up?
68
+ end
69
+
42
70
  private
43
71
 
72
+ attr_reader :relying_party
73
+
44
74
  def valid_type?
45
75
  type == TYPE_PUBLIC_KEY
46
76
  end
@@ -49,12 +79,16 @@ module WebAuthn
49
79
  raw_id && id && raw_id == WebAuthn.standard_encoder.decode(id)
50
80
  end
51
81
 
82
+ def valid_class?(challenge)
83
+ challenge.is_a?(String)
84
+ end
85
+
52
86
  def authenticator_data
53
87
  response&.authenticator_data
54
88
  end
55
89
 
56
90
  def encoder
57
- WebAuthn.configuration.encoder
91
+ relying_party.encoder
58
92
  end
59
93
  end
60
94
  end
@@ -16,7 +16,8 @@ module WebAuthn
16
16
  encoder.decode(challenge),
17
17
  public_key: encoder.decode(public_key),
18
18
  sign_count: sign_count,
19
- user_verification: user_verification
19
+ user_verification: user_verification,
20
+ rp_id: appid_extension_output ? appid : nil
20
21
  )
21
22
 
22
23
  true
@@ -31,5 +32,17 @@ module WebAuthn
31
32
  def raw_user_handle
32
33
  response.user_handle
33
34
  end
35
+
36
+ private
37
+
38
+ def appid_extension_output
39
+ return if client_extension_outputs.nil?
40
+
41
+ client_extension_outputs['appid']
42
+ end
43
+
44
+ def appid
45
+ URI.parse(relying_party.legacy_u2f_appid || raise("Unspecified legacy U2F AppID")).to_s
46
+ end
34
47
  end
35
48
  end
@@ -0,0 +1,120 @@
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", "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', 'AnonCA'],
28
+ attestation_root_certificates_finders: [],
29
+ legacy_u2f_appid: nil
30
+ )
31
+ @algorithms = algorithms
32
+ @encoding = encoding
33
+ @origin = origin
34
+ @id = id
35
+ @name = name
36
+ @verify_attestation_statement = verify_attestation_statement
37
+ @credential_options_timeout = credential_options_timeout
38
+ @silent_authentication = silent_authentication
39
+ @acceptable_attestation_types = acceptable_attestation_types
40
+ @legacy_u2f_appid = legacy_u2f_appid
41
+ self.attestation_root_certificates_finders = attestation_root_certificates_finders
42
+ end
43
+
44
+ attr_accessor :algorithms,
45
+ :encoding,
46
+ :origin,
47
+ :id,
48
+ :name,
49
+ :verify_attestation_statement,
50
+ :credential_options_timeout,
51
+ :silent_authentication,
52
+ :acceptable_attestation_types,
53
+ :legacy_u2f_appid
54
+
55
+ attr_reader :attestation_root_certificates_finders
56
+
57
+ # This is the user-data encoder.
58
+ # Used to decode user input and to encode data provided to the user.
59
+ def encoder
60
+ @encoder ||= WebAuthn::Encoder.new(encoding)
61
+ end
62
+
63
+ def attestation_root_certificates_finders=(finders)
64
+ if !finders.respond_to?(:each)
65
+ finders = [finders]
66
+ end
67
+
68
+ finders.each do |finder|
69
+ unless finder.respond_to?(:find)
70
+ raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
71
+ end
72
+ end
73
+
74
+ @attestation_root_certificates_finders = finders
75
+ end
76
+
77
+ def options_for_registration(**keyword_arguments)
78
+ WebAuthn::Credential.options_for_create(
79
+ **keyword_arguments,
80
+ relying_party: self
81
+ )
82
+ end
83
+
84
+ def verify_registration(raw_credential, challenge, user_verification: nil)
85
+ webauthn_credential = WebAuthn::Credential.from_create(raw_credential, relying_party: self)
86
+
87
+ if webauthn_credential.verify(challenge, user_verification: user_verification)
88
+ webauthn_credential
89
+ end
90
+ end
91
+
92
+ def options_for_authentication(**keyword_arguments)
93
+ WebAuthn::Credential.options_for_get(
94
+ **keyword_arguments,
95
+ relying_party: self
96
+ )
97
+ end
98
+
99
+ def verify_authentication(
100
+ raw_credential,
101
+ challenge,
102
+ user_verification: nil,
103
+ public_key: nil,
104
+ sign_count: nil
105
+ )
106
+ webauthn_credential = WebAuthn::Credential.from_get(raw_credential, relying_party: self)
107
+
108
+ stored_credential = yield(webauthn_credential) if block_given?
109
+
110
+ if webauthn_credential.verify(
111
+ challenge,
112
+ public_key: public_key || stored_credential.public_key,
113
+ sign_count: sign_count || stored_credential.sign_count,
114
+ user_verification: user_verification
115
+ )
116
+ block_given? ? [webauthn_credential, stored_credential] : webauthn_credential
117
+ end
118
+ end
119
+ end
120
+ end
@@ -31,7 +31,10 @@ module WebAuthn
31
31
  @credential ||=
32
32
  begin
33
33
  hash = authenticator_data.send(:credential)
34
- WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(hash[:id], hash[:public_key].serialize)
34
+ WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(
35
+ id: hash[:id],
36
+ public_key: hash[:public_key].serialize
37
+ )
35
38
  end
36
39
  end
37
40