webauthn 2.4.1 → 3.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -48
- data/CHANGELOG.md +4 -12
- data/SECURITY.md +2 -4
- data/lib/cose/rsapkcs1_algorithm.rb +0 -7
- data/lib/webauthn/attestation_object.rb +9 -5
- data/lib/webauthn/attestation_statement.rb +2 -2
- data/lib/webauthn/attestation_statement/base.rb +7 -10
- data/lib/webauthn/attestation_statement/none.rb +1 -7
- 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 +6 -5
- data/lib/webauthn/configuration.rb +36 -42
- 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_client.rb +4 -5
- data/lib/webauthn/public_key_credential.rb +15 -8
- data/lib/webauthn/public_key_credential/creation_options.rb +3 -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/relying_party.rb +117 -0
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +3 -3
- metadata +12 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f418eb52a085d8e7c03bd1c3d11b88ed8fe467f4ec01d3178836689d470f436
|
4
|
+
data.tar.gz: 4ab67e8804cbd7d785e29b94760af6db9d6b1e52de1a58bafc74aed19b5b7e21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccdcd22e494079eb67c122c03e3061166f91de50dd0c2bf8662748c976483a9d472fa9f8d6b67db9abf484e8eac182195b5f1e1cca8aaaf88146d831919f1c93
|
7
|
+
data.tar.gz: b2506b530c796ee57e5d037e7dd3692b1e359408fd9757141b6c4f042c43d57344baa09dc63d17ace549927eeea50abaa8c0771a0ab6da0ece5d880362d890db
|
data/.rubocop.yml
CHANGED
@@ -24,9 +24,6 @@ Layout:
|
|
24
24
|
Layout/ClassStructure:
|
25
25
|
Enabled: true
|
26
26
|
|
27
|
-
Layout/EmptyLinesAroundAttributeAccessor:
|
28
|
-
Enabled: true
|
29
|
-
|
30
27
|
Layout/FirstMethodArgumentLineBreak:
|
31
28
|
Enabled: true
|
32
29
|
|
@@ -41,54 +38,9 @@ Layout/MultilineAssignmentLayout:
|
|
41
38
|
Layout/MultilineMethodArgumentLineBreaks:
|
42
39
|
Enabled: true
|
43
40
|
|
44
|
-
Layout/SpaceAroundMethodCallOperator:
|
45
|
-
Enabled: true
|
46
|
-
|
47
41
|
Lint:
|
48
42
|
Enabled: true
|
49
43
|
|
50
|
-
Lint/DeprecatedOpenSSLConstant:
|
51
|
-
Enabled: true
|
52
|
-
|
53
|
-
Lint/MixedRegexpCaptureTypes:
|
54
|
-
Enabled: true
|
55
|
-
|
56
|
-
Lint/RaiseException:
|
57
|
-
Enabled: true
|
58
|
-
|
59
|
-
Lint/StructNewOverride:
|
60
|
-
Enabled: true
|
61
|
-
|
62
|
-
Lint/BinaryOperatorWithIdenticalOperands:
|
63
|
-
Enabled: true
|
64
|
-
|
65
|
-
Lint/DuplicateElsifCondition:
|
66
|
-
Enabled: true
|
67
|
-
|
68
|
-
Lint/DuplicateRescueException:
|
69
|
-
Enabled: true
|
70
|
-
|
71
|
-
Lint/EmptyConditionalBody:
|
72
|
-
Enabled: true
|
73
|
-
|
74
|
-
Lint/FloatComparison:
|
75
|
-
Enabled: true
|
76
|
-
|
77
|
-
Lint/MissingSuper:
|
78
|
-
Enabled: true
|
79
|
-
|
80
|
-
Lint/OutOfRangeRegexpRef:
|
81
|
-
Enabled: true
|
82
|
-
|
83
|
-
Lint/SelfAssignment:
|
84
|
-
Enabled: true
|
85
|
-
|
86
|
-
Lint/TopLevelReturnWithArgument:
|
87
|
-
Enabled: true
|
88
|
-
|
89
|
-
Lint/UnreachableLoop:
|
90
|
-
Enabled: true
|
91
|
-
|
92
44
|
Naming:
|
93
45
|
Enabled: true
|
94
46
|
|
data/CHANGELOG.md
CHANGED
@@ -1,17 +1,10 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [
|
4
|
-
|
5
|
-
### Fixed
|
6
|
-
|
7
|
-
- Fix verification of new credential if no attestation provided and 'None' type is not among configured `acceptable_attestation_types`. I.e. reject it instead of letting it go through.
|
8
|
-
|
9
|
-
## [v2.4.0] - 2020-09-03
|
3
|
+
## [v3.0.0.alpha1] - 2020-06-27
|
10
4
|
|
11
5
|
### Added
|
12
6
|
|
13
|
-
-
|
14
|
-
- `FakeClient#get` accepts `user_handle:` keyword argument ([@lgarron])
|
7
|
+
- Ability to define multiple relying parties with the introduction of the `WebAuthn::RelyingParty` class ([@padulafacundo], [@brauliomartinezlm])
|
15
8
|
|
16
9
|
## [v2.3.0] - 2020-06-27
|
17
10
|
|
@@ -307,8 +300,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
307
300
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
308
301
|
- Works with ruby 2.5
|
309
302
|
|
310
|
-
[
|
311
|
-
[v2.4.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v2.4.0/
|
303
|
+
[v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha1/
|
312
304
|
[v2.3.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.1...v2.3.0/
|
313
305
|
[v2.2.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.2.0...v2.2.1/
|
314
306
|
[v2.2.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.1.0...v2.2.0/
|
@@ -336,6 +328,7 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
336
328
|
[v0.2.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.1.0...v0.2.0/
|
337
329
|
[v0.1.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.0.0...v0.1.0/
|
338
330
|
|
331
|
+
[@brauliomartinezlm]: https://github.com/brauliomartinezlm
|
339
332
|
[@bdewater]: https://github.com/bdewater
|
340
333
|
[@jdongelmans]: https://github.com/jdongelmans
|
341
334
|
[@kalebtesfay]: https://github.com/kalebtesfay
|
@@ -344,4 +337,3 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
344
337
|
[@ssuttner]: https://github.com/ssuttner
|
345
338
|
[@padulafacundo]: https://github.com/padulafacundo
|
346
339
|
[@santiagorodriguez96]: https://github.com/santiagorodriguez96
|
347
|
-
[@lgarron]: https://github.com/lgarron
|
data/SECURITY.md
CHANGED
@@ -4,11 +4,9 @@
|
|
4
4
|
|
5
5
|
| Version | Supported |
|
6
6
|
| ------- | ------------------ |
|
7
|
-
| 2.4.z | :white_check_mark: |
|
8
|
-
| 2.3.z | :white_check_mark: |
|
9
7
|
| 2.2.z | :white_check_mark: |
|
10
|
-
| 2.1.z | :
|
11
|
-
| 2.0.z | :
|
8
|
+
| 2.1.z | :white_check_mark: |
|
9
|
+
| 2.0.z | :white_check_mark: |
|
12
10
|
| 1.18.z | :white_check_mark: |
|
13
11
|
| < 1.18 | :x: |
|
14
12
|
|
@@ -40,11 +40,4 @@ end
|
|
40
40
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-257, "RS256", hash_function: "SHA256"))
|
41
41
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-258, "RS384", hash_function: "SHA384"))
|
42
42
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-259, "RS512", hash_function: "SHA512"))
|
43
|
-
|
44
|
-
# Patch openssl-signature_algorithm gem to support discouraged/deprecated RSA-PKCS#1 with SHA-1
|
45
|
-
# (RS1 in JOSE/COSE terminology) algorithm needed for WebAuthn.
|
46
|
-
OpenSSL::SignatureAlgorithm::RSAPKCS1.const_set(
|
47
|
-
:ACCEPTED_HASH_FUNCTIONS,
|
48
|
-
OpenSSL::SignatureAlgorithm::RSAPKCS1::ACCEPTED_HASH_FUNCTIONS + ["SHA1"]
|
49
|
-
)
|
50
43
|
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-65535, "RS1", hash_function: "SHA1"))
|
@@ -10,18 +10,22 @@ module WebAuthn
|
|
10
10
|
class AttestationObject
|
11
11
|
extend Forwardable
|
12
12
|
|
13
|
-
def self.deserialize(attestation_object)
|
14
|
-
from_map(CBOR.decode(attestation_object))
|
13
|
+
def self.deserialize(attestation_object, relying_party)
|
14
|
+
from_map(CBOR.decode(attestation_object), relying_party)
|
15
15
|
end
|
16
16
|
|
17
|
-
def self.from_map(map)
|
17
|
+
def self.from_map(map, relying_party)
|
18
18
|
new(
|
19
19
|
authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
|
20
|
-
attestation_statement: WebAuthn::AttestationStatement.from(
|
20
|
+
attestation_statement: WebAuthn::AttestationStatement.from(
|
21
|
+
map["fmt"],
|
22
|
+
map["attStmt"],
|
23
|
+
relying_party: relying_party
|
24
|
+
)
|
21
25
|
)
|
22
26
|
end
|
23
27
|
|
24
|
-
attr_reader :authenticator_data, :attestation_statement
|
28
|
+
attr_reader :authenticator_data, :attestation_statement, :relying_party
|
25
29
|
|
26
30
|
def initialize(authenticator_data:, attestation_statement:)
|
27
31
|
@authenticator_data = authenticator_data
|
@@ -28,11 +28,11 @@ module WebAuthn
|
|
28
28
|
ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
|
29
29
|
}.freeze
|
30
30
|
|
31
|
-
def self.from(format, statement)
|
31
|
+
def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
|
32
32
|
klass = FORMAT_TO_CLASS[format]
|
33
33
|
|
34
34
|
if klass
|
35
|
-
klass.new(statement)
|
35
|
+
klass.new(statement, relying_party)
|
36
36
|
else
|
37
37
|
raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
|
38
38
|
end
|
@@ -26,8 +26,9 @@ module WebAuthn
|
|
26
26
|
class Base
|
27
27
|
AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
|
28
28
|
|
29
|
-
def initialize(statement)
|
29
|
+
def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
|
30
30
|
@statement = statement
|
31
|
+
@relying_party = relying_party
|
31
32
|
end
|
32
33
|
|
33
34
|
def valid?(_authenticator_data, _client_data_hash)
|
@@ -54,7 +55,7 @@ module WebAuthn
|
|
54
55
|
|
55
56
|
private
|
56
57
|
|
57
|
-
attr_reader :statement
|
58
|
+
attr_reader :statement, :relying_party
|
58
59
|
|
59
60
|
def matching_aaguid?(attested_credential_data_aaguid)
|
60
61
|
extension = attestation_certificate&.extensions&.detect { |ext| ext.oid == AAGUID_EXTENSION_OID }
|
@@ -95,10 +96,10 @@ module WebAuthn
|
|
95
96
|
|
96
97
|
def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
|
97
98
|
if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
|
98
|
-
|
99
|
+
relying_party.acceptable_attestation_types.include?(attestation_type) &&
|
99
100
|
valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
|
100
101
|
else
|
101
|
-
|
102
|
+
relying_party.acceptable_attestation_types.include?(attestation_type)
|
102
103
|
end
|
103
104
|
end
|
104
105
|
|
@@ -122,7 +123,7 @@ module WebAuthn
|
|
122
123
|
|
123
124
|
def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
|
124
125
|
root_certificates =
|
125
|
-
|
126
|
+
relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
|
126
127
|
if certs.empty?
|
127
128
|
finder.find(
|
128
129
|
attestation_format: format,
|
@@ -169,14 +170,10 @@ module WebAuthn
|
|
169
170
|
def cose_algorithm
|
170
171
|
@cose_algorithm ||=
|
171
172
|
COSE::Algorithm.find(algorithm).tap do |alg|
|
172
|
-
alg &&
|
173
|
+
alg && relying_party.algorithms.include?(alg.name) ||
|
173
174
|
raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
|
174
175
|
end
|
175
176
|
end
|
176
|
-
|
177
|
-
def configuration
|
178
|
-
WebAuthn.configuration
|
179
|
-
end
|
180
177
|
end
|
181
178
|
end
|
182
179
|
end
|
@@ -6,18 +6,12 @@ module WebAuthn
|
|
6
6
|
module AttestationStatement
|
7
7
|
class None < Base
|
8
8
|
def valid?(*_args)
|
9
|
-
if statement == {}
|
9
|
+
if statement == {}
|
10
10
|
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE, nil]
|
11
11
|
else
|
12
12
|
false
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def attestation_type
|
19
|
-
WebAuthn::AttestationStatement::ATTESTATION_TYPE_NONE
|
20
|
-
end
|
21
15
|
end
|
22
16
|
end
|
23
17
|
end
|
@@ -10,8 +10,8 @@ module WebAuthn
|
|
10
10
|
class SignCountVerificationError < VerificationError; end
|
11
11
|
|
12
12
|
class AuthenticatorAssertionResponse < AuthenticatorResponse
|
13
|
-
def self.from_client(response)
|
14
|
-
encoder =
|
13
|
+
def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
|
14
|
+
encoder = relying_party.encoder
|
15
15
|
|
16
16
|
user_handle =
|
17
17
|
if response["userHandle"]
|
@@ -22,7 +22,8 @@ module WebAuthn
|
|
22
22
|
authenticator_data: encoder.decode(response["authenticatorData"]),
|
23
23
|
client_data_json: encoder.decode(response["clientDataJSON"]),
|
24
24
|
signature: encoder.decode(response["signature"]),
|
25
|
-
user_handle: user_handle
|
25
|
+
user_handle: user_handle,
|
26
|
+
relying_party: relying_party
|
26
27
|
)
|
27
28
|
end
|
28
29
|
|
@@ -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
|
@@ -20,13 +20,14 @@ module WebAuthn
|
|
20
20
|
class UserVerifiedVerificationError < VerificationError; end
|
21
21
|
|
22
22
|
class AuthenticatorResponse
|
23
|
-
def initialize(client_data_json:)
|
23
|
+
def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
|
24
24
|
@client_data_json = client_data_json
|
25
|
+
@relying_party = relying_party
|
25
26
|
end
|
26
27
|
|
27
28
|
def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
|
28
|
-
expected_origin ||=
|
29
|
-
rp_id ||=
|
29
|
+
expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
|
30
|
+
rp_id ||= relying_party.id
|
30
31
|
|
31
32
|
verify_item(:type)
|
32
33
|
verify_item(:token_binding)
|
@@ -35,7 +36,7 @@ module WebAuthn
|
|
35
36
|
verify_item(:authenticator_data)
|
36
37
|
verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
|
37
38
|
|
38
|
-
if !
|
39
|
+
if !relying_party.silent_authentication
|
39
40
|
verify_item(:user_presence)
|
40
41
|
end
|
41
42
|
|
@@ -58,7 +59,7 @@ module WebAuthn
|
|
58
59
|
|
59
60
|
private
|
60
61
|
|
61
|
-
attr_reader :client_data_json
|
62
|
+
attr_reader :client_data_json, :relying_party
|
62
63
|
|
63
64
|
def verify_item(item, *args)
|
64
65
|
if send("valid_#{item}?", *args)
|
@@ -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,54 +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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
35
39
|
|
36
40
|
def initialize
|
37
|
-
@
|
38
|
-
@encoding = WebAuthn::Encoder::STANDARD_ENCODING
|
39
|
-
@verify_attestation_statement = true
|
40
|
-
@credential_options_timeout = 120000
|
41
|
-
@silent_authentication = false
|
42
|
-
@acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA']
|
43
|
-
@attestation_root_certificates_finders = []
|
41
|
+
@relying_party = RelyingParty.new
|
44
42
|
end
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
def encoder
|
49
|
-
@encoder ||= WebAuthn::Encoder.new(encoding)
|
44
|
+
def rp_name
|
45
|
+
relying_party.name
|
50
46
|
end
|
51
47
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
end
|
48
|
+
def rp_name=(name)
|
49
|
+
relying_party.name = name
|
50
|
+
end
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
52
|
+
def rp_id
|
53
|
+
relying_party.id
|
54
|
+
end
|
62
55
|
|
63
|
-
|
56
|
+
def rp_id=(id)
|
57
|
+
relying_party.id = id
|
64
58
|
end
|
65
59
|
end
|
66
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
|
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,8 +73,7 @@ module WebAuthn
|
|
73
73
|
user_present: true,
|
74
74
|
user_verified: false,
|
75
75
|
sign_count: nil,
|
76
|
-
extensions: nil
|
77
|
-
user_handle: nil)
|
76
|
+
extensions: nil)
|
78
77
|
rp_id ||= URI.parse(origin).host
|
79
78
|
|
80
79
|
client_data_json = data_json_for(:get, encoder.decode(challenge))
|
@@ -98,14 +97,14 @@ module WebAuthn
|
|
98
97
|
"clientDataJSON" => encoder.encode(client_data_json),
|
99
98
|
"authenticatorData" => encoder.encode(assertion[:authenticator_data]),
|
100
99
|
"signature" => encoder.encode(assertion[:signature]),
|
101
|
-
"userHandle" =>
|
100
|
+
"userHandle" => nil
|
102
101
|
}
|
103
102
|
}
|
104
103
|
end
|
105
104
|
|
106
105
|
private
|
107
106
|
|
108
|
-
attr_reader :authenticator
|
107
|
+
attr_reader :authenticator
|
109
108
|
|
110
109
|
def data_json_for(method, challenge)
|
111
110
|
data = {
|
@@ -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
|
@@ -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,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
|
@@ -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
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -37,17 +37,17 @@ Gem::Specification.new do |spec|
|
|
37
37
|
spec.add_dependency "awrence", "~> 1.1"
|
38
38
|
spec.add_dependency "bindata", "~> 2.4"
|
39
39
|
spec.add_dependency "cbor", "~> 0.5.9"
|
40
|
-
spec.add_dependency "cose", "~> 1.
|
40
|
+
spec.add_dependency "cose", "~> 1.0"
|
41
41
|
spec.add_dependency "openssl", "~> 2.0"
|
42
42
|
spec.add_dependency "safety_net_attestation", "~> 0.4.0"
|
43
43
|
spec.add_dependency "securecompare", "~> 1.0"
|
44
|
-
spec.add_dependency "tpm-key_attestation", "~> 0.
|
44
|
+
spec.add_dependency "tpm-key_attestation", "~> 0.9.0"
|
45
45
|
|
46
46
|
spec.add_development_dependency "appraisal", "~> 2.3.0"
|
47
47
|
spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
|
48
48
|
spec.add_development_dependency "byebug", "~> 11.0"
|
49
49
|
spec.add_development_dependency "rake", "~> 13.0"
|
50
50
|
spec.add_development_dependency "rspec", "~> 3.8"
|
51
|
-
spec.add_development_dependency "rubocop", "0.
|
51
|
+
spec.add_development_dependency "rubocop", "0.80.1"
|
52
52
|
spec.add_development_dependency "rubocop-rspec", "~> 1.38.1"
|
53
53
|
end
|
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:
|
4
|
+
version: 3.0.0.alpha1
|
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:
|
12
|
+
date: 2020-06-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: android_key_attestation
|
@@ -73,14 +73,14 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '1.
|
76
|
+
version: '1.0'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '1.
|
83
|
+
version: '1.0'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: openssl
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,14 +129,14 @@ dependencies:
|
|
129
129
|
requirements:
|
130
130
|
- - "~>"
|
131
131
|
- !ruby/object:Gem::Version
|
132
|
-
version: 0.
|
132
|
+
version: 0.9.0
|
133
133
|
type: :runtime
|
134
134
|
prerelease: false
|
135
135
|
version_requirements: !ruby/object:Gem::Requirement
|
136
136
|
requirements:
|
137
137
|
- - "~>"
|
138
138
|
- !ruby/object:Gem::Version
|
139
|
-
version: 0.
|
139
|
+
version: 0.9.0
|
140
140
|
- !ruby/object:Gem::Dependency
|
141
141
|
name: appraisal
|
142
142
|
requirement: !ruby/object:Gem::Requirement
|
@@ -219,14 +219,14 @@ dependencies:
|
|
219
219
|
requirements:
|
220
220
|
- - '='
|
221
221
|
- !ruby/object:Gem::Version
|
222
|
-
version:
|
222
|
+
version: 0.80.1
|
223
223
|
type: :development
|
224
224
|
prerelease: false
|
225
225
|
version_requirements: !ruby/object:Gem::Requirement
|
226
226
|
requirements:
|
227
227
|
- - '='
|
228
228
|
- !ruby/object:Gem::Version
|
229
|
-
version:
|
229
|
+
version: 0.80.1
|
230
230
|
- !ruby/object:Gem::Dependency
|
231
231
|
name: rubocop-rspec
|
232
232
|
requirement: !ruby/object:Gem::Requirement
|
@@ -313,6 +313,7 @@ files:
|
|
313
313
|
- lib/webauthn/public_key_credential/user_entity.rb
|
314
314
|
- lib/webauthn/public_key_credential_with_assertion.rb
|
315
315
|
- lib/webauthn/public_key_credential_with_attestation.rb
|
316
|
+
- lib/webauthn/relying_party.rb
|
316
317
|
- lib/webauthn/security_utils.rb
|
317
318
|
- lib/webauthn/u2f_migrator.rb
|
318
319
|
- lib/webauthn/version.rb
|
@@ -337,11 +338,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
337
338
|
version: '2.4'
|
338
339
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
339
340
|
requirements:
|
340
|
-
- - "
|
341
|
+
- - ">"
|
341
342
|
- !ruby/object:Gem::Version
|
342
|
-
version:
|
343
|
+
version: 1.3.1
|
343
344
|
requirements: []
|
344
|
-
rubygems_version: 3.
|
345
|
+
rubygems_version: 3.1.4
|
345
346
|
signing_key:
|
346
347
|
specification_version: 4
|
347
348
|
summary: WebAuthn ruby server library
|