webauthn 2.5.2 → 3.0.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +0 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +14 -3
- data/docs/advanced_configuration.md +2 -2
- data/lib/webauthn/attestation_object.rb +9 -5
- data/lib/webauthn/attestation_statement/base.rb +7 -10
- data/lib/webauthn/attestation_statement.rb +2 -2
- 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 -5
- data/lib/webauthn/authenticator_response.rb +6 -5
- data/lib/webauthn/configuration.rb +36 -38
- data/lib/webauthn/credential.rb +5 -4
- data/lib/webauthn/fake_client.rb +2 -2
- 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/public_key_credential.rb +15 -8
- data/lib/webauthn/relying_party.rb +117 -0
- data/lib/webauthn/u2f_migrator.rb +4 -1
- data/lib/webauthn/version.rb +1 -1
- data/webauthn.gemspec +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f9f14328571fd39b5e19c9a6ec5b427cfd4dea9fc2ab4774135f4453e14b676
|
4
|
+
data.tar.gz: 1e33b7564ea1b3b8aeb32864782a0dad1e0207f1c70c037eb80140db2272ae01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1e22bb07b1ebced7851c592a2a4e8b0e6772e502f402bd841383f8071963e3b0df8c75da66db5623840a32f14799439d09a52c8dabcdd4e5edbef8a0a419a00
|
7
|
+
data.tar.gz: 1a1b1c647a06398edad9d1da94ca8de53202e184f8930b2fe001f82e17d8416516cbaf4db4fd012fd44d1ac5da32c6d8d32c971507584bf00ef9d74b15d535b5
|
data/.github/workflows/build.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.0.0.alpha2] - 2022-09-12
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Rebased support for multiple relying parties from v3.0.0.alpha1 on top of v2.5.2, the previous alpha version was based on v2.3.0 ([@bdewater])
|
8
|
+
|
9
|
+
### BREAKING CHANGES
|
10
|
+
|
11
|
+
- Bumped minimum required Ruby version to 2.5 ([@bdewater])
|
12
|
+
|
3
13
|
## [v3.0.0.alpha1] - 2020-06-27
|
4
14
|
|
5
15
|
### Added
|
6
16
|
|
7
17
|
- Ability to define multiple relying parties with the introduction of the `WebAuthn::RelyingParty` class ([@padulafacundo], [@brauliomartinezlm])
|
8
18
|
|
9
|
-
## [v2.5.
|
19
|
+
## [v2.5.2] - 2022-07-13
|
10
20
|
|
11
21
|
### Added
|
12
22
|
|
@@ -340,8 +350,9 @@ Note: Both additions should help making it compatible with Chrome for Android 70
|
|
340
350
|
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
341
351
|
- Works with ruby 2.5
|
342
352
|
|
343
|
-
[v3.0.0.
|
344
|
-
[
|
353
|
+
[v3.0.0.alpha2]: https://github.com/cedarcode/webauthn-ruby/compare/2-stable...v3.0.0.alpha2/
|
354
|
+
[v3.0.0.alpha1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.3.0...v3.0.0.alpha1
|
355
|
+
[v2.5.2]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.1...v2.5.2/
|
345
356
|
[v2.5.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.5.0...v2.5.1/
|
346
357
|
[v2.5.0]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.1...v2.5.0/
|
347
358
|
[v2.4.1]: https://github.com/cedarcode/webauthn-ruby/compare/v2.4.0...v2.4.1/
|
@@ -40,7 +40,7 @@ Intead of the [Global Configuration](../README.md#configuration) you place in `c
|
|
40
40
|
# In this case the default would be "admin.example.com", but you can set it to
|
41
41
|
# the suffix "example.com"
|
42
42
|
#
|
43
|
-
#
|
43
|
+
# id: "example.com"
|
44
44
|
|
45
45
|
# Configure preferred binary-to-text encoding scheme. This should match the encoding scheme
|
46
46
|
# used in your client-side (user agent) code before sending the credential to the server.
|
@@ -171,4 +171,4 @@ end
|
|
171
171
|
|
172
172
|
Adding a configuration for a new instance does not mean you need to get rid of your Global configuration. They can co-exist in your application and be both available for the different usages you might have. `WebAuthn.configuration.relying_party` will always return the global one while `WebAuthn::RelyingParty.new`, executed anywhere in your codebase, will allow you to create a different instance as you see the need. They will not collide and instead operate in isolation without any shared state.
|
173
173
|
|
174
|
-
The gem API described in the current [Usage](../README.md#usage) section for the [Global Configuration](../README.md#configuration) approach will still valid but the [Instance Based API](#instance-based-api) also works with the global `relying_party` that is maintain globally at `WebAuthn.configuration.relying_party`.
|
174
|
+
The gem API described in the current [Usage](../README.md#usage) section for the [Global Configuration](../README.md#configuration) approach will still valid but the [Instance Based API](#instance-based-api) also works with the global `relying_party` that is maintain globally at `WebAuthn.configuration.relying_party`.
|
@@ -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,8 +28,9 @@ module WebAuthn
|
|
28
28
|
class Base
|
29
29
|
AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
|
30
30
|
|
31
|
-
def initialize(statement)
|
31
|
+
def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
|
32
32
|
@statement = statement
|
33
|
+
@relying_party = relying_party
|
33
34
|
end
|
34
35
|
|
35
36
|
def valid?(_authenticator_data, _client_data_hash)
|
@@ -50,7 +51,7 @@ module WebAuthn
|
|
50
51
|
|
51
52
|
private
|
52
53
|
|
53
|
-
attr_reader :statement
|
54
|
+
attr_reader :statement, :relying_party
|
54
55
|
|
55
56
|
def matching_aaguid?(attested_credential_data_aaguid)
|
56
57
|
extension = attestation_certificate&.find_extension(AAGUID_EXTENSION_OID)
|
@@ -93,10 +94,10 @@ module WebAuthn
|
|
93
94
|
|
94
95
|
def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
|
95
96
|
if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
|
96
|
-
|
97
|
+
relying_party.acceptable_attestation_types.include?(attestation_type) &&
|
97
98
|
valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
|
98
99
|
else
|
99
|
-
|
100
|
+
relying_party.acceptable_attestation_types.include?(attestation_type)
|
100
101
|
end
|
101
102
|
end
|
102
103
|
|
@@ -120,7 +121,7 @@ module WebAuthn
|
|
120
121
|
|
121
122
|
def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
|
122
123
|
root_certificates =
|
123
|
-
|
124
|
+
relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
|
124
125
|
if certs.empty?
|
125
126
|
finder.find(
|
126
127
|
attestation_format: format,
|
@@ -158,14 +159,10 @@ module WebAuthn
|
|
158
159
|
def cose_algorithm
|
159
160
|
@cose_algorithm ||=
|
160
161
|
COSE::Algorithm.find(algorithm).tap do |alg|
|
161
|
-
alg &&
|
162
|
+
alg && relying_party.algorithms.include?(alg.name) ||
|
162
163
|
raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
|
163
164
|
end
|
164
165
|
end
|
165
|
-
|
166
|
-
def configuration
|
167
|
-
WebAuthn.configuration
|
168
|
-
end
|
169
166
|
end
|
170
167
|
end
|
171
168
|
end
|
@@ -31,11 +31,11 @@ module WebAuthn
|
|
31
31
|
ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
|
32
32
|
}.freeze
|
33
33
|
|
34
|
-
def self.from(format, statement)
|
34
|
+
def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
|
35
35
|
klass = FORMAT_TO_CLASS[format]
|
36
36
|
|
37
37
|
if klass
|
38
|
-
klass.new(statement)
|
38
|
+
klass.new(statement, relying_party)
|
39
39
|
else
|
40
40
|
raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
|
41
41
|
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?
|
@@ -22,9 +22,8 @@ module WebAuthn
|
|
22
22
|
count_bytes_remaining :trailing_bytes_length
|
23
23
|
string :trailing_bytes, length: :trailing_bytes_length
|
24
24
|
|
25
|
-
# TODO: use keyword_init when we dropped Ruby 2.4 support
|
26
25
|
Credential =
|
27
|
-
Struct.new(:id, :public_key) do
|
26
|
+
Struct.new(:id, :public_key, :algorithm, keyword_init: true) do
|
28
27
|
def public_key_object
|
29
28
|
COSE::Key.deserialize(public_key).to_pkey
|
30
29
|
end
|
@@ -47,7 +46,7 @@ module WebAuthn
|
|
47
46
|
def credential
|
48
47
|
@credential ||=
|
49
48
|
if valid?
|
50
|
-
Credential.new(id, public_key)
|
49
|
+
Credential.new(id: id, public_key: public_key, algorithm: algorithm)
|
51
50
|
end
|
52
51
|
end
|
53
52
|
|
@@ -59,10 +58,16 @@ module WebAuthn
|
|
59
58
|
|
60
59
|
private
|
61
60
|
|
61
|
+
def algorithm
|
62
|
+
COSE::Algorithm.find(cose_key.alg).name
|
63
|
+
end
|
64
|
+
|
62
65
|
def valid_credential_public_key?
|
63
|
-
cose_key
|
66
|
+
!!cose_key.alg
|
67
|
+
end
|
64
68
|
|
65
|
-
|
69
|
+
def cose_key
|
70
|
+
@cose_key ||= COSE::Key.deserialize(public_key)
|
66
71
|
end
|
67
72
|
|
68
73
|
def public_key
|
@@ -19,13 +19,14 @@ module WebAuthn
|
|
19
19
|
class UserVerifiedVerificationError < VerificationError; end
|
20
20
|
|
21
21
|
class AuthenticatorResponse
|
22
|
-
def initialize(client_data_json:)
|
22
|
+
def initialize(client_data_json:, relying_party: WebAuthn.configuration.relying_party)
|
23
23
|
@client_data_json = client_data_json
|
24
|
+
@relying_party = relying_party
|
24
25
|
end
|
25
26
|
|
26
27
|
def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
|
27
|
-
expected_origin ||=
|
28
|
-
rp_id ||=
|
28
|
+
expected_origin ||= relying_party.origin || raise("Unspecified expected origin")
|
29
|
+
rp_id ||= relying_party.id
|
29
30
|
|
30
31
|
verify_item(:type)
|
31
32
|
verify_item(:token_binding)
|
@@ -34,7 +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
|
|
37
|
-
if !
|
38
|
+
if !relying_party.silent_authentication
|
38
39
|
verify_item(:user_presence)
|
39
40
|
end
|
40
41
|
|
@@ -57,7 +58,7 @@ module WebAuthn
|
|
57
58
|
|
58
59
|
private
|
59
60
|
|
60
|
-
attr_reader :client_data_json
|
61
|
+
attr_reader :client_data_json, :relying_party
|
61
62
|
|
62
63
|
def verify_item(item, *args)
|
63
64
|
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,50 +12,49 @@ module WebAuthn
|
|
13
12
|
yield(configuration)
|
14
13
|
end
|
15
14
|
|
16
|
-
class RootCertificateFinderNotSupportedError < Error; end
|
17
|
-
|
18
15
|
class Configuration
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
def_delegators :@relying_party,
|
19
|
+
:algorithms,
|
20
|
+
:algorithms=,
|
21
|
+
:encoding,
|
22
|
+
:encoding=,
|
23
|
+
:origin,
|
24
|
+
:origin=,
|
25
|
+
:verify_attestation_statement,
|
26
|
+
:verify_attestation_statement=,
|
27
|
+
:credential_options_timeout,
|
28
|
+
:credential_options_timeout=,
|
29
|
+
:silent_authentication,
|
30
|
+
:silent_authentication=,
|
31
|
+
:acceptable_attestation_types,
|
32
|
+
:acceptable_attestation_types=,
|
33
|
+
:attestation_root_certificates_finders,
|
34
|
+
:attestation_root_certificates_finders=,
|
35
|
+
:encoder,
|
36
|
+
:encoder=
|
37
|
+
|
38
|
+
attr_reader :relying_party
|
31
39
|
|
32
40
|
def initialize
|
33
|
-
@
|
34
|
-
@encoding = WebAuthn::Encoder::STANDARD_ENCODING
|
35
|
-
@verify_attestation_statement = true
|
36
|
-
@credential_options_timeout = 120000
|
37
|
-
@silent_authentication = false
|
38
|
-
@acceptable_attestation_types = ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA', 'AnonCA']
|
39
|
-
@attestation_root_certificates_finders = []
|
41
|
+
@relying_party = RelyingParty.new
|
40
42
|
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
def encoder
|
45
|
-
@encoder ||= WebAuthn::Encoder.new(encoding)
|
44
|
+
def rp_name
|
45
|
+
relying_party.name
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
end
|
48
|
+
def rp_name=(name)
|
49
|
+
relying_party.name = name
|
50
|
+
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
52
|
+
def rp_id
|
53
|
+
relying_party.id
|
54
|
+
end
|
58
55
|
|
59
|
-
|
56
|
+
def rp_id=(id)
|
57
|
+
relying_party.id = id
|
60
58
|
end
|
61
59
|
end
|
62
60
|
end
|
data/lib/webauthn/credential.rb
CHANGED
@@ -4,6 +4,7 @@ require "webauthn/public_key_credential/creation_options"
|
|
4
4
|
require "webauthn/public_key_credential/request_options"
|
5
5
|
require "webauthn/public_key_credential_with_assertion"
|
6
6
|
require "webauthn/public_key_credential_with_attestation"
|
7
|
+
require "webauthn/relying_party"
|
7
8
|
|
8
9
|
module WebAuthn
|
9
10
|
module Credential
|
@@ -15,12 +16,12 @@ module WebAuthn
|
|
15
16
|
WebAuthn::PublicKeyCredential::RequestOptions.new(**keyword_arguments)
|
16
17
|
end
|
17
18
|
|
18
|
-
def self.from_create(credential)
|
19
|
-
WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential)
|
19
|
+
def self.from_create(credential, relying_party: WebAuthn.configuration.relying_party)
|
20
|
+
WebAuthn::PublicKeyCredentialWithAttestation.from_client(credential, relying_party: relying_party)
|
20
21
|
end
|
21
22
|
|
22
|
-
def self.from_get(credential)
|
23
|
-
WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential)
|
23
|
+
def self.from_get(credential, relying_party: WebAuthn.configuration.relying_party)
|
24
|
+
WebAuthn::PublicKeyCredentialWithAssertion.from_client(credential, relying_party: relying_party)
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
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,
|
@@ -111,7 +111,7 @@ module WebAuthn
|
|
111
111
|
|
112
112
|
private
|
113
113
|
|
114
|
-
attr_reader :authenticator
|
114
|
+
attr_reader :authenticator
|
115
115
|
|
116
116
|
def data_json_for(method, challenge)
|
117
117
|
data = {
|
@@ -39,8 +39,8 @@ module WebAuthn
|
|
39
39
|
|
40
40
|
@rp =
|
41
41
|
if rp.is_a?(Hash)
|
42
|
-
rp[:name] ||=
|
43
|
-
rp[:id] ||=
|
42
|
+
rp[:name] ||= relying_party.name
|
43
|
+
rp[:id] ||= relying_party.id
|
44
44
|
|
45
45
|
RPEntity.new(**rp)
|
46
46
|
else
|
@@ -76,7 +76,7 @@ module WebAuthn
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def pub_key_cred_params_from_algs
|
79
|
-
Array(algs ||
|
79
|
+
Array(algs || relying_party.algorithms).map do |alg|
|
80
80
|
alg_id =
|
81
81
|
if alg.is_a?(String) || alg.is_a?(Symbol)
|
82
82
|
COSE::Algorithm.by_name(alg.to_s).id
|
@@ -8,10 +8,11 @@ module WebAuthn
|
|
8
8
|
class Options
|
9
9
|
CHALLENGE_LENGTH = 32
|
10
10
|
|
11
|
-
attr_reader :timeout, :extensions
|
11
|
+
attr_reader :timeout, :extensions, :relying_party
|
12
12
|
|
13
|
-
def initialize(timeout:
|
14
|
-
@
|
13
|
+
def initialize(timeout: nil, extensions: nil, relying_party: WebAuthn.configuration.relying_party)
|
14
|
+
@relying_party = relying_party
|
15
|
+
@timeout = timeout || default_timeout
|
15
16
|
@extensions = extensions
|
16
17
|
end
|
17
18
|
|
@@ -49,7 +50,7 @@ module WebAuthn
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def encoder
|
52
|
-
|
53
|
+
relying_party.encoder
|
53
54
|
end
|
54
55
|
|
55
56
|
def raw_challenge
|
@@ -57,11 +58,7 @@ module WebAuthn
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def default_timeout
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
def configuration
|
64
|
-
WebAuthn.configuration
|
61
|
+
relying_party.credential_options_timeout
|
65
62
|
end
|
66
63
|
|
67
64
|
def as_public_key_descriptors(ids)
|
@@ -10,7 +10,7 @@ module WebAuthn
|
|
10
10
|
def initialize(rp_id: nil, allow_credentials: nil, allow: nil, user_verification: nil, **keyword_arguments)
|
11
11
|
super(**keyword_arguments)
|
12
12
|
|
13
|
-
@rp_id = rp_id ||
|
13
|
+
@rp_id = rp_id || relying_party.id
|
14
14
|
@allow_credentials = allow_credentials
|
15
15
|
@allow = allow
|
16
16
|
@user_verification = user_verification
|
@@ -6,22 +6,31 @@ module WebAuthn
|
|
6
6
|
class PublicKeyCredential
|
7
7
|
attr_reader :type, :id, :raw_id, :client_extension_outputs, :response
|
8
8
|
|
9
|
-
def self.from_client(credential)
|
9
|
+
def self.from_client(credential, relying_party: WebAuthn.configuration.relying_party)
|
10
10
|
new(
|
11
11
|
type: credential["type"],
|
12
12
|
id: credential["id"],
|
13
|
-
raw_id:
|
13
|
+
raw_id: relying_party.encoder.decode(credential["rawId"]),
|
14
14
|
client_extension_outputs: credential["clientExtensionResults"],
|
15
|
-
response: response_class.from_client(credential["response"])
|
15
|
+
response: response_class.from_client(credential["response"], relying_party: relying_party),
|
16
|
+
encoder: relying_party.encoder
|
16
17
|
)
|
17
18
|
end
|
18
19
|
|
19
|
-
def initialize(
|
20
|
+
def initialize(
|
21
|
+
type:,
|
22
|
+
id:,
|
23
|
+
raw_id:,
|
24
|
+
response:,
|
25
|
+
client_extension_outputs: {},
|
26
|
+
encoder: WebAuthn.configuration.encoder
|
27
|
+
)
|
20
28
|
@type = type
|
21
29
|
@id = id
|
22
30
|
@raw_id = raw_id
|
23
31
|
@client_extension_outputs = client_extension_outputs
|
24
32
|
@response = response
|
33
|
+
@encoder = encoder
|
25
34
|
end
|
26
35
|
|
27
36
|
def verify(*_args)
|
@@ -41,6 +50,8 @@ module WebAuthn
|
|
41
50
|
|
42
51
|
private
|
43
52
|
|
53
|
+
attr_reader :encoder
|
54
|
+
|
44
55
|
def valid_type?
|
45
56
|
type == TYPE_PUBLIC_KEY
|
46
57
|
end
|
@@ -52,9 +63,5 @@ module WebAuthn
|
|
52
63
|
def authenticator_data
|
53
64
|
response&.authenticator_data
|
54
65
|
end
|
55
|
-
|
56
|
-
def encoder
|
57
|
-
WebAuthn.configuration.encoder
|
58
|
-
end
|
59
66
|
end
|
60
67
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "webauthn/credential"
|
5
|
+
require "webauthn/encoder"
|
6
|
+
require "webauthn/error"
|
7
|
+
|
8
|
+
module WebAuthn
|
9
|
+
class RootCertificateFinderNotSupportedError < Error; end
|
10
|
+
|
11
|
+
class RelyingParty
|
12
|
+
def self.if_pss_supported(algorithm)
|
13
|
+
OpenSSL::PKey::RSA.instance_methods.include?(:verify_pss) ? algorithm : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
DEFAULT_ALGORITHMS = ["ES256", "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', 'AnonCA'],
|
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
|
@@ -31,7 +31,10 @@ module WebAuthn
|
|
31
31
|
@credential ||=
|
32
32
|
begin
|
33
33
|
hash = authenticator_data.send(:credential)
|
34
|
-
WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(
|
34
|
+
WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new(
|
35
|
+
id: hash[:id],
|
36
|
+
public_key: hash[:public_key].serialize
|
37
|
+
)
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
|
-
spec.required_ruby_version = ">= 2.
|
34
|
+
spec.required_ruby_version = ">= 2.5"
|
35
35
|
|
36
36
|
spec.add_dependency "android_key_attestation", "~> 0.3.0"
|
37
37
|
spec.add_dependency "awrence", "~> 1.1"
|
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.alpha2
|
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: 2022-
|
12
|
+
date: 2022-09-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: android_key_attestation
|
@@ -302,6 +302,7 @@ files:
|
|
302
302
|
- lib/webauthn/public_key_credential/user_entity.rb
|
303
303
|
- lib/webauthn/public_key_credential_with_assertion.rb
|
304
304
|
- lib/webauthn/public_key_credential_with_attestation.rb
|
305
|
+
- lib/webauthn/relying_party.rb
|
305
306
|
- lib/webauthn/u2f_migrator.rb
|
306
307
|
- lib/webauthn/version.rb
|
307
308
|
- webauthn.gemspec
|
@@ -320,12 +321,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
320
321
|
requirements:
|
321
322
|
- - ">="
|
322
323
|
- !ruby/object:Gem::Version
|
323
|
-
version: '2.
|
324
|
+
version: '2.5'
|
324
325
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
325
326
|
requirements:
|
326
|
-
- - "
|
327
|
+
- - ">"
|
327
328
|
- !ruby/object:Gem::Version
|
328
|
-
version:
|
329
|
+
version: 1.3.1
|
329
330
|
requirements: []
|
330
331
|
rubygems_version: 3.2.32
|
331
332
|
signing_key:
|