webauthn 2.3.0 → 3.0.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +32 -0
  3. data/.github/workflows/git.yml +21 -0
  4. data/.rubocop.yml +57 -1
  5. data/CHANGELOG.md +79 -0
  6. data/README.md +8 -5
  7. data/SECURITY.md +6 -3
  8. data/docs/advanced_configuration.md +174 -0
  9. data/docs/u2f_migration.md +14 -20
  10. data/lib/cose/rsapkcs1_algorithm.rb +7 -0
  11. data/lib/webauthn/attestation_object.rb +9 -5
  12. data/lib/webauthn/attestation_statement/android_key.rb +0 -4
  13. data/lib/webauthn/attestation_statement/android_safetynet.rb +1 -5
  14. data/lib/webauthn/attestation_statement/apple.rb +65 -0
  15. data/lib/webauthn/attestation_statement/base.rb +18 -32
  16. data/lib/webauthn/attestation_statement/none.rb +7 -1
  17. data/lib/webauthn/attestation_statement/packed.rb +1 -1
  18. data/lib/webauthn/attestation_statement/tpm.rb +2 -2
  19. data/lib/webauthn/attestation_statement.rb +6 -3
  20. data/lib/webauthn/authenticator_assertion_response.rb +4 -3
  21. data/lib/webauthn/authenticator_attestation_response.rb +10 -7
  22. data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -5
  23. data/lib/webauthn/authenticator_data.rb +10 -2
  24. data/lib/webauthn/authenticator_response.rb +7 -7
  25. data/lib/webauthn/configuration.rb +38 -42
  26. data/lib/webauthn/credential.rb +5 -4
  27. data/lib/webauthn/credential_creation_options.rb +2 -0
  28. data/lib/webauthn/credential_request_options.rb +2 -0
  29. data/lib/webauthn/fake_authenticator/attestation_object.rb +8 -0
  30. data/lib/webauthn/fake_authenticator/authenticator_data.rb +20 -5
  31. data/lib/webauthn/fake_authenticator.rb +19 -3
  32. data/lib/webauthn/fake_client.rb +20 -5
  33. data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
  34. data/lib/webauthn/public_key_credential/entity.rb +3 -4
  35. data/lib/webauthn/public_key_credential/options.rb +9 -8
  36. data/lib/webauthn/public_key_credential/request_options.rb +11 -1
  37. data/lib/webauthn/public_key_credential.rb +24 -5
  38. data/lib/webauthn/public_key_credential_with_assertion.rb +14 -1
  39. data/lib/webauthn/relying_party.rb +120 -0
  40. data/lib/webauthn/u2f_migrator.rb +4 -1
  41. data/lib/webauthn/version.rb +1 -1
  42. data/webauthn.gemspec +7 -8
  43. metadata +40 -59
  44. data/.travis.yml +0 -39
  45. data/Appraisals +0 -21
  46. data/gemfiles/cose_head.gemfile +0 -7
  47. data/gemfiles/openssl_2_0.gemfile +0 -7
  48. data/gemfiles/openssl_2_1.gemfile +0 -7
  49. data/gemfiles/openssl_2_2.gemfile +0 -7
  50. data/gemfiles/openssl_head.gemfile +0 -7
  51. data/lib/webauthn/security_utils.rb +0 -20
  52. data/script/ci/install-openssl +0 -7
  53. data/script/ci/install-ruby +0 -13
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "webauthn/attestation_statement/base"
5
+
6
+ module WebAuthn
7
+ module AttestationStatement
8
+ class Apple < Base
9
+ # Source: https://www.apple.com/certificateauthority/private/
10
+ ROOT_CERTIFICATE =
11
+ OpenSSL::X509::Certificate.new(<<~PEM)
12
+ -----BEGIN CERTIFICATE-----
13
+ MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w
14
+ HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ
15
+ bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx
16
+ NTAwMDAwMFowSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEG
17
+ A1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49
18
+ AgEGBSuBBAAiA2IABCJCQ2pTVhzjl4Wo6IhHtMSAzO2cv+H9DQKev3//fG59G11k
19
+ xu9eI0/7o6V5uShBpe1u6l6mS19S1FEh6yGljnZAJ+2GNP1mi/YK2kSXIuTHjxA/
20
+ pcoRf7XkOtO4o1qlcaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJtdk
21
+ 2cV4wlpn0afeaxLQG2PxxtcwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA
22
+ MGQCMFrZ+9DsJ1PW9hfNdBywZDsWDbWFp28it1d/5w2RPkRX3Bbn/UbDTNLx7Jr3
23
+ jAGGiQIwHFj+dJZYUJR786osByBelJYsVZd2GbHQu209b5RCmGQ21gpSAk9QZW4B
24
+ 1bWeT0vT
25
+ -----END CERTIFICATE-----
26
+ PEM
27
+
28
+ NONCE_EXTENSION_OID = "1.2.840.113635.100.8.2"
29
+
30
+ def valid?(authenticator_data, client_data_hash)
31
+ valid_nonce?(authenticator_data, client_data_hash) &&
32
+ matching_public_key?(authenticator_data) &&
33
+ trustworthy? &&
34
+ [attestation_type, attestation_trust_path]
35
+ end
36
+
37
+ private
38
+
39
+ def valid_nonce?(authenticator_data, client_data_hash)
40
+ extension = cred_cert&.find_extension(NONCE_EXTENSION_OID)
41
+
42
+ if extension
43
+ sequence = OpenSSL::ASN1.decode(extension.value_der)
44
+
45
+ sequence.tag == OpenSSL::ASN1::SEQUENCE &&
46
+ sequence.value.size == 1 &&
47
+ sequence.value[0].value[0].value ==
48
+ OpenSSL::Digest::SHA256.digest(authenticator_data.data + client_data_hash)
49
+ end
50
+ end
51
+
52
+ def attestation_type
53
+ WebAuthn::AttestationStatement::ATTESTATION_TYPE_ANONCA
54
+ end
55
+
56
+ def cred_cert
57
+ attestation_certificate
58
+ end
59
+
60
+ def default_root_certificates
61
+ [ROOT_CERTIFICATE]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -16,18 +16,21 @@ 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"
19
20
 
20
21
  ATTESTATION_TYPES_WITH_ROOT = [
21
22
  ATTESTATION_TYPE_BASIC,
22
23
  ATTESTATION_TYPE_BASIC_OR_ATTCA,
23
- ATTESTATION_TYPE_ATTCA
24
+ ATTESTATION_TYPE_ATTCA,
25
+ ATTESTATION_TYPE_ANONCA
24
26
  ].freeze
25
27
 
26
28
  class Base
27
29
  AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
28
30
 
29
- def initialize(statement)
31
+ def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
30
32
  @statement = statement
33
+ @relying_party = relying_party
31
34
  end
32
35
 
33
36
  def valid?(_authenticator_data, _client_data_hash)
@@ -42,32 +45,28 @@ module WebAuthn
42
45
  certificates&.first
43
46
  end
44
47
 
45
- def certificate_chain
46
- if certificates
47
- certificates[1..-1]
48
- end
49
- end
50
-
51
48
  def attestation_certificate_key_id
52
- raw_subject_key_identifier&.unpack("H*")&.[](0)
49
+ attestation_certificate.subject_key_identifier&.unpack("H*")&.[](0)
53
50
  end
54
51
 
55
52
  private
56
53
 
57
- attr_reader :statement
54
+ attr_reader :statement, :relying_party
58
55
 
59
56
  def matching_aaguid?(attested_credential_data_aaguid)
60
- extension = attestation_certificate&.extensions&.detect { |ext| ext.oid == AAGUID_EXTENSION_OID }
57
+ extension = attestation_certificate&.find_extension(AAGUID_EXTENSION_OID)
61
58
  if extension
62
- # `extension.value` mangles data into ASCII, so we must manually compare bytes
63
- # see https://github.com/ruby/openssl/pull/234
64
- extension.to_der[-WebAuthn::AuthenticatorData::AttestedCredentialData::AAGUID_LENGTH..-1] ==
65
- attested_credential_data_aaguid
59
+ aaguid_value = OpenSSL::ASN1.decode(extension.value_der).value
60
+ aaguid_value == attested_credential_data_aaguid
66
61
  else
67
62
  true
68
63
  end
69
64
  end
70
65
 
66
+ def matching_public_key?(authenticator_data)
67
+ attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
68
+ end
69
+
71
70
  def certificates
72
71
  @certificates ||=
73
72
  raw_certificates&.map do |raw_certificate|
@@ -95,10 +94,10 @@ module WebAuthn
95
94
 
96
95
  def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
97
96
  if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
98
- configuration.acceptable_attestation_types.include?(attestation_type) &&
97
+ relying_party.acceptable_attestation_types.include?(attestation_type) &&
99
98
  valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
100
99
  else
101
- configuration.acceptable_attestation_types.include?(attestation_type)
100
+ relying_party.acceptable_attestation_types.include?(attestation_type)
102
101
  end
103
102
  end
104
103
 
@@ -122,7 +121,7 @@ module WebAuthn
122
121
 
123
122
  def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
124
123
  root_certificates =
125
- configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
124
+ relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
126
125
  if certs.empty?
127
126
  finder.find(
128
127
  attestation_format: format,
@@ -141,15 +140,6 @@ module WebAuthn
141
140
  end
142
141
  end
143
142
 
144
- def raw_subject_key_identifier
145
- extension = attestation_certificate.extensions.detect { |ext| ext.oid == "subjectKeyIdentifier" }
146
- return unless extension
147
-
148
- ext_asn1 = OpenSSL::ASN1.decode(extension.to_der)
149
- ext_value = ext_asn1.value.last
150
- OpenSSL::ASN1.decode(ext_value.value).value
151
- end
152
-
153
143
  def valid_signature?(authenticator_data, client_data_hash, public_key = attestation_certificate.public_key)
154
144
  raise("Incompatible algorithm and key") unless cose_algorithm.compatible_key?(public_key)
155
145
 
@@ -169,14 +159,10 @@ module WebAuthn
169
159
  def cose_algorithm
170
160
  @cose_algorithm ||=
171
161
  COSE::Algorithm.find(algorithm).tap do |alg|
172
- alg && configuration.algorithms.include?(alg.name) ||
162
+ alg && relying_party.algorithms.include?(alg.name) ||
173
163
  raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
174
164
  end
175
165
  end
176
-
177
- def configuration
178
- WebAuthn.configuration
179
- end
180
166
  end
181
167
  end
182
168
  end
@@ -6,12 +6,18 @@ module WebAuthn
6
6
  module AttestationStatement
7
7
  class None < Base
8
8
  def valid?(*_args)
9
- if statement == {}
9
+ if statement == {} && trustworthy?
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
15
21
  end
16
22
  end
17
23
  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.extensions.find { |ext| ext.oid == 'basicConstraints' }&.value == 'CA:FALSE'
49
+ attestation_certificate.find_extension('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
- root_certificates: root_certificates(aaguid: aaguid)
45
+ trusted_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::ROOT_CERTIFICATES
57
+ ::TPM::KeyAttestation::TRUSTED_CERTIFICATES
58
58
  end
59
59
 
60
60
  def tpm_algorithm
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "webauthn/attestation_statement/android_key"
4
4
  require "webauthn/attestation_statement/android_safetynet"
5
+ require "webauthn/attestation_statement/apple"
5
6
  require "webauthn/attestation_statement/fido_u2f"
6
7
  require "webauthn/attestation_statement/none"
7
8
  require "webauthn/attestation_statement/packed"
@@ -18,6 +19,7 @@ module WebAuthn
18
19
  ATTESTATION_FORMAT_ANDROID_SAFETYNET = "android-safetynet"
19
20
  ATTESTATION_FORMAT_ANDROID_KEY = "android-key"
20
21
  ATTESTATION_FORMAT_TPM = "tpm"
22
+ ATTESTATION_FORMAT_APPLE = "apple"
21
23
 
22
24
  FORMAT_TO_CLASS = {
23
25
  ATTESTATION_FORMAT_NONE => WebAuthn::AttestationStatement::None,
@@ -25,14 +27,15 @@ module WebAuthn
25
27
  ATTESTATION_FORMAT_PACKED => WebAuthn::AttestationStatement::Packed,
26
28
  ATTESTATION_FORMAT_ANDROID_SAFETYNET => WebAuthn::AttestationStatement::AndroidSafetynet,
27
29
  ATTESTATION_FORMAT_ANDROID_KEY => WebAuthn::AttestationStatement::AndroidKey,
28
- ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
30
+ ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM,
31
+ ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
29
32
  }.freeze
30
33
 
31
- def self.from(format, statement)
34
+ def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
32
35
  klass = FORMAT_TO_CLASS[format]
33
36
 
34
37
  if klass
35
- klass.new(statement)
38
+ klass.new(statement, relying_party)
36
39
  else
37
40
  raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
38
41
  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
 
@@ -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?
@@ -22,9 +22,8 @@ module WebAuthn
22
22
  count_bytes_remaining :trailing_bytes_length
23
23
  string :trailing_bytes, length: :trailing_bytes_length
24
24
 
25
- # TODO: use keyword_init when we dropped Ruby 2.4 support
26
25
  Credential =
27
- Struct.new(:id, :public_key) do
26
+ Struct.new(:id, :public_key, :algorithm, keyword_init: true) do
28
27
  def public_key_object
29
28
  COSE::Key.deserialize(public_key).to_pkey
30
29
  end
@@ -47,7 +46,7 @@ module WebAuthn
47
46
  def credential
48
47
  @credential ||=
49
48
  if valid?
50
- Credential.new(id, public_key)
49
+ Credential.new(id: id, public_key: public_key, algorithm: algorithm)
51
50
  end
52
51
  end
53
52
 
@@ -59,10 +58,16 @@ module WebAuthn
59
58
 
60
59
  private
61
60
 
61
+ def algorithm
62
+ COSE::Algorithm.find(cose_key.alg).name
63
+ end
64
+
62
65
  def valid_credential_public_key?
63
- cose_key = COSE::Key.deserialize(public_key)
66
+ !!cose_key.alg
67
+ end
64
68
 
65
- !!cose_key.alg && WebAuthn.configuration.algorithms.include?(COSE::Algorithm.find(cose_key.alg).name)
69
+ def cose_key
70
+ @cose_key ||= COSE::Key.deserialize(public_key)
66
71
  end
67
72
 
68
73
  def public_key
@@ -19,9 +19,9 @@ module WebAuthn
19
19
  struct :flags do
20
20
  bit1 :extension_data_included
21
21
  bit1 :attested_credential_data_included
22
- bit1 :reserved_for_future_use_4
23
- bit1 :reserved_for_future_use_3
24
22
  bit1 :reserved_for_future_use_2
23
+ bit1 :backup_state
24
+ bit1 :backup_eligibility
25
25
  bit1 :user_verified
26
26
  bit1 :reserved_for_future_use_1
27
27
  bit1 :user_present
@@ -58,6 +58,14 @@ module WebAuthn
58
58
  flags.user_verified == 1
59
59
  end
60
60
 
61
+ def credential_backup_eligible?
62
+ flags.backup_eligibility == 1
63
+ end
64
+
65
+ def credential_backed_up?
66
+ flags.backup_state == 1
67
+ end
68
+
61
69
  def attested_credential_data_included?
62
70
  flags.attested_credential_data_included == 1
63
71
  end
@@ -3,7 +3,6 @@
3
3
  require "webauthn/authenticator_data"
4
4
  require "webauthn/client_data"
5
5
  require "webauthn/error"
6
- require "webauthn/security_utils"
7
6
 
8
7
  module WebAuthn
9
8
  TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
@@ -20,13 +19,14 @@ module WebAuthn
20
19
  class UserVerifiedVerificationError < VerificationError; end
21
20
 
22
21
  class AuthenticatorResponse
23
- def initialize(client_data_json:)
22
+ def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
24
23
  @client_data_json = client_data_json
24
+ @relying_party = relying_party
25
25
  end
26
26
 
27
27
  def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
28
- expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
29
- rp_id ||= WebAuthn.configuration.rp_id
28
+ expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
29
+ rp_id ||= relying_party.id
30
30
 
31
31
  verify_item(:type)
32
32
  verify_item(:token_binding)
@@ -35,7 +35,7 @@ module WebAuthn
35
35
  verify_item(:authenticator_data)
36
36
  verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
37
37
 
38
- if !WebAuthn.configuration.silent_authentication
38
+ if !relying_party.silent_authentication
39
39
  verify_item(:user_presence)
40
40
  end
41
41
 
@@ -58,7 +58,7 @@ module WebAuthn
58
58
 
59
59
  private
60
60
 
61
- attr_reader :client_data_json
61
+ attr_reader :client_data_json, :relying_party
62
62
 
63
63
  def verify_item(item, *args)
64
64
  if send("valid_#{item}?", *args)
@@ -79,7 +79,7 @@ module WebAuthn
79
79
  end
80
80
 
81
81
  def valid_challenge?(expected_challenge)
82
- WebAuthn::SecurityUtils.secure_compare(client_data.challenge, expected_challenge)
82
+ OpenSSL.secure_compare(client_data.challenge, expected_challenge)
83
83
  end
84
84
 
85
85
  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,54 +12,51 @@ module WebAuthn
13
12
  yield(configuration)
14
13
  end
15
14
 
16
- class RootCertificateFinderNotSupportedError < Error; end
17
-
18
15
  class Configuration
19
- def self.if_pss_supported(algorithm)
20
- OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
21
- end
22
-
23
- DEFAULT_ALGORITHMS = ["ES256", if_pss_supported("PS256"), "RS256"].compact.freeze
24
-
25
- attr_accessor :algorithms
26
- attr_accessor :encoding
27
- attr_accessor :origin
28
- attr_accessor :rp_id
29
- attr_accessor :rp_name
30
- attr_accessor :verify_attestation_statement
31
- attr_accessor :credential_options_timeout
32
- attr_accessor :silent_authentication
33
- attr_accessor :acceptable_attestation_types
34
- 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
35
41
 
36
42
  def initialize
37
- @algorithms = DEFAULT_ALGORITHMS.dup
38
- @encoding = WebAuthn::Encoder::STANDARD_ENCODING
39
- @verify_attestation_statement = true
40
- @credential_options_timeout = 120000
41
- @silent_authentication = false
42
- @acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA']
43
- @attestation_root_certificates_finders = []
43
+ @relying_party = RelyingParty.new
44
44
  end
45
45
 
46
- # This is the user-data encoder.
47
- # Used to decode user input and to encode data provided to the user.
48
- def encoder
49
- @encoder ||= WebAuthn::Encoder.new(encoding)
46
+ def rp_name
47
+ relying_party.name
50
48
  end
51
49
 
52
- def attestation_root_certificates_finders=(finders)
53
- if !finders.respond_to?(:each)
54
- finders = [finders]
55
- end
50
+ def rp_name=(name)
51
+ relying_party.name = name
52
+ end
56
53
 
57
- finders.each do |finder|
58
- unless finder.respond_to?(:find)
59
- raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
60
- end
61
- end
54
+ def rp_id
55
+ relying_party.id
56
+ end
62
57
 
63
- @attestation_root_certificates_finders = finders
58
+ def rp_id=(id)
59
+ relying_party.id = id
64
60
  end
65
61
  end
66
62
  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,6 +32,8 @@ module WebAuthn
32
32
  user_display_name: nil,
33
33
  rp_name: nil
34
34
  )
35
+ super()
36
+
35
37
  @attestation = attestation
36
38
  @authenticator_selection = authenticator_selection
37
39
  @exclude_credentials = exclude_credentials
@@ -16,6 +16,8 @@ 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
+
19
21
  @allow_credentials = allow_credentials
20
22
  @extensions = extensions
21
23
  @user_verification = user_verification
@@ -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
  )