webauthn 2.1.0 → 3.4.0
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/dependabot.yml +6 -0
- data/.github/workflows/build.yml +50 -0
- data/.github/workflows/git.yml +21 -0
- data/.rubocop.yml +121 -13
- data/CHANGELOG.md +169 -0
- data/CONTRIBUTING.md +0 -5
- data/README.md +80 -14
- data/SECURITY.md +7 -4
- data/docs/advanced_configuration.md +174 -0
- data/docs/u2f_migration.md +14 -20
- data/lib/cose/rsapkcs1_algorithm.rb +50 -0
- data/lib/webauthn/attestation_object.rb +47 -0
- data/lib/webauthn/attestation_statement/android_key.rb +27 -33
- data/lib/webauthn/attestation_statement/android_safetynet.rb +27 -11
- data/lib/webauthn/attestation_statement/apple.rb +65 -0
- data/lib/webauthn/attestation_statement/base.rb +114 -21
- 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 +14 -42
- data/lib/webauthn/attestation_statement/tpm.rb +38 -75
- data/lib/webauthn/attestation_statement.rb +24 -21
- data/lib/webauthn/authenticator_assertion_response.rb +22 -11
- data/lib/webauthn/authenticator_attestation_response.rb +31 -92
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +33 -49
- data/lib/webauthn/authenticator_data.rb +59 -51
- data/lib/webauthn/authenticator_response.rb +24 -11
- data/lib/webauthn/client_data.rb +4 -6
- data/lib/webauthn/configuration.rb +38 -40
- data/lib/webauthn/credential.rb +4 -4
- data/lib/webauthn/credential_creation_options.rb +2 -0
- data/lib/webauthn/credential_request_options.rb +2 -0
- data/lib/webauthn/encoder.rb +13 -4
- data/lib/webauthn/fake_authenticator/attestation_object.rb +25 -4
- data/lib/webauthn/fake_authenticator/authenticator_data.rb +25 -10
- data/lib/webauthn/fake_authenticator.rb +49 -8
- data/lib/webauthn/fake_client.rb +41 -8
- data/lib/webauthn/json_serializer.rb +45 -0
- data/lib/webauthn/public_key.rb +21 -2
- data/lib/webauthn/public_key_credential/creation_options.rb +3 -3
- data/lib/webauthn/public_key_credential/entity.rb +5 -28
- data/lib/webauthn/public_key_credential/options.rb +11 -32
- data/lib/webauthn/public_key_credential/request_options.rb +11 -1
- data/lib/webauthn/public_key_credential.rb +52 -8
- data/lib/webauthn/public_key_credential_with_assertion.rb +16 -2
- data/lib/webauthn/public_key_credential_with_attestation.rb +2 -2
- data/lib/webauthn/relying_party.rb +137 -0
- data/lib/webauthn/u2f_migrator.rb +8 -4
- data/lib/webauthn/version.rb +1 -1
- data/lib/webauthn.rb +1 -0
- data/webauthn.gemspec +15 -12
- metadata +56 -60
- data/.travis.yml +0 -36
- data/Appraisals +0 -17
- data/gemfiles/cose_head.gemfile +0 -7
- data/gemfiles/openssl_2_0.gemfile +0 -7
- data/gemfiles/openssl_2_1.gemfile +0 -7
- data/gemfiles/openssl_head.gemfile +0 -7
- 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/s_certify_info.rb +0 -14
- data/lib/tpm/s_attest.rb +0 -26
- data/lib/tpm/sized_buffer.rb +0 -13
- data/lib/tpm/t_public/s_ecc_parms.rb +0 -17
- data/lib/tpm/t_public/s_rsa_parms.rb +0 -17
- data/lib/tpm/t_public.rb +0 -32
- 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/security_utils.rb +0 -20
- data/lib/webauthn/signature_verifier.rb +0 -77
@@ -1,61 +1,77 @@
|
|
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"
|
19
|
+
ATTESTATION_TYPE_ANONCA = "AnonCA"
|
15
20
|
|
16
|
-
|
17
|
-
|
21
|
+
ATTESTATION_TYPES_WITH_ROOT = [
|
22
|
+
ATTESTATION_TYPE_BASIC,
|
23
|
+
ATTESTATION_TYPE_BASIC_OR_ATTCA,
|
24
|
+
ATTESTATION_TYPE_ATTCA,
|
25
|
+
ATTESTATION_TYPE_ANONCA
|
26
|
+
].freeze
|
18
27
|
|
28
|
+
class Base
|
19
29
|
AAGUID_EXTENSION_OID = "1.3.6.1.4.1.45724.1.1.4"
|
20
30
|
|
21
|
-
def initialize(statement)
|
31
|
+
def initialize(statement, relying_party = WebAuthn.configuration.relying_party)
|
22
32
|
@statement = statement
|
33
|
+
@relying_party = relying_party
|
23
34
|
end
|
24
35
|
|
25
36
|
def valid?(_authenticator_data, _client_data_hash)
|
26
37
|
raise NotImplementedError
|
27
38
|
end
|
28
39
|
|
40
|
+
def format
|
41
|
+
WebAuthn::AttestationStatement::FORMAT_TO_CLASS.key(self.class)
|
42
|
+
end
|
43
|
+
|
29
44
|
def attestation_certificate
|
30
45
|
certificates&.first
|
31
46
|
end
|
32
47
|
|
33
|
-
def
|
34
|
-
|
35
|
-
certificates[1..-1]
|
36
|
-
end
|
48
|
+
def attestation_certificate_key_id
|
49
|
+
attestation_certificate.subject_key_identifier&.unpack("H*")&.[](0)
|
37
50
|
end
|
38
51
|
|
39
52
|
private
|
40
53
|
|
41
|
-
attr_reader :statement
|
54
|
+
attr_reader :statement, :relying_party
|
42
55
|
|
43
56
|
def matching_aaguid?(attested_credential_data_aaguid)
|
44
|
-
extension = attestation_certificate&.
|
57
|
+
extension = attestation_certificate&.find_extension(AAGUID_EXTENSION_OID)
|
45
58
|
if extension
|
46
|
-
|
47
|
-
|
48
|
-
extension.to_der[-WebAuthn::AuthenticatorData::AttestedCredentialData::AAGUID_LENGTH..-1] ==
|
49
|
-
attested_credential_data_aaguid
|
59
|
+
aaguid_value = OpenSSL::ASN1.decode(extension.value_der).value
|
60
|
+
aaguid_value == attested_credential_data_aaguid
|
50
61
|
else
|
51
62
|
true
|
52
63
|
end
|
53
64
|
end
|
54
65
|
|
66
|
+
def matching_public_key?(authenticator_data)
|
67
|
+
attestation_certificate.public_key.to_der == authenticator_data.credential.public_key_object.to_der
|
68
|
+
end
|
69
|
+
|
55
70
|
def certificates
|
56
|
-
@certificates ||=
|
57
|
-
|
58
|
-
|
71
|
+
@certificates ||=
|
72
|
+
raw_certificates&.map do |raw_certificate|
|
73
|
+
OpenSSL::X509::Certificate.new(raw_certificate)
|
74
|
+
end
|
59
75
|
end
|
60
76
|
|
61
77
|
def algorithm
|
@@ -66,10 +82,6 @@ module WebAuthn
|
|
66
82
|
statement["x5c"]
|
67
83
|
end
|
68
84
|
|
69
|
-
def raw_ecdaa_key_id
|
70
|
-
statement["ecdaaKeyId"]
|
71
|
-
end
|
72
|
-
|
73
85
|
def signature
|
74
86
|
statement["sig"]
|
75
87
|
end
|
@@ -79,6 +91,87 @@ module WebAuthn
|
|
79
91
|
certificates
|
80
92
|
end
|
81
93
|
end
|
94
|
+
|
95
|
+
def trustworthy?(aaguid: nil, attestation_certificate_key_id: nil)
|
96
|
+
if ATTESTATION_TYPES_WITH_ROOT.include?(attestation_type)
|
97
|
+
relying_party.acceptable_attestation_types.include?(attestation_type) &&
|
98
|
+
valid_certificate_chain?(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id)
|
99
|
+
else
|
100
|
+
relying_party.acceptable_attestation_types.include?(attestation_type)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def valid_certificate_chain?(aaguid: nil, attestation_certificate_key_id: nil)
|
105
|
+
root_certificates = root_certificates(
|
106
|
+
aaguid: aaguid,
|
107
|
+
attestation_certificate_key_id: attestation_certificate_key_id
|
108
|
+
)
|
109
|
+
|
110
|
+
if certificates&.one? && root_certificates.include?(attestation_certificate)
|
111
|
+
return true
|
112
|
+
end
|
113
|
+
|
114
|
+
attestation_root_certificates_store(
|
115
|
+
aaguid: aaguid,
|
116
|
+
attestation_certificate_key_id: attestation_certificate_key_id
|
117
|
+
).verify(attestation_certificate, attestation_trust_path)
|
118
|
+
end
|
119
|
+
|
120
|
+
def attestation_root_certificates_store(aaguid: nil, attestation_certificate_key_id: nil)
|
121
|
+
OpenSSL::X509::Store.new.tap do |store|
|
122
|
+
root_certificates(
|
123
|
+
aaguid: aaguid,
|
124
|
+
attestation_certificate_key_id: attestation_certificate_key_id
|
125
|
+
).each do |cert|
|
126
|
+
store.add_cert(cert)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def root_certificates(aaguid: nil, attestation_certificate_key_id: nil)
|
132
|
+
root_certificates =
|
133
|
+
relying_party.attestation_root_certificates_finders.reduce([]) do |certs, finder|
|
134
|
+
if certs.empty?
|
135
|
+
finder.find(
|
136
|
+
attestation_format: format,
|
137
|
+
aaguid: aaguid,
|
138
|
+
attestation_certificate_key_id: attestation_certificate_key_id
|
139
|
+
) || []
|
140
|
+
else
|
141
|
+
certs
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
if root_certificates.empty? && respond_to?(:default_root_certificates, true)
|
146
|
+
default_root_certificates
|
147
|
+
else
|
148
|
+
root_certificates
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def valid_signature?(authenticator_data, client_data_hash, public_key = attestation_certificate.public_key)
|
153
|
+
raise("Incompatible algorithm and key") unless cose_algorithm.compatible_key?(public_key)
|
154
|
+
|
155
|
+
cose_algorithm.verify(
|
156
|
+
public_key,
|
157
|
+
signature,
|
158
|
+
verification_data(authenticator_data, client_data_hash)
|
159
|
+
)
|
160
|
+
rescue COSE::Error
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
164
|
+
def verification_data(authenticator_data, client_data_hash)
|
165
|
+
authenticator_data.data + client_data_hash
|
166
|
+
end
|
167
|
+
|
168
|
+
def cose_algorithm
|
169
|
+
@cose_algorithm ||=
|
170
|
+
COSE::Algorithm.find(algorithm).tap do |alg|
|
171
|
+
alg && relying_party.algorithms.include?(alg.name) ||
|
172
|
+
raise(UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}")
|
173
|
+
end
|
174
|
+
end
|
82
175
|
end
|
83
176
|
end
|
84
177
|
end
|
@@ -4,7 +4,6 @@ require "cose"
|
|
4
4
|
require "openssl"
|
5
5
|
require "webauthn/attestation_statement/base"
|
6
6
|
require "webauthn/attestation_statement/fido_u2f/public_key"
|
7
|
-
require "webauthn/signature_verifier"
|
8
7
|
|
9
8
|
module WebAuthn
|
10
9
|
module AttestationStatement
|
@@ -19,7 +18,8 @@ module WebAuthn
|
|
19
18
|
valid_credential_public_key?(authenticator_data.credential.public_key) &&
|
20
19
|
valid_aaguid?(authenticator_data.attested_credential_data.raw_aaguid) &&
|
21
20
|
valid_signature?(authenticator_data, client_data_hash) &&
|
22
|
-
|
21
|
+
trustworthy?(attestation_certificate_key_id: attestation_certificate_key_id) &&
|
22
|
+
[attestation_type, attestation_trust_path]
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -47,10 +47,8 @@ module WebAuthn
|
|
47
47
|
attested_credential_data_aaguid == WebAuthn::AuthenticatorData::AttestedCredentialData::ZEROED_AAGUID
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
51
|
-
|
52
|
-
.new(VALID_ATTESTATION_CERTIFICATE_ALGORITHM, certificate_public_key)
|
53
|
-
.verify(signature, verification_data(authenticator_data, client_data_hash))
|
50
|
+
def algorithm
|
51
|
+
VALID_ATTESTATION_CERTIFICATE_ALGORITHM.id
|
54
52
|
end
|
55
53
|
|
56
54
|
def verification_data(authenticator_data, client_data_hash)
|
@@ -64,6 +62,10 @@ module WebAuthn
|
|
64
62
|
def public_key_u2f(cose_key_data)
|
65
63
|
PublicKey.new(cose_key_data)
|
66
64
|
end
|
65
|
+
|
66
|
+
def attestation_type
|
67
|
+
WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA
|
68
|
+
end
|
67
69
|
end
|
68
70
|
end
|
69
71
|
end
|
@@ -6,12 +6,18 @@ module WebAuthn
|
|
6
6
|
module AttestationStatement
|
7
7
|
class None < Base
|
8
8
|
def valid?(*_args)
|
9
|
-
if statement == {}
|
9
|
+
if statement == {} && trustworthy?
|
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
|
15
21
|
end
|
16
22
|
end
|
17
23
|
end
|
@@ -2,25 +2,21 @@
|
|
2
2
|
|
3
3
|
require "openssl"
|
4
4
|
require "webauthn/attestation_statement/base"
|
5
|
-
require "webauthn/signature_verifier"
|
6
5
|
|
7
6
|
module WebAuthn
|
8
7
|
# Implements https://www.w3.org/TR/2018/CR-webauthn-20180807/#packed-attestation
|
9
|
-
# ECDAA attestation is unsupported.
|
10
8
|
module AttestationStatement
|
11
9
|
class Packed < Base
|
12
10
|
# Follows "Verification procedure"
|
13
11
|
def valid?(authenticator_data, client_data_hash)
|
14
|
-
check_unsupported_feature
|
15
|
-
|
16
12
|
valid_format? &&
|
17
13
|
valid_algorithm?(authenticator_data.credential) &&
|
18
|
-
valid_certificate_chain? &&
|
19
14
|
valid_ec_public_keys?(authenticator_data.credential) &&
|
20
15
|
meet_certificate_requirement? &&
|
21
16
|
matching_aaguid?(authenticator_data.attested_credential_data.raw_aaguid) &&
|
22
17
|
valid_signature?(authenticator_data, client_data_hash) &&
|
23
|
-
|
18
|
+
trustworthy?(aaguid: authenticator_data.aaguid) &&
|
19
|
+
[attestation_type, attestation_trust_path]
|
24
20
|
end
|
25
21
|
|
26
22
|
private
|
@@ -30,27 +26,11 @@ module WebAuthn
|
|
30
26
|
end
|
31
27
|
|
32
28
|
def self_attestation?
|
33
|
-
!raw_certificates
|
29
|
+
!raw_certificates
|
34
30
|
end
|
35
31
|
|
36
32
|
def valid_format?
|
37
|
-
algorithm && signature
|
38
|
-
[raw_certificates, raw_ecdaa_key_id].compact.size < 2
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
def check_unsupported_feature
|
43
|
-
if raw_ecdaa_key_id
|
44
|
-
raise NotSupportedError, "ecdaaKeyId of the packed attestation format is not implemented yet"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def valid_certificate_chain?
|
49
|
-
if certificate_chain
|
50
|
-
certificate_chain.all? { |c| certificate_in_use?(c) }
|
51
|
-
else
|
52
|
-
true
|
53
|
-
end
|
33
|
+
algorithm && signature
|
54
34
|
end
|
55
35
|
|
56
36
|
def valid_ec_public_keys?(credential)
|
@@ -65,35 +45,27 @@ module WebAuthn
|
|
65
45
|
subject = attestation_certificate.subject.to_a
|
66
46
|
|
67
47
|
attestation_certificate.version == 2 &&
|
68
|
-
certificate_in_use?(attestation_certificate) &&
|
69
48
|
subject.assoc('OU')&.at(1) == "Authenticator Attestation" &&
|
70
|
-
attestation_certificate.
|
49
|
+
attestation_certificate.find_extension('basicConstraints')&.value == 'CA:FALSE'
|
71
50
|
else
|
72
51
|
true
|
73
52
|
end
|
74
53
|
end
|
75
54
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
55
|
+
def attestation_type
|
56
|
+
if attestation_trust_path
|
57
|
+
WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA # FIXME: use metadata if available
|
58
|
+
else
|
59
|
+
WebAuthn::AttestationStatement::ATTESTATION_TYPE_SELF
|
60
|
+
end
|
80
61
|
end
|
81
62
|
|
82
63
|
def valid_signature?(authenticator_data, client_data_hash)
|
83
|
-
|
84
|
-
|
64
|
+
super(
|
65
|
+
authenticator_data,
|
66
|
+
client_data_hash,
|
85
67
|
attestation_certificate&.public_key || authenticator_data.credential.public_key_object
|
86
68
|
)
|
87
|
-
|
88
|
-
signature_verifier.verify(signature, authenticator_data.data + client_data_hash)
|
89
|
-
end
|
90
|
-
|
91
|
-
def attestation_type_and_trust_path
|
92
|
-
if attestation_trust_path
|
93
|
-
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA, attestation_trust_path]
|
94
|
-
else
|
95
|
-
[WebAuthn::AttestationStatement::ATTESTATION_TYPE_SELF, nil]
|
96
|
-
end
|
97
69
|
end
|
98
70
|
end
|
99
71
|
end
|
@@ -2,98 +2,63 @@
|
|
2
2
|
|
3
3
|
require "cose/algorithm"
|
4
4
|
require "openssl"
|
5
|
-
require "tpm/
|
5
|
+
require "tpm/key_attestation"
|
6
6
|
require "webauthn/attestation_statement/base"
|
7
|
-
require "webauthn/attestation_statement/tpm/cert_info"
|
8
|
-
require "webauthn/attestation_statement/tpm/pub_area"
|
9
|
-
require "webauthn/signature_verifier"
|
10
7
|
|
11
8
|
module WebAuthn
|
12
9
|
module AttestationStatement
|
13
10
|
class TPM < Base
|
14
|
-
CERTIFICATE_V3 = 2
|
15
|
-
CERTIFICATE_EMPTY_NAME = OpenSSL::X509::Name.new([]).freeze
|
16
|
-
CERTIFICATE_SAN_DIRECTORY_NAME = 4
|
17
|
-
OID_TCG_AT_TPM_MANUFACTURER = "2.23.133.2.1"
|
18
|
-
OID_TCG_AT_TPM_MODEL = "2.23.133.2.2"
|
19
|
-
OID_TCG_AT_TPM_VERSION = "2.23.133.2.3"
|
20
|
-
OID_TCG_KP_AIK_CERTIFICATE = "2.23.133.8.3"
|
21
11
|
TPM_V2 = "2.0"
|
22
12
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
13
|
+
COSE_ALG_TO_TPM = {
|
14
|
+
"RS1" => { signature: ::TPM::ALG_RSASSA, hash: ::TPM::ALG_SHA1 },
|
15
|
+
"RS256" => { signature: ::TPM::ALG_RSASSA, hash: ::TPM::ALG_SHA256 },
|
16
|
+
"PS256" => { signature: ::TPM::ALG_RSAPSS, hash: ::TPM::ALG_SHA256 },
|
17
|
+
"ES256" => { signature: ::TPM::ALG_ECDSA, hash: ::TPM::ALG_SHA256 },
|
18
|
+
}.freeze
|
27
19
|
|
20
|
+
def valid?(authenticator_data, client_data_hash)
|
21
|
+
attestation_type == ATTESTATION_TYPE_ATTCA &&
|
28
22
|
ver == TPM_V2 &&
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
raise(
|
38
|
-
WebAuthn::AttestationStatement::Base::NotSupportedError,
|
39
|
-
"Attestation type ECDAA is not supported"
|
40
|
-
)
|
41
|
-
end
|
23
|
+
valid_key_attestation?(
|
24
|
+
authenticator_data.data + client_data_hash,
|
25
|
+
authenticator_data.credential.public_key_object,
|
26
|
+
authenticator_data.aaguid
|
27
|
+
) &&
|
28
|
+
matching_aaguid?(authenticator_data.attested_credential_data.raw_aaguid) &&
|
29
|
+
trustworthy?(aaguid: authenticator_data.aaguid) &&
|
30
|
+
[attestation_type, attestation_trust_path]
|
42
31
|
end
|
43
32
|
|
44
33
|
private
|
45
34
|
|
46
|
-
def
|
47
|
-
|
48
|
-
.new(
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
certificate_in_use?(attestation_certificate) &&
|
59
|
-
extensions.find { |ext| ext.oid == 'basicConstraints' }&.value == "CA:FALSE" &&
|
60
|
-
extensions.find { |ext| ext.oid == "extendedKeyUsage" }&.value == OID_TCG_KP_AIK_CERTIFICATE
|
61
|
-
end
|
62
|
-
|
63
|
-
def valid_subject_alternative_name?
|
64
|
-
extension = attestation_certificate.extensions.detect { |ext| ext.oid == "subjectAltName" }
|
65
|
-
return unless extension&.critical?
|
66
|
-
|
67
|
-
san_asn1 = OpenSSL::ASN1.decode(extension).find do |val|
|
68
|
-
val.tag_class == :UNIVERSAL && val.tag == OpenSSL::ASN1::OCTET_STRING
|
69
|
-
end
|
70
|
-
directory_name = OpenSSL::ASN1.decode(san_asn1.value).find do |val|
|
71
|
-
val.tag_class == :CONTEXT_SPECIFIC && val.tag == CERTIFICATE_SAN_DIRECTORY_NAME
|
72
|
-
end
|
73
|
-
name = OpenSSL::X509::Name.new(directory_name.value.first).to_a
|
74
|
-
manufacturer = name.assoc(OID_TCG_AT_TPM_MANUFACTURER).at(1)
|
75
|
-
model = name.assoc(OID_TCG_AT_TPM_MODEL).at(1)
|
76
|
-
version = name.assoc(OID_TCG_AT_TPM_VERSION).at(1)
|
77
|
-
|
78
|
-
::TPM::VENDOR_IDS[manufacturer] && !model.empty? && !version.empty?
|
79
|
-
end
|
80
|
-
|
81
|
-
def certificate_in_use?(certificate)
|
82
|
-
now = Time.now
|
35
|
+
def valid_key_attestation?(certified_extra_data, key, aaguid)
|
36
|
+
key_attestation =
|
37
|
+
::TPM::KeyAttestation.new(
|
38
|
+
statement["certInfo"],
|
39
|
+
signature,
|
40
|
+
statement["pubArea"],
|
41
|
+
certificates,
|
42
|
+
OpenSSL::Digest.digest(cose_algorithm.hash_function, certified_extra_data),
|
43
|
+
signature_algorithm: tpm_algorithm[:signature],
|
44
|
+
hash_algorithm: tpm_algorithm[:hash],
|
45
|
+
trusted_certificates: root_certificates(aaguid: aaguid)
|
46
|
+
)
|
83
47
|
|
84
|
-
|
48
|
+
key_attestation.valid? && key_attestation.key && key_attestation.key.to_pem == key.to_pem
|
85
49
|
end
|
86
50
|
|
87
|
-
def
|
88
|
-
|
51
|
+
def valid_certificate_chain?(**_)
|
52
|
+
# Already performed as part of #valid_key_attestation?
|
53
|
+
true
|
89
54
|
end
|
90
55
|
|
91
|
-
def
|
92
|
-
|
56
|
+
def default_root_certificates
|
57
|
+
::TPM::KeyAttestation::TRUSTED_CERTIFICATES
|
93
58
|
end
|
94
59
|
|
95
|
-
def
|
96
|
-
|
60
|
+
def tpm_algorithm
|
61
|
+
COSE_ALG_TO_TPM[cose_algorithm.name] || raise("Unsupported algorithm #{cose_algorithm.name}")
|
97
62
|
end
|
98
63
|
|
99
64
|
def ver
|
@@ -105,10 +70,8 @@ module WebAuthn
|
|
105
70
|
end
|
106
71
|
|
107
72
|
def attestation_type
|
108
|
-
if raw_certificates
|
73
|
+
if raw_certificates
|
109
74
|
ATTESTATION_TYPE_ATTCA
|
110
|
-
elsif raw_ecdaa_key_id && !raw_certificates
|
111
|
-
ATTESTATION_TYPE_ECDAA
|
112
75
|
else
|
113
76
|
raise "Attestation type invalid"
|
114
77
|
end
|
@@ -1,5 +1,12 @@
|
|
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/apple"
|
6
|
+
require "webauthn/attestation_statement/fido_u2f"
|
7
|
+
require "webauthn/attestation_statement/none"
|
8
|
+
require "webauthn/attestation_statement/packed"
|
9
|
+
require "webauthn/attestation_statement/tpm"
|
3
10
|
require "webauthn/error"
|
4
11
|
|
5
12
|
module WebAuthn
|
@@ -12,29 +19,25 @@ module WebAuthn
|
|
12
19
|
ATTESTATION_FORMAT_ANDROID_SAFETYNET = "android-safetynet"
|
13
20
|
ATTESTATION_FORMAT_ANDROID_KEY = "android-key"
|
14
21
|
ATTESTATION_FORMAT_TPM = "tpm"
|
22
|
+
ATTESTATION_FORMAT_APPLE = "apple"
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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)
|
24
|
+
FORMAT_TO_CLASS = {
|
25
|
+
ATTESTATION_FORMAT_NONE => WebAuthn::AttestationStatement::None,
|
26
|
+
ATTESTATION_FORMAT_FIDO_U2F => WebAuthn::AttestationStatement::FidoU2f,
|
27
|
+
ATTESTATION_FORMAT_PACKED => WebAuthn::AttestationStatement::Packed,
|
28
|
+
ATTESTATION_FORMAT_ANDROID_SAFETYNET => WebAuthn::AttestationStatement::AndroidSafetynet,
|
29
|
+
ATTESTATION_FORMAT_ANDROID_KEY => WebAuthn::AttestationStatement::AndroidKey,
|
30
|
+
ATTESTATION_FORMAT_TPM => WebAuthn::AttestationStatement::TPM,
|
31
|
+
ATTESTATION_FORMAT_APPLE => WebAuthn::AttestationStatement::Apple
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
def self.from(format, statement, relying_party: WebAuthn.configuration.relying_party)
|
35
|
+
klass = FORMAT_TO_CLASS[format]
|
36
|
+
|
37
|
+
if klass
|
38
|
+
klass.new(statement, relying_party)
|
36
39
|
else
|
37
|
-
raise
|
40
|
+
raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
|
38
41
|
end
|
39
42
|
end
|
40
43
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require "webauthn/authenticator_data"
|
4
4
|
require "webauthn/authenticator_response"
|
5
5
|
require "webauthn/encoder"
|
6
|
-
require "webauthn/signature_verifier"
|
7
6
|
require "webauthn/public_key"
|
8
7
|
|
9
8
|
module WebAuthn
|
@@ -11,8 +10,8 @@ module WebAuthn
|
|
11
10
|
class SignCountVerificationError < VerificationError; end
|
12
11
|
|
13
12
|
class AuthenticatorAssertionResponse < AuthenticatorResponse
|
14
|
-
def self.from_client(response)
|
15
|
-
encoder =
|
13
|
+
def self.from_client(response, relying_party: WebAuthn.configuration.relying_party)
|
14
|
+
encoder = relying_party.encoder
|
16
15
|
|
17
16
|
user_handle =
|
18
17
|
if response["userHandle"]
|
@@ -23,7 +22,8 @@ module WebAuthn
|
|
23
22
|
authenticator_data: encoder.decode(response["authenticatorData"]),
|
24
23
|
client_data_json: encoder.decode(response["clientDataJSON"]),
|
25
24
|
signature: encoder.decode(response["signature"]),
|
26
|
-
user_handle: user_handle
|
25
|
+
user_handle: user_handle,
|
26
|
+
relying_party: relying_party
|
27
27
|
)
|
28
28
|
end
|
29
29
|
|
@@ -37,9 +37,22 @@ module WebAuthn
|
|
37
37
|
@user_handle = user_handle
|
38
38
|
end
|
39
39
|
|
40
|
-
def verify(
|
41
|
-
|
42
|
-
|
40
|
+
def verify(
|
41
|
+
expected_challenge,
|
42
|
+
expected_origin = nil,
|
43
|
+
public_key:,
|
44
|
+
sign_count:,
|
45
|
+
user_presence: nil,
|
46
|
+
user_verification: nil,
|
47
|
+
rp_id: nil
|
48
|
+
)
|
49
|
+
super(
|
50
|
+
expected_challenge,
|
51
|
+
expected_origin,
|
52
|
+
user_presence: user_presence,
|
53
|
+
user_verification: user_verification,
|
54
|
+
rp_id: rp_id
|
55
|
+
)
|
43
56
|
verify_item(:signature, WebAuthn::PublicKey.deserialize(public_key))
|
44
57
|
verify_item(:sign_count, sign_count)
|
45
58
|
|
@@ -47,7 +60,7 @@ module WebAuthn
|
|
47
60
|
end
|
48
61
|
|
49
62
|
def authenticator_data
|
50
|
-
@authenticator_data ||= WebAuthn::AuthenticatorData.
|
63
|
+
@authenticator_data ||= WebAuthn::AuthenticatorData.deserialize(authenticator_data_bytes)
|
51
64
|
end
|
52
65
|
|
53
66
|
private
|
@@ -55,9 +68,7 @@ module WebAuthn
|
|
55
68
|
attr_reader :authenticator_data_bytes, :signature
|
56
69
|
|
57
70
|
def valid_signature?(webauthn_public_key)
|
58
|
-
|
59
|
-
.new(webauthn_public_key.alg, webauthn_public_key.pkey)
|
60
|
-
.verify(signature, authenticator_data_bytes + client_data.hash)
|
71
|
+
webauthn_public_key.verify(signature, authenticator_data_bytes + client_data.hash)
|
61
72
|
end
|
62
73
|
|
63
74
|
def valid_sign_count?(stored_sign_count)
|