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
@@ -15,12 +15,12 @@ module WebAuthn
15
15
  WebAuthn::PublicKeyCredential::RequestOptions.new(**keyword_arguments)
16
16
  end
17
17
 
18
- def self.from_create(credential)
19
- WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential)
18
+ def self.from_create(credential, relying_party: WebAuthn.configuration.relying_party)
19
+ WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential, relying_party: relying_party)
20
20
  end
21
21
 
22
- def self.from_get(credential)
23
- WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential)
22
+ def self.from_get(credential, relying_party: WebAuthn.configuration.relying_party)
23
+ WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential, relying_party: relying_party)
24
24
  end
25
25
  end
26
26
  end
@@ -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
@@ -20,9 +20,12 @@ module WebAuthn
20
20
  def encode(data)
21
21
  case encoding
22
22
  when :base64
23
- Base64.strict_encode64(data)
23
+ [data].pack("m0") # Base64.strict_encode64(data)
24
24
  when :base64url
25
- Base64.urlsafe_encode64(data, padding: false)
25
+ data = [data].pack("m0") # Base64.urlsafe_encode64(data, padding: false)
26
+ data.chomp!("==") or data.chomp!("=")
27
+ data.tr!("+/", "-_")
28
+ data
26
29
  when nil, false
27
30
  data
28
31
  else
@@ -33,9 +36,15 @@ module WebAuthn
33
36
  def decode(data)
34
37
  case encoding
35
38
  when :base64
36
- Base64.strict_decode64(data)
39
+ data.unpack1("m0") # Base64.strict_decode64(data)
37
40
  when :base64url
38
- Base64.urlsafe_decode64(data)
41
+ if !data.end_with?("=") && data.length % 4 != 0 # Base64.urlsafe_decode64(data)
42
+ data = data.ljust((data.length + 3) & ~3, "=")
43
+ data.tr!("-_", "+/")
44
+ else
45
+ data = data.tr("-_", "+/")
46
+ end
47
+ data.unpack1("m0")
39
48
  when nil, false
40
49
  data
41
50
  else
@@ -13,8 +13,11 @@ 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
- sign_count: 0
19
+ sign_count: 0,
20
+ extensions: nil
18
21
  )
19
22
  @client_data_hash = client_data_hash
20
23
  @rp_id_hash = rp_id_hash
@@ -22,8 +25,11 @@ module WebAuthn
22
25
  @credential_key = credential_key
23
26
  @user_present = user_present
24
27
  @user_verified = user_verified
28
+ @backup_eligibility = backup_eligibility
29
+ @backup_state = backup_state
25
30
  @attested_credential_data = attested_credential_data
26
31
  @sign_count = sign_count
32
+ @extensions = extensions
27
33
  end
28
34
 
29
35
  def serialize
@@ -43,8 +49,11 @@ module WebAuthn
43
49
  :credential_key,
44
50
  :user_present,
45
51
  :user_verified,
52
+ :backup_eligibility,
53
+ :backup_state,
46
54
  :attested_credential_data,
47
- :sign_count
55
+ :sign_count,
56
+ :extensions
48
57
  )
49
58
 
50
59
  def authenticator_data
@@ -52,7 +61,7 @@ module WebAuthn
52
61
  begin
53
62
  credential_data =
54
63
  if attested_credential_data
55
- { id: credential_id, public_key: credential_key.public_key }
64
+ { id: credential_id, public_key: credential_public_key }
56
65
  end
57
66
 
58
67
  AuthenticatorData.new(
@@ -60,10 +69,22 @@ module WebAuthn
60
69
  credential: credential_data,
61
70
  user_present: user_present,
62
71
  user_verified: user_verified,
63
- sign_count: 0
72
+ backup_eligibility: backup_eligibility,
73
+ backup_state: backup_state,
74
+ sign_count: 0,
75
+ extensions: extensions
64
76
  )
65
77
  end
66
78
  end
79
+
80
+ def credential_public_key
81
+ case credential_key
82
+ when OpenSSL::PKey::RSA, OpenSSL::PKey::EC
83
+ credential_key.public_key
84
+ when OpenSSL::PKey::PKey
85
+ OpenSSL::PKey.read(credential_key.public_to_der)
86
+ end
87
+ end
67
88
  end
68
89
  end
69
90
  end
@@ -15,11 +15,13 @@ module WebAuthn
15
15
  rp_id_hash:,
16
16
  credential: {
17
17
  id: SecureRandom.random_bytes(16),
18
- public_key: OpenSSL::PKey::EC.new("prime256v1").generate_key.public_key
18
+ public_key: OpenSSL::PKey::EC.generate("prime256v1").public_key
19
19
  },
20
20
  sign_count: 0,
21
21
  user_present: true,
22
22
  user_verified: !user_present,
23
+ backup_eligibility: false,
24
+ backup_state: false,
23
25
  aaguid: AAGUID,
24
26
  extensions: { "fakeExtension" => "fakeExtensionValue" }
25
27
  )
@@ -28,6 +30,8 @@ module WebAuthn
28
30
  @sign_count = sign_count
29
31
  @user_present = user_present
30
32
  @user_verified = user_verified
33
+ @backup_eligibility = backup_eligibility
34
+ @backup_state = backup_state
31
35
  @aaguid = aaguid
32
36
  @extensions = extensions
33
37
  end
@@ -38,7 +42,13 @@ module WebAuthn
38
42
 
39
43
  private
40
44
 
41
- attr_reader :rp_id_hash, :credential, :user_present, :user_verified, :extensions
45
+ attr_reader :rp_id_hash,
46
+ :credential,
47
+ :user_present,
48
+ :user_verified,
49
+ :extensions,
50
+ :backup_eligibility,
51
+ :backup_state
42
52
 
43
53
  def flags
44
54
  [
@@ -46,8 +56,8 @@ module WebAuthn
46
56
  bit(:user_present),
47
57
  reserved_for_future_use_bit,
48
58
  bit(:user_verified),
49
- reserved_for_future_use_bit,
50
- reserved_for_future_use_bit,
59
+ bit(:backup_eligibility),
60
+ bit(:backup_state),
51
61
  reserved_for_future_use_bit,
52
62
  attested_credential_data_included_bit,
53
63
  extension_data_included_bit
@@ -108,15 +118,19 @@ module WebAuthn
108
118
  end
109
119
 
110
120
  def context
111
- { user_present: user_present, user_verified: user_verified }
121
+ {
122
+ user_present: user_present,
123
+ user_verified: user_verified,
124
+ backup_eligibility: backup_eligibility,
125
+ backup_state: backup_state
126
+ }
112
127
  end
113
128
 
114
129
  def cose_credential_public_key
115
130
  case credential[:public_key]
116
131
  when OpenSSL::PKey::RSA
117
132
  key = COSE::Key::RSA.from_pkey(credential[:public_key])
118
- # FIXME: Remove once writer in cose
119
- key.instance_variable_set(:@alg, -257)
133
+ key.alg = -257
120
134
  when OpenSSL::PKey::EC::Point
121
135
  alg = {
122
136
  COSE::Key::Curve.by_name("P-256").id => -7,
@@ -125,9 +139,10 @@ module WebAuthn
125
139
  }
126
140
 
127
141
  key = COSE::Key::EC2.from_pkey(credential[:public_key])
128
- # FIXME: Remove once writer in cose
129
- key.instance_variable_set(:@alg, alg[key.crv])
130
-
142
+ key.alg = alg[key.crv]
143
+ when OpenSSL::PKey::PKey
144
+ key = COSE::Key::OKP.from_pkey(credential[:public_key])
145
+ key.alg = -8
131
146
  end
132
147
 
133
148
  key.serialize
@@ -17,10 +17,14 @@ module WebAuthn
17
17
  client_data_hash:,
18
18
  user_present: true,
19
19
  user_verified: false,
20
+ backup_eligibility: false,
21
+ backup_state: false,
20
22
  attested_credential_data: true,
21
- sign_count: nil
23
+ algorithm: nil,
24
+ sign_count: nil,
25
+ extensions: nil
22
26
  )
23
- credential_id, credential_key, credential_sign_count = new_credential
27
+ credential_id, credential_key, credential_sign_count = new_credential(algorithm)
24
28
  sign_count ||= credential_sign_count
25
29
 
26
30
  credentials[rp_id] ||= {}
@@ -36,8 +40,11 @@ module WebAuthn
36
40
  credential_key: credential_key,
37
41
  user_present: user_present,
38
42
  user_verified: user_verified,
43
+ backup_eligibility: backup_eligibility,
44
+ backup_state: backup_state,
39
45
  attested_credential_data: attested_credential_data,
40
- sign_count: sign_count
46
+ sign_count: sign_count,
47
+ extensions: extensions
41
48
  ).serialize
42
49
  end
43
50
 
@@ -46,13 +53,24 @@ module WebAuthn
46
53
  client_data_hash:,
47
54
  user_present: true,
48
55
  user_verified: false,
56
+ backup_eligibility: false,
57
+ backup_state: false,
49
58
  aaguid: AuthenticatorData::AAGUID,
50
- sign_count: nil
59
+ sign_count: nil,
60
+ extensions: nil,
61
+ allow_credentials: nil
51
62
  )
52
63
  credential_options = credentials[rp_id]
53
64
 
54
65
  if credential_options
55
- credential_id, credential = credential_options.first
66
+ allow_credentials ||= credential_options.keys
67
+ credential_id = (credential_options.keys & allow_credentials).first
68
+ unless credential_id
69
+ raise "No matching credentials (allowed=#{allow_credentials}) " \
70
+ "found for RP #{rp_id} among credentials=#{credential_options}"
71
+ end
72
+
73
+ credential = credential_options[credential_id]
56
74
  credential_key = credential[:credential_key]
57
75
  credential_sign_count = credential[:sign_count]
58
76
 
@@ -60,12 +78,22 @@ module WebAuthn
60
78
  rp_id_hash: hashed(rp_id),
61
79
  user_present: user_present,
62
80
  user_verified: user_verified,
81
+ backup_eligibility: backup_eligibility,
82
+ backup_state: backup_state,
63
83
  aaguid: aaguid,
64
84
  credential: nil,
65
85
  sign_count: sign_count || credential_sign_count,
86
+ extensions: extensions
66
87
  ).serialize
67
88
 
68
- signature = credential_key.sign("SHA256", authenticator_data + client_data_hash)
89
+ signature_digest_algorithm =
90
+ case credential_key
91
+ when OpenSSL::PKey::RSA, OpenSSL::PKey::EC
92
+ 'SHA256'
93
+ when OpenSSL::PKey::PKey
94
+ nil
95
+ end
96
+ signature = credential_key.sign(signature_digest_algorithm, authenticator_data + client_data_hash)
69
97
  credential[:sign_count] += 1
70
98
 
71
99
  {
@@ -82,8 +110,21 @@ module WebAuthn
82
110
 
83
111
  attr_reader :credentials
84
112
 
85
- def new_credential
86
- [SecureRandom.random_bytes(16), OpenSSL::PKey::EC.new("prime256v1").generate_key, 0]
113
+ def new_credential(algorithm)
114
+ algorithm ||= 'ES256'
115
+ credential_key =
116
+ case algorithm
117
+ when 'ES256'
118
+ OpenSSL::PKey::EC.generate('prime256v1')
119
+ when 'RS256'
120
+ OpenSSL::PKey::RSA.new(2048)
121
+ when 'EdDSA'
122
+ OpenSSL::PKey.generate_key("ED25519")
123
+ else
124
+ raise "Unsupported algorithm #{algorithm}"
125
+ end
126
+
127
+ [SecureRandom.random_bytes(16), credential_key, 0]
87
128
  end
88
129
 
89
130
  def hashed(target)
@@ -10,7 +10,7 @@ module WebAuthn
10
10
  class FakeClient
11
11
  TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
12
12
 
13
- attr_reader :origin, :token_binding
13
+ attr_reader :origin, :token_binding, :encoding
14
14
 
15
15
  def initialize(
16
16
  origin = fake_origin,
@@ -29,7 +29,11 @@ module WebAuthn
29
29
  rp_id: nil,
30
30
  user_present: true,
31
31
  user_verified: false,
32
- attested_credential_data: true
32
+ backup_eligibility: false,
33
+ backup_state: false,
34
+ attested_credential_data: true,
35
+ credential_algorithm: nil,
36
+ extensions: nil
33
37
  )
34
38
  rp_id ||= URI.parse(origin).host
35
39
 
@@ -41,12 +45,19 @@ module WebAuthn
41
45
  client_data_hash: client_data_hash,
42
46
  user_present: user_present,
43
47
  user_verified: user_verified,
44
- attested_credential_data: attested_credential_data
48
+ backup_eligibility: backup_eligibility,
49
+ backup_state: backup_state,
50
+ attested_credential_data: attested_credential_data,
51
+ algorithm: credential_algorithm,
52
+ extensions: extensions
45
53
  )
46
54
 
47
55
  id =
48
56
  if attested_credential_data
49
- WebAuthn::AuthenticatorData.new(CBOR.decode(attestation_object)["authData"]).credential.id
57
+ WebAuthn::AuthenticatorData
58
+ .deserialize(CBOR.decode(attestation_object)["authData"])
59
+ .attested_credential_data
60
+ .id
50
61
  else
51
62
  "id-for-pk-without-attested-credential-data"
52
63
  end
@@ -55,43 +66,65 @@ module WebAuthn
55
66
  "type" => "public-key",
56
67
  "id" => internal_encoder.encode(id),
57
68
  "rawId" => encoder.encode(id),
69
+ "authenticatorAttachment" => 'platform',
70
+ "clientExtensionResults" => extensions,
58
71
  "response" => {
59
72
  "attestationObject" => encoder.encode(attestation_object),
60
- "clientDataJSON" => encoder.encode(client_data_json)
73
+ "clientDataJSON" => encoder.encode(client_data_json),
74
+ "transports" => ["internal"],
61
75
  }
62
76
  }
63
77
  end
64
78
 
65
- def get(challenge: fake_challenge, rp_id: nil, user_present: true, user_verified: false, sign_count: nil)
79
+ def get(challenge: fake_challenge,
80
+ rp_id: nil,
81
+ user_present: true,
82
+ user_verified: false,
83
+ backup_eligibility: false,
84
+ backup_state: true,
85
+ sign_count: nil,
86
+ extensions: nil,
87
+ user_handle: nil,
88
+ allow_credentials: nil)
66
89
  rp_id ||= URI.parse(origin).host
67
90
 
68
91
  client_data_json = data_json_for(:get, encoder.decode(challenge))
69
92
  client_data_hash = hashed(client_data_json)
70
93
 
94
+ if allow_credentials
95
+ allow_credentials = allow_credentials.map { |credential| encoder.decode(credential) }
96
+ end
97
+
71
98
  assertion = authenticator.get_assertion(
72
99
  rp_id: rp_id,
73
100
  client_data_hash: client_data_hash,
74
101
  user_present: user_present,
75
102
  user_verified: user_verified,
103
+ backup_eligibility: backup_eligibility,
104
+ backup_state: backup_state,
76
105
  sign_count: sign_count,
106
+ extensions: extensions,
107
+ allow_credentials: allow_credentials
77
108
  )
78
109
 
79
110
  {
80
111
  "type" => "public-key",
81
112
  "id" => internal_encoder.encode(assertion[:credential_id]),
82
113
  "rawId" => encoder.encode(assertion[:credential_id]),
114
+ "clientExtensionResults" => extensions,
115
+ "authenticatorAttachment" => 'platform',
83
116
  "response" => {
84
117
  "clientDataJSON" => encoder.encode(client_data_json),
85
118
  "authenticatorData" => encoder.encode(assertion[:authenticator_data]),
86
119
  "signature" => encoder.encode(assertion[:signature]),
87
- "userHandle" => nil
120
+ "userHandle" => user_handle ? encoder.encode(user_handle) : nil
88
121
  }
89
122
  }
90
123
  end
91
124
 
92
125
  private
93
126
 
94
- attr_reader :authenticator, :encoding
127
+ attr_reader :authenticator
95
128
 
96
129
  def data_json_for(method, challenge)
97
130
  data = {
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebAuthn
4
+ module JSONSerializer
5
+ # Argument wildcard for Ruby on Rails controller automatic object JSON serialization
6
+ def as_json(*)
7
+ deep_camelize_keys(to_hash)
8
+ end
9
+
10
+ private
11
+
12
+ def to_hash
13
+ attributes.each_with_object({}) do |attribute_name, hash|
14
+ value = send(attribute_name)
15
+
16
+ if value.respond_to?(:as_json)
17
+ value = value.as_json
18
+ end
19
+
20
+ if value
21
+ hash[attribute_name] = value
22
+ end
23
+ end
24
+ end
25
+
26
+ def deep_camelize_keys(object)
27
+ case object
28
+ when Hash
29
+ object.each_with_object({}) do |(key, value), result|
30
+ result[camelize(key)] = deep_camelize_keys(value)
31
+ end
32
+ when Array
33
+ object.map { |element| deep_camelize_keys(element) }
34
+ else
35
+ object
36
+ end
37
+ end
38
+
39
+ def camelize(term)
40
+ first_term, *rest = term.to_s.split('_')
41
+
42
+ [first_term, *rest.map(&:capitalize)].join.to_sym
43
+ end
44
+ end
45
+ end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "webauthn/attestation_statement/fido_u2f/public_key"
4
- require "cose/key"
5
3
  require "cose/algorithm"
4
+ require "cose/error"
5
+ require "cose/key"
6
+ require "cose/rsapkcs1_algorithm"
7
+ require "webauthn/attestation_statement/fido_u2f/public_key"
6
8
 
7
9
  module WebAuthn
8
10
  class PublicKey
11
+ class UnsupportedAlgorithm < Error; end
12
+
9
13
  def self.deserialize(public_key)
10
14
  cose_key =
11
15
  if WebAuthn::AttestationStatement::FidoU2f::PublicKey.uncompressed_point?(public_key)
@@ -45,5 +49,20 @@ module WebAuthn
45
49
  def alg
46
50
  @cose_key.alg
47
51
  end
52
+
53
+ def verify(signature, verification_data)
54
+ cose_algorithm.verify(pkey, signature, verification_data)
55
+ rescue COSE::Error
56
+ false
57
+ end
58
+
59
+ private
60
+
61
+ def cose_algorithm
62
+ @cose_algorithm ||= COSE::Algorithm.find(alg) || raise(
63
+ UnsupportedAlgorithm,
64
+ "The public key algorithm #{alg} is not among the available COSE algorithms"
65
+ )
66
+ end
48
67
  end
49
68
  end
@@ -39,8 +39,8 @@ module WebAuthn
39
39
 
40
40
  @rp =
41
41
  if rp.is_a?(Hash)
42
- rp[:name] ||= configuration.rp_name
43
- rp[:id] ||= configuration.rp_id
42
+ rp[:name] ||= relying_party.name
43
+ rp[:id] ||= relying_party.id
44
44
 
45
45
  RPEntity.new(**rp)
46
46
  else
@@ -76,7 +76,7 @@ module WebAuthn
76
76
  end
77
77
 
78
78
  def pub_key_cred_params_from_algs
79
- Array(algs || configuration.algorithms).map do |alg|
79
+ Array(algs || relying_party.algorithms).map do |alg|
80
80
  alg_id =
81
81
  if alg.is_a?(String) || alg.is_a?(Symbol)
82
82
  COSE::Algorithm.by_name(alg.to_s).id
@@ -1,43 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "awrence"
4
-
5
3
  module WebAuthn
6
4
  class PublicKeyCredential
7
5
  class Entity
8
- attr_reader :name, :icon
6
+ include JSONSerializer
9
7
 
10
- def initialize(name:, icon: nil)
11
- @name = name
12
- @icon = icon
13
- end
8
+ attr_reader :name
14
9
 
15
- def as_json
16
- to_hash.to_camelback_keys
10
+ def initialize(name:)
11
+ @name = name
17
12
  end
18
13
 
19
14
  private
20
15
 
21
- def to_hash
22
- hash = {}
23
-
24
- attributes.each do |attribute_name|
25
- value = send(attribute_name)
26
-
27
- if value.respond_to?(:as_json)
28
- value = value.as_json
29
- end
30
-
31
- if value
32
- hash[attribute_name] = value
33
- end
34
- end
35
-
36
- hash
37
- end
38
-
39
16
  def attributes
40
- [:name, :icon]
17
+ [:name]
41
18
  end
42
19
  end
43
20
  end
@@ -1,55 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "awrence"
4
3
  require "securerandom"
5
4
 
6
5
  module WebAuthn
7
6
  class PublicKeyCredential
8
7
  class Options
8
+ include JSONSerializer
9
+
9
10
  CHALLENGE_LENGTH = 32
10
11
 
11
- attr_reader :timeout, :extensions
12
+ attr_reader :timeout, :extensions, :relying_party
12
13
 
13
- def initialize(timeout: default_timeout, extensions: nil)
14
- @timeout = timeout
15
- @extensions = extensions
14
+ def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
15
+ @relying_party = relying_party
16
+ @timeout = timeout || default_timeout
17
+ @extensions = default_extensions.merge(extensions || {})
16
18
  end
17
19
 
18
20
  def challenge
19
21
  encoder.encode(raw_challenge)
20
22
  end
21
23
 
22
- # Argument wildcard for Ruby on Rails controller automatic object JSON serialization
23
- def as_json(*)
24
- to_hash.to_camelback_keys
25
- end
26
-
27
24
  private
28
25
 
29
- def to_hash
30
- hash = {}
31
-
32
- attributes.each do |attribute_name|
33
- value = send(attribute_name)
34
-
35
- if value.respond_to?(:as_json)
36
- value = value.as_json
37
- end
38
-
39
- if value
40
- hash[attribute_name] = value
41
- end
42
- end
43
-
44
- hash
45
- end
46
-
47
26
  def attributes
48
27
  [:challenge, :timeout, :extensions]
49
28
  end
50
29
 
51
30
  def encoder
52
- WebAuthn.configuration.encoder
31
+ relying_party.encoder
53
32
  end
54
33
 
55
34
  def raw_challenge
@@ -57,11 +36,11 @@ module WebAuthn
57
36
  end
58
37
 
59
38
  def default_timeout
60
- configuration.credential_options_timeout
39
+ relying_party.credential_options_timeout
61
40
  end
62
41
 
63
- def configuration
64
- WebAuthn.configuration
42
+ def default_extensions
43
+ {}
65
44
  end
66
45
 
67
46
  def as_public_key_descriptors(ids)