webauthn 2.1.0 → 2.4.1
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 +113 -13
- data/.travis.yml +21 -18
- data/Appraisals +4 -0
- data/CHANGELOG.md +41 -0
- data/CONTRIBUTING.md +0 -5
- data/README.md +70 -8
- data/SECURITY.md +6 -4
- data/gemfiles/openssl_2_2.gemfile +7 -0
- data/lib/cose/rsapkcs1_algorithm.rb +50 -0
- data/lib/webauthn/attestation_object.rb +43 -0
- data/lib/webauthn/attestation_statement.rb +20 -20
- data/lib/webauthn/attestation_statement/android_key.rb +28 -30
- data/lib/webauthn/attestation_statement/android_safetynet.rb +27 -7
- data/lib/webauthn/attestation_statement/base.rb +108 -10
- data/lib/webauthn/attestation_statement/fido_u2f.rb +8 -6
- data/lib/webauthn/attestation_statement/none.rb +7 -1
- data/lib/webauthn/attestation_statement/packed.rb +13 -41
- data/lib/webauthn/attestation_statement/tpm.rb +38 -75
- data/lib/webauthn/authenticator_assertion_response.rb +3 -7
- data/lib/webauthn/authenticator_attestation_response.rb +19 -84
- data/lib/webauthn/authenticator_data.rb +51 -51
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +29 -50
- data/lib/webauthn/authenticator_response.rb +3 -0
- data/lib/webauthn/credential_creation_options.rb +2 -0
- data/lib/webauthn/credential_request_options.rb +2 -0
- data/lib/webauthn/fake_authenticator.rb +7 -3
- data/lib/webauthn/fake_authenticator/attestation_object.rb +7 -3
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +2 -4
- data/lib/webauthn/fake_client.rb +19 -5
- data/lib/webauthn/public_key.rb +21 -2
- data/lib/webauthn/public_key_credential.rb +13 -3
- data/lib/webauthn/u2f_migrator.rb +5 -4
- data/lib/webauthn/version.rb +1 -1
- data/script/ci/install-openssl +7 -0
- data/script/ci/install-ruby +13 -0
- data/webauthn.gemspec +13 -9
- metadata +54 -41
- data/lib/android_safetynet/attestation_response.rb +0 -116
- data/lib/cose/rsassa_algorithm.rb +0 -10
- data/lib/tpm/constants.rb +0 -44
- data/lib/tpm/s_attest.rb +0 -26
- data/lib/tpm/s_attest/s_certify_info.rb +0 -14
- data/lib/tpm/sized_buffer.rb +0 -13
- data/lib/tpm/t_public.rb +0 -32
- data/lib/tpm/t_public/s_ecc_parms.rb +0 -17
- data/lib/tpm/t_public/s_rsa_parms.rb +0 -17
- data/lib/webauthn/attestation_statement/android_key/authorization_list.rb +0 -39
- data/lib/webauthn/attestation_statement/android_key/key_description.rb +0 -37
- data/lib/webauthn/attestation_statement/tpm/cert_info.rb +0 -44
- data/lib/webauthn/attestation_statement/tpm/pub_area.rb +0 -85
- data/lib/webauthn/signature_verifier.rb +0 -77
data/SECURITY.md
CHANGED
@@ -4,11 +4,13 @@
|
|
4
4
|
|
5
5
|
| Version | Supported |
|
6
6
|
| ------- | ------------------ |
|
7
|
-
| 2.
|
8
|
-
| 2.
|
7
|
+
| 2.4.z | :white_check_mark: |
|
8
|
+
| 2.3.z | :white_check_mark: |
|
9
|
+
| 2.2.z | :white_check_mark: |
|
10
|
+
| 2.1.z | :x: |
|
11
|
+
| 2.0.z | :x: |
|
9
12
|
| 1.18.z | :white_check_mark: |
|
10
|
-
| 1.
|
11
|
-
| < 1.17 | :x: |
|
13
|
+
| < 1.18 | :x: |
|
12
14
|
|
13
15
|
## Reporting a Vulnerability
|
14
16
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cose"
|
4
|
+
require "cose/algorithm/signature_algorithm"
|
5
|
+
require "cose/error"
|
6
|
+
require "cose/key/rsa"
|
7
|
+
require "openssl/signature_algorithm/rsapkcs1"
|
8
|
+
|
9
|
+
class RSAPKCS1Algorithm < COSE::Algorithm::SignatureAlgorithm
|
10
|
+
attr_reader :hash_function
|
11
|
+
|
12
|
+
def initialize(*args, hash_function:)
|
13
|
+
super(*args)
|
14
|
+
|
15
|
+
@hash_function = hash_function
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def signature_algorithm_class
|
21
|
+
OpenSSL::SignatureAlgorithm::RSAPKCS1
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_key?(key)
|
25
|
+
to_cose_key(key).is_a?(COSE::Key::RSA)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_pkey(key)
|
29
|
+
case key
|
30
|
+
when COSE::Key::RSA
|
31
|
+
key.to_pkey
|
32
|
+
when OpenSSL::PKey::RSA
|
33
|
+
key
|
34
|
+
else
|
35
|
+
raise(COSE::Error, "Incompatible key for algorithm")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-257, "RS256", hash_function: "SHA256"))
|
41
|
+
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-258, "RS384", hash_function: "SHA384"))
|
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
|
+
COSE::Algorithm.register(RSAPKCS1Algorithm.new(-65535, "RS1", hash_function: "SHA1"))
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cbor"
|
4
|
+
require "forwardable"
|
5
|
+
require "openssl"
|
6
|
+
require "webauthn/attestation_statement"
|
7
|
+
require "webauthn/authenticator_data"
|
8
|
+
|
9
|
+
module WebAuthn
|
10
|
+
class AttestationObject
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def self.deserialize(attestation_object)
|
14
|
+
from_map(CBOR.decode(attestation_object))
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_map(map)
|
18
|
+
new(
|
19
|
+
authenticator_data: WebAuthn::AuthenticatorData.deserialize(map["authData"]),
|
20
|
+
attestation_statement: WebAuthn::AttestationStatement.from(map["fmt"], map["attStmt"])
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :authenticator_data, :attestation_statement
|
25
|
+
|
26
|
+
def initialize(authenticator_data:, attestation_statement:)
|
27
|
+
@authenticator_data = authenticator_data
|
28
|
+
@attestation_statement = attestation_statement
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid_attested_credential?
|
32
|
+
authenticator_data.attested_credential_data_included? &&
|
33
|
+
authenticator_data.attested_credential_data.valid?
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_attestation_statement?(client_data_hash)
|
37
|
+
attestation_statement.valid?(authenticator_data, client_data_hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def_delegators :authenticator_data, :credential, :aaguid
|
41
|
+
def_delegators :attestation_statement, :attestation_certificate_key_id
|
42
|
+
end
|
43
|
+
end
|
@@ -1,5 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "webauthn/attestation_statement/android_key"
|
4
|
+
require "webauthn/attestation_statement/android_safetynet"
|
5
|
+
require "webauthn/attestation_statement/fido_u2f"
|
6
|
+
require "webauthn/attestation_statement/none"
|
7
|
+
require "webauthn/attestation_statement/packed"
|
8
|
+
require "webauthn/attestation_statement/tpm"
|
3
9
|
require "webauthn/error"
|
4
10
|
|
5
11
|
module WebAuthn
|
@@ -13,28 +19,22 @@ module WebAuthn
|
|
13
19
|
ATTESTATION_FORMAT_ANDROID_KEY = "android-key"
|
14
20
|
ATTESTATION_FORMAT_TPM = "tpm"
|
15
21
|
|
22
|
+
FORMAT_TO_CLASS = {
|
23
|
+
ATTESTATION_FORMAT_NONE => WebAuthn::AttestationStatement::None,
|
24
|
+
ATTESTATION_FORMAT_FIDO_U2F => WebAuthn::AttestationStatement::FidoU2f,
|
25
|
+
ATTESTATION_FORMAT_PACKED => WebAuthn::AttestationStatement::Packed,
|
26
|
+
ATTESTATION_FORMAT_ANDROID_SAFETYNET => WebAuthn::AttestationStatement::AndroidSafetynet,
|
27
|
+
ATTESTATION_FORMAT_ANDROID_KEY => WebAuthn::AttestationStatement::AndroidKey,
|
28
|
+
ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM
|
29
|
+
}.freeze
|
30
|
+
|
16
31
|
def self.from(format, statement)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
when ATTESTATION_FORMAT_FIDO_U2F
|
22
|
-
require "webauthn/attestation_statement/fido_u2f"
|
23
|
-
WebAuthn::AttestationStatement::FidoU2f.new(statement)
|
24
|
-
when ATTESTATION_FORMAT_PACKED
|
25
|
-
require "webauthn/attestation_statement/packed"
|
26
|
-
WebAuthn::AttestationStatement::Packed.new(statement)
|
27
|
-
when ATTESTATION_FORMAT_ANDROID_SAFETYNET
|
28
|
-
require "webauthn/attestation_statement/android_safetynet"
|
29
|
-
WebAuthn::AttestationStatement::AndroidSafetynet.new(statement)
|
30
|
-
when ATTESTATION_FORMAT_ANDROID_KEY
|
31
|
-
require "webauthn/attestation_statement/android_key"
|
32
|
-
WebAuthn::AttestationStatement::AndroidKey.new(statement)
|
33
|
-
when ATTESTATION_FORMAT_TPM
|
34
|
-
require "webauthn/attestation_statement/tpm"
|
35
|
-
WebAuthn::AttestationStatement::TPM.new(statement)
|
32
|
+
klass = FORMAT_TO_CLASS[format]
|
33
|
+
|
34
|
+
if klass
|
35
|
+
klass.new(statement)
|
36
36
|
else
|
37
|
-
raise
|
37
|
+
raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -1,73 +1,71 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "android_key_attestation"
|
3
4
|
require "openssl"
|
4
|
-
require "webauthn/attestation_statement/android_key/key_description"
|
5
5
|
require "webauthn/attestation_statement/base"
|
6
|
-
require "webauthn/security_utils"
|
7
|
-
require "webauthn/signature_verifier"
|
8
6
|
|
9
7
|
module WebAuthn
|
10
8
|
module AttestationStatement
|
11
9
|
class AndroidKey < Base
|
12
|
-
EXTENSION_DATA_OID = "1.3.6.1.4.1.11129.2.1.17"
|
13
|
-
|
14
|
-
# https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/keymaster_defs.h
|
15
|
-
KM_ORIGIN_GENERATED = 0
|
16
|
-
KM_PURPOSE_SIGN = 2
|
17
|
-
|
18
10
|
def valid?(authenticator_data, client_data_hash)
|
19
11
|
valid_signature?(authenticator_data, client_data_hash) &&
|
20
12
|
matching_public_key?(authenticator_data) &&
|
21
13
|
valid_attestation_challenge?(client_data_hash) &&
|
22
|
-
|
14
|
+
all_applications_fields_not_set? &&
|
23
15
|
valid_authorization_list_origin? &&
|
24
16
|
valid_authorization_list_purpose? &&
|
25
|
-
|
17
|
+
trustworthy?(aaguid: authenticator_data.aaguid) &&
|
18
|
+
[attestation_type, attestation_trust_path]
|
26
19
|
end
|
27
20
|
|
28
21
|
private
|
29
22
|
|
30
|
-
def valid_signature?(authenticator_data, client_data_hash)
|
31
|
-
WebAuthn::SignatureVerifier
|
32
|
-
.new(algorithm, attestation_certificate.public_key)
|
33
|
-
.verify(signature, authenticator_data.data + client_data_hash)
|
34
|
-
end
|
35
|
-
|
36
23
|
def matching_public_key?(authenticator_data)
|
37
24
|
attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
|
38
25
|
end
|
39
26
|
|
40
27
|
def valid_attestation_challenge?(client_data_hash)
|
41
|
-
|
28
|
+
android_key_attestation.verify_challenge(client_data_hash)
|
29
|
+
rescue AndroidKeyAttestation::ChallengeMismatchError
|
30
|
+
false
|
42
31
|
end
|
43
32
|
|
44
|
-
def
|
45
|
-
|
33
|
+
def valid_certificate_chain?(aaguid: nil, **_)
|
34
|
+
android_key_attestation.verify_certificate_chain(root_certificates: root_certificates(aaguid: aaguid))
|
35
|
+
rescue AndroidKeyAttestation::CertificateVerificationError
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def all_applications_fields_not_set?
|
40
|
+
!tee_enforced.all_applications && !software_enforced.all_applications
|
46
41
|
end
|
47
42
|
|
48
43
|
def valid_authorization_list_origin?
|
49
|
-
tee_enforced.origin ==
|
44
|
+
tee_enforced.origin == :generated || software_enforced.origin == :generated
|
50
45
|
end
|
51
46
|
|
52
47
|
def valid_authorization_list_purpose?
|
53
|
-
tee_enforced.purpose ==
|
48
|
+
tee_enforced.purpose == [:sign] || software_enforced.purpose == [:sign]
|
54
49
|
end
|
55
50
|
|
56
51
|
def tee_enforced
|
57
|
-
|
52
|
+
android_key_attestation.tee_enforced
|
58
53
|
end
|
59
54
|
|
60
55
|
def software_enforced
|
61
|
-
|
56
|
+
android_key_attestation.software_enforced
|
57
|
+
end
|
58
|
+
|
59
|
+
def attestation_type
|
60
|
+
WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC
|
62
61
|
end
|
63
62
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
raw_key_description = OpenSSL::ASN1.decode(extension_data).value.last
|
63
|
+
def default_root_certificates
|
64
|
+
AndroidKeyAttestation::Statement::GOOGLE_ROOT_CERTIFICATES
|
65
|
+
end
|
68
66
|
|
69
|
-
|
70
|
-
|
67
|
+
def android_key_attestation
|
68
|
+
@android_key_attestation ||= AndroidKeyAttestation::Statement.new(*certificates)
|
71
69
|
end
|
72
70
|
end
|
73
71
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "safety_net_attestation"
|
4
4
|
require "openssl"
|
5
5
|
require "webauthn/attestation_statement/base"
|
6
6
|
|
@@ -12,11 +12,12 @@ module WebAuthn
|
|
12
12
|
valid_response?(authenticator_data, client_data_hash) &&
|
13
13
|
valid_version? &&
|
14
14
|
cts_profile_match? &&
|
15
|
-
|
15
|
+
trustworthy?(aaguid: authenticator_data.aaguid) &&
|
16
|
+
[attestation_type, attestation_trust_path]
|
16
17
|
end
|
17
18
|
|
18
19
|
def attestation_certificate
|
19
|
-
|
20
|
+
attestation_trust_path.first
|
20
21
|
end
|
21
22
|
|
22
23
|
private
|
@@ -25,8 +26,9 @@ module WebAuthn
|
|
25
26
|
nonce = Digest::SHA256.base64digest(authenticator_data.data + client_data_hash)
|
26
27
|
|
27
28
|
begin
|
28
|
-
attestation_response
|
29
|
-
|
29
|
+
attestation_response
|
30
|
+
.verify(nonce, trusted_certificates: root_certificates(aaguid: authenticator_data.aaguid), time: time)
|
31
|
+
rescue SafetyNetAttestation::Error
|
30
32
|
false
|
31
33
|
end
|
32
34
|
end
|
@@ -40,12 +42,30 @@ module WebAuthn
|
|
40
42
|
attestation_response.cts_profile_match?
|
41
43
|
end
|
42
44
|
|
45
|
+
def valid_certificate_chain?(**_)
|
46
|
+
# Already performed as part of #valid_response?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def attestation_type
|
51
|
+
WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC
|
52
|
+
end
|
53
|
+
|
54
|
+
# SafetyNetAttestation returns full chain including root, WebAuthn expects only the x5c certificates
|
43
55
|
def attestation_trust_path
|
44
|
-
attestation_response.certificate_chain
|
56
|
+
attestation_response.certificate_chain[0..-2]
|
45
57
|
end
|
46
58
|
|
47
59
|
def attestation_response
|
48
|
-
@attestation_response ||= ::
|
60
|
+
@attestation_response ||= SafetyNetAttestation::Statement.new(statement["response"])
|
61
|
+
end
|
62
|
+
|
63
|
+
def default_root_certificates
|
64
|
+
SafetyNetAttestation::Statement::GOOGLE_ROOT_CERTIFICATES
|
65
|
+
end
|
66
|
+
|
67
|
+
def time
|
68
|
+
Time.now
|
49
69
|
end
|
50
70
|
end
|
51
71
|
end
|
@@ -1,21 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "cose/algorithm"
|
4
|
+
require "cose/error"
|
5
|
+
require "cose/rsapkcs1_algorithm"
|
3
6
|
require "openssl"
|
4
7
|
require "webauthn/authenticator_data/attested_credential_data"
|
5
8
|
require "webauthn/error"
|
6
9
|
|
7
10
|
module WebAuthn
|
8
11
|
module AttestationStatement
|
12
|
+
class UnsupportedAlgorithm < Error; end
|
13
|
+
|
9
14
|
ATTESTATION_TYPE_NONE = "None"
|
10
15
|
ATTESTATION_TYPE_BASIC = "Basic"
|
11
16
|
ATTESTATION_TYPE_SELF = "Self"
|
12
17
|
ATTESTATION_TYPE_ATTCA = "AttCA"
|
13
|
-
ATTESTATION_TYPE_ECDAA = "ECDAA"
|
14
18
|
ATTESTATION_TYPE_BASIC_OR_ATTCA = "Basic_or_AttCA"
|
15
19
|
|
16
|
-
|
17
|
-
|
20
|
+
ATTESTATION_TYPES_WITH_ROOT = [
|
21
|
+
ATTESTATION_TYPE_BASIC,
|
22
|
+
ATTESTATION_TYPE_BASIC_OR_ATTCA,
|
23
|
+
ATTESTATION_TYPE_ATTCA
|
24
|
+
].freeze
|
18
25
|
|
26
|
+
class Base
|
19
27
|
AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
|
20
28
|
|
21
29
|
def initialize(statement)
|
@@ -26,6 +34,10 @@ module WebAuthn
|
|
26
34
|
raise NotImplementedError
|
27
35
|
end
|
28
36
|
|
37
|
+
def format
|
38
|
+
WebAuthn::AttestationStatement::FORMAT_TO_CLASS.key(self.class)
|
39
|
+
end
|
40
|
+
|
29
41
|
def attestation_certificate
|
30
42
|
certificates&.first
|
31
43
|
end
|
@@ -36,6 +48,10 @@ module WebAuthn
|
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
51
|
+
def attestation_certificate_key_id
|
52
|
+
raw_subject_key_identifier&.unpack("H*")&.[](0)
|
53
|
+
end
|
54
|
+
|
39
55
|
private
|
40
56
|
|
41
57
|
attr_reader :statement
|
@@ -53,9 +69,10 @@ module WebAuthn
|
|
53
69
|
end
|
54
70
|
|
55
71
|
def certificates
|
56
|
-
@certificates ||=
|
57
|
-
|
58
|
-
|
72
|
+
@certificates ||=
|
73
|
+
raw_certificates&.map do |raw_certificate|
|
74
|
+
OpenSSL::X509::Certificate.new(raw_certificate)
|
75
|
+
end
|
59
76
|
end
|
60
77
|
|
61
78
|
def algorithm
|
@@ -66,10 +83,6 @@ module WebAuthn
|
|
66
83
|
statement["x5c"]
|
67
84
|
end
|
68
85
|
|
69
|
-
def raw_ecdaa_key_id
|
70
|
-
statement["ecdaaKeyId"]
|
71
|
-
end
|
72
|
-
|
73
86
|
def signature
|
74
87
|
statement["sig"]
|
75
88
|
end
|
@@ -79,6 +92,91 @@ module WebAuthn
|
|
79
92
|
certificates
|
80
93
|
end
|
81
94
|
end
|
95
|
+
|
96
|
+
def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
|
97
|
+
if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
|
98
|
+
configuration.acceptable_attestation_types.include?(attestation_type) &&
|
99
|
+
valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
|
100
|
+
else
|
101
|
+
configuration.acceptable_attestation_types.include?(attestation_type)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid_certificate_chain?(aaguid: nil, attestation_certificate_key_id: nil)
|
106
|
+
attestation_root_certificates_store(
|
107
|
+
aaguid: aaguid,
|
108
|
+
attestation_certificate_key_id: attestation_certificate_key_id
|
109
|
+
).verify(attestation_certificate, attestation_trust_path)
|
110
|
+
end
|
111
|
+
|
112
|
+
def attestation_root_certificates_store(aaguid: nil, attestation_certificate_key_id: nil)
|
113
|
+
OpenSSL::X509::Store.new.tap do |store|
|
114
|
+
root_certificates(
|
115
|
+
aaguid: aaguid,
|
116
|
+
attestation_certificate_key_id: attestation_certificate_key_id
|
117
|
+
).each do |cert|
|
118
|
+
store.add_cert(cert)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
|
124
|
+
root_certificates =
|
125
|
+
configuration.attestation_root_certificates_finders.reduce([]) do |certs, finder|
|
126
|
+
if certs.empty?
|
127
|
+
finder.find(
|
128
|
+
attestation_format: format,
|
129
|
+
aaguid: aaguid,
|
130
|
+
attestation_certificate_key_id: attestation_certificate_key_id
|
131
|
+
) || []
|
132
|
+
else
|
133
|
+
certs
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if root_certificates.empty? && respond_to?(:default_root_certificates, true)
|
138
|
+
default_root_certificates
|
139
|
+
else
|
140
|
+
root_certificates
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def raw_subject_key_identifier
|
145
|
+
extension = attestation_certificate.extensions.detect { |ext| ext.oid == "subjectKeyIdentifier" }
|
146
|
+
return unless extension
|
147
|
+
|
148
|
+
ext_asn1 = OpenSSL::ASN1.decode(extension.to_der)
|
149
|
+
ext_value = ext_asn1.value.last
|
150
|
+
OpenSSL::ASN1.decode(ext_value.value).value
|
151
|
+
end
|
152
|
+
|
153
|
+
def valid_signature?(authenticator_data, client_data_hash, public_key = attestation_certificate.public_key)
|
154
|
+
raise("Incompatible algorithm and key") unless cose_algorithm.compatible_key?(public_key)
|
155
|
+
|
156
|
+
cose_algorithm.verify(
|
157
|
+
public_key,
|
158
|
+
signature,
|
159
|
+
verification_data(authenticator_data, client_data_hash)
|
160
|
+
)
|
161
|
+
rescue COSE::Error
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
165
|
+
def verification_data(authenticator_data, client_data_hash)
|
166
|
+
authenticator_data.data + client_data_hash
|
167
|
+
end
|
168
|
+
|
169
|
+
def cose_algorithm
|
170
|
+
@cose_algorithm ||=
|
171
|
+
COSE::Algorithm.find(algorithm).tap do |alg|
|
172
|
+
alg && configuration.algorithms.include?(alg.name) ||
|
173
|
+
raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def configuration
|
178
|
+
WebAuthn.configuration
|
179
|
+
end
|
82
180
|
end
|
83
181
|
end
|
84
182
|
end
|