webauthn 2.5.2 → 3.0.0.alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/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:
|