FreeRDP
Loading...
Searching...
No Matches
Test_x509_utils.c
1#include <winpr/file.h>
2#include <winpr/string.h>
3#include <winpr/print.h>
4
5#include <freerdp/crypto/certificate.h>
6
7#include "../x509_utils.h"
8
9typedef char* (*get_field_pr)(const X509*);
10typedef struct
11{
12 enum
13 {
14 DISABLED,
15 ENABLED,
16 } status;
17 const char* field_description;
18 get_field_pr get_field;
19 const char* expected_result;
20} certificate_test_t;
21
22static char* x509_utils_subject_common_name_wo_length(const X509* xcert)
23{
24 size_t length = 0;
25 return x509_utils_get_common_name(xcert, &length);
26}
27
28static char* certificate_path(const char* filename)
29{
30 /*
31 Assume the .pem file is in the same directory as this source file.
32 Assume that __FILE__ will be a valid path to this file, even from the current working directory
33 where the tests are run. (ie. no chdir occurs between compilation and test running, or __FILE__
34 is an absolute path).
35 */
36 static const char dirsep = '/';
37#ifdef TEST_SOURCE_DIR
38 const char* file = TEST_SOURCE_DIR;
39 const size_t flen = strlen(file) + sizeof(dirsep) + strlen(filename) + sizeof(char);
40 char* result = calloc(1, flen);
41 if (!result)
42 return nullptr;
43 (void)_snprintf(result, flen, "%s%c%s", file, dirsep, filename);
44 return result;
45#else
46 const char* file = __FILE__;
47 const char* last_dirsep = strrchr(file, dirsep);
48
49 if (last_dirsep)
50 {
51 const size_t filenameLen = strlen(filename);
52 const size_t dirsepLen = last_dirsep - file + 1;
53 char* result = malloc(dirsepLen + filenameLen + 1);
54 if (!result)
55 return nullptr;
56 strncpy(result, file, dirsepLen);
57 strncpy(result + dirsepLen, filename, filenameLen + 1);
58 return result;
59 }
60 else
61 {
62 /* No dirsep => relative path in same directory */
63 return _strdup(filename);
64 }
65#endif
66}
67
68static const certificate_test_t certificate_tests[] = {
69
70 { ENABLED, "Certificate Common Name", x509_utils_subject_common_name_wo_length,
71 "TESTJEAN TESTMARTIN 9999999" },
72
73 { ENABLED, "Certificate subject", x509_utils_get_subject,
74 "CN = TESTJEAN TESTMARTIN 9999999, C = FR, O = MINISTERE DES TESTS, OU = 0002 110014016, OU "
75 "= PERSONNES, UID = 9999999, GN = TESTJEAN, SN = TESTMARTIN" },
76
77 { DISABLED, "Kerberos principal name", nullptr, "testjean.testmartin@kpn.test.example.com" },
78
79 { ENABLED, "Certificate e-mail", x509_utils_get_email, "testjean.testmartin@test.example.com"
80
81 },
82
83 { ENABLED, "Microsoft's Universal Principal Name", x509_utils_get_upn,
84 "testjean.testmartin.9999999@upn.test.example.com" },
85
86 { ENABLED, "Certificate issuer", x509_utils_get_issuer,
87 "CN = ADMINISTRATION CENTRALE DES TESTS, C = FR, O = MINISTERE DES TESTS, OU = 0002 "
88 "110014016" },
89};
90
91static int TestCertificateFile(const char* certificate_path,
92 const certificate_test_t* ccertificate_tests, size_t count)
93{
94 int success = 0;
95
96 X509* certificate = x509_utils_from_pem(certificate_path, strlen(certificate_path), TRUE);
97
98 if (!certificate)
99 {
100 printf("%s: failure: cannot read certificate file '%s'\n", __func__, certificate_path);
101 success = -1;
102 goto fail;
103 }
104
105 for (size_t i = 0; i < count; i++)
106 {
107 const certificate_test_t* test = &ccertificate_tests[i];
108 char* result = nullptr;
109
110 if (test->status == DISABLED)
111 {
112 continue;
113 }
114
115 result = (test->get_field ? test->get_field(certificate) : nullptr);
116
117 if (result)
118 {
119 printf("%s: crypto got %-40s -> \"%s\"\n", __func__, test->field_description, result);
120
121 if (0 != strcmp(result, test->expected_result))
122 {
123 printf("%s: failure: for %s, actual: \"%s\", expected \"%s\"\n", __func__,
124 test->field_description, result, test->expected_result);
125 success = -1;
126 }
127
128 free(result);
129 }
130 else
131 {
132 printf("%s: failure: cannot get %s\n", __func__, test->field_description);
133 }
134 }
135
136fail:
137 X509_free(certificate);
138 return success;
139}
140
141/* clang-format off */
142/*
143These certificates were generated with the following commands:
144
145openssl ecparam -name P-256 -out /tmp/p256.pem
146openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha1_cert.pem -sha1
147openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha256_cert.pem -sha256
148openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha384_cert.pem -sha384
149openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha512_cert.pem -sha512
150
151openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha1_cert.pem -sha1
152openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha256_cert.pem -sha256
153openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha384_cert.pem -sha384
154openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha512_cert.pem -sha512
155
156openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha1_cert.pem -sha1 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
157openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha256_cert.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
158openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha384_cert.pem -sha384 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
159openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha512_cert.pem -sha512 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
160openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha256_mgf1_sha384_cert.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest -sigopt rsa_mgf1_md:sha384
161*/
162/* clang-format on */
163
164typedef struct
165{
166 const char* filename;
167 WINPR_MD_TYPE expected;
168} signature_alg_test_t;
169
170static const signature_alg_test_t signature_alg_tests[] = {
171 { "rsa_pkcs1_sha1_cert.pem", WINPR_MD_SHA1 },
172 { "rsa_pkcs1_sha256_cert.pem", WINPR_MD_SHA256 },
173 { "rsa_pkcs1_sha384_cert.pem", WINPR_MD_SHA384 },
174 { "rsa_pkcs1_sha512_cert.pem", WINPR_MD_SHA512 },
175
176 { "ecdsa_sha1_cert.pem", WINPR_MD_SHA1 },
177 { "ecdsa_sha256_cert.pem", WINPR_MD_SHA256 },
178 { "ecdsa_sha384_cert.pem", WINPR_MD_SHA384 },
179 { "ecdsa_sha512_cert.pem", WINPR_MD_SHA512 },
180
181 { "rsa_pss_sha1_cert.pem", WINPR_MD_SHA1 },
182 { "rsa_pss_sha256_cert.pem", WINPR_MD_SHA256 },
183 { "rsa_pss_sha384_cert.pem", WINPR_MD_SHA384 },
184 { "rsa_pss_sha512_cert.pem", WINPR_MD_SHA512 },
185 /*
186 PSS may use different digests for the message hash and MGF-1 hash. In this case, RFC 5929
187 leaves the tls-server-end-point hash unspecified, so it should return WINPR_MD_NONE.
188 */
189 { "rsa_pss_sha256_mgf1_sha384_cert.pem", WINPR_MD_NONE },
190};
191
192static int TestSignatureAlgorithm(const signature_alg_test_t* test)
193{
194 int success = 0;
195 WINPR_MD_TYPE signature_alg = WINPR_MD_NONE;
196 char* path = certificate_path(test->filename);
197 X509* certificate = x509_utils_from_pem(path, strlen(path), TRUE);
198
199 if (!certificate)
200 {
201 printf("%s: failure: cannot read certificate file '%s'\n", __func__, path);
202 success = -1;
203 goto fail;
204 }
205
206 signature_alg = x509_utils_get_signature_alg(certificate);
207 if (signature_alg != test->expected)
208 {
209 const char* signature_alg_string =
210 signature_alg == WINPR_MD_NONE ? "none" : winpr_md_type_to_string(signature_alg);
211 const char* expected_string =
212 test->expected == WINPR_MD_NONE ? "none" : winpr_md_type_to_string(test->expected);
213 printf("%s: failure: for \"%s\", actual: %s, expected %s\n", __func__, test->filename,
214 signature_alg_string, expected_string);
215 success = -1;
216 goto fail;
217 }
218
219fail:
220 X509_free(certificate);
221 free(path);
222 return success;
223}
224
225/*
226for alg in ed25519 ed448 ML-DSA-87; do
227 openssl req -x509 -newkey $alg -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" \
228 -out "$(echo $alg | tr 'A-Z-' 'a-z_')_cert.pem"
229done
230
231Signature algorithms without a hash: RFC 5929 leaves tls-server-end-point undefined for them, the
232hash X509_digest_sig() picks is used. NULL: no such hash, the caller uses the signature's own hash
233(or none). Ed448 (SHAKE256) has none: X509_digest_sig() needs an output length for it.
234*/
235typedef struct
236{
237 const char* filename;
238 const char* hash;
239 size_t len;
240 const char* data;
241} hashless_test_t;
242
243static const hashless_test_t hashless_tests[] = {
244#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(LIBRESSL_VERSION_NUMBER)
245 { "ed25519_cert.pem", "sha512", 64,
246 "C90BB22E50EB78613F5653A90F62DEF03E08BCFDC125748980073D0ECEACE02AE31EAA9EA13A199BAAB11B1241DE"
247 "9FEDECEE70C6B1A26D661B27FAF70DD8687F" },
248 { "ed448_cert.pem", "SHAKE256", 32,
249 "C72FF0B946C03583BAB0827497EE9601F8E45551A9C2E6E20C2AC95C31688298" },
250#if OPENSSL_VERSION_NUMBER >= 0x30500000L
251 { "ml_dsa_87_cert.pem", "sha256", 32,
252 "8862AE0B056198D99A0FBEC3596B59683E79EB3863058BD57896AAEBBD761931" },
253#endif
254#endif
255 { "rsa_pkcs1_sha256_cert.pem", nullptr, 0, nullptr },
256 { "ecdsa_sha384_cert.pem", nullptr, 0, nullptr },
257 { "rsa_pss_sha256_mgf1_sha384_cert.pem", nullptr, 0, nullptr },
258};
259
260static int TestHashlessSignatureHash(const hashless_test_t* test)
261{
262 int rc = -1;
263 size_t length = 0;
264 BYTE* hash = nullptr;
265 BYTE* expected = nullptr;
266 size_t expected_length = 0;
267 char* path = certificate_path(test->filename);
268 X509* certificate = path ? x509_utils_from_pem(path, strlen(path), TRUE) : nullptr;
269
270 if (!certificate)
271 {
272 printf("%s: failure: cannot read certificate file '%s'\n", __func__,
273 path ? path : "(null)");
274 goto fail;
275 }
276
277 hash = x509_utils_get_hashless_signature_hash(certificate, &length);
278 if (test->len != length)
279 {
280 printf("%s: failure: \"%s\" has a return length %" PRIuz ", but expected %" PRIuz "\n",
281 __func__, test->filename, length, test->len);
282 goto fail;
283 }
284
285 if (!test->hash)
286 {
287 if (hash)
288 {
289 printf("%s: failure: \"%s\" has a hash in its signature algorithm\n", __func__,
290 test->filename);
291 goto fail;
292 }
293 rc = 0;
294 goto fail;
295 }
296 else
297 {
298 char* cmp = winpr_BinToHexString(hash, length, FALSE);
299 if (!cmp)
300 {
301 printf("%s: failure: \"%s\" has a hash of type %s but could not convert data to HEX\n",
302 __func__, test->filename, test->hash);
303 goto fail;
304 }
305 const int c = memcmp(cmp, test->data, test->len);
306 free(cmp);
307 if (c != 0)
308 goto fail;
309 }
310
311 expected = x509_utils_get_hash(certificate, test->hash, &expected_length);
312 winpr_HexDump("testtest", WLOG_INFO, expected, expected_length);
313 if (!hash || !expected || (length != expected_length) || (memcmp(hash, expected, length) != 0))
314 {
315 printf("%s: failure: \"%s\" is not hashed with %s\n", __func__, test->filename, test->hash);
316 goto fail;
317 }
318 rc = 0;
319fail:
320 free(expected);
321 free(hash);
322 X509_free(certificate);
323 free(path);
324 return rc;
325}
326
327/* The public key CredSSP binds is the certificate's SubjectPublicKey, for any key type */
328static int TestPublicKey(const char* filename)
329{
330 int rc = -1;
331 BYTE* key = nullptr;
332 DWORD key_length = 0;
333 const unsigned char* spk = nullptr;
334 int spk_length = 0;
335 char* path = certificate_path(filename);
336 X509* x509 = path ? x509_utils_from_pem(path, strlen(path), TRUE) : nullptr;
337 rdpCertificate* certificate = path ? freerdp_certificate_new_from_file(path) : nullptr;
338
339 if (!x509 || !certificate ||
340 (X509_PUBKEY_get0_param(nullptr, &spk, &spk_length, nullptr, X509_get_X509_PUBKEY(x509)) !=
341 1))
342 {
343 printf("%s: failure: cannot read certificate file '%s'\n", __func__,
344 path ? path : "(null)");
345 goto fail;
346 }
347
348 if (!freerdp_certificate_get_public_key(certificate, &key, &key_length) ||
349 (key_length != (DWORD)spk_length) || (memcmp(key, spk, key_length) != 0))
350 {
351 printf("%s: failure: \"%s\" public key is not its SubjectPublicKey\n", __func__, filename);
352 goto fail;
353 }
354 rc = 0;
355fail:
356 free(key);
357 freerdp_certificate_free(certificate);
358 X509_free(x509);
359 free(path);
360 return rc;
361}
362
363static const char* public_key_tests[] = {
364 "rsa_pkcs1_sha256_cert.pem",
365 "ecdsa_sha256_cert.pem",
366#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(LIBRESSL_VERSION_NUMBER)
367 "ed25519_cert.pem",
368#if OPENSSL_VERSION_NUMBER >= 0x30500000L
369 "ml_dsa_87_cert.pem",
370#endif
371#endif
372};
373
374/*
375SAN='DNS:*.wildcard.example.com,DNS:*.com,DNS:*..com,DNS:*.com.,DNS:host.example.com'
376openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes \
377 -subj "/CN=*.wildcard.example.com" -addext "subjectAltName=$SAN" \
378 -out Test_x509_wildcard_cert.pem
379*/
380static int TestWildcardNames(void)
381{
382 int rc = -1;
383 size_t count = 0;
384 size_t* lengths = nullptr;
385 char** names = nullptr;
386 char* common_name = nullptr;
387 char* path = certificate_path("Test_x509_wildcard_cert.pem");
388 X509* certificate = path ? x509_utils_from_pem(path, strlen(path), TRUE) : nullptr;
389
390 if (!certificate)
391 {
392 printf("%s: failure: cannot read certificate file '%s'\n", __func__,
393 path ? path : "(null)");
394 goto fail;
395 }
396
397 common_name = x509_utils_get_common_name(certificate, nullptr);
398 if (!common_name || (strcmp(common_name, "*.wildcard.example.com") != 0))
399 {
400 printf("%s: failure: wildcard common name not returned: \"%s\"\n", __func__,
401 common_name ? common_name : "(null)");
402 goto fail;
403 }
404
405 names = x509_utils_get_dns_names(certificate, &count, &lengths);
406 if (!names || (count != 2) || (strcmp(names[0], "*.wildcard.example.com") != 0) ||
407 (strcmp(names[1], "host.example.com") != 0))
408 {
409 printf("%s: failure: expected only the valid wildcard and host names, got %" PRIuz "\n",
410 __func__, count);
411 goto fail;
412 }
413
414 rc = 0;
415fail:
416 x509_utils_dns_names_free(count, lengths, names);
417 free(common_name);
418 X509_free(certificate);
419 free(path);
420 return rc;
421}
422
423int Test_x509_utils(int argc, char* argv[])
424{
425 char* cert_path = certificate_path("Test_x509_cert_info.pem");
426 int ret = 0;
427 WINPR_UNUSED(argc);
428 WINPR_UNUSED(argv);
429
430 ret = TestCertificateFile(cert_path, certificate_tests, ARRAYSIZE(certificate_tests));
431 free(cert_path);
432 if (ret != 0)
433 return ret;
434
435 ret = TestWildcardNames();
436 if (ret != 0)
437 return ret;
438
439 for (size_t i = 0; i < ARRAYSIZE(signature_alg_tests); i++)
440 {
441 ret = TestSignatureAlgorithm(&signature_alg_tests[i]);
442 if (ret != 0)
443 return ret;
444 }
445
446 for (size_t i = 0; i < ARRAYSIZE(hashless_tests); i++)
447 {
448 ret = TestHashlessSignatureHash(&hashless_tests[i]);
449 if (ret != 0)
450 return ret;
451 }
452
453 for (size_t i = 0; i < ARRAYSIZE(public_key_tests); i++)
454 {
455 ret = TestPublicKey(public_key_tests[i]);
456 if (ret != 0)
457 return ret;
458 }
459
460 return ret;
461}