webauthn 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
  )