forked from keylime/keylime
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_certificate_modeltype.py
More file actions
320 lines (257 loc) · 15.5 KB
/
Copy pathtest_certificate_modeltype.py
File metadata and controls
320 lines (257 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
"""
Unit tests for the Certificate ModelType class.
This module tests the certificate model type functionality including
encoding inference and ASN.1 compliance checking.
"""
import base64
import unittest
import cryptography.x509
from cryptography.hazmat.primitives.serialization import Encoding
from keylime.certificate_wrapper import CertificateWrapper, wrap_certificate
from keylime.models.base.types.certificate import Certificate
class TestCertificateModelType(unittest.TestCase):
"""Test cases for Certificate ModelType class."""
def setUp(self):
"""Set up test fixtures."""
self.cert_type = Certificate()
# Compliant certificate for testing (loads fine with python-cryptography)
self.compliant_cert_pem = """-----BEGIN CERTIFICATE-----
MIIClzCCAX+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARUZXN0
MB4XDTI1MDkxMTEyNDU1MVoXDTI2MDkxMTEyNDU1MVowDzENMAsGA1UEAwwEVGVz
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO2V27HsMnKczHCaLgf9
FtxuorvkA5OMkz6KsW1eyryHr0TJ801prLpeNnMZ3U4pqLMqocMc7T2KO6nPZJxO
7zRzehyo9pBBVO4pUR1QMGoTWuJQbqNieDQ4V9dW67N5wp/UWEkK6CNNd6aXjswb
dVaDbIfDL8hMX6Lil3+pTysRWGqjRvBGJxS9r/mYRAvbz1JHPjfegSc0uxnUE+qZ
SrbWa3TN82LX6jw6tKk0Z3CcPJC6QN+ijCxxAoHyLRYUIgZbAKe/FGRbjO0fuW11
L7TcE1k3eaC7RkvotIaCOW/RMOkwKu1MbCzFEA2YRYf9covEwdItzI4FE++ZJrsz
LhUCAwEAaTANBgkqhkiG9w0BAQsFAAOCAQEAeqqJT0LnmAluAjrsCSK/eYYjwjhZ
aKMi/iBO10zfb+GvT4yqEL5gnuWxJEx4TTcDww1clvOC1EcPUZFaKR3GIBGy0ZgJ
zGCfg+sC6liyZ+4PSWSJHD2dT5N3IGp4/hPsrhKnVb9fYbRc0Bc5VHeS9QQoSJDH
f9EbxCcwdErVllRter29OZCb4XnEEbTqLIKRYVrbsu/t4C+vzi0tmKg5HZXf9PMo
D28zJGsCAr8sKW/iUKObqDOHEn56lk12NTJmJmi+g6rEikk/0czJlRjSGnJQLjUg
d4wslruibXBsLPtJw2c6vTC2SV2F1PXwy5j1OKU+D6nxaaItQvWADEjcTg==
-----END CERTIFICATE-----"""
# Malformed certificate that requires pyasn1 re-encoding (fails with python-cryptography)
self.malformed_cert_b64 = (
"MIIDUjCCAvegAwIBAgILAI5xYHQ14nH5hdYwCgYIKoZIzj0EAwIwVTFTMB8GA1UEAxMYTnV2b3Rv"
"biBUUE0gUm9vdCBDQSAyMTExMCUGA1UEChMeTnV2b3RvbiBUZWNobm9sb2d5IENvcnBvcmF0aW9u"
"MAkGA1UEBhMCVFcwHhcNMTkwNzIzMTcxNTEzWhcNMzkwNzE5MTcxNTEzWjAAMIIBIjANBgkqhkiG"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk8kCj7srY/Zlvm1795fVXdyX44w5qsd1m5VywMDgSOavzPKO"
"kgbHgQNx6Ak5+4Q43EJ/5qsaDBv59F8W7K69maUwcMNq1xpuq0V/LiwgJVAtc3CdvlxtwQrn7+Uq"
"ieIGf+i8sGxpeUCSmYHJPTHNHqjQnvUtdGoy/+WO0i7WsAvX3k/gHHr4p58a8urjJ1RG2Lk1g48D"
"ESwl+D7atQEPWzgjr6vK/s5KpLrn7M+dh97TUbG1510AOWBPP35MtT8IZbqC4hs2Ol16gT1M3a9e"
"+GaMZkItLUwV76vKDNEgTZG8M1C9OItA/xwzlfXbPepzpxWb4kzHS4qZoQtl4vBZrQIDAQABo4IB"
"NjCCATIwUAYDVR0RAQH/BEYwRKRCMEAxPjAUBgVngQUCARMLaWQ6NEU1NDQzMDAwEAYFZ4EFAgIT"
"B05QQ1Q3NXgwFAYFZ4EFAgMTC2lkOjAwMDcwMDAyMAwGA1UdEwEB/wQCMAAwEAYDVR0lBAkwBwYF"
"Z4EFCAEwHwYDVR0jBBgwFoAUI/TiKtO+N0pEl3KVSqKDrtdSVy4wDgYDVR0PAQH/BAQDAgUgMCIG"
"A1UdCQQbMBkwFwYFZ4EFAhAxDjAMDAMyLjACAQACAgCKMGkGCCsGAQUFBwEBBF0wWzBZBggrBgEF"
"BQcwAoZNaHR0cHM6Ly93d3cubnV2b3Rvbi5jb20vc2VjdXJpdHkvTlRDLVRQTS1FSy1DZXJ0L051"
"dm90b24gVFBNIFJvb3QgQ0EgMjExMS5jZXIwCgYIKoZIzj0EAwIDSQAwRgIhAPHOFiBDZd0dfml2"
"a/KlPFhmX7Ahpd0Wq11ZUW1/ixviAiEAlex8BB5nsR6w8QrANwCxc7fH/YnbjXfMCFiWzeZH7ps="
)
# Load certificates for testing
self.compliant_cert = cryptography.x509.load_pem_x509_certificate(self.compliant_cert_pem.encode())
self.malformed_cert_der = base64.b64decode(self.malformed_cert_b64)
def test_infer_encoding_wrapped_certificate(self):
"""Test that CertificateWrapper objects are identified as 'wrapped'."""
wrapped_cert = wrap_certificate(self.compliant_cert, None)
encoding = self.cert_type.infer_encoding(wrapped_cert)
self.assertEqual(encoding, "wrapped")
def test_infer_encoding_raw_certificate(self):
"""Test that raw cryptography.x509.Certificate objects are identified as 'decoded'."""
encoding = self.cert_type.infer_encoding(self.compliant_cert)
self.assertEqual(encoding, "decoded")
def test_infer_encoding_der_bytes(self):
"""Test that DER bytes are identified as 'der'."""
der_bytes = self.compliant_cert.public_bytes(Encoding.DER)
encoding = self.cert_type.infer_encoding(der_bytes)
self.assertEqual(encoding, "der")
def test_infer_encoding_pem_string(self):
"""Test that PEM strings are identified as 'pem'."""
encoding = self.cert_type.infer_encoding(self.compliant_cert_pem)
self.assertEqual(encoding, "pem")
def test_infer_encoding_base64_string(self):
"""Test that Base64 strings are identified as 'base64'."""
encoding = self.cert_type.infer_encoding(self.malformed_cert_b64)
self.assertEqual(encoding, "base64")
def test_infer_encoding_none_for_invalid(self):
"""Test that invalid types return None."""
encoding = self.cert_type.infer_encoding(12345) # type: ignore[arg-type] # Testing invalid type
self.assertIsNone(encoding)
def test_asn1_compliant_wrapped_without_original_bytes(self):
"""Test that CertificateWrapper without original bytes is ASN.1 compliant."""
wrapped_cert = wrap_certificate(self.compliant_cert, None)
compliant = self.cert_type.asn1_compliant(wrapped_cert)
self.assertTrue(compliant)
def test_asn1_compliant_wrapped_with_original_bytes(self):
"""Test that CertificateWrapper with original bytes is not ASN.1 compliant."""
wrapped_cert = wrap_certificate(self.compliant_cert, b"fake_original_bytes")
compliant = self.cert_type.asn1_compliant(wrapped_cert)
self.assertFalse(compliant)
def test_asn1_compliant_raw_certificate(self):
"""Test that raw cryptography.x509.Certificate returns None (already decoded)."""
compliant = self.cert_type.asn1_compliant(self.compliant_cert)
self.assertIsNone(compliant)
def test_asn1_compliant_pem_strings(self):
"""Test ASN.1 compliance checking on PEM strings."""
# The regular certificate and TPM certificate from test_registrar_db.py are actually ASN.1 compliant
# and can be loaded directly by python-cryptography without requiring pyasn1 re-encoding
compliant_regular = self.cert_type.asn1_compliant(self.compliant_cert_pem)
# Only test one certificate since both are the same type (ASN.1 compliant)
# Should be ASN.1 compliant (True) since it loads fine with python-cryptography
self.assertTrue(compliant_regular)
def test_asn1_compliant_der_and_base64(self):
"""Test ASN.1 compliance checking on DER and Base64 formats."""
# Test DER bytes - regular certificate should be compliant
der_bytes = self.compliant_cert.public_bytes(Encoding.DER)
compliant_der = self.cert_type.asn1_compliant(der_bytes)
self.assertTrue(compliant_der)
# Test Base64 string - regular certificate should be compliant
b64_string = base64.b64encode(der_bytes).decode("utf-8")
compliant_b64 = self.cert_type.asn1_compliant(b64_string)
self.assertTrue(compliant_b64)
def test_asn1_compliant_malformed_certificate(self):
"""Test ASN.1 compliance checking on a truly malformed certificate."""
# Test the malformed certificate that requires pyasn1 re-encoding
compliant = self.cert_type.asn1_compliant(self.malformed_cert_b64)
self.assertFalse(compliant) # Should be non-compliant since it needs pyasn1 fallback
def test_asn1_compliant_invalid_data(self):
"""Test that invalid certificate data is not ASN.1 compliant."""
compliant = self.cert_type.asn1_compliant("invalid_certificate_data")
self.assertFalse(compliant)
def test_cast_wrapped_certificate(self):
"""Test that CertificateWrapper objects are returned unchanged."""
wrapped_cert = wrap_certificate(self.compliant_cert, None)
result = self.cert_type.cast(wrapped_cert)
self.assertIs(result, wrapped_cert)
def test_cast_raw_certificate_to_wrapped(self):
"""Test that raw certificates are wrapped without original bytes."""
result = self.cert_type.cast(self.compliant_cert)
self.assertIsInstance(result, CertificateWrapper)
assert result is not None # For type checker
self.assertFalse(result.has_original_bytes)
def test_cast_pem_strings(self):
"""Test casting PEM strings to CertificateWrapper."""
# Test regular certificate - should be ASN.1 compliant, no original bytes needed
result_regular = self.cert_type.cast(self.compliant_cert_pem)
self.assertIsInstance(result_regular, CertificateWrapper)
assert result_regular is not None # For type checker
self.assertFalse(result_regular.has_original_bytes)
# Note: Only testing compliant certificate since we now use one consistent certificate for all compliant scenarios
def test_cast_malformed_certificate(self):
"""Test casting the malformed certificate that requires pyasn1 re-encoding."""
result = self.cert_type.cast(self.malformed_cert_b64)
self.assertIsInstance(result, CertificateWrapper)
assert result is not None # For type checker
# Malformed certificate should have original bytes since it needs re-encoding
self.assertTrue(result.has_original_bytes)
def test_cast_der_bytes(self):
"""Test casting DER bytes to CertificateWrapper."""
der_bytes = self.compliant_cert.public_bytes(Encoding.DER)
result = self.cert_type.cast(der_bytes)
self.assertIsInstance(result, CertificateWrapper)
def test_cast_none_value(self):
"""Test that None values return None."""
result = self.cert_type.cast(None)
self.assertIsNone(result)
def test_cast_empty_string(self):
"""Test that empty strings return None."""
result = self.cert_type.cast("")
self.assertIsNone(result)
def test_infer_encoding_disabled_string(self):
"""Test that the string 'disabled' is identified as 'disabled'."""
encoding = self.cert_type.infer_encoding("disabled")
self.assertEqual(encoding, "disabled")
def test_asn1_compliant_disabled_string(self):
"""Test that 'disabled' string is considered ASN.1 compliant."""
compliant = self.cert_type.asn1_compliant("disabled")
self.assertTrue(compliant)
def test_cast_invalid_type_raises_type_error(self):
"""Test that casting an invalid type raises TypeError."""
with self.assertRaises(TypeError) as context:
self.cert_type.cast(12345) # type: ignore[arg-type]
self.assertIn("should be one of 'str', 'bytes' or 'cryptography.x509.Certificate'", str(context.exception))
def test_cast_invalid_der_data_raises_value_error(self):
"""Test that casting invalid DER bytes raises ValueError."""
invalid_der = b"invalid_der_data"
with self.assertRaises(ValueError) as context:
self.cert_type.cast(invalid_der)
self.assertIn("appears DER encoded but cannot be deserialized", str(context.exception))
def test_cast_invalid_pem_data_raises_value_error(self):
"""Test that casting invalid PEM string raises ValueError."""
invalid_pem = "-----BEGIN CERTIFICATE-----\ninvalid_pem_data\n-----END CERTIFICATE-----"
with self.assertRaises(ValueError) as context:
self.cert_type.cast(invalid_pem)
# The error message may vary depending on which parser fails
error_msg = str(context.exception)
self.assertTrue(
"appears PEM encoded but cannot be deserialized" in error_msg or "Incorrect padding" in error_msg,
f"Unexpected error message: {error_msg}",
)
def test_cast_invalid_base64_data_raises_value_error(self):
"""Test that casting invalid Base64 string raises ValueError."""
invalid_b64 = "!!!invalid_base64!!!"
with self.assertRaises(ValueError) as context:
self.cert_type.cast(invalid_b64)
self.assertIn("appears Base64 encoded but cannot be deserialized", str(context.exception))
def test_dump_none_returns_none(self):
"""Test that dumping None returns None."""
result = self.cert_type._dump(None) # pylint: disable=protected-access
self.assertIsNone(result)
def test_dump_valid_certificate_returns_base64(self):
"""Test that dumping a valid certificate returns Base64 DER string."""
result = self.cert_type._dump(self.compliant_cert_pem) # pylint: disable=protected-access
self.assertIsInstance(result, str)
assert result is not None # For type checker
# Verify it's valid Base64
decoded = base64.b64decode(result)
# Verify it can be loaded as a certificate
cert = cryptography.x509.load_der_x509_certificate(decoded)
self.assertIsInstance(cert, cryptography.x509.Certificate)
def test_dump_wrapped_certificate_returns_base64(self):
"""Test that dumping a CertificateWrapper returns Base64 DER string."""
wrapped_cert = wrap_certificate(self.compliant_cert, None)
result = self.cert_type._dump(wrapped_cert) # pylint: disable=protected-access
self.assertIsInstance(result, str)
assert result is not None # For type checker
# Verify it's valid Base64
decoded = base64.b64decode(result)
cert = cryptography.x509.load_der_x509_certificate(decoded)
self.assertIsInstance(cert, cryptography.x509.Certificate)
def test_render_none_returns_none(self):
"""Test that rendering None returns None."""
result = self.cert_type.render(None)
self.assertIsNone(result)
def test_render_valid_certificate_returns_pem(self):
"""Test that rendering a valid certificate returns PEM string."""
result = self.cert_type.render(self.compliant_cert)
self.assertIsInstance(result, str)
assert result is not None # For type checker
self.assertTrue(result.startswith("-----BEGIN CERTIFICATE-----"))
self.assertTrue(result.endswith("-----END CERTIFICATE-----\n"))
def test_render_wrapped_certificate_returns_pem(self):
"""Test that rendering a CertificateWrapper returns PEM string."""
wrapped_cert = wrap_certificate(self.compliant_cert, None)
result = self.cert_type.render(wrapped_cert)
self.assertIsInstance(result, str)
assert result is not None # For type checker
self.assertTrue(result.startswith("-----BEGIN CERTIFICATE-----"))
self.assertTrue(result.endswith("-----END CERTIFICATE-----\n"))
def test_render_base64_string_returns_pem(self):
"""Test that rendering a Base64 string returns PEM string."""
der_bytes = self.compliant_cert.public_bytes(Encoding.DER)
b64_string = base64.b64encode(der_bytes).decode("utf-8")
result = self.cert_type.render(b64_string)
self.assertIsInstance(result, str)
assert result is not None # For type checker
self.assertTrue(result.startswith("-----BEGIN CERTIFICATE-----"))
self.assertTrue(result.endswith("-----END CERTIFICATE-----\n"))
def test_native_type_property(self):
"""Test that native_type property returns CertificateWrapper class."""
self.assertEqual(self.cert_type.native_type, CertificateWrapper)
def test_generate_error_msg(self):
"""Test that generate_error_msg returns appropriate error message."""
msg = self.cert_type.generate_error_msg(None)
self.assertEqual(msg, "must be a valid X.509 certificate in PEM format or otherwise encoded using Base64")
if __name__ == "__main__":
unittest.main()