webauthn 3.0.0 → 3.4.2
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/actions/install-openssl/action.yml +55 -0
- data/.github/actions/install-ruby/action.yml +84 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/build.yml +60 -10
- data/.github/workflows/git.yml +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +77 -0
- data/README.md +5 -4
- data/docs/advanced_configuration.md +16 -15
- data/lib/webauthn/attestation_statement/base.rb +10 -1
- data/lib/webauthn/authenticator_assertion_response.rb +16 -2
- data/lib/webauthn/authenticator_attestation_response.rb +5 -4
- data/lib/webauthn/authenticator_response.rb +16 -6
- data/lib/webauthn/client_data.rb +4 -6
- data/lib/webauthn/configuration.rb +2 -0
- data/lib/webauthn/credential.rb +0 -1
- data/lib/webauthn/encoder.rb +5 -33
- data/lib/webauthn/encoders.rb +62 -0
- data/lib/webauthn/fake_authenticator/attestation_object.rb +10 -1
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +3 -1
- data/lib/webauthn/fake_authenticator.rb +25 -4
- data/lib/webauthn/fake_client.rb +6 -1
- data/lib/webauthn/json_serializer.rb +45 -0
- data/lib/webauthn/public_key_credential/entity.rb +2 -24
- data/lib/webauthn/public_key_credential/options.rb +2 -24
- data/lib/webauthn/public_key_credential.rb +17 -2
- data/lib/webauthn/public_key_credential_with_assertion.rb +2 -1
- data/lib/webauthn/public_key_credential_with_attestation.rb +2 -2
- data/lib/webauthn/relying_party.rb +24 -7
- data/lib/webauthn/u2f_migrator.rb +5 -3
- data/lib/webauthn/version.rb +1 -1
- data/lib/webauthn.rb +1 -0
- data/webauthn.gemspec +5 -6
- metadata +26 -29
data/lib/webauthn/credential.rb
CHANGED
@@ -4,7 +4,6 @@ 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"
|
8
7
|
|
9
8
|
module WebAuthn
|
10
9
|
module Credential
|
data/lib/webauthn/encoder.rb
CHANGED
@@ -1,46 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "webauthn/encoders"
|
4
4
|
|
5
5
|
module WebAuthn
|
6
|
-
def self.standard_encoder
|
7
|
-
@standard_encoder ||= Encoder.new
|
8
|
-
end
|
9
|
-
|
10
6
|
class Encoder
|
7
|
+
extend Forwardable
|
8
|
+
|
11
9
|
# https://www.w3.org/TR/webauthn-2/#base64url-encoding
|
12
10
|
STANDARD_ENCODING = :base64url
|
13
11
|
|
14
|
-
|
12
|
+
def_delegators :@encoder_klass, :encode, :decode
|
15
13
|
|
16
14
|
def initialize(encoding = STANDARD_ENCODING)
|
17
|
-
@
|
18
|
-
end
|
19
|
-
|
20
|
-
def encode(data)
|
21
|
-
case encoding
|
22
|
-
when :base64
|
23
|
-
Base64.strict_encode64(data)
|
24
|
-
when :base64url
|
25
|
-
Base64.urlsafe_encode64(data, padding: false)
|
26
|
-
when nil, false
|
27
|
-
data
|
28
|
-
else
|
29
|
-
raise "Unsupported or unknown encoding: #{encoding}"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def decode(data)
|
34
|
-
case encoding
|
35
|
-
when :base64
|
36
|
-
Base64.strict_decode64(data)
|
37
|
-
when :base64url
|
38
|
-
Base64.urlsafe_decode64(data)
|
39
|
-
when nil, false
|
40
|
-
data
|
41
|
-
else
|
42
|
-
raise "Unsupported or unknown encoding: #{encoding}"
|
43
|
-
end
|
15
|
+
@encoder_klass = Encoders.lookup(encoding)
|
44
16
|
end
|
45
17
|
end
|
46
18
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WebAuthn
|
4
|
+
def self.standard_encoder
|
5
|
+
@standard_encoder ||= Encoders.lookup(Encoder::STANDARD_ENCODING)
|
6
|
+
end
|
7
|
+
|
8
|
+
module Encoders
|
9
|
+
class << self
|
10
|
+
def lookup(encoding)
|
11
|
+
case encoding
|
12
|
+
when :base64
|
13
|
+
Base64Encoder
|
14
|
+
when :base64url
|
15
|
+
Base64UrlEncoder
|
16
|
+
when nil, false
|
17
|
+
NullEncoder
|
18
|
+
else
|
19
|
+
raise "Unsupported or unknown encoding: #{encoding}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Base64Encoder
|
25
|
+
def self.encode(data)
|
26
|
+
[data].pack("m0") # Base64.strict_encode64(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.decode(data)
|
30
|
+
data.unpack1("m0") # Base64.strict_decode64(data)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Base64UrlEncoder
|
35
|
+
def self.encode(data)
|
36
|
+
data = [data].pack("m0") # Base64.urlsafe_encode64(data, padding: false)
|
37
|
+
data.chomp!("==") or data.chomp!("=")
|
38
|
+
data.tr!("+/", "-_")
|
39
|
+
data
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.decode(data)
|
43
|
+
if !data.end_with?("=") && data.length % 4 != 0 # Base64.urlsafe_decode64(data)
|
44
|
+
data = data.ljust((data.length + 3) & ~3, "=")
|
45
|
+
end
|
46
|
+
|
47
|
+
data = data.tr("-_", "+/")
|
48
|
+
data.unpack1("m0")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class NullEncoder
|
53
|
+
def self.encode(data)
|
54
|
+
data
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.decode(data)
|
58
|
+
data
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -61,7 +61,7 @@ module WebAuthn
|
|
61
61
|
begin
|
62
62
|
credential_data =
|
63
63
|
if attested_credential_data
|
64
|
-
{ id: credential_id, public_key:
|
64
|
+
{ id: credential_id, public_key: credential_public_key }
|
65
65
|
end
|
66
66
|
|
67
67
|
AuthenticatorData.new(
|
@@ -76,6 +76,15 @@ module WebAuthn
|
|
76
76
|
)
|
77
77
|
end
|
78
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
|
79
88
|
end
|
80
89
|
end
|
81
90
|
end
|
@@ -20,10 +20,11 @@ module WebAuthn
|
|
20
20
|
backup_eligibility: false,
|
21
21
|
backup_state: false,
|
22
22
|
attested_credential_data: true,
|
23
|
+
algorithm: nil,
|
23
24
|
sign_count: nil,
|
24
25
|
extensions: nil
|
25
26
|
)
|
26
|
-
credential_id, credential_key, credential_sign_count = new_credential
|
27
|
+
credential_id, credential_key, credential_sign_count = new_credential(algorithm)
|
27
28
|
sign_count ||= credential_sign_count
|
28
29
|
|
29
30
|
credentials[rp_id] ||= {}
|
@@ -85,7 +86,14 @@ module WebAuthn
|
|
85
86
|
extensions: extensions
|
86
87
|
).serialize
|
87
88
|
|
88
|
-
|
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)
|
89
97
|
credential[:sign_count] += 1
|
90
98
|
|
91
99
|
{
|
@@ -102,8 +110,21 @@ module WebAuthn
|
|
102
110
|
|
103
111
|
attr_reader :credentials
|
104
112
|
|
105
|
-
def new_credential
|
106
|
-
|
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]
|
107
128
|
end
|
108
129
|
|
109
130
|
def hashed(target)
|
data/lib/webauthn/fake_client.rb
CHANGED
@@ -32,6 +32,7 @@ module WebAuthn
|
|
32
32
|
backup_eligibility: false,
|
33
33
|
backup_state: false,
|
34
34
|
attested_credential_data: true,
|
35
|
+
credential_algorithm: nil,
|
35
36
|
extensions: nil
|
36
37
|
)
|
37
38
|
rp_id ||= URI.parse(origin).host
|
@@ -47,6 +48,7 @@ module WebAuthn
|
|
47
48
|
backup_eligibility: backup_eligibility,
|
48
49
|
backup_state: backup_state,
|
49
50
|
attested_credential_data: attested_credential_data,
|
51
|
+
algorithm: credential_algorithm,
|
50
52
|
extensions: extensions
|
51
53
|
)
|
52
54
|
|
@@ -64,10 +66,12 @@ module WebAuthn
|
|
64
66
|
"type" => "public-key",
|
65
67
|
"id" => internal_encoder.encode(id),
|
66
68
|
"rawId" => encoder.encode(id),
|
69
|
+
"authenticatorAttachment" => 'platform',
|
67
70
|
"clientExtensionResults" => extensions,
|
68
71
|
"response" => {
|
69
72
|
"attestationObject" => encoder.encode(attestation_object),
|
70
|
-
"clientDataJSON" => encoder.encode(client_data_json)
|
73
|
+
"clientDataJSON" => encoder.encode(client_data_json),
|
74
|
+
"transports" => ["internal"],
|
71
75
|
}
|
72
76
|
}
|
73
77
|
end
|
@@ -108,6 +112,7 @@ module WebAuthn
|
|
108
112
|
"id" => internal_encoder.encode(assertion[:credential_id]),
|
109
113
|
"rawId" => encoder.encode(assertion[:credential_id]),
|
110
114
|
"clientExtensionResults" => extensions,
|
115
|
+
"authenticatorAttachment" => 'platform',
|
111
116
|
"response" => {
|
112
117
|
"clientDataJSON" => encoder.encode(client_data_json),
|
113
118
|
"authenticatorData" => encoder.encode(assertion[:authenticator_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,40 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "awrence"
|
4
|
-
|
5
3
|
module WebAuthn
|
6
4
|
class PublicKeyCredential
|
7
5
|
class Entity
|
6
|
+
include JSONSerializer
|
7
|
+
|
8
8
|
attr_reader :name
|
9
9
|
|
10
10
|
def initialize(name:)
|
11
11
|
@name = name
|
12
12
|
end
|
13
13
|
|
14
|
-
def as_json
|
15
|
-
to_hash.to_camelback_keys
|
16
|
-
end
|
17
|
-
|
18
14
|
private
|
19
15
|
|
20
|
-
def to_hash
|
21
|
-
hash = {}
|
22
|
-
|
23
|
-
attributes.each do |attribute_name|
|
24
|
-
value = send(attribute_name)
|
25
|
-
|
26
|
-
if value.respond_to?(:as_json)
|
27
|
-
value = value.as_json
|
28
|
-
end
|
29
|
-
|
30
|
-
if value
|
31
|
-
hash[attribute_name] = value
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
hash
|
36
|
-
end
|
37
|
-
|
38
16
|
def attributes
|
39
17
|
[:name]
|
40
18
|
end
|
@@ -1,11 +1,12 @@
|
|
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
12
|
attr_reader :timeout, :extensions, :relying_party
|
@@ -20,31 +21,8 @@ module WebAuthn
|
|
20
21
|
encoder.encode(raw_challenge)
|
21
22
|
end
|
22
23
|
|
23
|
-
# Argument wildcard for Ruby on Rails controller automatic object JSON serialization
|
24
|
-
def as_json(*)
|
25
|
-
to_hash.to_camelback_keys
|
26
|
-
end
|
27
|
-
|
28
24
|
private
|
29
25
|
|
30
|
-
def to_hash
|
31
|
-
hash = {}
|
32
|
-
|
33
|
-
attributes.each do |attribute_name|
|
34
|
-
value = send(attribute_name)
|
35
|
-
|
36
|
-
if value.respond_to?(:as_json)
|
37
|
-
value = value.as_json
|
38
|
-
end
|
39
|
-
|
40
|
-
if value
|
41
|
-
hash[attribute_name] = value
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
hash
|
46
|
-
end
|
47
|
-
|
48
26
|
def attributes
|
49
27
|
[:challenge, :timeout, :extensions]
|
50
28
|
end
|
@@ -4,7 +4,9 @@ require "webauthn/encoder"
|
|
4
4
|
|
5
5
|
module WebAuthn
|
6
6
|
class PublicKeyCredential
|
7
|
-
|
7
|
+
class InvalidChallengeError < Error; end
|
8
|
+
|
9
|
+
attr_reader :type, :id, :raw_id, :client_extension_outputs, :authenticator_attachment, :response
|
8
10
|
|
9
11
|
def self.from_client(credential, relying_party: WebAuthn.configuration.relying_party)
|
10
12
|
new(
|
@@ -12,6 +14,7 @@ module WebAuthn
|
|
12
14
|
id: credential["id"],
|
13
15
|
raw_id: relying_party.encoder.decode(credential["rawId"]),
|
14
16
|
client_extension_outputs: credential["clientExtensionResults"],
|
17
|
+
authenticator_attachment: credential["authenticatorAttachment"],
|
15
18
|
response: response_class.from_client(credential["response"], relying_party: relying_party),
|
16
19
|
relying_party: relying_party
|
17
20
|
)
|
@@ -22,6 +25,7 @@ module WebAuthn
|
|
22
25
|
id:,
|
23
26
|
raw_id:,
|
24
27
|
response:,
|
28
|
+
authenticator_attachment: nil,
|
25
29
|
client_extension_outputs: {},
|
26
30
|
relying_party: WebAuthn.configuration.relying_party
|
27
31
|
)
|
@@ -29,11 +33,18 @@ module WebAuthn
|
|
29
33
|
@id = id
|
30
34
|
@raw_id = raw_id
|
31
35
|
@client_extension_outputs = client_extension_outputs
|
36
|
+
@authenticator_attachment = authenticator_attachment
|
32
37
|
@response = response
|
33
38
|
@relying_party = relying_party
|
34
39
|
end
|
35
40
|
|
36
|
-
def verify(*_args)
|
41
|
+
def verify(challenge, *_args)
|
42
|
+
unless valid_class?(challenge)
|
43
|
+
msg = "challenge must be a String. input challenge class: #{challenge.class}"
|
44
|
+
|
45
|
+
raise(InvalidChallengeError, msg)
|
46
|
+
end
|
47
|
+
|
37
48
|
valid_type? || raise("invalid type")
|
38
49
|
valid_id? || raise("invalid id")
|
39
50
|
|
@@ -68,6 +79,10 @@ module WebAuthn
|
|
68
79
|
raw_id && id && raw_id == WebAuthn.standard_encoder.decode(id)
|
69
80
|
end
|
70
81
|
|
82
|
+
def valid_class?(challenge)
|
83
|
+
challenge.is_a?(String)
|
84
|
+
end
|
85
|
+
|
71
86
|
def authenticator_data
|
72
87
|
response&.authenticator_data
|
73
88
|
end
|
@@ -9,13 +9,14 @@ module WebAuthn
|
|
9
9
|
WebAuthn::AuthenticatorAssertionResponse
|
10
10
|
end
|
11
11
|
|
12
|
-
def verify(challenge, public_key:, sign_count:, user_verification: nil)
|
12
|
+
def verify(challenge, public_key:, sign_count:, user_presence: nil, user_verification: nil)
|
13
13
|
super
|
14
14
|
|
15
15
|
response.verify(
|
16
16
|
encoder.decode(challenge),
|
17
17
|
public_key: encoder.decode(public_key),
|
18
18
|
sign_count: sign_count,
|
19
|
+
user_presence: user_presence,
|
19
20
|
user_verification: user_verification,
|
20
21
|
rp_id: appid_extension_output ? appid : nil
|
21
22
|
)
|
@@ -9,10 +9,10 @@ module WebAuthn
|
|
9
9
|
WebAuthn::AuthenticatorAttestationResponse
|
10
10
|
end
|
11
11
|
|
12
|
-
def verify(challenge, user_verification: nil)
|
12
|
+
def verify(challenge, user_presence: nil, user_verification: nil)
|
13
13
|
super
|
14
14
|
|
15
|
-
response.verify(encoder.decode(challenge), user_verification: user_verification)
|
15
|
+
response.verify(encoder.decode(challenge), user_presence: user_presence, user_verification: user_verification)
|
16
16
|
|
17
17
|
true
|
18
18
|
end
|
@@ -9,15 +9,16 @@ module WebAuthn
|
|
9
9
|
class RootCertificateFinderNotSupportedError < Error; end
|
10
10
|
|
11
11
|
class RelyingParty
|
12
|
+
DEFAULT_ALGORITHMS = ["ES256", "PS256", "RS256"].compact.freeze
|
13
|
+
|
12
14
|
def self.if_pss_supported(algorithm)
|
13
15
|
OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
|
14
16
|
end
|
15
17
|
|
16
|
-
DEFAULT_ALGORITHMS = ["ES256", "PS256", "RS256"].compact.freeze
|
17
|
-
|
18
18
|
def initialize(
|
19
19
|
algorithms: DEFAULT_ALGORITHMS.dup,
|
20
20
|
encoding: WebAuthn::Encoder::STANDARD_ENCODING,
|
21
|
+
allowed_origins: nil,
|
21
22
|
origin: nil,
|
22
23
|
id: nil,
|
23
24
|
name: nil,
|
@@ -30,7 +31,7 @@ module WebAuthn
|
|
30
31
|
)
|
31
32
|
@algorithms = algorithms
|
32
33
|
@encoding = encoding
|
33
|
-
@
|
34
|
+
@allowed_origins = allowed_origins
|
34
35
|
@id = id
|
35
36
|
@name = name
|
36
37
|
@verify_attestation_statement = verify_attestation_statement
|
@@ -38,12 +39,13 @@ module WebAuthn
|
|
38
39
|
@silent_authentication = silent_authentication
|
39
40
|
@acceptable_attestation_types = acceptable_attestation_types
|
40
41
|
@legacy_u2f_appid = legacy_u2f_appid
|
42
|
+
self.origin = origin
|
41
43
|
self.attestation_root_certificates_finders = attestation_root_certificates_finders
|
42
44
|
end
|
43
45
|
|
44
46
|
attr_accessor :algorithms,
|
45
47
|
:encoding,
|
46
|
-
:
|
48
|
+
:allowed_origins,
|
47
49
|
:id,
|
48
50
|
:name,
|
49
51
|
:verify_attestation_statement,
|
@@ -52,7 +54,7 @@ module WebAuthn
|
|
52
54
|
:acceptable_attestation_types,
|
53
55
|
:legacy_u2f_appid
|
54
56
|
|
55
|
-
attr_reader :attestation_root_certificates_finders
|
57
|
+
attr_reader :attestation_root_certificates_finders, :origin
|
56
58
|
|
57
59
|
# This is the user-data encoder.
|
58
60
|
# Used to decode user input and to encode data provided to the user.
|
@@ -81,10 +83,10 @@ module WebAuthn
|
|
81
83
|
)
|
82
84
|
end
|
83
85
|
|
84
|
-
def verify_registration(raw_credential, challenge, user_verification: nil)
|
86
|
+
def verify_registration(raw_credential, challenge, user_presence: nil, user_verification: nil)
|
85
87
|
webauthn_credential = WebAuthn::Credential.from_create(raw_credential, relying_party: self)
|
86
88
|
|
87
|
-
if webauthn_credential.verify(challenge, user_verification: user_verification)
|
89
|
+
if webauthn_credential.verify(challenge, user_presence: user_presence, user_verification: user_verification)
|
88
90
|
webauthn_credential
|
89
91
|
end
|
90
92
|
end
|
@@ -99,6 +101,7 @@ module WebAuthn
|
|
99
101
|
def verify_authentication(
|
100
102
|
raw_credential,
|
101
103
|
challenge,
|
104
|
+
user_presence: nil,
|
102
105
|
user_verification: nil,
|
103
106
|
public_key: nil,
|
104
107
|
sign_count: nil
|
@@ -111,10 +114,24 @@ module WebAuthn
|
|
111
114
|
challenge,
|
112
115
|
public_key: public_key || stored_credential.public_key,
|
113
116
|
sign_count: sign_count || stored_credential.sign_count,
|
117
|
+
user_presence: user_presence,
|
114
118
|
user_verification: user_verification
|
115
119
|
)
|
116
120
|
block_given? ? [webauthn_credential, stored_credential] : webauthn_credential
|
117
121
|
end
|
118
122
|
end
|
123
|
+
|
124
|
+
# DEPRECATED: This method will be removed in future.
|
125
|
+
def origin=(new_origin)
|
126
|
+
return if new_origin.nil?
|
127
|
+
|
128
|
+
warn(
|
129
|
+
"DEPRECATION WARNING: `WebAuthn.origin` is deprecated and will be removed in future. "\
|
130
|
+
"Please use `WebAuthn.allowed_origins` instead "\
|
131
|
+
"that also allows configuring multiple origins per Relying Party"
|
132
|
+
)
|
133
|
+
|
134
|
+
@allowed_origins ||= Array(new_origin) # rubocop:disable Naming/MemoizedInstanceVariableName
|
135
|
+
end
|
119
136
|
end
|
120
137
|
end
|
@@ -43,7 +43,9 @@ module WebAuthn
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def attestation_trust_path
|
46
|
-
@attestation_trust_path ||= [
|
46
|
+
@attestation_trust_path ||= [
|
47
|
+
OpenSSL::X509::Certificate.new(WebAuthn::Encoders::Base64Encoder.decode(@certificate))
|
48
|
+
]
|
47
49
|
end
|
48
50
|
|
49
51
|
private
|
@@ -51,14 +53,14 @@ module WebAuthn
|
|
51
53
|
# https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#u2f-authenticatorMakeCredential-interoperability
|
52
54
|
# Let credentialId be a credentialIdLength byte array initialized with CTAP1/U2F response key handle bytes.
|
53
55
|
def credential_id
|
54
|
-
|
56
|
+
WebAuthn::Encoders::Base64UrlEncoder.decode(@key_handle)
|
55
57
|
end
|
56
58
|
|
57
59
|
# Let x9encodedUserPublicKey be the user public key returned in the U2F registration response message [U2FRawMsgs].
|
58
60
|
# Let coseEncodedCredentialPublicKey be the result of converting x9encodedUserPublicKey’s value from ANS X9.62 /
|
59
61
|
# Sec-1 v2 uncompressed curve point representation [SEC1V2] to COSE_Key representation ([RFC8152] Section 7).
|
60
62
|
def credential_cose_key
|
61
|
-
decoded_public_key =
|
63
|
+
decoded_public_key = WebAuthn::Encoders::Base64Encoder.decode(@public_key)
|
62
64
|
if WebAuthn::AttestationStatement::FidoU2f::PublicKey.uncompressed_point?(decoded_public_key)
|
63
65
|
COSE::Key::EC2.new(
|
64
66
|
alg: COSE::Algorithm.by_name("ES256").id,
|
data/lib/webauthn/version.rb
CHANGED
data/lib/webauthn.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -34,19 +34,18 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.required_ruby_version = ">= 2.5"
|
35
35
|
|
36
36
|
spec.add_dependency "android_key_attestation", "~> 0.3.0"
|
37
|
-
spec.add_dependency "awrence", "~> 1.1"
|
38
37
|
spec.add_dependency "bindata", "~> 2.4"
|
39
38
|
spec.add_dependency "cbor", "~> 0.5.9"
|
40
39
|
spec.add_dependency "cose", "~> 1.1"
|
41
40
|
spec.add_dependency "openssl", ">= 2.2"
|
42
|
-
spec.add_dependency "safety_net_attestation", "~> 0.
|
43
|
-
spec.add_dependency "tpm-key_attestation", "~> 0.
|
41
|
+
spec.add_dependency "safety_net_attestation", "~> 0.5.0"
|
42
|
+
spec.add_dependency "tpm-key_attestation", "~> 0.14.0"
|
44
43
|
|
45
44
|
spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
|
46
45
|
spec.add_development_dependency "byebug", "~> 11.0"
|
47
46
|
spec.add_development_dependency "rake", "~> 13.0"
|
48
47
|
spec.add_development_dependency "rspec", "~> 3.8"
|
49
|
-
spec.add_development_dependency "rubocop", "~> 1
|
50
|
-
spec.add_development_dependency "rubocop-rake", "~> 0.5
|
51
|
-
spec.add_development_dependency "rubocop-rspec", "
|
48
|
+
spec.add_development_dependency "rubocop", "~> 1"
|
49
|
+
spec.add_development_dependency "rubocop-rake", "~> 0.5"
|
50
|
+
spec.add_development_dependency "rubocop-rspec", ">= 2.2", "< 4.0"
|
52
51
|
end
|