]>
Commit | Line | Data |
---|---|---|
9a2fd434 DB |
1 | /* |
2 | * Copyright (C) 2015 Red Hat, Inc. | |
3 | * | |
4 | * This library is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU Lesser General Public | |
6 | * License as published by the Free Software Foundation; either | |
7 | * version 2.1 of the License, or (at your option) any later version. | |
8 | * | |
9 | * This library is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * Lesser General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU Lesser General Public | |
15 | * License along with this library. If not, see | |
16 | * <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | * Author: Daniel P. Berrange <[email protected]> | |
19 | */ | |
20 | ||
681c28a3 | 21 | #include "qemu/osdep.h" |
9a2fd434 | 22 | |
9a2fd434 DB |
23 | #include "crypto-tls-x509-helpers.h" |
24 | #include "qemu/sockets.h" | |
25 | ||
26 | #ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT | |
27 | ||
28 | /* | |
29 | * This stores some static data that is needed when | |
30 | * encoding extensions in the x509 certs | |
31 | */ | |
32 | ASN1_TYPE pkix_asn1; | |
33 | ||
34 | /* | |
35 | * To avoid consuming random entropy to generate keys, | |
36 | * here's one we prepared earlier :-) | |
37 | */ | |
38 | gnutls_x509_privkey_t privkey; | |
39 | # define PRIVATE_KEY \ | |
40 | "-----BEGIN PRIVATE KEY-----\n" \ | |
41 | "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALVcr\n" \ | |
42 | "BL40Tm6yq88FBhJNw1aaoCjmtg0l4dWQZ/e9Fimx4ARxFpT+ji4FE\n" \ | |
43 | "Cgl9s/SGqC+1nvlkm9ViSo0j7MKDbnDB+VRHDvMAzQhA2X7e8M0n9\n" \ | |
44 | "rPolUY2lIVC83q0BBaOBkCj2RSmT2xTEbbC2xLukSrg2WP/ihVOxc\n" \ | |
45 | "kXRuyFtzAgMBAAECgYB7slBexDwXrtItAMIH6m/U+LUpNe0Xx48OL\n" \ | |
46 | "IOn4a4whNgO/o84uIwygUK27ZGFZT0kAGAk8CdF9hA6ArcbQ62s1H\n" \ | |
47 | "myxrUbF9/mrLsQw1NEqpuUk9Ay2Tx5U/wPx35S3W/X2AvR/ZpTnCn\n" \ | |
48 | "2q/7ym9fyiSoj86drD7BTvmKXlOnOwQJBAPOFMp4mMa9NGpGuEssO\n" \ | |
49 | "m3Uwbp6lhcP0cA9MK+iOmeANpoKWfBdk5O34VbmeXnGYWEkrnX+9J\n" \ | |
50 | "bM4wVhnnBWtgBMCQQC+qAEmvwcfhauERKYznMVUVksyeuhxhCe7EK\n" \ | |
51 | "mPh+U2+g0WwdKvGDgO0PPt1gq0ILEjspMDeMHVdTwkaVBo/uMhAkA\n" \ | |
52 | "Z5SsZyCP2aTOPFDypXRdI4eqRcjaEPOUBq27r3uYb/jeboVb2weLa\n" \ | |
53 | "L1MmVuHiIHoa5clswPdWVI2y0em2IGoDAkBPSp/v9VKJEZabk9Frd\n" \ | |
54 | "a+7u4fanrM9QrEjY3KhduslSilXZZSxrWjjAJPyPiqFb3M8XXA26W\n" \ | |
55 | "nz1KYGnqYKhLcBAkB7dt57n9xfrhDpuyVEv+Uv1D3VVAhZlsaZ5Pp\n" \ | |
56 | "dcrhrkJn2sa/+O8OKvdrPSeeu/N5WwYhJf61+CPoenMp7IFci\n" \ | |
57 | "-----END PRIVATE KEY-----\n" | |
58 | ||
59 | /* | |
60 | * This loads the private key we defined earlier | |
61 | */ | |
62 | static gnutls_x509_privkey_t test_tls_load_key(void) | |
63 | { | |
64 | gnutls_x509_privkey_t key; | |
65 | const gnutls_datum_t data = { (unsigned char *)PRIVATE_KEY, | |
66 | strlen(PRIVATE_KEY) }; | |
67 | int err; | |
68 | ||
69 | err = gnutls_x509_privkey_init(&key); | |
70 | if (err < 0) { | |
71 | g_critical("Failed to init key %s", gnutls_strerror(err)); | |
72 | abort(); | |
73 | } | |
74 | ||
75 | err = gnutls_x509_privkey_import(key, &data, | |
76 | GNUTLS_X509_FMT_PEM); | |
77 | if (err < 0) { | |
78 | if (err != GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR && | |
79 | err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { | |
80 | g_critical("Failed to import key %s", gnutls_strerror(err)); | |
81 | abort(); | |
82 | } | |
83 | ||
84 | err = gnutls_x509_privkey_import_pkcs8( | |
85 | key, &data, GNUTLS_X509_FMT_PEM, NULL, 0); | |
86 | if (err < 0) { | |
87 | g_critical("Failed to import PKCS8 key %s", gnutls_strerror(err)); | |
88 | abort(); | |
89 | } | |
90 | } | |
91 | ||
92 | return key; | |
93 | } | |
94 | ||
95 | ||
96 | void test_tls_init(const char *keyfile) | |
97 | { | |
98 | gnutls_global_init(); | |
99 | ||
100 | if (asn1_array2tree(pkix_asn1_tab, &pkix_asn1, NULL) != ASN1_SUCCESS) { | |
101 | abort(); | |
102 | } | |
103 | ||
104 | privkey = test_tls_load_key(); | |
105 | if (!g_file_set_contents(keyfile, PRIVATE_KEY, -1, NULL)) { | |
106 | abort(); | |
107 | } | |
108 | } | |
109 | ||
110 | ||
111 | void test_tls_cleanup(const char *keyfile) | |
112 | { | |
113 | asn1_delete_structure(&pkix_asn1); | |
114 | unlink(keyfile); | |
115 | } | |
116 | ||
117 | /* | |
118 | * Turns an ASN1 object into a DER encoded byte array | |
119 | */ | |
120 | static void test_tls_der_encode(ASN1_TYPE src, | |
121 | const char *src_name, | |
122 | gnutls_datum_t *res) | |
123 | { | |
124 | int size; | |
125 | char *data = NULL; | |
126 | ||
127 | size = 0; | |
128 | asn1_der_coding(src, src_name, NULL, &size, NULL); | |
129 | ||
130 | data = g_new0(char, size); | |
131 | ||
132 | asn1_der_coding(src, src_name, data, &size, NULL); | |
133 | ||
134 | res->data = (unsigned char *)data; | |
135 | res->size = size; | |
136 | } | |
137 | ||
138 | ||
139 | static void | |
140 | test_tls_get_ipaddr(const char *addrstr, | |
141 | char **data, | |
142 | int *datalen) | |
143 | { | |
144 | struct addrinfo *res; | |
145 | struct addrinfo hints; | |
146 | ||
147 | memset(&hints, 0, sizeof(hints)); | |
148 | hints.ai_flags = AI_NUMERICHOST; | |
149 | g_assert(getaddrinfo(addrstr, NULL, &hints, &res) == 0); | |
150 | ||
151 | *datalen = res->ai_addrlen; | |
152 | *data = g_new(char, *datalen); | |
153 | memcpy(*data, res->ai_addr, *datalen); | |
7b35030e | 154 | freeaddrinfo(res); |
9a2fd434 DB |
155 | } |
156 | ||
157 | /* | |
158 | * This is a fairly lame x509 certificate generator. | |
159 | * | |
160 | * Do not copy/use this code for generating real certificates | |
161 | * since it leaves out many things that you would want in | |
162 | * certificates for real world usage. | |
163 | * | |
164 | * This is good enough only for doing tests of the QEMU | |
165 | * TLS certificate code | |
166 | */ | |
167 | void | |
168 | test_tls_generate_cert(QCryptoTLSTestCertReq *req, | |
169 | gnutls_x509_crt_t ca) | |
170 | { | |
171 | gnutls_x509_crt_t crt; | |
172 | int err; | |
173 | static char buffer[1024 * 1024]; | |
174 | size_t size = sizeof(buffer); | |
175 | char serial[5] = { 1, 2, 3, 4, 0 }; | |
176 | gnutls_datum_t der; | |
177 | time_t start = time(NULL) + (60 * 60 * req->start_offset); | |
178 | time_t expire = time(NULL) + (60 * 60 * (req->expire_offset | |
179 | ? req->expire_offset : 24)); | |
180 | ||
181 | /* | |
182 | * Prepare our new certificate object | |
183 | */ | |
184 | err = gnutls_x509_crt_init(&crt); | |
185 | if (err < 0) { | |
186 | g_critical("Failed to initialize certificate %s", gnutls_strerror(err)); | |
187 | abort(); | |
188 | } | |
189 | err = gnutls_x509_crt_set_key(crt, privkey); | |
190 | if (err < 0) { | |
191 | g_critical("Failed to set certificate key %s", gnutls_strerror(err)); | |
192 | abort(); | |
193 | } | |
194 | ||
195 | /* | |
196 | * A v3 certificate is required in order to be able | |
197 | * set any of the basic constraints, key purpose and | |
198 | * key usage data | |
199 | */ | |
200 | gnutls_x509_crt_set_version(crt, 3); | |
201 | ||
202 | if (req->country) { | |
203 | err = gnutls_x509_crt_set_dn_by_oid( | |
204 | crt, GNUTLS_OID_X520_COUNTRY_NAME, 0, | |
205 | req->country, strlen(req->country)); | |
206 | if (err < 0) { | |
207 | g_critical("Failed to set certificate country name %s", | |
208 | gnutls_strerror(err)); | |
209 | abort(); | |
210 | } | |
211 | } | |
212 | if (req->cn) { | |
213 | err = gnutls_x509_crt_set_dn_by_oid( | |
214 | crt, GNUTLS_OID_X520_COMMON_NAME, 0, | |
215 | req->cn, strlen(req->cn)); | |
216 | if (err < 0) { | |
217 | g_critical("Failed to set certificate common name %s", | |
218 | gnutls_strerror(err)); | |
219 | abort(); | |
220 | } | |
221 | } | |
222 | ||
223 | /* | |
224 | * Setup the subject altnames, which are used | |
225 | * for hostname checks in live sessions | |
226 | */ | |
227 | if (req->altname1) { | |
228 | err = gnutls_x509_crt_set_subject_alt_name( | |
229 | crt, GNUTLS_SAN_DNSNAME, | |
230 | req->altname1, | |
231 | strlen(req->altname1), | |
232 | GNUTLS_FSAN_APPEND); | |
233 | if (err < 0) { | |
234 | g_critical("Failed to set certificate alt name %s", | |
235 | gnutls_strerror(err)); | |
236 | abort(); | |
237 | } | |
238 | } | |
239 | if (req->altname2) { | |
240 | err = gnutls_x509_crt_set_subject_alt_name( | |
241 | crt, GNUTLS_SAN_DNSNAME, | |
242 | req->altname2, | |
243 | strlen(req->altname2), | |
244 | GNUTLS_FSAN_APPEND); | |
245 | if (err < 0) { | |
246 | g_critical("Failed to set certificate %s alt name", | |
247 | gnutls_strerror(err)); | |
248 | abort(); | |
249 | } | |
250 | } | |
251 | ||
252 | /* | |
253 | * IP address need to be put into the cert in their | |
254 | * raw byte form, not strings, hence this is a little | |
255 | * more complicated | |
256 | */ | |
257 | if (req->ipaddr1) { | |
258 | char *data; | |
259 | int len; | |
260 | ||
261 | test_tls_get_ipaddr(req->ipaddr1, &data, &len); | |
262 | ||
263 | err = gnutls_x509_crt_set_subject_alt_name( | |
264 | crt, GNUTLS_SAN_IPADDRESS, | |
265 | data, len, GNUTLS_FSAN_APPEND); | |
266 | if (err < 0) { | |
267 | g_critical("Failed to set certificate alt name %s", | |
268 | gnutls_strerror(err)); | |
269 | abort(); | |
270 | } | |
271 | g_free(data); | |
272 | } | |
273 | if (req->ipaddr2) { | |
274 | char *data; | |
275 | int len; | |
276 | ||
277 | test_tls_get_ipaddr(req->ipaddr2, &data, &len); | |
278 | ||
279 | err = gnutls_x509_crt_set_subject_alt_name( | |
280 | crt, GNUTLS_SAN_IPADDRESS, | |
281 | data, len, GNUTLS_FSAN_APPEND); | |
282 | if (err < 0) { | |
283 | g_critical("Failed to set certificate alt name %s", | |
284 | gnutls_strerror(err)); | |
285 | abort(); | |
286 | } | |
287 | g_free(data); | |
288 | } | |
289 | ||
290 | ||
291 | /* | |
292 | * Basic constraints are used to decide if the cert | |
293 | * is for a CA or not. We can't use the convenient | |
294 | * gnutls API for setting this, since it hardcodes | |
295 | * the 'critical' field which we want control over | |
296 | */ | |
297 | if (req->basicConstraintsEnable) { | |
298 | ASN1_TYPE ext = ASN1_TYPE_EMPTY; | |
299 | ||
300 | asn1_create_element(pkix_asn1, "PKIX1.BasicConstraints", &ext); | |
301 | asn1_write_value(ext, "cA", | |
302 | req->basicConstraintsIsCA ? "TRUE" : "FALSE", 1); | |
303 | asn1_write_value(ext, "pathLenConstraint", NULL, 0); | |
304 | test_tls_der_encode(ext, "", &der); | |
305 | err = gnutls_x509_crt_set_extension_by_oid( | |
306 | crt, "2.5.29.19", | |
307 | der.data, der.size, | |
308 | req->basicConstraintsCritical); | |
309 | if (err < 0) { | |
310 | g_critical("Failed to set certificate basic constraints %s", | |
311 | gnutls_strerror(err)); | |
312 | g_free(der.data); | |
313 | abort(); | |
314 | } | |
315 | asn1_delete_structure(&ext); | |
316 | g_free(der.data); | |
317 | } | |
318 | ||
319 | /* | |
320 | * Next up the key usage extension. Again we can't | |
321 | * use the gnutls API since it hardcodes the extension | |
322 | * to be 'critical' | |
323 | */ | |
324 | if (req->keyUsageEnable) { | |
325 | ASN1_TYPE ext = ASN1_TYPE_EMPTY; | |
326 | char str[2]; | |
327 | ||
328 | str[0] = req->keyUsageValue & 0xff; | |
329 | str[1] = (req->keyUsageValue >> 8) & 0xff; | |
330 | ||
331 | asn1_create_element(pkix_asn1, "PKIX1.KeyUsage", &ext); | |
332 | asn1_write_value(ext, "", str, 9); | |
333 | test_tls_der_encode(ext, "", &der); | |
334 | err = gnutls_x509_crt_set_extension_by_oid( | |
335 | crt, "2.5.29.15", | |
336 | der.data, der.size, | |
337 | req->keyUsageCritical); | |
338 | if (err < 0) { | |
339 | g_critical("Failed to set certificate key usage %s", | |
340 | gnutls_strerror(err)); | |
341 | g_free(der.data); | |
342 | abort(); | |
343 | } | |
344 | asn1_delete_structure(&ext); | |
345 | g_free(der.data); | |
346 | } | |
347 | ||
348 | /* | |
349 | * Finally the key purpose extension. This time | |
350 | * gnutls has the opposite problem, always hardcoding | |
351 | * it to be non-critical. So once again we have to | |
352 | * set this the hard way building up ASN1 data ourselves | |
353 | */ | |
354 | if (req->keyPurposeEnable) { | |
355 | ASN1_TYPE ext = ASN1_TYPE_EMPTY; | |
356 | ||
357 | asn1_create_element(pkix_asn1, "PKIX1.ExtKeyUsageSyntax", &ext); | |
358 | if (req->keyPurposeOID1) { | |
359 | asn1_write_value(ext, "", "NEW", 1); | |
360 | asn1_write_value(ext, "?LAST", req->keyPurposeOID1, 1); | |
361 | } | |
362 | if (req->keyPurposeOID2) { | |
363 | asn1_write_value(ext, "", "NEW", 1); | |
364 | asn1_write_value(ext, "?LAST", req->keyPurposeOID2, 1); | |
365 | } | |
366 | test_tls_der_encode(ext, "", &der); | |
367 | err = gnutls_x509_crt_set_extension_by_oid( | |
368 | crt, "2.5.29.37", | |
369 | der.data, der.size, | |
370 | req->keyPurposeCritical); | |
371 | if (err < 0) { | |
372 | g_critical("Failed to set certificate key purpose %s", | |
373 | gnutls_strerror(err)); | |
374 | g_free(der.data); | |
375 | abort(); | |
376 | } | |
377 | asn1_delete_structure(&ext); | |
378 | g_free(der.data); | |
379 | } | |
380 | ||
381 | /* | |
382 | * Any old serial number will do, so lets pick 5 | |
383 | */ | |
384 | err = gnutls_x509_crt_set_serial(crt, serial, 5); | |
385 | if (err < 0) { | |
386 | g_critical("Failed to set certificate serial %s", | |
387 | gnutls_strerror(err)); | |
388 | abort(); | |
389 | } | |
390 | ||
391 | err = gnutls_x509_crt_set_activation_time(crt, start); | |
392 | if (err < 0) { | |
393 | g_critical("Failed to set certificate activation %s", | |
394 | gnutls_strerror(err)); | |
395 | abort(); | |
396 | } | |
397 | err = gnutls_x509_crt_set_expiration_time(crt, expire); | |
398 | if (err < 0) { | |
399 | g_critical("Failed to set certificate expiration %s", | |
400 | gnutls_strerror(err)); | |
401 | abort(); | |
402 | } | |
403 | ||
404 | ||
405 | /* | |
406 | * If no 'ca' is set then we are self signing | |
407 | * the cert. This is done for the root CA certs | |
408 | */ | |
409 | err = gnutls_x509_crt_sign(crt, ca ? ca : crt, privkey); | |
410 | if (err < 0) { | |
411 | g_critical("Failed to sign certificate %s", | |
412 | gnutls_strerror(err)); | |
413 | abort(); | |
414 | } | |
415 | ||
416 | /* | |
417 | * Finally write the new cert out to disk | |
418 | */ | |
419 | err = gnutls_x509_crt_export( | |
420 | crt, GNUTLS_X509_FMT_PEM, buffer, &size); | |
421 | if (err < 0) { | |
422 | g_critical("Failed to export certificate %s: %d", | |
423 | gnutls_strerror(err), err); | |
424 | abort(); | |
425 | } | |
426 | ||
427 | if (!g_file_set_contents(req->filename, buffer, -1, NULL)) { | |
428 | g_critical("Failed to write certificate %s", | |
429 | req->filename); | |
430 | abort(); | |
431 | } | |
432 | ||
433 | req->crt = crt; | |
434 | } | |
435 | ||
436 | ||
437 | void test_tls_write_cert_chain(const char *filename, | |
438 | gnutls_x509_crt_t *certs, | |
439 | size_t ncerts) | |
440 | { | |
441 | size_t i; | |
442 | size_t capacity = 1024, offset = 0; | |
443 | char *buffer = g_new0(char, capacity); | |
444 | int err; | |
445 | ||
446 | for (i = 0; i < ncerts; i++) { | |
447 | size_t len = capacity - offset; | |
448 | retry: | |
449 | err = gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM, | |
450 | buffer + offset, &len); | |
451 | if (err < 0) { | |
452 | if (err == GNUTLS_E_SHORT_MEMORY_BUFFER) { | |
453 | buffer = g_renew(char, buffer, offset + len); | |
454 | capacity = offset + len; | |
455 | goto retry; | |
456 | } | |
457 | g_critical("Failed to export certificate chain %s: %d", | |
458 | gnutls_strerror(err), err); | |
459 | abort(); | |
460 | } | |
461 | offset += len; | |
462 | } | |
463 | ||
464 | if (!g_file_set_contents(filename, buffer, offset, NULL)) { | |
465 | abort(); | |
466 | } | |
7b35030e | 467 | g_free(buffer); |
9a2fd434 DB |
468 | } |
469 | ||
470 | ||
471 | void test_tls_discard_cert(QCryptoTLSTestCertReq *req) | |
472 | { | |
473 | if (!req->crt) { | |
474 | return; | |
475 | } | |
476 | ||
477 | gnutls_x509_crt_deinit(req->crt); | |
478 | req->crt = NULL; | |
479 | ||
480 | if (getenv("QEMU_TEST_DEBUG_CERTS") == NULL) { | |
481 | unlink(req->filename); | |
482 | } | |
483 | } | |
484 | ||
485 | #endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */ |