webauthn 2.5.2 → 3.0.0.alpha1
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/.rubocop.yml +0 -56
- data/.travis.yml +39 -0
- data/Appraisals +21 -0
- data/CHANGELOG.md +0 -51
- data/README.md +2 -5
- data/SECURITY.md +3 -6
- data/docs/u2f_migration.md +2 -3
- data/gemfiles/cose_head.gemfile +7 -0
- data/gemfiles/openssl_2_0.gemfile +7 -0
- data/gemfiles/openssl_2_1.gemfile +7 -0
- data/gemfiles/openssl_2_2.gemfile +7 -0
- data/gemfiles/openssl_head.gemfile +7 -0
- data/lib/cose/rsapkcs1_algorithm.rb +0 -7
- data/lib/webauthn/attestation_object.rb +9 -5
- data/lib/webauthn/attestation_statement/android_key.rb +4 -0
- data/lib/webauthn/attestation_statement/android_safetynet.rb +5 -1
- data/lib/webauthn/attestation_statement/base.rb +29 -21
- data/lib/webauthn/attestation_statement/none.rb +1 -7
- data/lib/webauthn/attestation_statement/packed.rb +1 -1
- data/lib/webauthn/attestation_statement/tpm.rb +2 -2
- data/lib/webauthn/attestation_statement.rb +3 -6
- 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 -4
- data/lib/webauthn/authenticator_response.rb +8 -6
- data/lib/webauthn/configuration.rb +36 -38
- data/lib/webauthn/credential.rb +5 -4
- data/lib/webauthn/credential_creation_options.rb +0 -2
- data/lib/webauthn/credential_request_options.rb +0 -2
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +1 -1
- data/lib/webauthn/fake_authenticator.rb +3 -11
- data/lib/webauthn/fake_client.rb +5 -12
- data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
- data/lib/webauthn/public_key_credential/entity.rb +4 -3
- data/lib/webauthn/public_key_credential/options.rb +6 -9
- data/lib/webauthn/public_key_credential/request_options.rb +1 -1
- data/lib/webauthn/public_key_credential.rb +15 -8
- data/lib/webauthn/relying_party.rb +117 -0
- data/lib/webauthn/security_utils.rb +20 -0
- data/lib/webauthn/version.rb +1 -1
- data/script/ci/install-openssl +7 -0
- data/script/ci/install-ruby +13 -0
- data/webauthn.gemspec +7 -6
- metadata +61 -46
- data/.github/workflows/build.yml +0 -32
- data/.github/workflows/git.yml +0 -21
- data/docs/advanced_configuration.md +0 -174
- data/lib/webauthn/attestation_statement/apple.rb +0 -65
@@ -18,12 +18,13 @@ module WebAuthn
|
|
18
18
|
class AuthenticatorAttestationResponse < AuthenticatorResponse
|
19
19
|
extend Forwardable
|
20
20
|
|
21
|
-
def self.from_client(response)
|
22
|
-
encoder =
|
21
|
+
def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
|
22
|
+
encoder = relying_party.encoder
|
23
23
|
|
24
24
|
new(
|
25
25
|
attestation_object: encoder.decode(response["attestationObject"]),
|
26
|
-
client_data_json: encoder.decode(response["clientDataJSON"])
|
26
|
+
client_data_json: encoder.decode(response["clientDataJSON"]),
|
27
|
+
relying_party: relying_party
|
27
28
|
)
|
28
29
|
end
|
29
30
|
|
@@ -33,13 +34,14 @@ module WebAuthn
|
|
33
34
|
super(**options)
|
34
35
|
|
35
36
|
@attestation_object_bytes = attestation_object
|
37
|
+
@relying_party = relying_party
|
36
38
|
end
|
37
39
|
|
38
40
|
def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
|
39
41
|
super
|
40
42
|
|
41
43
|
verify_item(:attested_credential)
|
42
|
-
if
|
44
|
+
if relying_party.verify_attestation_statement
|
43
45
|
verify_item(:attestation_statement)
|
44
46
|
end
|
45
47
|
|
@@ -47,7 +49,7 @@ module WebAuthn
|
|
47
49
|
end
|
48
50
|
|
49
51
|
def attestation_object
|
50
|
-
@attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes)
|
52
|
+
@attestation_object ||= WebAuthn::AttestationObject.deserialize(attestation_object_bytes, relying_party)
|
51
53
|
end
|
52
54
|
|
53
55
|
def_delegators(
|
@@ -63,14 +65,15 @@ module WebAuthn
|
|
63
65
|
|
64
66
|
private
|
65
67
|
|
66
|
-
attr_reader :attestation_object_bytes
|
68
|
+
attr_reader :attestation_object_bytes, :relying_party
|
67
69
|
|
68
70
|
def type
|
69
71
|
WebAuthn::TYPES[:create]
|
70
72
|
end
|
71
73
|
|
72
74
|
def valid_attested_credential?
|
73
|
-
attestation_object.valid_attested_credential?
|
75
|
+
attestation_object.valid_attested_credential? &&
|
76
|
+
relying_party.algorithms.include?(authenticator_data.credential.algorithm)
|
74
77
|
end
|
75
78
|
|
76
79
|
def valid_attestation_statement?
|
@@ -24,7 +24,7 @@ module WebAuthn
|
|
24
24
|
|
25
25
|
# TODO: use keyword_init when we dropped Ruby 2.4 support
|
26
26
|
Credential =
|
27
|
-
Struct.new(:id, :public_key) do
|
27
|
+
Struct.new(:id, :public_key, :algorithm) do
|
28
28
|
def public_key_object
|
29
29
|
COSE::Key.deserialize(public_key).to_pkey
|
30
30
|
end
|
@@ -47,7 +47,7 @@ module WebAuthn
|
|
47
47
|
def credential
|
48
48
|
@credential ||=
|
49
49
|
if valid?
|
50
|
-
Credential.new(id, public_key)
|
50
|
+
Credential.new(id, public_key, algorithm)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
@@ -59,10 +59,16 @@ module WebAuthn
|
|
59
59
|
|
60
60
|
private
|
61
61
|
|
62
|
+
def algorithm
|
63
|
+
COSE::Algorithm.find(cose_key.alg).name
|
64
|
+
end
|
65
|
+
|
62
66
|
def valid_credential_public_key?
|
63
|
-
cose_key
|
67
|
+
!!cose_key.alg
|
68
|
+
end
|
64
69
|
|
65
|
-
|
70
|
+
def cose_key
|
71
|
+
@cose_key ||= COSE::Key.deserialize(public_key)
|
66
72
|
end
|
67
73
|
|
68
74
|
def public_key
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "webauthn/authenticator_data"
|
4
4
|
require "webauthn/client_data"
|
5
5
|
require "webauthn/error"
|
6
|
+
require "webauthn/security_utils"
|
6
7
|
|
7
8
|
module WebAuthn
|
8
9
|
TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
|
@@ -19,13 +20,14 @@ module WebAuthn
|
|
19
20
|
class UserVerifiedVerificationError < VerificationError; end
|
20
21
|
|
21
22
|
class AuthenticatorResponse
|
22
|
-
def initialize(client_data_json:)
|
23
|
+
def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
|
23
24
|
@client_data_json = client_data_json
|
25
|
+
@relying_party = relying_party
|
24
26
|
end
|
25
27
|
|
26
28
|
def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
|
27
|
-
expected_origin ||=
|
28
|
-
rp_id ||=
|
29
|
+
expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
|
30
|
+
rp_id ||= relying_party.id
|
29
31
|
|
30
32
|
verify_item(:type)
|
31
33
|
verify_item(:token_binding)
|
@@ -34,7 +36,7 @@ module WebAuthn
|
|
34
36
|
verify_item(:authenticator_data)
|
35
37
|
verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
|
36
38
|
|
37
|
-
if !
|
39
|
+
if !relying_party.silent_authentication
|
38
40
|
verify_item(:user_presence)
|
39
41
|
end
|
40
42
|
|
@@ -57,7 +59,7 @@ module WebAuthn
|
|
57
59
|
|
58
60
|
private
|
59
61
|
|
60
|
-
attr_reader :client_data_json
|
62
|
+
attr_reader :client_data_json, :relying_party
|
61
63
|
|
62
64
|
def verify_item(item, *args)
|
63
65
|
if send("valid_#{item}?", *args)
|
@@ -78,7 +80,7 @@ module WebAuthn
|
|
78
80
|
end
|
79
81
|
|
80
82
|
def valid_challenge?(expected_challenge)
|
81
|
-
|
83
|
+
WebAuthn::SecurityUtils.secure_compare(client_data.challenge, expected_challenge)
|
82
84
|
end
|
83
85
|
|
84
86
|
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,49 @@ 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
|
+
|
38
|
+
attr_reader :relying_party
|
31
39
|
|
32
40
|
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 = []
|
41
|
+
@relying_party = RelyingParty.new
|
40
42
|
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
def encoder
|
45
|
-
@encoder ||= WebAuthn::Encoder.new(encoding)
|
44
|
+
def rp_name
|
45
|
+
relying_party.name
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
end
|
48
|
+
def rp_name=(name)
|
49
|
+
relying_party.name = name
|
50
|
+
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
52
|
+
def rp_id
|
53
|
+
relying_party.id
|
54
|
+
end
|
58
55
|
|
59
|
-
|
56
|
+
def rp_id=(id)
|
57
|
+
relying_party.id = id
|
60
58
|
end
|
61
59
|
end
|
62
60
|
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
|
@@ -16,8 +16,6 @@ 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
|
-
|
21
19
|
@allow_credentials = allow_credentials
|
22
20
|
@extensions = extensions
|
23
21
|
@user_verification = user_verification
|
@@ -15,7 +15,7 @@ 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.new("prime256v1").generate_key.public_key
|
19
19
|
},
|
20
20
|
sign_count: 0,
|
21
21
|
user_present: true,
|
@@ -50,20 +50,12 @@ module WebAuthn
|
|
50
50
|
user_verified: false,
|
51
51
|
aaguid: AuthenticatorData::AAGUID,
|
52
52
|
sign_count: nil,
|
53
|
-
extensions: nil
|
54
|
-
allow_credentials: nil
|
53
|
+
extensions: nil
|
55
54
|
)
|
56
55
|
credential_options = credentials[rp_id]
|
57
56
|
|
58
57
|
if credential_options
|
59
|
-
|
60
|
-
credential_id = (credential_options.keys & allow_credentials).first
|
61
|
-
unless credential_id
|
62
|
-
raise "No matching credentials (allowed=#{allow_credentials}) " \
|
63
|
-
"found for RP #{rp_id} among credentials=#{credential_options}"
|
64
|
-
end
|
65
|
-
|
66
|
-
credential = credential_options[credential_id]
|
58
|
+
credential_id, credential = credential_options.first
|
67
59
|
credential_key = credential[:credential_key]
|
68
60
|
credential_sign_count = credential[:sign_count]
|
69
61
|
|
@@ -95,7 +87,7 @@ module WebAuthn
|
|
95
87
|
attr_reader :credentials
|
96
88
|
|
97
89
|
def new_credential
|
98
|
-
[SecureRandom.random_bytes(16), OpenSSL::PKey::EC.
|
90
|
+
[SecureRandom.random_bytes(16), OpenSSL::PKey::EC.new("prime256v1").generate_key, 0]
|
99
91
|
end
|
100
92
|
|
101
93
|
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,
|
@@ -73,26 +73,19 @@ module WebAuthn
|
|
73
73
|
user_present: true,
|
74
74
|
user_verified: false,
|
75
75
|
sign_count: nil,
|
76
|
-
extensions: nil
|
77
|
-
user_handle: nil,
|
78
|
-
allow_credentials: nil)
|
76
|
+
extensions: nil)
|
79
77
|
rp_id ||= URI.parse(origin).host
|
80
78
|
|
81
79
|
client_data_json = data_json_for(:get, encoder.decode(challenge))
|
82
80
|
client_data_hash = hashed(client_data_json)
|
83
81
|
|
84
|
-
if allow_credentials
|
85
|
-
allow_credentials = allow_credentials.map { |credential| encoder.decode(credential) }
|
86
|
-
end
|
87
|
-
|
88
82
|
assertion = authenticator.get_assertion(
|
89
83
|
rp_id: rp_id,
|
90
84
|
client_data_hash: client_data_hash,
|
91
85
|
user_present: user_present,
|
92
86
|
user_verified: user_verified,
|
93
87
|
sign_count: sign_count,
|
94
|
-
extensions: extensions
|
95
|
-
allow_credentials: allow_credentials
|
88
|
+
extensions: extensions
|
96
89
|
)
|
97
90
|
|
98
91
|
{
|
@@ -104,14 +97,14 @@ module WebAuthn
|
|
104
97
|
"clientDataJSON" => encoder.encode(client_data_json),
|
105
98
|
"authenticatorData" => encoder.encode(assertion[:authenticator_data]),
|
106
99
|
"signature" => encoder.encode(assertion[:signature]),
|
107
|
-
"userHandle" =>
|
100
|
+
"userHandle" => nil
|
108
101
|
}
|
109
102
|
}
|
110
103
|
end
|
111
104
|
|
112
105
|
private
|
113
106
|
|
114
|
-
attr_reader :authenticator
|
107
|
+
attr_reader :authenticator
|
115
108
|
|
116
109
|
def data_json_for(method, challenge)
|
117
110
|
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
|
@@ -5,10 +5,11 @@ require "awrence"
|
|
5
5
|
module WebAuthn
|
6
6
|
class PublicKeyCredential
|
7
7
|
class Entity
|
8
|
-
attr_reader :name
|
8
|
+
attr_reader :name, :icon
|
9
9
|
|
10
|
-
def initialize(name:)
|
10
|
+
def initialize(name:, icon: nil)
|
11
11
|
@name = name
|
12
|
+
@icon = icon
|
12
13
|
end
|
13
14
|
|
14
15
|
def as_json
|
@@ -36,7 +37,7 @@ module WebAuthn
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def attributes
|
39
|
-
[:name]
|
40
|
+
[:name, :icon]
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
@@ -8,10 +8,11 @@ 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
|
-
@
|
13
|
+
def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
|
14
|
+
@relying_party = relying_party
|
15
|
+
@timeout = timeout || default_timeout
|
15
16
|
@extensions = extensions
|
16
17
|
end
|
17
18
|
|
@@ -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,7 @@ module WebAuthn
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def default_timeout
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
def configuration
|
64
|
-
WebAuthn.configuration
|
61
|
+
relying_party.credential_options_timeout
|
65
62
|
end
|
66
63
|
|
67
64
|
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
|
@@ -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
|
+
encoder: relying_party.encoder
|
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
|
+
encoder: WebAuthn.configuration.encoder
|
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
|
+
@encoder = encoder
|
25
34
|
end
|
26
35
|
|
27
36
|
def verify(*_args)
|
@@ -41,6 +50,8 @@ module WebAuthn
|
|
41
50
|
|
42
51
|
private
|
43
52
|
|
53
|
+
attr_reader :encoder
|
54
|
+
|
44
55
|
def valid_type?
|
45
56
|
type == TYPE_PUBLIC_KEY
|
46
57
|
end
|
@@ -52,9 +63,5 @@ module WebAuthn
|
|
52
63
|
def authenticator_data
|
53
64
|
response&.authenticator_data
|
54
65
|
end
|
55
|
-
|
56
|
-
def encoder
|
57
|
-
WebAuthn.configuration.encoder
|
58
|
-
end
|
59
66
|
end
|
60
67
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "webauthn/credential"
|
5
|
+
require "webauthn/encoder"
|
6
|
+
require "webauthn/error"
|
7
|
+
|
8
|
+
module WebAuthn
|
9
|
+
class RootCertificateFinderNotSupportedError < Error; end
|
10
|
+
|
11
|
+
class RelyingParty
|
12
|
+
def self.if_pss_supported(algorithm)
|
13
|
+
OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
DEFAULT_ALGORITHMS = ["ES256", if_pss_supported("PS256"), "RS256"].compact.freeze
|
17
|
+
|
18
|
+
def initialize(
|
19
|
+
algorithms: DEFAULT_ALGORITHMS.dup,
|
20
|
+
encoding: WebAuthn::Encoder::STANDARD_ENCODING,
|
21
|
+
origin: nil,
|
22
|
+
id: nil,
|
23
|
+
name: nil,
|
24
|
+
verify_attestation_statement: true,
|
25
|
+
credential_options_timeout: 120000,
|
26
|
+
silent_authentication: false,
|
27
|
+
acceptable_attestation_types: ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA'],
|
28
|
+
attestation_root_certificates_finders: []
|
29
|
+
)
|
30
|
+
@algorithms = algorithms
|
31
|
+
@encoding = encoding
|
32
|
+
@origin = origin
|
33
|
+
@id = id
|
34
|
+
@name = name
|
35
|
+
@verify_attestation_statement = verify_attestation_statement
|
36
|
+
@credential_options_timeout = credential_options_timeout
|
37
|
+
@silent_authentication = silent_authentication
|
38
|
+
@acceptable_attestation_types = acceptable_attestation_types
|
39
|
+
self.attestation_root_certificates_finders = attestation_root_certificates_finders
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_accessor :algorithms,
|
43
|
+
:encoding,
|
44
|
+
:origin,
|
45
|
+
:id,
|
46
|
+
:name,
|
47
|
+
:verify_attestation_statement,
|
48
|
+
:credential_options_timeout,
|
49
|
+
:silent_authentication,
|
50
|
+
:acceptable_attestation_types
|
51
|
+
|
52
|
+
attr_reader :attestation_root_certificates_finders
|
53
|
+
|
54
|
+
# This is the user-data encoder.
|
55
|
+
# Used to decode user input and to encode data provided to the user.
|
56
|
+
def encoder
|
57
|
+
@encoder ||= WebAuthn::Encoder.new(encoding)
|
58
|
+
end
|
59
|
+
|
60
|
+
def attestation_root_certificates_finders=(finders)
|
61
|
+
if !finders.respond_to?(:each)
|
62
|
+
finders = [finders]
|
63
|
+
end
|
64
|
+
|
65
|
+
finders.each do |finder|
|
66
|
+
unless finder.respond_to?(:find)
|
67
|
+
raise RootCertificateFinderNotSupportedError, "Finder must implement `find` method"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@attestation_root_certificates_finders = finders
|
72
|
+
end
|
73
|
+
|
74
|
+
def options_for_registration(**keyword_arguments)
|
75
|
+
WebAuthn::Credential.options_for_create(
|
76
|
+
**keyword_arguments,
|
77
|
+
relying_party: self
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def verify_registration(raw_credential, challenge, user_verification: nil)
|
82
|
+
webauthn_credential = WebAuthn::Credential.from_create(raw_credential, relying_party: self)
|
83
|
+
|
84
|
+
if webauthn_credential.verify(challenge, user_verification: user_verification)
|
85
|
+
webauthn_credential
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def options_for_authentication(**keyword_arguments)
|
90
|
+
WebAuthn::Credential.options_for_get(
|
91
|
+
**keyword_arguments,
|
92
|
+
relying_party: self
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def verify_authentication(
|
97
|
+
raw_credential,
|
98
|
+
challenge,
|
99
|
+
user_verification: nil,
|
100
|
+
public_key: nil,
|
101
|
+
sign_count: nil
|
102
|
+
)
|
103
|
+
webauthn_credential = WebAuthn::Credential.from_get(raw_credential, relying_party: self)
|
104
|
+
|
105
|
+
stored_credential = yield(webauthn_credential) if block_given?
|
106
|
+
|
107
|
+
if webauthn_credential.verify(
|
108
|
+
challenge,
|
109
|
+
public_key: public_key || stored_credential.public_key,
|
110
|
+
sign_count: sign_count || stored_credential.sign_count,
|
111
|
+
user_verification: user_verification
|
112
|
+
)
|
113
|
+
block_given? ? [webauthn_credential, stored_credential] : webauthn_credential
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securecompare"
|
4
|
+
|
5
|
+
module WebAuthn
|
6
|
+
module SecurityUtils
|
7
|
+
# Constant time string comparison, for variable length strings.
|
8
|
+
# This code was adapted from Rails ActiveSupport::SecurityUtils
|
9
|
+
#
|
10
|
+
# The values are first processed by SHA256, so that we don't leak length info
|
11
|
+
# via timing attacks.
|
12
|
+
def secure_compare(first_string, second_string)
|
13
|
+
first_string_sha256 = ::Digest::SHA256.digest(first_string)
|
14
|
+
second_string_sha256 = ::Digest::SHA256.digest(second_string)
|
15
|
+
|
16
|
+
SecureCompare.compare(first_string_sha256, second_string_sha256) && first_string == second_string
|
17
|
+
end
|
18
|
+
module_function :secure_compare
|
19
|
+
end
|
20
|
+
end
|
data/lib/webauthn/version.rb
CHANGED