webauthn 2.4.1 → 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 -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
|