webauthn 2.1.0 → 3.4.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/build.yml +50 -0
  4. data/.github/workflows/git.yml +21 -0
  5. data/.rubocop.yml +121 -13
  6. data/CHANGELOG.md +169 -0
  7. data/CONTRIBUTING.md +0 -5
  8. data/README.md +80 -14
  9. data/SECURITY.md +7 -4
  10. data/docs/advanced_configuration.md +174 -0
  11. data/docs/u2f_migration.md +14 -20
  12. data/lib/cose/rsapkcs1_algorithm.rb +50 -0
  13. data/lib/webauthn/attestation_object.rb +47 -0
  14. data/lib/webauthn/attestation_statement/android_key.rb +27 -33
  15. data/lib/webauthn/attestation_statement/android_safetynet.rb +27 -11
  16. data/lib/webauthn/attestation_statement/apple.rb +65 -0
  17. data/lib/webauthn/attestation_statement/base.rb +114 -21
  18. data/lib/webauthn/attestation_statement/fido_u2f.rb +8 -6
  19. data/lib/webauthn/attestation_statement/none.rb +7 -1
  20. data/lib/webauthn/attestation_statement/packed.rb +14 -42
  21. data/lib/webauthn/attestation_statement/tpm.rb +38 -75
  22. data/lib/webauthn/attestation_statement.rb +24 -21
  23. data/lib/webauthn/authenticator_assertion_response.rb +22 -11
  24. data/lib/webauthn/authenticator_attestation_response.rb +31 -92
  25. data/lib/webauthn/authenticator_data/attested_credential_data.rb +33 -49
  26. data/lib/webauthn/authenticator_data.rb +59 -51
  27. data/lib/webauthn/authenticator_response.rb +24 -11
  28. data/lib/webauthn/client_data.rb +4 -6
  29. data/lib/webauthn/configuration.rb +38 -40
  30. data/lib/webauthn/credential.rb +4 -4
  31. data/lib/webauthn/credential_creation_options.rb +2 -0
  32. data/lib/webauthn/credential_request_options.rb +2 -0
  33. data/lib/webauthn/encoder.rb +13 -4
  34. data/lib/webauthn/fake_authenticator/attestation_object.rb +25 -4
  35. data/lib/webauthn/fake_authenticator/authenticator_data.rb +25 -10
  36. data/lib/webauthn/fake_authenticator.rb +49 -8
  37. data/lib/webauthn/fake_client.rb +41 -8
  38. data/lib/webauthn/json_serializer.rb +45 -0
  39. data/lib/webauthn/public_key.rb +21 -2
  40. data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
  41. data/lib/webauthn/public_key_credential/entity.rb +5 -28
  42. data/lib/webauthn/public_key_credential/options.rb +11 -32
  43. data/lib/webauthn/public_key_credential/request_options.rb +11 -1
  44. data/lib/webauthn/public_key_credential.rb +52 -8
  45. data/lib/webauthn/public_key_credential_with_assertion.rb +16 -2
  46. data/lib/webauthn/public_key_credential_with_attestation.rb +2 -2
  47. data/lib/webauthn/relying_party.rb +137 -0
  48. data/lib/webauthn/u2f_migrator.rb +8 -4
  49. data/lib/webauthn/version.rb +1 -1
  50. data/lib/webauthn.rb +1 -0
  51. data/webauthn.gemspec +15 -12
  52. metadata +56 -60
  53. data/.travis.yml +0 -36
  54. data/Appraisals +0 -17
  55. data/gemfiles/cose_head.gemfile +0 -7
  56. data/gemfiles/openssl_2_0.gemfile +0 -7
  57. data/gemfiles/openssl_2_1.gemfile +0 -7
  58. data/gemfiles/openssl_head.gemfile +0 -7
  59. data/lib/android_safetynet/attestation_response.rb +0 -116
  60. data/lib/cose/rsassa_algorithm.rb +0 -10
  61. data/lib/tpm/constants.rb +0 -44
  62. data/lib/tpm/s_attest/s_certify_info.rb +0 -14
  63. data/lib/tpm/s_attest.rb +0 -26
  64. data/lib/tpm/sized_buffer.rb +0 -13
  65. data/lib/tpm/t_public/s_ecc_parms.rb +0 -17
  66. data/lib/tpm/t_public/s_rsa_parms.rb +0 -17
  67. data/lib/tpm/t_public.rb +0 -32
  68. data/lib/webauthn/attestation_statement/android_key/authorization_list.rb +0 -39
  69. data/lib/webauthn/attestation_statement/android_key/key_description.rb +0 -37
  70. data/lib/webauthn/attestation_statement/tpm/cert_info.rb +0 -44
  71. data/lib/webauthn/attestation_statement/tpm/pub_area.rb +0 -85
  72. data/lib/webauthn/security_utils.rb +0 -20
  73. data/lib/webauthn/signature_verifier.rb +0 -77
@@ -1,145 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "cbor"
4
+ require "forwardable"
4
5
  require "uri"
5
6
  require "openssl"
6
7
 
7
- require "webauthn/authenticator_data"
8
+ require "webauthn/attestation_object"
8
9
  require "webauthn/authenticator_response"
9
- require "webauthn/attestation_statement"
10
10
  require "webauthn/client_data"
11
11
  require "webauthn/encoder"
12
12
 
13
13
  module WebAuthn
14
14
  class AttestationStatementVerificationError < VerificationError; end
15
- class AttestationTrustworthinessVerificationError < VerificationError; end
16
15
  class AttestedCredentialVerificationError < VerificationError; end
17
16
 
18
17
  class AuthenticatorAttestationResponse < AuthenticatorResponse
19
- def self.from_client(response)
20
- encoder = WebAuthn.configuration.encoder
18
+ extend Forwardable
19
+
20
+ def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
21
+ encoder = relying_party.encoder
21
22
 
22
23
  new(
23
24
  attestation_object: encoder.decode(response["attestationObject"]),
24
- client_data_json: encoder.decode(response["clientDataJSON"])
25
+ transports: response["transports"],
26
+ client_data_json: encoder.decode(response["clientDataJSON"]),
27
+ relying_party: relying_party
25
28
  )
26
29
  end
27
30
 
28
- attr_reader :attestation_type, :attestation_trust_path
31
+ attr_reader :attestation_type, :attestation_trust_path, :transports
29
32
 
30
- def initialize(attestation_object:, **options)
33
+ def initialize(attestation_object:, transports: [], **options)
31
34
  super(**options)
32
35
 
33
- @attestation_object = attestation_object
36
+ @attestation_object_bytes = attestation_object
37
+ @transports = transports
38
+ @relying_party = relying_party
34
39
  end
35
40
 
36
- def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
41
+ def verify(expected_challenge, expected_origin = nil, user_presence: nil, user_verification: nil, rp_id: nil)
37
42
  super
38
43
 
39
44
  verify_item(:attested_credential)
40
- if WebAuthn.configuration.verify_attestation_statement
45
+ if relying_party.verify_attestation_statement
41
46
  verify_item(:attestation_statement)
42
- verify_item(:attestation_trustworthiness) if WebAuthn.configuration.attestation_root_certificates_finders.any?
43
47
  end
44
48
 
45
49
  true
46
50
  end
47
51
 
48
- def credential
49
- authenticator_data.credential
50
- end
51
-
52
- def attestation_statement
53
- @attestation_statement ||=
54
- WebAuthn::AttestationStatement.from(attestation["fmt"], attestation["attStmt"])
52
+ def attestation_object
53
+ @attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes, relying_party)
55
54
  end
56
55
 
57
- def authenticator_data
58
- @authenticator_data ||= WebAuthn::AuthenticatorData.new(attestation["authData"])
59
- end
56
+ def_delegators(
57
+ :attestation_object,
58
+ :aaguid,
59
+ :attestation_statement,
60
+ :attestation_certificate_key_id,
61
+ :authenticator_data,
62
+ :credential
63
+ )
60
64
 
61
- def attestation_format
62
- attestation["fmt"]
63
- end
64
-
65
- def attestation
66
- @attestation ||= CBOR.decode(attestation_object)
67
- end
68
-
69
- def aaguid
70
- raw_aaguid = authenticator_data.attested_credential_data.raw_aaguid
71
- unless raw_aaguid == WebAuthn::AuthenticatorData::AttestedCredentialData::ZEROED_AAGUID
72
- authenticator_data.attested_credential_data.aaguid
73
- end
74
- end
75
-
76
- def attestation_certificate_key
77
- raw_subject_key_identifier(attestation_statement.attestation_certificate)&.unpack("H*")&.[](0)
78
- end
65
+ alias_method :attestation_certificate_key, :attestation_certificate_key_id
79
66
 
80
67
  private
81
68
 
82
- attr_reader :attestation_object
69
+ attr_reader :attestation_object_bytes, :relying_party
83
70
 
84
71
  def type
85
72
  WebAuthn::TYPES[:create]
86
73
  end
87
74
 
88
75
  def valid_attested_credential?
89
- authenticator_data.attested_credential_data_included? &&
90
- authenticator_data.attested_credential_data.valid?
76
+ attestation_object.valid_attested_credential? &&
77
+ relying_party.algorithms.include?(authenticator_data.credential.algorithm)
91
78
  end
92
79
 
93
80
  def valid_attestation_statement?
94
- @attestation_type, @attestation_trust_path = attestation_statement.valid?(authenticator_data, client_data.hash)
95
- end
96
-
97
- def valid_attestation_trustworthiness?
98
- case @attestation_type
99
- when WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE
100
- WebAuthn.configuration.acceptable_attestation_types.include?('None')
101
- when WebAuthn::AttestationStatement::ATTESTATION_TYPE_SELF
102
- WebAuthn.configuration.acceptable_attestation_types.include?('Self')
103
- else
104
- WebAuthn.configuration.acceptable_attestation_types.include?(@attestation_type) &&
105
- attestation_root_certificates_store.verify(leaf_certificate, signing_certificates)
106
- end
107
- end
108
-
109
- def raw_subject_key_identifier(certificate)
110
- extension = certificate.extensions.detect { |ext| ext.oid == "subjectKeyIdentifier" }
111
- return unless extension
112
-
113
- ext_asn1 = OpenSSL::ASN1.decode(extension.to_der)
114
- ext_value = ext_asn1.value.last
115
- OpenSSL::ASN1.decode(ext_value.value).value
116
- end
117
-
118
- def attestation_root_certificates_store
119
- certificates =
120
- WebAuthn.configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
121
- if certs.empty?
122
- finder.find(attestation_format: attestation_format,
123
- aaguid: aaguid,
124
- attestation_certificate_key_id: attestation_certificate_key) || []
125
- else
126
- certs
127
- end
128
- end
129
-
130
- OpenSSL::X509::Store.new.tap do |store|
131
- certificates.each do |cert|
132
- store.add_cert(cert)
133
- end
134
- end
135
- end
136
-
137
- def signing_certificates
138
- @attestation_trust_path[1..-1]
139
- end
140
-
141
- def leaf_certificate
142
- @attestation_trust_path.first
81
+ @attestation_type, @attestation_trust_path = attestation_object.valid_attestation_statement?(client_data.hash)
143
82
  end
144
83
  end
145
84
  end
@@ -1,34 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bindata"
3
4
  require "cose/key"
5
+ require "webauthn/error"
4
6
 
5
7
  module WebAuthn
6
- class AuthenticatorData
7
- class AttestedCredentialData
8
+ class AttestedCredentialDataFormatError < WebAuthn::Error; end
9
+
10
+ class AuthenticatorData < BinData::Record
11
+ class AttestedCredentialData < BinData::Record
8
12
  AAGUID_LENGTH = 16
9
13
  ZEROED_AAGUID = 0.chr * AAGUID_LENGTH
10
14
 
11
15
  ID_LENGTH_LENGTH = 2
12
16
 
13
- UINT16_BIG_ENDIAN_FORMAT = "n*"
17
+ endian :big
18
+
19
+ string :raw_aaguid, length: AAGUID_LENGTH
20
+ bit16 :id_length
21
+ string :id, read_length: :id_length
22
+ count_bytes_remaining :trailing_bytes_length
23
+ string :trailing_bytes, length: :trailing_bytes_length
14
24
 
15
- # FIXME: use keyword_init when we dropped Ruby 2.4 support
16
- Credential = Struct.new(:id, :public_key) do
17
- def public_key_object
18
- COSE::Key.deserialize(public_key).to_pkey
25
+ Credential =
26
+ Struct.new(:id, :public_key, :algorithm, keyword_init: true) do
27
+ def public_key_object
28
+ COSE::Key.deserialize(public_key).to_pkey
29
+ end
19
30
  end
20
- end
21
31
 
22
- def initialize(data)
23
- @data = data
32
+ def self.deserialize(data)
33
+ read(data)
34
+ rescue EOFError
35
+ raise AttestedCredentialDataFormatError
24
36
  end
25
37
 
26
38
  def valid?
27
- data.length >= AAGUID_LENGTH + ID_LENGTH_LENGTH && valid_credential_public_key?
28
- end
29
-
30
- def raw_aaguid
31
- data_at(0, AAGUID_LENGTH)
39
+ valid_credential_public_key?
32
40
  end
33
41
 
34
42
  def aaguid
@@ -37,62 +45,38 @@ module WebAuthn
37
45
 
38
46
  def credential
39
47
  @credential ||=
40
- if id
41
- Credential.new(id, public_key)
48
+ if valid?
49
+ Credential.new(id: id, public_key: public_key, algorithm: algorithm)
42
50
  end
43
51
  end
44
52
 
45
53
  def length
46
54
  if valid?
47
- public_key_position + public_key_length
55
+ AAGUID_LENGTH + ID_LENGTH_LENGTH + id_length + public_key_length
48
56
  end
49
57
  end
50
58
 
51
59
  private
52
60
 
53
- attr_reader :data
61
+ def algorithm
62
+ COSE::Algorithm.find(cose_key.alg).name
63
+ end
54
64
 
55
65
  def valid_credential_public_key?
56
- cose_key = COSE::Key.deserialize(public_key)
57
-
58
66
  !!cose_key.alg
59
67
  end
60
68
 
61
- def id
62
- if valid?
63
- data_at(id_position, id_length)
64
- end
69
+ def cose_key
70
+ @cose_key ||= COSE::Key.deserialize(public_key)
65
71
  end
66
72
 
67
73
  def public_key
68
- @public_key ||= data_at(public_key_position, public_key_length)
69
- end
70
-
71
- def id_position
72
- id_length_position + ID_LENGTH_LENGTH
73
- end
74
-
75
- def id_length
76
- @id_length ||= data_at(id_length_position, ID_LENGTH_LENGTH).unpack(UINT16_BIG_ENDIAN_FORMAT)[0]
77
- end
78
-
79
- def id_length_position
80
- AAGUID_LENGTH
81
- end
82
-
83
- def public_key_position
84
- id_position + id_length
74
+ trailing_bytes[0..public_key_length - 1]
85
75
  end
86
76
 
87
77
  def public_key_length
88
78
  @public_key_length ||=
89
- CBOR.encode(CBOR::Unpacker.new(StringIO.new(data_at(public_key_position))).each.first).length
90
- end
91
-
92
- def data_at(position, length = nil)
93
- length ||= data.size - position
94
-
95
- data[position..(position + length - 1)]
79
+ CBOR.encode(CBOR::Unpacker.new(StringIO.new(trailing_bytes)).each.first).length
96
80
  end
97
81
  end
98
82
  end
@@ -1,32 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bindata"
3
4
  require "webauthn/authenticator_data/attested_credential_data"
5
+ require "webauthn/error"
4
6
 
5
7
  module WebAuthn
6
- class AuthenticatorData
7
- RP_ID_HASH_POSITION = 0
8
+ class AuthenticatorDataFormatError < WebAuthn::Error; end
8
9
 
10
+ class AuthenticatorData < BinData::Record
9
11
  RP_ID_HASH_LENGTH = 32
10
12
  FLAGS_LENGTH = 1
11
13
  SIGN_COUNT_LENGTH = 4
12
14
 
13
- SIGN_COUNT_POSITION = RP_ID_HASH_LENGTH + FLAGS_LENGTH
15
+ endian :big
14
16
 
15
- USER_PRESENT_FLAG_POSITION = 0
16
- USER_VERIFIED_FLAG_POSITION = 2
17
- ATTESTED_CREDENTIAL_DATA_INCLUDED_FLAG_POSITION = 6
18
- EXTENSION_DATA_INCLUDED_FLAG_POSITION = 7
17
+ count_bytes_remaining :data_length
18
+ string :rp_id_hash, length: RP_ID_HASH_LENGTH
19
+ struct :flags do
20
+ bit1 :extension_data_included
21
+ bit1 :attested_credential_data_included
22
+ bit1 :reserved_for_future_use_2
23
+ bit1 :backup_state
24
+ bit1 :backup_eligibility
25
+ bit1 :user_verified
26
+ bit1 :reserved_for_future_use_1
27
+ bit1 :user_present
28
+ end
29
+ bit32 :sign_count
30
+ count_bytes_remaining :trailing_bytes_length
31
+ string :trailing_bytes, length: :trailing_bytes_length
19
32
 
20
- def initialize(data)
21
- @data = data
33
+ def self.deserialize(data)
34
+ read(data)
35
+ rescue EOFError
36
+ raise AuthenticatorDataFormatError
22
37
  end
23
38
 
24
- attr_reader :data
39
+ def data
40
+ to_binary_s
41
+ end
25
42
 
26
43
  def valid?
27
- valid_length? &&
28
- (!attested_credential_data_included? || attested_credential_data.valid?) &&
29
- (!extension_data_included? || extension_data)
44
+ (!attested_credential_data_included? || attested_credential_data.valid?) &&
45
+ (!extension_data_included? || extension_data) &&
46
+ valid_length?
30
47
  end
31
48
 
32
49
  def user_flagged?
@@ -34,26 +51,27 @@ module WebAuthn
34
51
  end
35
52
 
36
53
  def user_present?
37
- flags[USER_PRESENT_FLAG_POSITION] == "1"
54
+ flags.user_present == 1
38
55
  end
39
56
 
40
57
  def user_verified?
41
- flags[USER_VERIFIED_FLAG_POSITION] == "1"
58
+ flags.user_verified == 1
42
59
  end
43
60
 
44
- def attested_credential_data_included?
45
- flags[ATTESTED_CREDENTIAL_DATA_INCLUDED_FLAG_POSITION] == "1"
61
+ def credential_backup_eligible?
62
+ flags.backup_eligibility == 1
46
63
  end
47
64
 
48
- def extension_data_included?
49
- flags[EXTENSION_DATA_INCLUDED_FLAG_POSITION] == "1"
65
+ def credential_backed_up?
66
+ flags.backup_state == 1
50
67
  end
51
68
 
52
- def rp_id_hash
53
- @rp_id_hash ||=
54
- if valid?
55
- data_at(RP_ID_HASH_POSITION, RP_ID_HASH_LENGTH)
56
- end
69
+ def attested_credential_data_included?
70
+ flags.attested_credential_data_included == 1
71
+ end
72
+
73
+ def extension_data_included?
74
+ flags.extension_data_included == 1
57
75
  end
58
76
 
59
77
  def credential
@@ -62,35 +80,39 @@ module WebAuthn
62
80
  end
63
81
  end
64
82
 
65
- def sign_count
66
- @sign_count ||= data_at(SIGN_COUNT_POSITION, SIGN_COUNT_LENGTH).unpack('L>')[0]
67
- end
68
-
69
83
  def attested_credential_data
70
84
  @attested_credential_data ||=
71
- AttestedCredentialData.new(data_at(attested_credential_data_position))
85
+ AttestedCredentialData.deserialize(trailing_bytes)
86
+ rescue AttestedCredentialDataFormatError
87
+ raise AuthenticatorDataFormatError
72
88
  end
73
89
 
74
90
  def extension_data
75
91
  @extension_data ||= CBOR.decode(raw_extension_data)
76
92
  end
77
93
 
78
- def flags
79
- @flags ||= data_at(flags_position, FLAGS_LENGTH).unpack("b*")[0]
94
+ def aaguid
95
+ raw_aaguid = attested_credential_data.raw_aaguid
96
+
97
+ unless raw_aaguid == WebAuthn::AuthenticatorData::AttestedCredentialData::ZEROED_AAGUID
98
+ attested_credential_data.aaguid
99
+ end
80
100
  end
81
101
 
82
102
  private
83
103
 
84
104
  def valid_length?
85
- data.length == base_length + attested_credential_data_length + extension_data_length
105
+ data_length == base_length + attested_credential_data_length + extension_data_length
86
106
  end
87
107
 
88
108
  def raw_extension_data
89
- data_at(extension_data_position)
90
- end
91
-
92
- def attested_credential_data_position
93
- base_length
109
+ if extension_data_included?
110
+ if attested_credential_data_included?
111
+ trailing_bytes[attested_credential_data.length..-1]
112
+ else
113
+ trailing_bytes.snapshot
114
+ end
115
+ end
94
116
  end
95
117
 
96
118
  def attested_credential_data_length
@@ -109,22 +131,8 @@ module WebAuthn
109
131
  end
110
132
  end
111
133
 
112
- def extension_data_position
113
- base_length + attested_credential_data_length
114
- end
115
-
116
134
  def base_length
117
135
  RP_ID_HASH_LENGTH + FLAGS_LENGTH + SIGN_COUNT_LENGTH
118
136
  end
119
-
120
- def flags_position
121
- RP_ID_HASH_LENGTH
122
- end
123
-
124
- def data_at(position, length = nil)
125
- length ||= data.size - position
126
-
127
- data[position..(position + length - 1)]
128
- end
129
137
  end
130
138
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "webauthn/authenticator_data"
3
4
  require "webauthn/client_data"
4
5
  require "webauthn/error"
5
- require "webauthn/security_utils"
6
6
 
7
7
  module WebAuthn
8
8
  TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
@@ -19,22 +19,29 @@ module WebAuthn
19
19
  class UserVerifiedVerificationError < VerificationError; end
20
20
 
21
21
  class AuthenticatorResponse
22
- def initialize(client_data_json:)
22
+ def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
23
23
  @client_data_json = client_data_json
24
+ @relying_party = relying_party
24
25
  end
25
26
 
26
- 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
27
+ def verify(expected_challenge, expected_origin = nil, user_presence: nil, user_verification: nil, rp_id: nil)
28
+ expected_origin ||= relying_party.allowed_origins || raise("Unspecified expected origin")
29
+
30
+ rp_id ||= relying_party.id
29
31
 
30
32
  verify_item(:type)
31
33
  verify_item(:token_binding)
32
34
  verify_item(:challenge, expected_challenge)
33
35
  verify_item(:origin, expected_origin)
34
36
  verify_item(:authenticator_data)
35
- verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
36
37
 
37
- if !WebAuthn.configuration.silent_authentication
38
+ verify_item(
39
+ :rp_id,
40
+ rp_id || rp_id_from_origin(expected_origin)
41
+ )
42
+
43
+ # Fallback to RP configuration unless user_presence is passed in explicitely
44
+ if user_presence.nil? && !relying_party.silent_authentication || user_presence
38
45
  verify_item(:user_presence)
39
46
  end
40
47
 
@@ -57,7 +64,7 @@ module WebAuthn
57
64
 
58
65
  private
59
66
 
60
- attr_reader :client_data_json
67
+ attr_reader :client_data_json, :relying_party
61
68
 
62
69
  def verify_item(item, *args)
63
70
  if send("valid_#{item}?", *args)
@@ -78,19 +85,25 @@ module WebAuthn
78
85
  end
79
86
 
80
87
  def valid_challenge?(expected_challenge)
81
- WebAuthn::SecurityUtils.secure_compare(client_data.challenge, expected_challenge)
88
+ OpenSSL.secure_compare(client_data.challenge, expected_challenge)
82
89
  end
83
90
 
84
91
  def valid_origin?(expected_origin)
85
- expected_origin && (client_data.origin == expected_origin)
92
+ return false unless expected_origin
93
+
94
+ expected_origin.include?(client_data.origin)
86
95
  end
87
96
 
88
97
  def valid_rp_id?(rp_id)
98
+ return false unless rp_id
99
+
89
100
  OpenSSL::Digest::SHA256.digest(rp_id) == authenticator_data.rp_id_hash
90
101
  end
91
102
 
92
103
  def valid_authenticator_data?
93
104
  authenticator_data.valid?
105
+ rescue WebAuthn::AuthenticatorDataFormatError
106
+ false
94
107
  end
95
108
 
96
109
  def valid_user_presence?
@@ -102,7 +115,7 @@ module WebAuthn
102
115
  end
103
116
 
104
117
  def rp_id_from_origin(expected_origin)
105
- URI.parse(expected_origin).host
118
+ URI.parse(expected_origin.first).host if expected_origin.size == 1
106
119
  end
107
120
 
108
121
  def type
@@ -49,12 +49,10 @@ module WebAuthn
49
49
 
50
50
  def data
51
51
  @data ||=
52
- begin
53
- if client_data_json
54
- JSON.parse(client_data_json)
55
- else
56
- raise ClientDataMissingError, "Client Data JSON is missing"
57
- end
52
+ if client_data_json
53
+ JSON.parse(client_data_json)
54
+ else
55
+ raise ClientDataMissingError, "Client Data JSON is missing"
58
56
  end
59
57
  end
60
58
  end
@@ -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,53 @@ 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
16
+ extend Forwardable
22
17
 
23
- DEFAULT_ALGORITHMS = ["ES256", if_pss_supported("PS256"), "RS256"].compact.freeze
18
+ def_delegators :@relying_party,
19
+ :algorithms,
20
+ :algorithms=,
21
+ :encoding,
22
+ :encoding=,
23
+ :origin,
24
+ :origin=,
25
+ :allowed_origins,
26
+ :allowed_origins=,
27
+ :verify_attestation_statement,
28
+ :verify_attestation_statement=,
29
+ :credential_options_timeout,
30
+ :credential_options_timeout=,
31
+ :silent_authentication,
32
+ :silent_authentication=,
33
+ :acceptable_attestation_types,
34
+ :acceptable_attestation_types=,
35
+ :attestation_root_certificates_finders,
36
+ :attestation_root_certificates_finders=,
37
+ :encoder,
38
+ :encoder=,
39
+ :legacy_u2f_appid,
40
+ :legacy_u2f_appid=
24
41
 
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
42
+ attr_reader :relying_party
35
43
 
36
44
  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 = []
45
+ @relying_party = RelyingParty.new
44
46
  end
45
47
 
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)
48
+ def rp_name
49
+ relying_party.name
50
50
  end
51
51
 
52
- def attestation_root_certificates_finders=(finders)
53
- if !finders.respond_to?(:each)
54
- finders = [finders]
55
- end
52
+ def rp_name=(name)
53
+ relying_party.name = name
54
+ end
56
55
 
57
- finders.each do |finder|
58
- unless finder.respond_to?(:find)
59
- raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
60
- end
61
- end
56
+ def rp_id
57
+ relying_party.id
58
+ end
62
59
 
63
- @attestation_root_certificates_finders = finders
60
+ def rp_id=(id)
61
+ relying_party.id = id
64
62
  end
65
63
  end
66
64
  end