webauthn 1.15.0 → 1.16.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/.travis.yml +3 -1
- data/CHANGELOG.md +9 -0
- data/README.md +1 -0
- data/lib/cose/algorithm.rb +2 -0
- data/lib/webauthn/authenticator_assertion_response.rb +2 -2
- data/lib/webauthn/authenticator_attestation_response.rb +1 -1
- data/lib/webauthn/authenticator_data.rb +20 -8
- data/lib/webauthn/authenticator_response.rb +11 -1
- data/lib/webauthn/configuration.rb +11 -0
- data/lib/webauthn/credential_creation_options.rb +37 -7
- data/lib/webauthn/credential_request_options.rb +18 -4
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +22 -7
- data/lib/webauthn/public_key_credential.rb +36 -0
- data/lib/webauthn/signature_verifier.rb +19 -6
- data/lib/webauthn/version.rb +1 -1
- data/lib/webauthn.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42985c0e7686cf588be02d1afef8e315e0692561533f076e36419cd2ef3b2c74
|
4
|
+
data.tar.gz: 3970cd2424034f2a28e1ffb92954151aa1ae113391731f63f9d1734c10a61bce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6993542bbc3ec899929f6ba5e13b34a347692f13da9efd7cab541de38e0473f2271c0c9e67085bd5e22439c59c97c7dc86680b7579cf578fd973eabc23fea2c4
|
7
|
+
data.tar.gz: c30c9f9e9c43fddf01621eff5f1068b9ecc280649801a53ba7daa736fb1eb88724efaf8e50e4d8768544ce8b2a725318693eec3b84c6d8cef0137d8d712f6e85
|
data/.travis.yml
CHANGED
@@ -4,7 +4,8 @@ cache: bundler
|
|
4
4
|
|
5
5
|
rvm:
|
6
6
|
- ruby-head
|
7
|
-
- 2.
|
7
|
+
- 2.7.0-preview1
|
8
|
+
- 2.6.3
|
8
9
|
- 2.5.5
|
9
10
|
- 2.4.6
|
10
11
|
- 2.3.8
|
@@ -17,6 +18,7 @@ matrix:
|
|
17
18
|
fast_finish: true
|
18
19
|
allow_failures:
|
19
20
|
- rvm: ruby-head
|
21
|
+
- rvm: 2.7.0-preview1
|
20
22
|
|
21
23
|
before_install:
|
22
24
|
- wget http://archive.ubuntu.com/ubuntu/pool/universe/f/faketime/libfaketime_0.9.7-3_amd64.deb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.16.0] - 2019-06-13
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Ability to enforce [user verification](https://www.w3.org/TR/webauthn/#user-verification) with extra argument in the `#verify` method.
|
8
|
+
- Support RS1 (RSA w/ SHA-1) credentials. Off by default. Enable by adding `"RS1"` to `WebAuthn.configuration.algorithms` array.
|
9
|
+
- Support PS256 (RSA Probabilistic Signature Scheme w/ SHA-256) credentials. On by default. Thank you @bdewater.
|
10
|
+
|
3
11
|
## [v1.15.0] - 2019-05-16
|
4
12
|
|
5
13
|
### Added
|
@@ -179,6 +187,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
179
187
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
180
188
|
- Works with ruby 2.5
|
181
189
|
|
190
|
+
[v1.16.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.15.0...v1.16.0/
|
182
191
|
[v1.15.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.14.0...v1.15.0/
|
183
192
|
[v1.14.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.13.0...v1.14.0/
|
184
193
|
[v1.13.0]: https://github.com/cedarcode/webauthn-ruby/compare/v1.12.0...v1.13.0/
|
data/README.md
CHANGED
@@ -23,6 +23,7 @@ WebAuthn (Web Authentication) is a W3C standard for secure public-key authentica
|
|
23
23
|
- WebAuthn [article](https://en.wikipedia.org/wiki/WebAuthn) in Wikipedia
|
24
24
|
- [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) in MDN
|
25
25
|
- WebAuthn [article with talk](https://developers.google.com/web/updates/2018/05/webauthn) in Google Developers
|
26
|
+
- How to use [WebAuthn in Android apps](https://developers.google.com/identity/fido/android/native-apps)
|
26
27
|
|
27
28
|
## Prerequisites
|
28
29
|
|
data/lib/cose/algorithm.rb
CHANGED
@@ -27,4 +27,6 @@ module COSE
|
|
27
27
|
end
|
28
28
|
|
29
29
|
COSE::Algorithm.register(-7, "ES256", "SHA256", COSE::Key::EC2::KTY_EC2, "prime256v1")
|
30
|
+
COSE::Algorithm.register(-37, "PS256", "SHA256", COSE::Key::RSA::KTY_RSA)
|
30
31
|
COSE::Algorithm.register(-257, "RS256", "SHA256", COSE::Key::RSA::KTY_RSA)
|
32
|
+
COSE::Algorithm.register(-65535, "RS1", "SHA1", COSE::Key::RSA::KTY_RSA)
|
@@ -20,8 +20,8 @@ module WebAuthn
|
|
20
20
|
@signature = signature
|
21
21
|
end
|
22
22
|
|
23
|
-
def verify(expected_challenge, expected_origin = nil, allowed_credentials:, rp_id: nil)
|
24
|
-
super(expected_challenge, expected_origin, rp_id: rp_id)
|
23
|
+
def verify(expected_challenge, expected_origin = nil, allowed_credentials:, user_verification: nil, rp_id: nil)
|
24
|
+
super(expected_challenge, expected_origin, user_verification: user_verification, rp_id: rp_id)
|
25
25
|
|
26
26
|
verify_item(:credential, allowed_credentials)
|
27
27
|
verify_item(:signature, credential_cose_key(allowed_credentials))
|
@@ -21,7 +21,7 @@ module WebAuthn
|
|
21
21
|
@attestation_object = attestation_object
|
22
22
|
end
|
23
23
|
|
24
|
-
def verify(expected_challenge, expected_origin = nil, rp_id: nil)
|
24
|
+
def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
|
25
25
|
super
|
26
26
|
|
27
27
|
verify_item(:attestation_statement)
|
@@ -24,13 +24,9 @@ module WebAuthn
|
|
24
24
|
attr_reader :data
|
25
25
|
|
26
26
|
def valid?
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
(!extension_data_included? || extension_data)
|
31
|
-
else
|
32
|
-
data.length == base_length
|
33
|
-
end
|
27
|
+
valid_length? &&
|
28
|
+
(!attested_credential_data_included? || attested_credential_data.valid?) &&
|
29
|
+
(!extension_data_included? || extension_data)
|
34
30
|
end
|
35
31
|
|
36
32
|
def user_flagged?
|
@@ -74,7 +70,7 @@ module WebAuthn
|
|
74
70
|
end
|
75
71
|
|
76
72
|
def extension_data
|
77
|
-
@extension_data ||= CBOR.decode(
|
73
|
+
@extension_data ||= CBOR.decode(raw_extension_data)
|
78
74
|
end
|
79
75
|
|
80
76
|
def flags
|
@@ -83,6 +79,14 @@ module WebAuthn
|
|
83
79
|
|
84
80
|
private
|
85
81
|
|
82
|
+
def valid_length?
|
83
|
+
data.length == base_length + attested_credential_data_length + extension_data_length
|
84
|
+
end
|
85
|
+
|
86
|
+
def raw_extension_data
|
87
|
+
data_at(extension_data_position)
|
88
|
+
end
|
89
|
+
|
86
90
|
def attested_credential_data_position
|
87
91
|
base_length
|
88
92
|
end
|
@@ -95,6 +99,14 @@ module WebAuthn
|
|
95
99
|
end
|
96
100
|
end
|
97
101
|
|
102
|
+
def extension_data_length
|
103
|
+
if extension_data_included?
|
104
|
+
raw_extension_data.length
|
105
|
+
else
|
106
|
+
0
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
98
110
|
def extension_data_position
|
99
111
|
base_length + attested_credential_data_length
|
100
112
|
end
|
@@ -17,13 +17,14 @@ module WebAuthn
|
|
17
17
|
class TokenBindingVerificationError < VerificationError; end
|
18
18
|
class TypeVerificationError < VerificationError; end
|
19
19
|
class UserPresenceVerificationError < VerificationError; end
|
20
|
+
class UserVerifiedVerificationError < VerificationError; end
|
20
21
|
|
21
22
|
class AuthenticatorResponse
|
22
23
|
def initialize(client_data_json:)
|
23
24
|
@client_data_json = client_data_json
|
24
25
|
end
|
25
26
|
|
26
|
-
def verify(expected_challenge, expected_origin = nil, rp_id: nil)
|
27
|
+
def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
|
27
28
|
expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
|
28
29
|
rp_id ||= WebAuthn.configuration.rp_id
|
29
30
|
|
@@ -34,6 +35,7 @@ module WebAuthn
|
|
34
35
|
verify_item(:authenticator_data)
|
35
36
|
verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
|
36
37
|
verify_item(:user_presence)
|
38
|
+
verify_item(:user_verified, user_verification)
|
37
39
|
|
38
40
|
true
|
39
41
|
end
|
@@ -90,6 +92,14 @@ module WebAuthn
|
|
90
92
|
authenticator_data.user_flagged?
|
91
93
|
end
|
92
94
|
|
95
|
+
def valid_user_verified?(user_verification)
|
96
|
+
if user_verification
|
97
|
+
authenticator_data.user_verified?
|
98
|
+
else
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
93
103
|
def rp_id_from_origin(expected_origin)
|
94
104
|
URI.parse(expected_origin).host
|
95
105
|
end
|
@@ -10,8 +10,19 @@ module WebAuthn
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class Configuration
|
13
|
+
def self.if_pss_supported(algorithm)
|
14
|
+
OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
|
15
|
+
end
|
16
|
+
|
17
|
+
DEFAULT_ALGORITHMS = ["ES256", if_pss_supported("PS256"), "RS256"].compact.freeze
|
18
|
+
|
19
|
+
attr_accessor :algorithms
|
13
20
|
attr_accessor :origin
|
14
21
|
attr_accessor :rp_id
|
15
22
|
attr_accessor :rp_name
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@algorithms = DEFAULT_ALGORITHMS.dup
|
26
|
+
end
|
16
27
|
end
|
17
28
|
end
|
@@ -14,14 +14,24 @@ module WebAuthn
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class CredentialCreationOptions < CredentialOptions
|
17
|
-
DEFAULT_ALGORITHMS = ["ES256", "RS256"].freeze
|
18
17
|
DEFAULT_RP_NAME = "web-server"
|
19
18
|
|
20
|
-
|
21
|
-
{ type: "public-key", alg: COSE::Algorithm.by_name(alg_name).id }
|
22
|
-
end.freeze
|
19
|
+
attr_accessor :attestation, :authenticator_selection, :exclude_credentials, :extensions
|
23
20
|
|
24
|
-
def initialize(
|
21
|
+
def initialize(
|
22
|
+
attestation: nil,
|
23
|
+
authenticator_selection: nil,
|
24
|
+
exclude_credentials: nil,
|
25
|
+
extensions: nil,
|
26
|
+
user_id:,
|
27
|
+
user_name:,
|
28
|
+
user_display_name: nil,
|
29
|
+
rp_name: nil
|
30
|
+
)
|
31
|
+
@attestation = attestation
|
32
|
+
@authenticator_selection = authenticator_selection
|
33
|
+
@exclude_credentials = exclude_credentials
|
34
|
+
@extensions = extensions
|
25
35
|
@user_id = user_id
|
26
36
|
@user_name = user_name
|
27
37
|
@user_display_name = user_display_name
|
@@ -29,16 +39,36 @@ module WebAuthn
|
|
29
39
|
end
|
30
40
|
|
31
41
|
def to_h
|
32
|
-
{
|
42
|
+
options = {
|
33
43
|
challenge: challenge,
|
34
44
|
pubKeyCredParams: pub_key_cred_params,
|
35
45
|
user: { id: user.id, name: user.name, displayName: user.display_name },
|
36
46
|
rp: { name: rp.name }
|
37
47
|
}
|
48
|
+
|
49
|
+
if attestation
|
50
|
+
options[:attestation] = attestation
|
51
|
+
end
|
52
|
+
|
53
|
+
if authenticator_selection
|
54
|
+
options[:authenticatorSelection] = authenticator_selection
|
55
|
+
end
|
56
|
+
|
57
|
+
if exclude_credentials
|
58
|
+
options[:excludeCredentials] = exclude_credentials
|
59
|
+
end
|
60
|
+
|
61
|
+
if extensions
|
62
|
+
options[:extensions] = extensions
|
63
|
+
end
|
64
|
+
|
65
|
+
options
|
38
66
|
end
|
39
67
|
|
40
68
|
def pub_key_cred_params
|
41
|
-
|
69
|
+
WebAuthn.configuration.algorithms.map do |alg_name|
|
70
|
+
{ type: "public-key", alg: COSE::Algorithm.by_name(alg_name).id }
|
71
|
+
end
|
42
72
|
end
|
43
73
|
|
44
74
|
def rp
|
@@ -8,12 +8,26 @@ module WebAuthn
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class CredentialRequestOptions < CredentialOptions
|
11
|
-
|
12
|
-
|
11
|
+
attr_accessor :allow_credentials, :extensions, :user_verification
|
12
|
+
|
13
|
+
def initialize(allow_credentials: [], extensions: nil, user_verification: nil)
|
14
|
+
@allow_credentials = allow_credentials
|
15
|
+
@extensions = extensions
|
16
|
+
@user_verification = user_verification
|
13
17
|
end
|
14
18
|
|
15
|
-
def
|
16
|
-
|
19
|
+
def to_h
|
20
|
+
options = { challenge: challenge, allowCredentials: allow_credentials }
|
21
|
+
|
22
|
+
if extensions
|
23
|
+
options[:extensions] = extensions
|
24
|
+
end
|
25
|
+
|
26
|
+
if user_verification
|
27
|
+
options[:userVerification] = user_verification
|
28
|
+
end
|
29
|
+
|
30
|
+
options
|
17
31
|
end
|
18
32
|
end
|
19
33
|
end
|
@@ -9,23 +9,34 @@ module WebAuthn
|
|
9
9
|
class AuthenticatorData
|
10
10
|
AAGUID = SecureRandom.random_bytes(16)
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
|
12
|
+
def initialize(
|
13
|
+
rp_id_hash:,
|
14
|
+
credential: {
|
15
|
+
id: SecureRandom.random_bytes(16),
|
16
|
+
public_key: OpenSSL::PKey::EC.new("prime256v1").generate_key.public_key
|
17
|
+
},
|
18
|
+
sign_count: 0,
|
19
|
+
user_present: true,
|
20
|
+
user_verified: !user_present,
|
21
|
+
aaguid: AAGUID,
|
22
|
+
extensions: { "fakeExtension" => "fakeExtensionValue" }
|
23
|
+
)
|
14
24
|
@rp_id_hash = rp_id_hash
|
15
25
|
@credential = credential
|
16
26
|
@sign_count = sign_count
|
17
27
|
@user_present = user_present
|
18
28
|
@user_verified = user_verified
|
19
29
|
@aaguid = aaguid
|
30
|
+
@extensions = extensions
|
20
31
|
end
|
21
32
|
|
22
33
|
def serialize
|
23
|
-
rp_id_hash + flags + serialized_sign_count + attested_credential_data +
|
34
|
+
rp_id_hash + flags + serialized_sign_count + attested_credential_data + extension_data
|
24
35
|
end
|
25
36
|
|
26
37
|
private
|
27
38
|
|
28
|
-
attr_reader :rp_id_hash, :credential, :sign_count, :user_present, :user_verified
|
39
|
+
attr_reader :rp_id_hash, :credential, :sign_count, :user_present, :user_verified, :extensions
|
29
40
|
|
30
41
|
def flags
|
31
42
|
[
|
@@ -58,8 +69,12 @@ module WebAuthn
|
|
58
69
|
end
|
59
70
|
end
|
60
71
|
|
61
|
-
def
|
62
|
-
|
72
|
+
def extension_data
|
73
|
+
if extensions
|
74
|
+
CBOR.encode(extensions)
|
75
|
+
else
|
76
|
+
""
|
77
|
+
end
|
63
78
|
end
|
64
79
|
|
65
80
|
def bit(flag)
|
@@ -79,7 +94,7 @@ module WebAuthn
|
|
79
94
|
end
|
80
95
|
|
81
96
|
def extension_data_included_bit
|
82
|
-
if
|
97
|
+
if extension_data.empty?
|
83
98
|
"0"
|
84
99
|
else
|
85
100
|
"1"
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module WebAuthn
|
6
|
+
class PublicKeyCredential
|
7
|
+
VALID_TYPE = "public-key"
|
8
|
+
|
9
|
+
attr_reader :type, :id, :raw_id, :response
|
10
|
+
|
11
|
+
def initialize(type:, id:, raw_id:, response:)
|
12
|
+
@type = type
|
13
|
+
@id = id
|
14
|
+
@raw_id = raw_id
|
15
|
+
@response = response
|
16
|
+
end
|
17
|
+
|
18
|
+
def verify(*args)
|
19
|
+
valid_type? || raise("invalid type")
|
20
|
+
valid_id? || raise("invalid id")
|
21
|
+
response.verify(*args)
|
22
|
+
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def valid_type?
|
29
|
+
type == VALID_TYPE
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_id?
|
33
|
+
raw_id && id && raw_id == Base64.urlsafe_decode64(id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -23,7 +23,12 @@ module WebAuthn
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def verify(signature, verification_data)
|
26
|
-
|
26
|
+
if rsa_pss?
|
27
|
+
public_key.verify_pss(cose_algorithm.hash, signature, verification_data,
|
28
|
+
salt_length: :digest, mgf1_hash: cose_algorithm.hash)
|
29
|
+
else
|
30
|
+
public_key.verify(cose_algorithm.hash, signature, verification_data)
|
31
|
+
end
|
27
32
|
end
|
28
33
|
|
29
34
|
private
|
@@ -39,14 +44,22 @@ module WebAuthn
|
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
47
|
+
def rsa_pss?
|
48
|
+
cose_algorithm.name.start_with?("PS")
|
49
|
+
end
|
50
|
+
|
42
51
|
def validate
|
43
|
-
if cose_algorithm
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
else
|
52
|
+
if !cose_algorithm
|
53
|
+
raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
|
54
|
+
elsif !supported_algorithms.include?(cose_algorithm.name)
|
48
55
|
raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
|
56
|
+
elsif !KTY_MAP[cose_algorithm.kty].include?(public_key.class)
|
57
|
+
raise("Incompatible algorithm and key")
|
49
58
|
end
|
50
59
|
end
|
60
|
+
|
61
|
+
def supported_algorithms
|
62
|
+
WebAuthn.configuration.algorithms
|
63
|
+
end
|
51
64
|
end
|
52
65
|
end
|
data/lib/webauthn/version.rb
CHANGED
data/lib/webauthn.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webauthn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gonzalo Rodriguez
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-06-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bindata
|
@@ -256,6 +256,7 @@ files:
|
|
256
256
|
- lib/webauthn/fake_authenticator/attestation_object.rb
|
257
257
|
- lib/webauthn/fake_authenticator/authenticator_data.rb
|
258
258
|
- lib/webauthn/fake_client.rb
|
259
|
+
- lib/webauthn/public_key_credential.rb
|
259
260
|
- lib/webauthn/security_utils.rb
|
260
261
|
- lib/webauthn/signature_verifier.rb
|
261
262
|
- lib/webauthn/version.rb
|