webauthn 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +3 -7
  3. data/.github/workflows/git.yml +21 -0
  4. data/.rubocop.yml +1 -1
  5. data/CHANGELOG.md +42 -1
  6. data/README.md +5 -3
  7. data/docs/advanced_configuration.md +174 -0
  8. data/docs/u2f_migration.md +14 -20
  9. data/lib/webauthn/attestation_object.rb +9 -5
  10. data/lib/webauthn/attestation_statement/apple.rb +2 -2
  11. data/lib/webauthn/attestation_statement/base.rb +11 -25
  12. data/lib/webauthn/attestation_statement/packed.rb +1 -1
  13. data/lib/webauthn/attestation_statement/tpm.rb +2 -2
  14. data/lib/webauthn/attestation_statement.rb +2 -2
  15. data/lib/webauthn/authenticator_assertion_response.rb +4 -3
  16. data/lib/webauthn/authenticator_attestation_response.rb +10 -7
  17. data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -5
  18. data/lib/webauthn/authenticator_data.rb +10 -2
  19. data/lib/webauthn/authenticator_response.rb +7 -7
  20. data/lib/webauthn/configuration.rb +38 -38
  21. data/lib/webauthn/credential.rb +5 -4
  22. data/lib/webauthn/fake_authenticator/attestation_object.rb +8 -0
  23. data/lib/webauthn/fake_authenticator/authenticator_data.rb +20 -5
  24. data/lib/webauthn/fake_authenticator.rb +9 -1
  25. data/lib/webauthn/fake_client.rb +10 -2
  26. data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
  27. data/lib/webauthn/public_key_credential/options.rb +9 -8
  28. data/lib/webauthn/public_key_credential/request_options.rb +11 -1
  29. data/lib/webauthn/public_key_credential.rb +24 -5
  30. data/lib/webauthn/public_key_credential_with_assertion.rb +14 -1
  31. data/lib/webauthn/relying_party.rb +120 -0
  32. data/lib/webauthn/u2f_migrator.rb +4 -1
  33. data/lib/webauthn/version.rb +1 -1
  34. data/webauthn.gemspec +3 -5
  35. metadata +16 -45
  36. data/Appraisals +0 -9
  37. data/gemfiles/openssl_2_1.gemfile +0 -7
  38. data/gemfiles/openssl_2_2.gemfile +0 -7
  39. data/lib/webauthn/security_utils.rb +0 -20
@@ -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,50 +12,51 @@ module WebAuthn
13
12
  yield(configuration)
14
13
  end
15
14
 
16
- class RootCertificateFinderNotSupportedError < Error; end
17
-
18
15
  class Configuration
19
- DEFAULT_ALGORITHMS = ["ES256", "PS256", "RS256"].compact.freeze
20
-
21
- attr_accessor :algorithms
22
- attr_accessor :encoding
23
- attr_accessor :origin
24
- attr_accessor :rp_id
25
- attr_accessor :rp_name
26
- attr_accessor :verify_attestation_statement
27
- attr_accessor :credential_options_timeout
28
- attr_accessor :silent_authentication
29
- attr_accessor :acceptable_attestation_types
30
- 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
31
41
 
32
42
  def initialize
33
- @algorithms = DEFAULT_ALGORITHMS.dup
34
- @encoding = WebAuthn::Encoder::STANDARD_ENCODING
35
- @verify_attestation_statement = true
36
- @credential_options_timeout = 120000
37
- @silent_authentication = false
38
- @acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA', 'AnonCA']
39
- @attestation_root_certificates_finders = []
43
+ @relying_party = RelyingParty.new
40
44
  end
41
45
 
42
- # This is the user-data encoder.
43
- # Used to decode user input and to encode data provided to the user.
44
- def encoder
45
- @encoder ||= WebAuthn::Encoder.new(encoding)
46
+ def rp_name
47
+ relying_party.name
46
48
  end
47
49
 
48
- def attestation_root_certificates_finders=(finders)
49
- if !finders.respond_to?(:each)
50
- finders = [finders]
51
- end
50
+ def rp_name=(name)
51
+ relying_party.name = name
52
+ end
52
53
 
53
- finders.each do |finder|
54
- unless finder.respond_to?(:find)
55
- raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
56
- end
57
- end
54
+ def rp_id
55
+ relying_party.id
56
+ end
58
57
 
59
- @attestation_root_certificates_finders = finders
58
+ def rp_id=(id)
59
+ relying_party.id = id
60
60
  end
61
61
  end
62
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
@@ -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
  )
@@ -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,7 +118,12 @@ 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
@@ -17,6 +17,8 @@ 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
23
  sign_count: nil,
22
24
  extensions: nil
@@ -37,6 +39,8 @@ module WebAuthn
37
39
  credential_key: credential_key,
38
40
  user_present: user_present,
39
41
  user_verified: user_verified,
42
+ backup_eligibility: backup_eligibility,
43
+ backup_state: backup_state,
40
44
  attested_credential_data: attested_credential_data,
41
45
  sign_count: sign_count,
42
46
  extensions: extensions
@@ -48,6 +52,8 @@ module WebAuthn
48
52
  client_data_hash:,
49
53
  user_present: true,
50
54
  user_verified: false,
55
+ backup_eligibility: false,
56
+ backup_state: false,
51
57
  aaguid: AuthenticatorData::AAGUID,
52
58
  sign_count: nil,
53
59
  extensions: nil,
@@ -71,6 +77,8 @@ module WebAuthn
71
77
  rp_id_hash: hashed(rp_id),
72
78
  user_present: user_present,
73
79
  user_verified: user_verified,
80
+ backup_eligibility: backup_eligibility,
81
+ backup_state: backup_state,
74
82
  aaguid: aaguid,
75
83
  credential: nil,
76
84
  sign_count: sign_count || credential_sign_count,
@@ -95,7 +103,7 @@ module WebAuthn
95
103
  attr_reader :credentials
96
104
 
97
105
  def new_credential
98
- [SecureRandom.random_bytes(16), OpenSSL::PKey::EC.new("prime256v1").generate_key, 0]
106
+ [SecureRandom.random_bytes(16), OpenSSL::PKey::EC.generate("prime256v1"), 0]
99
107
  end
100
108
 
101
109
  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,6 +29,8 @@ module WebAuthn
29
29
  rp_id: nil,
30
30
  user_present: true,
31
31
  user_verified: false,
32
+ backup_eligibility: false,
33
+ backup_state: false,
32
34
  attested_credential_data: true,
33
35
  extensions: nil
34
36
  )
@@ -42,6 +44,8 @@ module WebAuthn
42
44
  client_data_hash: client_data_hash,
43
45
  user_present: user_present,
44
46
  user_verified: user_verified,
47
+ backup_eligibility: backup_eligibility,
48
+ backup_state: backup_state,
45
49
  attested_credential_data: attested_credential_data,
46
50
  extensions: extensions
47
51
  )
@@ -72,6 +76,8 @@ module WebAuthn
72
76
  rp_id: nil,
73
77
  user_present: true,
74
78
  user_verified: false,
79
+ backup_eligibility: false,
80
+ backup_state: true,
75
81
  sign_count: nil,
76
82
  extensions: nil,
77
83
  user_handle: nil,
@@ -90,6 +96,8 @@ module WebAuthn
90
96
  client_data_hash: client_data_hash,
91
97
  user_present: user_present,
92
98
  user_verified: user_verified,
99
+ backup_eligibility: backup_eligibility,
100
+ backup_state: backup_state,
93
101
  sign_count: sign_count,
94
102
  extensions: extensions,
95
103
  allow_credentials: allow_credentials
@@ -111,7 +119,7 @@ module WebAuthn
111
119
 
112
120
  private
113
121
 
114
- attr_reader :authenticator, :encoding
122
+ attr_reader :authenticator
115
123
 
116
124
  def data_json_for(method, challenge)
117
125
  data = {
@@ -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
@@ -8,11 +8,12 @@ module WebAuthn
8
8
  class Options
9
9
  CHALLENGE_LENGTH = 32
10
10
 
11
- attr_reader :timeout, :extensions
11
+ attr_reader :timeout, :extensions, :relying_party
12
12
 
13
- def initialize(timeout: default_timeout, extensions: nil)
14
- @timeout = timeout
15
- @extensions = extensions
13
+ def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
14
+ @relying_party = relying_party
15
+ @timeout = timeout || default_timeout
16
+ @extensions = default_extensions.merge(extensions || {})
16
17
  end
17
18
 
18
19
  def challenge
@@ -49,7 +50,7 @@ module WebAuthn
49
50
  end
50
51
 
51
52
  def encoder
52
- WebAuthn.configuration.encoder
53
+ relying_party.encoder
53
54
  end
54
55
 
55
56
  def raw_challenge
@@ -57,11 +58,11 @@ module WebAuthn
57
58
  end
58
59
 
59
60
  def default_timeout
60
- configuration.credential_options_timeout
61
+ relying_party.credential_options_timeout
61
62
  end
62
63
 
63
- def configuration
64
- WebAuthn.configuration
64
+ def default_extensions
65
+ {}
65
66
  end
66
67
 
67
68
  def as_public_key_descriptors(ids)
@@ -10,7 +10,7 @@ module WebAuthn
10
10
  def initialize(rp_id: nil, allow_credentials: nil, allow: nil, user_verification: nil, **keyword_arguments)
11
11
  super(**keyword_arguments)
12
12
 
13
- @rp_id = rp_id || configuration.rp_id
13
+ @rp_id = rp_id || relying_party.id
14
14
  @allow_credentials = allow_credentials
15
15
  @allow = allow
16
16
  @user_verification = user_verification
@@ -26,6 +26,16 @@ module WebAuthn
26
26
  super.concat([:allow_credentials, :rp_id, :user_verification])
27
27
  end
28
28
 
29
+ def default_extensions
30
+ extensions = super || {}
31
+
32
+ if relying_party.legacy_u2f_appid
33
+ extensions.merge!(appid: relying_party.legacy_u2f_appid)
34
+ end
35
+
36
+ extensions
37
+ end
38
+
29
39
  def allow_credentials_from_allow
30
40
  if allow
31
41
  as_public_key_descriptors(allow)
@@ -6,22 +6,31 @@ module WebAuthn
6
6
  class PublicKeyCredential
7
7
  attr_reader :type, :id, :raw_id, :client_extension_outputs, :response
8
8
 
9
- def self.from_client(credential)
9
+ def self.from_client(credential, relying_party: WebAuthn.configuration.relying_party)
10
10
  new(
11
11
  type: credential["type"],
12
12
  id: credential["id"],
13
- raw_id: WebAuthn.configuration.encoder.decode(credential["rawId"]),
13
+ raw_id: relying_party.encoder.decode(credential["rawId"]),
14
14
  client_extension_outputs: credential["clientExtensionResults"],
15
- response: response_class.from_client(credential["response"])
15
+ response: response_class.from_client(credential["response"], relying_party: relying_party),
16
+ relying_party: relying_party
16
17
  )
17
18
  end
18
19
 
19
- def initialize(type:, id:, raw_id:, client_extension_outputs: {}, response:)
20
+ def initialize(
21
+ type:,
22
+ id:,
23
+ raw_id:,
24
+ response:,
25
+ client_extension_outputs: {},
26
+ relying_party: WebAuthn.configuration.relying_party
27
+ )
20
28
  @type = type
21
29
  @id = id
22
30
  @raw_id = raw_id
23
31
  @client_extension_outputs = client_extension_outputs
24
32
  @response = response
33
+ @relying_party = relying_party
25
34
  end
26
35
 
27
36
  def verify(*_args)
@@ -39,8 +48,18 @@ module WebAuthn
39
48
  authenticator_data.extension_data if authenticator_data&.extension_data_included?
40
49
  end
41
50
 
51
+ def backup_eligible?
52
+ authenticator_data&.credential_backup_eligible?
53
+ end
54
+
55
+ def backed_up?
56
+ authenticator_data&.credential_backed_up?
57
+ end
58
+
42
59
  private
43
60
 
61
+ attr_reader :relying_party
62
+
44
63
  def valid_type?
45
64
  type == TYPE_PUBLIC_KEY
46
65
  end
@@ -54,7 +73,7 @@ module WebAuthn
54
73
  end
55
74
 
56
75
  def encoder
57
- WebAuthn.configuration.encoder
76
+ relying_party.encoder
58
77
  end
59
78
  end
60
79
  end
@@ -16,7 +16,8 @@ module WebAuthn
16
16
  encoder.decode(challenge),
17
17
  public_key: encoder.decode(public_key),
18
18
  sign_count: sign_count,
19
- user_verification: user_verification
19
+ user_verification: user_verification,
20
+ rp_id: appid_extension_output ? appid : nil
20
21
  )
21
22
 
22
23
  true
@@ -31,5 +32,17 @@ module WebAuthn
31
32
  def raw_user_handle
32
33
  response.user_handle
33
34
  end
35
+
36
+ private
37
+
38
+ def appid_extension_output
39
+ return if client_extension_outputs.nil?
40
+
41
+ client_extension_outputs['appid']
42
+ end
43
+
44
+ def appid
45
+ URI.parse(relying_party.legacy_u2f_appid || raise("Unspecified legacy U2F AppID")).to_s
46
+ end
34
47
  end
35
48
  end