webauthn 2.1.0 → 2.4.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +113 -13
  3. data/.travis.yml +21 -18
  4. data/Appraisals +4 -0
  5. data/CHANGELOG.md +41 -0
  6. data/CONTRIBUTING.md +0 -5
  7. data/README.md +70 -8
  8. data/SECURITY.md +6 -4
  9. data/gemfiles/openssl_2_2.gemfile +7 -0
  10. data/lib/cose/rsapkcs1_algorithm.rb +50 -0
  11. data/lib/webauthn/attestation_object.rb +43 -0
  12. data/lib/webauthn/attestation_statement.rb +20 -20
  13. data/lib/webauthn/attestation_statement/android_key.rb +28 -30
  14. data/lib/webauthn/attestation_statement/android_safetynet.rb +27 -7
  15. data/lib/webauthn/attestation_statement/base.rb +108 -10
  16. data/lib/webauthn/attestation_statement/fido_u2f.rb +8 -6
  17. data/lib/webauthn/attestation_statement/none.rb +7 -1
  18. data/lib/webauthn/attestation_statement/packed.rb +13 -41
  19. data/lib/webauthn/attestation_statement/tpm.rb +38 -75
  20. data/lib/webauthn/authenticator_assertion_response.rb +3 -7
  21. data/lib/webauthn/authenticator_attestation_response.rb +19 -84
  22. data/lib/webauthn/authenticator_data.rb +51 -51
  23. data/lib/webauthn/authenticator_data/attested_credential_data.rb +29 -50
  24. data/lib/webauthn/authenticator_response.rb +3 -0
  25. data/lib/webauthn/credential_creation_options.rb +2 -0
  26. data/lib/webauthn/credential_request_options.rb +2 -0
  27. data/lib/webauthn/fake_authenticator.rb +7 -3
  28. data/lib/webauthn/fake_authenticator/attestation_object.rb +7 -3
  29. data/lib/webauthn/fake_authenticator/authenticator_data.rb +2 -4
  30. data/lib/webauthn/fake_client.rb +19 -5
  31. data/lib/webauthn/public_key.rb +21 -2
  32. data/lib/webauthn/public_key_credential.rb +13 -3
  33. data/lib/webauthn/u2f_migrator.rb +5 -4
  34. data/lib/webauthn/version.rb +1 -1
  35. data/script/ci/install-openssl +7 -0
  36. data/script/ci/install-ruby +13 -0
  37. data/webauthn.gemspec +13 -9
  38. metadata +54 -41
  39. data/lib/android_safetynet/attestation_response.rb +0 -116
  40. data/lib/cose/rsassa_algorithm.rb +0 -10
  41. data/lib/tpm/constants.rb +0 -44
  42. data/lib/tpm/s_attest.rb +0 -26
  43. data/lib/tpm/s_attest/s_certify_info.rb +0 -14
  44. data/lib/tpm/sized_buffer.rb +0 -13
  45. data/lib/tpm/t_public.rb +0 -32
  46. data/lib/tpm/t_public/s_ecc_parms.rb +0 -17
  47. data/lib/tpm/t_public/s_rsa_parms.rb +0 -17
  48. data/lib/webauthn/attestation_statement/android_key/authorization_list.rb +0 -39
  49. data/lib/webauthn/attestation_statement/android_key/key_description.rb +0 -37
  50. data/lib/webauthn/attestation_statement/tpm/cert_info.rb +0 -44
  51. data/lib/webauthn/attestation_statement/tpm/pub_area.rb +0 -85
  52. data/lib/webauthn/signature_verifier.rb +0 -77
data/SECURITY.md CHANGED
@@ -4,11 +4,13 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------- | ------------------ |
7
- | 2.1.z | :white_check_mark: |
8
- | 2.0.z | :white_check_mark: |
7
+ | 2.4.z | :white_check_mark: |
8
+ | 2.3.z | :white_check_mark: |
9
+ | 2.2.z | :white_check_mark: |
10
+ | 2.1.z | :x: |
11
+ | 2.0.z | :x: |
9
12
  | 1.18.z | :white_check_mark: |
10
- | 1.17.z | :white_check_mark: |
11
- | < 1.17 | :x: |
13
+ | < 1.18 | :x: |
12
14
 
13
15
  ## Reporting a Vulnerability
14
16
 
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "openssl", "~> 2.2.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cose"
4
+ require "cose/algorithm/signature_algorithm"
5
+ require "cose/error"
6
+ require "cose/key/rsa"
7
+ require "openssl/signature_algorithm/rsapkcs1"
8
+
9
+ class RSAPKCS1Algorithm < COSE::Algorithm::SignatureAlgorithm
10
+ attr_reader :hash_function
11
+
12
+ def initialize(*args, hash_function:)
13
+ super(*args)
14
+
15
+ @hash_function = hash_function
16
+ end
17
+
18
+ private
19
+
20
+ def signature_algorithm_class
21
+ OpenSSL::SignatureAlgorithm::RSAPKCS1
22
+ end
23
+
24
+ def valid_key?(key)
25
+ to_cose_key(key).is_a?(COSE::Key::RSA)
26
+ end
27
+
28
+ def to_pkey(key)
29
+ case key
30
+ when COSE::Key::RSA
31
+ key.to_pkey
32
+ when OpenSSL::PKey::RSA
33
+ key
34
+ else
35
+ raise(COSE::Error, "Incompatible key for algorithm")
36
+ end
37
+ end
38
+ end
39
+
40
+ COSE::Algorithm.register(RSAPKCS1Algorithm.new(-257, "RS256", hash_function: "SHA256"))
41
+ COSE::Algorithm.register(RSAPKCS1Algorithm.new(-258, "RS384", hash_function: "SHA384"))
42
+ COSE::Algorithm.register(RSAPKCS1Algorithm.new(-259, "RS512", hash_function: "SHA512"))
43
+
44
+ # Patch openssl-signature_algorithm gem to support discouraged/deprecated RSA-PKCS#1 with SHA-1
45
+ # (RS1 in JOSE/COSE terminology) algorithm needed for WebAuthn.
46
+ OpenSSL::SignatureAlgorithm::RSAPKCS1.const_set(
47
+ :ACCEPTED_HASH_FUNCTIONS,
48
+ OpenSSL::SignatureAlgorithm::RSAPKCS1::ACCEPTED_HASH_FUNCTIONS + ["SHA1"]
49
+ )
50
+ COSE::Algorithm.register(RSAPKCS1Algorithm.new(-65535, "RS1", hash_function: "SHA1"))
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cbor"
4
+ require "forwardable"
5
+ require "openssl"
6
+ require "webauthn/attestation_statement"
7
+ require "webauthn/authenticator_data"
8
+
9
+ module WebAuthn
10
+ class AttestationObject
11
+ extend Forwardable
12
+
13
+ def self.deserialize(attestation_object)
14
+ from_map(CBOR.decode(attestation_object))
15
+ end
16
+
17
+ def self.from_map(map)
18
+ new(
19
+ authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
20
+ attestation_statement: WebAuthn::AttestationStatement.from(map["fmt"], map["attStmt"])
21
+ )
22
+ end
23
+
24
+ attr_reader :authenticator_data, :attestation_statement
25
+
26
+ def initialize(authenticator_data:, attestation_statement:)
27
+ @authenticator_data = authenticator_data
28
+ @attestation_statement = attestation_statement
29
+ end
30
+
31
+ def valid_attested_credential?
32
+ authenticator_data.attested_credential_data_included? &&
33
+ authenticator_data.attested_credential_data.valid?
34
+ end
35
+
36
+ def valid_attestation_statement?(client_data_hash)
37
+ attestation_statement.valid?(authenticator_data, client_data_hash)
38
+ end
39
+
40
+ def_delegators :authenticator_data, :credential, :aaguid
41
+ def_delegators :attestation_statement, :attestation_certificate_key_id
42
+ end
43
+ end
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "webauthn/attestation_statement/android_key"
4
+ require "webauthn/attestation_statement/android_safetynet"
5
+ require "webauthn/attestation_statement/fido_u2f"
6
+ require "webauthn/attestation_statement/none"
7
+ require "webauthn/attestation_statement/packed"
8
+ require "webauthn/attestation_statement/tpm"
3
9
  require "webauthn/error"
4
10
 
5
11
  module WebAuthn
@@ -13,28 +19,22 @@ module WebAuthn
13
19
  ATTESTATION_FORMAT_ANDROID_KEY = "android-key"
14
20
  ATTESTATION_FORMAT_TPM = "tpm"
15
21
 
22
+ FORMAT_TO_CLASS = {
23
+ ATTESTATION_FORMAT_NONE => WebAuthn::AttestationStatement::None,
24
+ ATTESTATION_FORMAT_FIDO_U2F => WebAuthn::AttestationStatement::FidoU2f,
25
+ ATTESTATION_FORMAT_PACKED => WebAuthn::AttestationStatement::Packed,
26
+ ATTESTATION_FORMAT_ANDROID_SAFETYNET => WebAuthn::AttestationStatement::AndroidSafetynet,
27
+ ATTESTATION_FORMAT_ANDROID_KEY => WebAuthn::AttestationStatement::AndroidKey,
28
+ ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
29
+ }.freeze
30
+
16
31
  def self.from(format, statement)
17
- case format
18
- when ATTESTATION_FORMAT_NONE
19
- require "webauthn/attestation_statement/none"
20
- WebAuthn::AttestationStatement::None.new(statement)
21
- when ATTESTATION_FORMAT_FIDO_U2F
22
- require "webauthn/attestation_statement/fido_u2f"
23
- WebAuthn::AttestationStatement::FidoU2f.new(statement)
24
- when ATTESTATION_FORMAT_PACKED
25
- require "webauthn/attestation_statement/packed"
26
- WebAuthn::AttestationStatement::Packed.new(statement)
27
- when ATTESTATION_FORMAT_ANDROID_SAFETYNET
28
- require "webauthn/attestation_statement/android_safetynet"
29
- WebAuthn::AttestationStatement::AndroidSafetynet.new(statement)
30
- when ATTESTATION_FORMAT_ANDROID_KEY
31
- require "webauthn/attestation_statement/android_key"
32
- WebAuthn::AttestationStatement::AndroidKey.new(statement)
33
- when ATTESTATION_FORMAT_TPM
34
- require "webauthn/attestation_statement/tpm"
35
- WebAuthn::AttestationStatement::TPM.new(statement)
32
+ klass = FORMAT_TO_CLASS[format]
33
+
34
+ if klass
35
+ klass.new(statement)
36
36
  else
37
- raise FormatNotSupportedError, "Unsupported attestation format '#{format}'"
37
+ raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
38
38
  end
39
39
  end
40
40
  end
@@ -1,73 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "android_key_attestation"
3
4
  require "openssl"
4
- require "webauthn/attestation_statement/android_key/key_description"
5
5
  require "webauthn/attestation_statement/base"
6
- require "webauthn/security_utils"
7
- require "webauthn/signature_verifier"
8
6
 
9
7
  module WebAuthn
10
8
  module AttestationStatement
11
9
  class AndroidKey < Base
12
- EXTENSION_DATA_OID = "1.3.6.1.4.1.11129.2.1.17"
13
-
14
- # https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/keymaster_defs.h
15
- KM_ORIGIN_GENERATED = 0
16
- KM_PURPOSE_SIGN = 2
17
-
18
10
  def valid?(authenticator_data, client_data_hash)
19
11
  valid_signature?(authenticator_data, client_data_hash) &&
20
12
  matching_public_key?(authenticator_data) &&
21
13
  valid_attestation_challenge?(client_data_hash) &&
22
- all_applications_field_not_present? &&
14
+ all_applications_fields_not_set? &&
23
15
  valid_authorization_list_origin? &&
24
16
  valid_authorization_list_purpose? &&
25
- [WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC, attestation_trust_path]
17
+ trustworthy?(aaguid: authenticator_data.aaguid) &&
18
+ [attestation_type, attestation_trust_path]
26
19
  end
27
20
 
28
21
  private
29
22
 
30
- def valid_signature?(authenticator_data, client_data_hash)
31
- WebAuthn::SignatureVerifier
32
- .new(algorithm, attestation_certificate.public_key)
33
- .verify(signature, authenticator_data.data + client_data_hash)
34
- end
35
-
36
23
  def matching_public_key?(authenticator_data)
37
24
  attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
38
25
  end
39
26
 
40
27
  def valid_attestation_challenge?(client_data_hash)
41
- WebAuthn::SecurityUtils.secure_compare(key_description.attestation_challenge, client_data_hash)
28
+ android_key_attestation.verify_challenge(client_data_hash)
29
+ rescue AndroidKeyAttestation::ChallengeMismatchError
30
+ false
42
31
  end
43
32
 
44
- def all_applications_field_not_present?
45
- tee_enforced.all_applications.nil? && software_enforced.all_applications.nil?
33
+ def valid_certificate_chain?(aaguid: nil, **_)
34
+ android_key_attestation.verify_certificate_chain(root_certificates: root_certificates(aaguid: aaguid))
35
+ rescue AndroidKeyAttestation::CertificateVerificationError
36
+ false
37
+ end
38
+
39
+ def all_applications_fields_not_set?
40
+ !tee_enforced.all_applications && !software_enforced.all_applications
46
41
  end
47
42
 
48
43
  def valid_authorization_list_origin?
49
- tee_enforced.origin == KM_ORIGIN_GENERATED || software_enforced.origin == KM_ORIGIN_GENERATED
44
+ tee_enforced.origin == :generated || software_enforced.origin == :generated
50
45
  end
51
46
 
52
47
  def valid_authorization_list_purpose?
53
- tee_enforced.purpose == KM_PURPOSE_SIGN || software_enforced.purpose == KM_PURPOSE_SIGN
48
+ tee_enforced.purpose == [:sign] || software_enforced.purpose == [:sign]
54
49
  end
55
50
 
56
51
  def tee_enforced
57
- key_description.tee_enforced
52
+ android_key_attestation.tee_enforced
58
53
  end
59
54
 
60
55
  def software_enforced
61
- key_description.software_enforced
56
+ android_key_attestation.software_enforced
57
+ end
58
+
59
+ def attestation_type
60
+ WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC
62
61
  end
63
62
 
64
- def key_description
65
- @key_description ||= begin
66
- extension_data = attestation_certificate.extensions.detect { |ext| ext.oid == EXTENSION_DATA_OID }
67
- raw_key_description = OpenSSL::ASN1.decode(extension_data).value.last
63
+ def default_root_certificates
64
+ AndroidKeyAttestation::Statement::GOOGLE_ROOT_CERTIFICATES
65
+ end
68
66
 
69
- KeyDescription.new(OpenSSL::ASN1.decode(raw_key_description.value).value)
70
- end
67
+ def android_key_attestation
68
+ @android_key_attestation ||= AndroidKeyAttestation::Statement.new(*certificates)
71
69
  end
72
70
  end
73
71
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "android_safetynet/attestation_response"
3
+ require "safety_net_attestation"
4
4
  require "openssl"
5
5
  require "webauthn/attestation_statement/base"
6
6
 
@@ -12,11 +12,12 @@ module WebAuthn
12
12
  valid_response?(authenticator_data, client_data_hash) &&
13
13
  valid_version? &&
14
14
  cts_profile_match? &&
15
- [WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC, attestation_trust_path]
15
+ trustworthy?(aaguid: authenticator_data.aaguid) &&
16
+ [attestation_type, attestation_trust_path]
16
17
  end
17
18
 
18
19
  def attestation_certificate
19
- attestation_response.leaf_certificate
20
+ attestation_trust_path.first
20
21
  end
21
22
 
22
23
  private
@@ -25,8 +26,9 @@ module WebAuthn
25
26
  nonce = Digest::SHA256.base64digest(authenticator_data.data + client_data_hash)
26
27
 
27
28
  begin
28
- attestation_response.verify(nonce, trustworthiness: false)
29
- rescue ::AndroidSafetynet::AttestationResponse::VerificationError
29
+ attestation_response
30
+ .verify(nonce, trusted_certificates: root_certificates(aaguid: authenticator_data.aaguid), time: time)
31
+ rescue SafetyNetAttestation::Error
30
32
  false
31
33
  end
32
34
  end
@@ -40,12 +42,30 @@ module WebAuthn
40
42
  attestation_response.cts_profile_match?
41
43
  end
42
44
 
45
+ def valid_certificate_chain?(**_)
46
+ # Already performed as part of #valid_response?
47
+ true
48
+ end
49
+
50
+ def attestation_type
51
+ WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC
52
+ end
53
+
54
+ # SafetyNetAttestation returns full chain including root, WebAuthn expects only the x5c certificates
43
55
  def attestation_trust_path
44
- attestation_response.certificate_chain
56
+ attestation_response.certificate_chain[0..-2]
45
57
  end
46
58
 
47
59
  def attestation_response
48
- @attestation_response ||= ::AndroidSafetynet::AttestationResponse.new(statement["response"])
60
+ @attestation_response ||= SafetyNetAttestation::Statement.new(statement["response"])
61
+ end
62
+
63
+ def default_root_certificates
64
+ SafetyNetAttestation::Statement::GOOGLE_ROOT_CERTIFICATES
65
+ end
66
+
67
+ def time
68
+ Time.now
49
69
  end
50
70
  end
51
71
  end
@@ -1,21 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cose/algorithm"
4
+ require "cose/error"
5
+ require "cose/rsapkcs1_algorithm"
3
6
  require "openssl"
4
7
  require "webauthn/authenticator_data/attested_credential_data"
5
8
  require "webauthn/error"
6
9
 
7
10
  module WebAuthn
8
11
  module AttestationStatement
12
+ class UnsupportedAlgorithm < Error; end
13
+
9
14
  ATTESTATION_TYPE_NONE = "None"
10
15
  ATTESTATION_TYPE_BASIC = "Basic"
11
16
  ATTESTATION_TYPE_SELF = "Self"
12
17
  ATTESTATION_TYPE_ATTCA = "AttCA"
13
- ATTESTATION_TYPE_ECDAA = "ECDAA"
14
18
  ATTESTATION_TYPE_BASIC_OR_ATTCA = "Basic_or_AttCA"
15
19
 
16
- class Base
17
- class NotSupportedError < Error; end
20
+ ATTESTATION_TYPES_WITH_ROOT = [
21
+ ATTESTATION_TYPE_BASIC,
22
+ ATTESTATION_TYPE_BASIC_OR_ATTCA,
23
+ ATTESTATION_TYPE_ATTCA
24
+ ].freeze
18
25
 
26
+ class Base
19
27
  AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
20
28
 
21
29
  def initialize(statement)
@@ -26,6 +34,10 @@ module WebAuthn
26
34
  raise NotImplementedError
27
35
  end
28
36
 
37
+ def format
38
+ WebAuthn::AttestationStatement::FORMAT_TO_CLASS.key(self.class)
39
+ end
40
+
29
41
  def attestation_certificate
30
42
  certificates&.first
31
43
  end
@@ -36,6 +48,10 @@ module WebAuthn
36
48
  end
37
49
  end
38
50
 
51
+ def attestation_certificate_key_id
52
+ raw_subject_key_identifier&.unpack("H*")&.[](0)
53
+ end
54
+
39
55
  private
40
56
 
41
57
  attr_reader :statement
@@ -53,9 +69,10 @@ module WebAuthn
53
69
  end
54
70
 
55
71
  def certificates
56
- @certificates ||= raw_certificates&.map do |raw_certificate|
57
- OpenSSL::X509::Certificate.new(raw_certificate)
58
- end
72
+ @certificates ||=
73
+ raw_certificates&.map do |raw_certificate|
74
+ OpenSSL::X509::Certificate.new(raw_certificate)
75
+ end
59
76
  end
60
77
 
61
78
  def algorithm
@@ -66,10 +83,6 @@ module WebAuthn
66
83
  statement["x5c"]
67
84
  end
68
85
 
69
- def raw_ecdaa_key_id
70
- statement["ecdaaKeyId"]
71
- end
72
-
73
86
  def signature
74
87
  statement["sig"]
75
88
  end
@@ -79,6 +92,91 @@ module WebAuthn
79
92
  certificates
80
93
  end
81
94
  end
95
+
96
+ def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
97
+ if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
98
+ configuration.acceptable_attestation_types.include?(attestation_type) &&
99
+ valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
100
+ else
101
+ configuration.acceptable_attestation_types.include?(attestation_type)
102
+ end
103
+ end
104
+
105
+ def valid_certificate_chain?(aaguid: nil, attestation_certificate_key_id: nil)
106
+ attestation_root_certificates_store(
107
+ aaguid: aaguid,
108
+ attestation_certificate_key_id: attestation_certificate_key_id
109
+ ).verify(attestation_certificate, attestation_trust_path)
110
+ end
111
+
112
+ def attestation_root_certificates_store(aaguid: nil, attestation_certificate_key_id: nil)
113
+ OpenSSL::X509::Store.new.tap do |store|
114
+ root_certificates(
115
+ aaguid: aaguid,
116
+ attestation_certificate_key_id: attestation_certificate_key_id
117
+ ).each do |cert|
118
+ store.add_cert(cert)
119
+ end
120
+ end
121
+ end
122
+
123
+ def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
124
+ root_certificates =
125
+ configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
126
+ if certs.empty?
127
+ finder.find(
128
+ attestation_format: format,
129
+ aaguid: aaguid,
130
+ attestation_certificate_key_id: attestation_certificate_key_id
131
+ ) || []
132
+ else
133
+ certs
134
+ end
135
+ end
136
+
137
+ if root_certificates.empty? && respond_to?(:default_root_certificates, true)
138
+ default_root_certificates
139
+ else
140
+ root_certificates
141
+ end
142
+ end
143
+
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
+ def valid_signature?(authenticator_data, client_data_hash, public_key = attestation_certificate.public_key)
154
+ raise("Incompatible algorithm and key") unless cose_algorithm.compatible_key?(public_key)
155
+
156
+ cose_algorithm.verify(
157
+ public_key,
158
+ signature,
159
+ verification_data(authenticator_data, client_data_hash)
160
+ )
161
+ rescue COSE::Error
162
+ false
163
+ end
164
+
165
+ def verification_data(authenticator_data, client_data_hash)
166
+ authenticator_data.data + client_data_hash
167
+ end
168
+
169
+ def cose_algorithm
170
+ @cose_algorithm ||=
171
+ COSE::Algorithm.find(algorithm).tap do |alg|
172
+ alg && configuration.algorithms.include?(alg.name) ||
173
+ raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
174
+ end
175
+ end
176
+
177
+ def configuration
178
+ WebAuthn.configuration
179
+ end
82
180
  end
83
181
  end
84
182
  end