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.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +3 -7
- data/.github/workflows/git.yml +21 -0
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +42 -1
- data/README.md +5 -3
- data/docs/advanced_configuration.md +174 -0
- data/docs/u2f_migration.md +14 -20
- data/lib/webauthn/attestation_object.rb +9 -5
- data/lib/webauthn/attestation_statement/apple.rb +2 -2
- data/lib/webauthn/attestation_statement/base.rb +11 -25
- data/lib/webauthn/attestation_statement/packed.rb +1 -1
- data/lib/webauthn/attestation_statement/tpm.rb +2 -2
- data/lib/webauthn/attestation_statement.rb +2 -2
- data/lib/webauthn/authenticator_assertion_response.rb +4 -3
- data/lib/webauthn/authenticator_attestation_response.rb +10 -7
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +10 -5
- data/lib/webauthn/authenticator_data.rb +10 -2
- data/lib/webauthn/authenticator_response.rb +7 -7
- data/lib/webauthn/configuration.rb +38 -38
- data/lib/webauthn/credential.rb +5 -4
- data/lib/webauthn/fake_authenticator/attestation_object.rb +8 -0
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +20 -5
- data/lib/webauthn/fake_authenticator.rb +9 -1
- data/lib/webauthn/fake_client.rb +10 -2
- data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
- data/lib/webauthn/public_key_credential/options.rb +9 -8
- data/lib/webauthn/public_key_credential/request_options.rb +11 -1
- data/lib/webauthn/public_key_credential.rb +24 -5
- data/lib/webauthn/public_key_credential_with_assertion.rb +14 -1
- data/lib/webauthn/relying_party.rb +120 -0
- data/lib/webauthn/u2f_migrator.rb +4 -1
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +3 -5
- metadata +16 -45
- data/Appraisals +0 -9
- data/gemfiles/openssl_2_1.gemfile +0 -7
- data/gemfiles/openssl_2_2.gemfile +0 -7
- 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
|
66
|
+
!!cose_key.alg
|
67
|
+
end
|
64
68
|
|
65
|
-
|
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 ||=
|
29
|
-
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 !
|
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
|
-
|
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
|
4
|
-
require
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
@
|
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
|
-
|
43
|
-
|
44
|
-
def encoder
|
45
|
-
@encoder ||= WebAuthn::Encoder.new(encoding)
|
46
|
+
def rp_name
|
47
|
+
relying_party.name
|
46
48
|
end
|
47
49
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
end
|
50
|
+
def rp_name=(name)
|
51
|
+
relying_party.name = name
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
54
|
+
def rp_id
|
55
|
+
relying_party.id
|
56
|
+
end
|
58
57
|
|
59
|
-
|
58
|
+
def rp_id=(id)
|
59
|
+
relying_party.id = id
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
data/lib/webauthn/credential.rb
CHANGED
@@ -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.
|
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,
|
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
|
-
|
50
|
-
|
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
|
-
{
|
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.
|
106
|
+
[SecureRandom.random_bytes(16), OpenSSL::PKey::EC.generate("prime256v1"), 0]
|
99
107
|
end
|
100
108
|
|
101
109
|
def hashed(target)
|
data/lib/webauthn/fake_client.rb
CHANGED
@@ -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
|
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] ||=
|
43
|
-
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 ||
|
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:
|
14
|
-
@
|
15
|
-
@
|
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
|
-
|
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
|
-
|
61
|
+
relying_party.credential_options_timeout
|
61
62
|
end
|
62
63
|
|
63
|
-
def
|
64
|
-
|
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 ||
|
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:
|
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(
|
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
|
-
|
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
|