]>
Commit | Line | Data |
---|---|---|
e689684c SB |
1 | /* SPDX-License-Identifier: BSD-3-Clause */ |
2 | /* | |
3 | * swtpm_localca.c: A tool for creating TPM 1.2 and TPM 2 certificates localy or using pkcs11 | |
4 | * | |
5 | * Author: Stefan Berger, stefanb@linux.ibm.com | |
6 | * | |
7 | * Copyright (c) IBM Corporation, 2021 | |
8 | */ | |
9 | ||
10 | #include "config.h" | |
11 | ||
12 | #include <errno.h> | |
13 | #include <fcntl.h> | |
14 | #include <getopt.h> | |
15 | #include <pwd.h> | |
16 | #include <regex.h> | |
17 | #include <stdio.h> | |
18 | #include <stdlib.h> | |
19 | #include <string.h> | |
20 | #include <unistd.h> | |
21 | #include <sys/file.h> | |
22 | #include <sys/stat.h> | |
23 | #include <sys/types.h> | |
24 | ||
25 | #include <glib.h> | |
26 | ||
27 | #include "swtpm_utils.h" | |
28 | #include "swtpm_localca_conf.h" | |
29 | #include "swtpm_localca_utils.h" | |
30 | ||
31 | #define SETUP_TPM2_F 1 | |
32 | /* for TPM 2 EK */ | |
33 | #define ALLOW_SIGNING_F 2 | |
34 | #define DECRYPTION_F 4 | |
35 | ||
36 | /* Default logging goes to stderr */ | |
37 | gchar *gl_LOGFILE = NULL; | |
38 | ||
39 | #define LOCALCA_OPTIONS "swtpm-localca.options" | |
40 | #define LOCALCA_CONFIG "swtpm-localca.conf" | |
41 | ||
42 | #if defined __APPLE__ | |
43 | # define CERTTOOL_NAME "gnutls-certtool" | |
44 | #else | |
45 | # define CERTTOOL_NAME "certtool" | |
46 | #endif | |
47 | ||
48 | /* initialize the path of the options and config files */ | |
49 | static int init(gchar **options_file, gchar **config_file) | |
50 | { | |
51 | const char *xch = getenv("XDG_CONFIG_HOME"); | |
52 | const char *home = getenv("HOME"); | |
53 | char path[PATH_MAX]; | |
54 | const char *p = NULL; | |
55 | int ret = 0; | |
56 | ||
57 | if (xch != NULL && | |
58 | (p = pathjoin(path, sizeof(path), xch, LOCALCA_OPTIONS, NULL)) != NULL && | |
59 | access(p, R_OK) == 0) { | |
60 | /* p is good */ | |
61 | } else if (home != NULL && | |
62 | (p = pathjoin(path, sizeof(path), home, ".config", LOCALCA_OPTIONS)) != NULL && | |
63 | access(p, R_OK) == 0) { | |
64 | /* p is good */ | |
65 | } else { | |
66 | p = pathjoin(path, sizeof(path), G_DIR_SEPARATOR_S, SYSCONFDIR, LOCALCA_OPTIONS); | |
67 | } | |
68 | *options_file = g_strdup(p); | |
69 | ||
70 | if (xch != NULL && | |
71 | (p = pathjoin(path, sizeof(path), xch, LOCALCA_CONFIG, NULL)) != NULL && | |
72 | access(p, R_OK) == 0) { | |
73 | /* p is good */ | |
74 | } else if (home != NULL && | |
75 | (p = pathjoin(path, sizeof(path), home, ".config", LOCALCA_CONFIG)) != NULL && | |
76 | access(p, R_OK) == 0) { | |
77 | /* p is good */ | |
78 | } else { | |
79 | p = pathjoin(path, sizeof(path), G_DIR_SEPARATOR_S, SYSCONFDIR, LOCALCA_CONFIG); | |
80 | } | |
81 | *config_file = g_strdup(p); | |
82 | ||
83 | return ret; | |
84 | } | |
85 | ||
86 | /* Run the certtool command line prepared in cmd. Display error message | |
87 | * in case of failure and also display the keyfile if something goes wrong. | |
88 | */ | |
89 | static int run_certtool(gchar **cmd, gchar **env, const char *msg, gchar *keyfile) | |
90 | { | |
91 | g_autofree gchar *standard_error = NULL; | |
92 | gint exit_status; | |
93 | GError *error = NULL; | |
94 | gboolean success; | |
95 | ||
96 | success = g_spawn_sync(NULL, cmd, env, G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, | |
97 | NULL, &standard_error, &exit_status, &error); | |
98 | if (!success || exit_status != 0) { | |
99 | logerr(gl_LOGFILE, "%s" , msg); | |
100 | if (keyfile) | |
101 | logerr(gl_LOGFILE, " %s:", keyfile); | |
102 | if (!success) { | |
103 | logerr(gl_LOGFILE, "%s\n", error->message); | |
104 | g_error_free(error); | |
105 | } else { | |
106 | logerr(gl_LOGFILE, "%s\n", standard_error); | |
107 | } | |
108 | return 1; | |
109 | } | |
110 | return 0; | |
111 | } | |
112 | ||
113 | /* Create a root CA key and cert and a local CA key and cert. The latter will be | |
114 | * used for signing the TPM certs. | |
115 | */ | |
116 | static int create_localca_cert(const gchar *lockfile, const gchar *statedir, | |
117 | const gchar *signkey, const gchar *signkey_password, | |
118 | const gchar *issuercert) | |
119 | { | |
120 | int lockfd; | |
121 | int ret = 1; | |
122 | struct stat statbuf; | |
123 | int template1_file_fd = -1; | |
124 | int template2_file_fd = -1; | |
125 | g_autofree gchar *template1_file = NULL; | |
126 | g_autofree gchar *template2_file = NULL; | |
127 | gchar **certtool_env = NULL; | |
128 | ||
129 | lockfd = lock_file(lockfile); | |
130 | if (lockfd < 0) | |
131 | return 1; | |
132 | ||
133 | if (stat(statedir, &statbuf) != 0) { | |
134 | if (makedir(statedir, "statedir") != 0) | |
135 | goto error; | |
136 | } | |
137 | ||
138 | if (access(signkey, R_OK) != 0) { | |
139 | g_autofree gchar *directory = g_path_get_dirname(signkey); | |
140 | g_autofree gchar *cakey = g_strjoin(G_DIR_SEPARATOR_S, directory, "swtpm-localca-rootca-privkey.pem", NULL); | |
141 | g_autofree gchar *cacert = g_strjoin(G_DIR_SEPARATOR_S, directory, "swtpm-localca-rootca-cert.pem", NULL); | |
142 | const gchar *swtpm_rootca_password = g_getenv("SWTPM_ROOTCA_PASSWORD"); | |
143 | g_autofree gchar *certtool = g_find_program_in_path(CERTTOOL_NAME); | |
144 | g_autofree gchar **cmd = NULL; | |
145 | g_autofree gchar *fc = NULL; | |
146 | const char *filecontent; | |
147 | ||
148 | if (certtool == NULL) { | |
149 | logerr(gl_LOGFILE, "Could not find %s in PATH.\n", CERTTOOL_NAME); | |
150 | goto error; | |
151 | } | |
152 | ||
153 | /* generate the root-CA's private key */ | |
154 | cmd = concat_arrays(cmd, (gchar*[]){ | |
155 | (gchar *)certtool, "--generate-privkey", "--outfile", cakey, NULL | |
156 | }, TRUE); | |
157 | if (swtpm_rootca_password != NULL) | |
158 | cmd = concat_arrays(cmd, (gchar*[]){ | |
159 | "--password", (gchar *)swtpm_rootca_password, NULL | |
160 | }, TRUE); | |
161 | if (run_certtool(cmd, certtool_env, "Could not create root-CA key", cakey)) | |
162 | goto error; | |
163 | ||
164 | if (chmod(cakey, S_IRUSR | S_IWUSR | S_IRGRP) != 0) { | |
165 | logerr(gl_LOGFILE, "Could not chmod %s: %s\n", cakey, strerror(errno)); | |
166 | goto error; | |
167 | } | |
168 | ||
169 | certtool_env = g_environ_setenv(NULL, "PATH", g_getenv("PATH"), TRUE); | |
170 | ||
171 | /* create the root-CA's cert */ | |
172 | filecontent = "cn=swtpm-localca-rootca\n" | |
173 | "ca\n" | |
174 | "cert_signing_key\n" | |
71c353a7 | 175 | "expiration_days = 3600\n"; |
e689684c SB |
176 | template1_file_fd = write_to_tempfile(&template1_file, |
177 | (const unsigned char *)filecontent, strlen(filecontent)); | |
178 | if (template1_file_fd < 0) | |
179 | goto error; | |
180 | ||
181 | g_free(cmd); | |
182 | cmd = concat_arrays(NULL, | |
183 | (gchar *[]) { | |
184 | certtool, | |
185 | "--generate-self-signed", | |
186 | "--template", template1_file, | |
187 | "--outfile", cacert, | |
188 | "--load-privkey", cakey, | |
189 | NULL | |
190 | }, FALSE); | |
191 | if (swtpm_rootca_password != NULL) | |
192 | certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", swtpm_rootca_password, TRUE); | |
193 | ||
194 | if (run_certtool(cmd, certtool_env, "Could not create root-CA:", NULL)) | |
195 | goto error; | |
196 | ||
197 | g_free(cmd); | |
198 | ||
199 | /* create the intermediate CA's key */ | |
200 | cmd = concat_arrays(NULL, | |
201 | (gchar *[]) { | |
202 | certtool, "--generate-privkey", "--outfile", (gchar *)signkey, NULL | |
203 | }, FALSE); | |
204 | if (signkey_password != NULL) | |
205 | cmd = concat_arrays(cmd, (gchar *[]){ | |
206 | "--password", (gchar *)signkey_password, NULL}, | |
207 | TRUE); | |
208 | if (run_certtool(cmd, certtool_env, "Could not create local-CA key", cakey)) | |
209 | goto error; | |
210 | ||
211 | if (chmod(signkey, S_IRUSR | S_IWUSR | S_IRGRP) != 0) { | |
212 | logerr(gl_LOGFILE, "Could not chmod %s: %s\n", signkey, strerror(errno)); | |
213 | goto error; | |
214 | } | |
215 | ||
216 | filecontent = "cn=swtpm-localca\n" | |
217 | "ca\n" | |
218 | "cert_signing_key\n" | |
71c353a7 | 219 | "expiration_days = 3600\n"; |
e689684c SB |
220 | if (swtpm_rootca_password != NULL && signkey_password != NULL) |
221 | fc = g_strdup_printf("%spassword = %s\n", filecontent, swtpm_rootca_password); | |
222 | else | |
223 | fc = g_strdup(filecontent); | |
224 | ||
225 | template2_file_fd = write_to_tempfile(&template2_file, | |
226 | (const unsigned char *)fc, strlen(fc)); | |
227 | if (template2_file_fd < 0) | |
228 | goto error; | |
229 | ||
230 | g_free(cmd); | |
231 | cmd = concat_arrays(NULL, | |
232 | (gchar *[]) { | |
233 | certtool, | |
234 | "--generate-certificate", | |
235 | "--template", template2_file, | |
236 | "--outfile", (gchar *)issuercert, | |
237 | "--load-privkey", (gchar *)signkey, | |
238 | "--load-ca-privkey", cakey, | |
239 | "--load-ca-certificate", cacert, | |
240 | NULL | |
241 | }, FALSE); | |
242 | if (signkey_password != NULL) | |
243 | certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", signkey_password, TRUE); | |
244 | else if (swtpm_rootca_password != NULL) | |
245 | certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", swtpm_rootca_password, TRUE); | |
246 | ||
247 | if (run_certtool(cmd, certtool_env, "Could not create local-CA:", NULL)) | |
248 | goto error; | |
249 | } | |
250 | ||
251 | ret = 0; | |
252 | ||
253 | error: | |
254 | if (template1_file_fd >= 0) | |
255 | close(template1_file_fd); | |
256 | if (template1_file != NULL) | |
257 | unlink(template1_file); | |
258 | ||
259 | if (template2_file_fd >= 0) | |
260 | close(template2_file_fd); | |
261 | if (template2_file != NULL) | |
262 | unlink(template2_file); | |
263 | g_strfreev(certtool_env); | |
264 | ||
265 | unlock_file(lockfd); | |
266 | ||
267 | return ret; | |
268 | } | |
269 | ||
270 | /* Extract the ECC parameters from a string like x=12,y=34,id=secp384r1. | |
271 | * This function returns 1 on error, 2 if the ECC parameters could be extracted | |
272 | * and 0 if no parameters could be extracted (likely a modulus). | |
273 | */ | |
274 | static gboolean extract_ecc_params(const gchar *ekparams, gchar **ecc_x, gchar **ecc_y, gchar **ecc_curveid) | |
275 | { | |
276 | regmatch_t pmatch[5]; | |
277 | regex_t preg; | |
278 | int ret; | |
279 | ||
280 | if (regcomp(&preg, "x=([0-9A-Fa-f]+),y=([0-9A-Fa-f]+)(,id=([^,]+))?", | |
281 | REG_EXTENDED) != 0) { | |
282 | logerr(gl_LOGFILE, "Internal error: Could not compile regex\n"); | |
283 | return 1; | |
284 | } | |
285 | ||
286 | ret = 0; | |
287 | if (regexec(&preg, ekparams, 5, pmatch, 0) == 0) { | |
288 | *ecc_x = g_strndup(&ekparams[pmatch[1].rm_so], | |
289 | pmatch[1].rm_eo - pmatch[1].rm_so); | |
290 | *ecc_y = g_strndup(&ekparams[pmatch[2].rm_so], | |
291 | pmatch[2].rm_eo - pmatch[2].rm_so); | |
292 | if (pmatch[4].rm_so > 0 && pmatch[4].rm_eo > 0) | |
293 | *ecc_curveid = g_strndup(&ekparams[pmatch[4].rm_so], | |
294 | pmatch[4].rm_eo - pmatch[4].rm_so); | |
295 | ret = 2; | |
296 | } | |
297 | ||
298 | regfree(&preg); | |
299 | ||
300 | return ret; | |
301 | } | |
302 | ||
303 | /* Get the next serial number from the certserial file; if it contains | |
304 | * a non-numeric content start over with serial number '1'. | |
305 | */ | |
306 | static int get_next_serial(const gchar *certserial, const gchar *lockfile, | |
307 | gchar **serial_str) | |
308 | { | |
309 | g_autofree gchar *buffer = NULL; | |
310 | size_t buffer_len; | |
311 | unsigned long long serial, serial_n; | |
312 | char *endptr = NULL; | |
313 | int lockfd; | |
314 | int ret = 1; | |
315 | ||
316 | lockfd = lock_file(lockfile); | |
317 | if (lockfd < 0) | |
318 | return 1; | |
319 | ||
320 | if (access(certserial, R_OK) != 0) | |
321 | write_file(certserial, (unsigned char *)"1", 1); | |
322 | if (read_file(certserial, &buffer, &buffer_len) != 0) | |
323 | goto error; | |
324 | ||
325 | if (buffer_len > 0) { | |
326 | serial = strtoull(buffer, &endptr, 10); | |
327 | if (*endptr == '\0') { | |
328 | serial_n = serial + 1; | |
329 | } else { | |
330 | serial_n = 1; | |
331 | } | |
332 | } else { | |
333 | serial_n = 1; | |
334 | } | |
335 | *serial_str = g_strdup_printf("%llu", serial_n); | |
336 | write_file(certserial, (unsigned char *)*serial_str, strlen(*serial_str)); | |
337 | ret = 0; | |
338 | ||
339 | error: | |
340 | unlock_file(lockfd); | |
341 | ||
342 | return ret; | |
343 | } | |
344 | ||
345 | /* Create a TPM 1.2 or TPM 2 EK or platform cert */ | |
346 | static int create_cert(unsigned long flags, const gchar *typ, const gchar *directory, | |
347 | gchar *ekparams, const gchar *vmid, gchar **tpm_spec_params, | |
348 | gchar **tpm_attr_params, const gchar *signkey, | |
349 | const gchar *signkey_password, const gchar *issuercert, | |
350 | const gchar *parentkey_password, gchar **swtpm_cert_env, | |
351 | const gchar *certserial, const gchar *lockfile, | |
352 | const gchar *optsfile) | |
353 | { | |
354 | gchar ** optsfile_lines = NULL; | |
355 | g_autofree gchar **options = NULL; | |
356 | g_autofree gchar **keyparams = NULL; | |
357 | g_autofree gchar **cmd = NULL; | |
358 | g_autofree gchar *subject = NULL; | |
359 | g_autofree gchar *ecc_x = NULL; | |
360 | g_autofree gchar *ecc_y = NULL; | |
361 | g_autofree gchar *ecc_curveid = NULL; | |
362 | g_autofree gchar *certfile = NULL; | |
363 | g_autofree gchar *serial_str = NULL; | |
364 | gchar **to_free = NULL; | |
365 | gchar **split; | |
366 | const char *certtype; | |
367 | int signkey_pwd_fd = -1; | |
368 | int parentkey_pwd_fd = -1; | |
369 | g_autofree gchar *signkey_pwd_file = NULL; | |
370 | g_autofree gchar *signkey_pwd_file_param = NULL; | |
371 | g_autofree gchar *parentkey_pwd_file = NULL; | |
372 | g_autofree gchar *parentkey_pwd_file_param = NULL; | |
373 | gboolean success; | |
374 | g_autofree gchar *standard_output = NULL; | |
375 | g_autofree gchar *standard_error = NULL; | |
376 | g_autofree gchar *swtpm_cert_path = NULL; | |
377 | GError *error = NULL; | |
378 | gint exit_status; | |
379 | int ret = 1; | |
380 | size_t i, j; | |
381 | ||
382 | swtpm_cert_path = g_find_program_in_path("swtpm_cert"); | |
383 | if (swtpm_cert_path == NULL) { | |
384 | logerr(gl_LOGFILE, "Could not find swtpm_cert in PATH.\n"); | |
385 | return 1; | |
386 | } | |
387 | ||
388 | if (get_next_serial(certserial, lockfile, &serial_str) != 0) | |
389 | return 1; | |
390 | ||
391 | /* try to read the optsfile */ | |
392 | read_file_lines(optsfile, &optsfile_lines); | |
393 | ||
394 | /* split each line from the optsfile and add the stripped parameters to options */ | |
395 | for (i = 0; optsfile_lines != NULL && optsfile_lines[i] != NULL; i++) { | |
396 | gchar *chomped = g_strchomp(optsfile_lines[i]); | |
397 | if (strlen(chomped) == 0) | |
398 | continue; | |
399 | ||
400 | split = g_strsplit(chomped, " ", -1); | |
401 | for (j = 0; split[j] != NULL; j++) { | |
402 | chomped = g_strchomp(split[j]); | |
403 | if (strlen(chomped) > 0) { | |
404 | gchar *to_add = g_strdup(chomped); | |
405 | options = concat_arrays(options, (gchar *[]){to_add, NULL}, TRUE); | |
406 | /* need to collect this also to free later on */ | |
407 | to_free = concat_arrays(to_free, (gchar *[]){to_add, NULL}, TRUE); | |
408 | } | |
409 | } | |
410 | g_strfreev(split); | |
411 | } | |
412 | ||
413 | if (vmid != NULL) | |
414 | subject = g_strdup_printf("CN=%s", vmid); | |
415 | else | |
416 | subject = g_strdup("CN=unknown"); | |
417 | ||
418 | if (flags & SETUP_TPM2_F) | |
419 | options = concat_arrays(options, (gchar *[]){"--tpm2", NULL}, TRUE); | |
420 | else | |
421 | options = concat_arrays(options, (gchar *[]){"--add-header", NULL}, TRUE); | |
422 | ||
423 | if (strcmp(typ, "ek") == 0) { | |
424 | if (flags & ALLOW_SIGNING_F) | |
425 | options = concat_arrays(options, (gchar *[]){"--allow-signing", NULL}, TRUE); | |
426 | if (flags & DECRYPTION_F) | |
427 | options = concat_arrays(options, (gchar *[]){"--decryption", NULL}, TRUE); | |
428 | } | |
429 | ||
430 | switch (extract_ecc_params(ekparams, &ecc_x, &ecc_y, &ecc_curveid)) { | |
431 | case 1: | |
432 | goto error; | |
433 | case 2: | |
434 | keyparams = concat_arrays((gchar *[]){ | |
435 | "--ecc-x", ecc_x, | |
436 | "--ecc-y", ecc_y, | |
437 | NULL | |
438 | }, | |
439 | NULL, FALSE); | |
440 | if (ecc_curveid != NULL) | |
441 | keyparams = concat_arrays(keyparams, | |
442 | (gchar *[]){ | |
443 | "--ecc-curveid", ecc_curveid, | |
444 | NULL | |
445 | }, TRUE); | |
446 | break; | |
447 | case 0: | |
448 | keyparams = concat_arrays((gchar *[]){ | |
449 | "--modulus", ekparams, | |
450 | NULL}, | |
451 | NULL, FALSE); | |
452 | break; | |
453 | } | |
454 | ||
455 | cmd = concat_arrays((gchar *[]){ | |
456 | swtpm_cert_path, "--subject", subject, NULL | |
457 | }, options, FALSE); | |
458 | ||
459 | if (signkey_password != NULL) { | |
460 | signkey_pwd_fd = write_to_tempfile(&signkey_pwd_file, | |
461 | (unsigned char *)signkey_password, strlen(signkey_password)); | |
462 | if (signkey_pwd_fd < 0) | |
463 | goto error; | |
464 | ||
465 | signkey_pwd_file_param = g_strdup_printf("file:%s", signkey_pwd_file); | |
466 | cmd = concat_arrays(cmd, (gchar*[]){"--signkey-pwd", signkey_pwd_file_param, NULL}, TRUE); | |
467 | } | |
468 | if (parentkey_password != NULL) { | |
469 | parentkey_pwd_fd = write_to_tempfile(&parentkey_pwd_file, | |
470 | (unsigned char *)parentkey_password, strlen(parentkey_password)); | |
471 | if (parentkey_pwd_fd < 0) | |
472 | goto error; | |
473 | ||
474 | parentkey_pwd_file_param = g_strdup_printf("file:%s", parentkey_pwd_file); | |
475 | cmd = concat_arrays(cmd, (gchar*[]){"--parentkey-pwd", parentkey_pwd_file_param, NULL}, TRUE); | |
476 | } | |
477 | ||
478 | if (strcmp(typ, "ek") == 0) | |
479 | cmd = concat_arrays(cmd, tpm_spec_params, TRUE); | |
480 | ||
481 | cmd = concat_arrays(cmd, tpm_attr_params, TRUE); | |
482 | ||
483 | if (strcmp(typ, "platform") == 0) { | |
484 | certfile = g_strjoin(G_DIR_SEPARATOR_S, directory, "platform.cert", NULL); | |
485 | cmd = concat_arrays(cmd, | |
486 | (gchar *[]){ | |
487 | "--type", "platform", | |
488 | "--out-cert", certfile, | |
489 | NULL}, | |
490 | TRUE); | |
491 | } else { | |
492 | certfile = g_strjoin(G_DIR_SEPARATOR_S, directory, "ek.cert", NULL); | |
493 | cmd = concat_arrays(cmd, | |
494 | (gchar *[]){ | |
495 | "--out-cert", certfile, | |
496 | NULL | |
497 | }, TRUE); | |
498 | } | |
499 | ||
500 | cmd = concat_arrays(cmd, keyparams, TRUE); | |
501 | cmd = concat_arrays(cmd, (gchar *[]){ | |
502 | "--signkey", (gchar *)signkey, | |
503 | "--issuercert", (gchar *)issuercert, | |
71c353a7 | 504 | "--days", "3600", |
e689684c SB |
505 | "--serial", (gchar *)serial_str, |
506 | NULL | |
507 | }, TRUE); | |
508 | ||
509 | if (strcmp(typ, "ek") == 0) | |
510 | certtype = "EK"; | |
511 | else | |
512 | certtype = "platform"; | |
513 | #if 0 | |
514 | { | |
515 | g_autofree gchar *join = g_strjoinv(" ", cmd); | |
516 | fprintf(stderr, "Starting: %s\n", join); | |
517 | } | |
518 | #endif | |
519 | success = g_spawn_sync(NULL, cmd, swtpm_cert_env, G_SPAWN_DEFAULT, NULL, NULL, | |
520 | &standard_output, &standard_error, &exit_status, &error); | |
521 | if (!success) { | |
522 | logerr(gl_LOGFILE, "Could not run swtpm_cert: %s\n", error); | |
523 | g_error_free(error); | |
524 | goto error; | |
525 | } | |
526 | if (exit_status != 0) { | |
527 | logerr(gl_LOGFILE, "Could not create %s certificate locally\n", certtype); | |
528 | logerr(gl_LOGFILE, "%s\n", standard_error); | |
529 | goto error; | |
530 | } | |
531 | ||
532 | logit(gl_LOGFILE, "Successfully created %s certificate locally.\n", certtype); | |
533 | ret = 0; | |
534 | ||
535 | error: | |
536 | g_strfreev(optsfile_lines); | |
537 | g_strfreev(to_free); | |
538 | ||
539 | if (signkey_pwd_fd >= 0) | |
540 | close(signkey_pwd_fd); | |
541 | if (signkey_pwd_file) | |
542 | unlink(signkey_pwd_file); | |
543 | ||
544 | if (parentkey_pwd_fd >= 0) | |
545 | close(parentkey_pwd_fd); | |
546 | if (parentkey_pwd_file) | |
547 | unlink(parentkey_pwd_file); | |
548 | ||
549 | return ret; | |
550 | } | |
551 | ||
552 | static void usage(const char *prgname) | |
553 | { | |
554 | printf( | |
555 | "Usage: %s [options]\n" | |
556 | "\n" | |
557 | "The following options are supported:\n" | |
558 | "\n" | |
559 | "--type type The type of certificate to create: 'ek' or 'platform'\n" | |
560 | "--ek key-param The modulus of an RSA key or x=...,y=,... for an EC key\n" | |
561 | "--dir directory The directory to write the resulting certificate into\n" | |
562 | "--vmid vmid The ID of the virtual machine\n" | |
563 | "--optsfile file A file containing options to pass to swtpm_cert\n" | |
564 | "--configfile file A file containing configuration parameters for directory,\n" | |
565 | " signing key and password and certificate to use\n" | |
566 | "--logfile file A file to write a log into\n" | |
567 | "--tpm-spec-family s The implemented spec family, e.g., '2.0'\n" | |
568 | "--tpm-spec-revision i The spec revision of the TPM as integer; e.g., 146\n" | |
569 | "--tpm-spec-level i The spec level of the TPM; must be an integer; e.g. 0\n" | |
570 | "--tpm-manufacturer s The manufacturer of the TPM; e.g., id:00001014\n" | |
571 | "--tpm-model s The model of the TPM; e.g., 'swtpm'\n" | |
572 | "--tpm-version i The (firmware) version of the TPM; e.g., id:20160511\n" | |
573 | "--tpm2 Generate a certificate for a TPM 2\n" | |
574 | "--allow-signing The TPM 2's EK can be used for signing\n" | |
575 | "--decryption The TPM 2's EK can be used for decryption\n" | |
576 | "--help, -h, -? Display this help screen and exit\n" | |
577 | "\n" | |
578 | "\n" | |
579 | "The following environment variables are supported:\n" | |
580 | "\n" | |
581 | "SWTPM_ROOTCA_PASSWORD The root CA's private key password\n" | |
582 | "\n", prgname); | |
583 | } | |
584 | ||
585 | int main(int argc, char *argv[]) | |
586 | { | |
587 | int opt, option_index = 0; | |
84c10ba7 | 588 | static const struct option long_options[] = { |
e689684c SB |
589 | {"type", required_argument, NULL, 't'}, |
590 | {"ek", required_argument, NULL, 'e'}, | |
591 | {"dir", required_argument, NULL, 'd'}, | |
592 | {"vmid", required_argument, NULL, 'v'}, | |
593 | {"optsfile", required_argument, NULL, 'o'}, | |
594 | {"configfile", required_argument, NULL, 'c'}, | |
595 | {"logfile", required_argument, NULL, 'l'}, | |
596 | {"tpm-spec-family", required_argument, NULL, 'f'}, | |
597 | {"tpm-spec-revision", required_argument, NULL, 'r'}, | |
598 | {"tpm-spec-level", required_argument, NULL, '1'}, | |
599 | {"tpm-manufacturer", required_argument, NULL, 'a'}, | |
600 | {"tpm-model", required_argument, NULL, 'm'}, | |
601 | {"tpm-version", required_argument, NULL, 's'}, | |
602 | {"tpm2", no_argument, NULL, '2'}, | |
603 | {"allow-signing", no_argument, NULL, 'i'}, | |
604 | {"decryption", no_argument, NULL, 'y'}, | |
605 | {"help", no_argument, NULL, 'h'}, | |
606 | }; | |
607 | g_autofree gchar *default_options_file = NULL; | |
608 | g_autofree gchar *default_config_file = NULL; | |
609 | g_autofree gchar *optsfile = NULL; | |
610 | g_autofree gchar *configfile = NULL; | |
611 | unsigned long flags = 0; | |
612 | g_autofree gchar *typ =g_strdup(""); | |
613 | g_autofree gchar *ekparams = g_strdup(""); | |
614 | g_autofree gchar *directory = g_strdup("."); /* default to current directory */ | |
615 | g_autofree gchar *vmid = NULL; | |
616 | g_autofree gchar *lockfile = NULL; | |
617 | g_autofree gchar *statedir = NULL; | |
618 | g_autofree gchar *signkey = NULL; | |
619 | g_autofree gchar *signkey_password = NULL; | |
620 | g_autofree gchar *parentkey_password = NULL; | |
621 | g_autofree gchar *issuercert = NULL; | |
622 | g_autofree gchar *certserial = NULL; | |
623 | gchar **tpm_spec_params = NULL; | |
624 | gchar **tpm_attr_params = NULL; | |
625 | gchar **config_file_lines = NULL; | |
626 | gchar **swtpm_cert_env = NULL; | |
627 | const struct passwd *curr_user; | |
628 | struct stat statbuf; | |
629 | int ret = 1; | |
630 | ||
631 | if (init(&default_options_file, &default_config_file) < 0) | |
632 | goto error; | |
633 | optsfile = g_strdup(default_options_file); | |
634 | configfile = g_strdup(default_config_file); | |
635 | ||
636 | while ((opt = getopt_long(argc, argv, "h?", | |
637 | long_options, &option_index)) != -1) { | |
638 | switch (opt) { | |
639 | case 't': /* --type */ | |
640 | g_free(typ); | |
641 | typ = g_strdup(optarg); | |
642 | break; | |
643 | case 'e': /* --ek */ | |
644 | g_free(ekparams); | |
645 | ekparams = g_strdup(optarg); | |
646 | break; | |
647 | case 'd': /* --dir */ | |
648 | g_free(directory); | |
649 | directory = g_strdup(optarg); | |
650 | break; | |
651 | case 'v': /* --vmid */ | |
652 | g_free(vmid); | |
653 | vmid = g_strdup(optarg); | |
654 | break; | |
655 | case 'o': /* --optsfile */ | |
656 | g_free(optsfile); | |
657 | optsfile = g_strdup(optarg); | |
658 | break; | |
659 | case 'c': /* --configfile */ | |
660 | g_free(configfile); | |
661 | configfile = g_strdup(optarg); | |
662 | break; | |
663 | case 'l': /* --logfile */ | |
664 | g_free(gl_LOGFILE); | |
665 | gl_LOGFILE = g_strdup(optarg); | |
666 | break; | |
667 | case 'f': /* --tpm-spec-family */ | |
668 | case 'r': /* --tpm-spec-revision */ | |
669 | case '1': /* --tpm-spec-level */ | |
670 | tpm_spec_params = concat_arrays(tpm_spec_params, | |
671 | (gchar *[]) { | |
672 | g_strdup_printf("--%s", long_options[option_index].name), g_strdup(optarg), NULL | |
673 | }, TRUE); | |
674 | break; | |
675 | case 'a': /* --tpm-manufacturer */ | |
676 | case 'm': /* --tpm-model */ | |
677 | case 's': /* --tpm-version */ | |
678 | tpm_attr_params = concat_arrays(tpm_attr_params, | |
679 | (gchar *[]) { | |
680 | g_strdup_printf("--%s", long_options[option_index].name), g_strdup(optarg), NULL | |
681 | }, TRUE); | |
682 | break; | |
683 | case '2': /* --tpm2 */ | |
684 | flags |= SETUP_TPM2_F; | |
685 | break; | |
686 | case 'i': /* --allow-signing */ | |
687 | flags |= ALLOW_SIGNING_F; | |
688 | break; | |
689 | case 'y': /* --decryption */ | |
690 | flags |= DECRYPTION_F; | |
691 | break; | |
692 | case '?': | |
693 | case 'h': /* --help */ | |
694 | usage(argv[0]); | |
695 | ret = 0; | |
696 | goto out; | |
697 | default: | |
698 | fprintf(stderr, "Unknown option code %d\n", opt); | |
699 | usage(argv[0]); | |
700 | goto error; | |
701 | } | |
702 | } | |
703 | ||
704 | curr_user = getpwuid(getuid()); | |
705 | ||
706 | if (gl_LOGFILE != NULL) { | |
707 | FILE *tmpfile; | |
708 | ||
709 | if (stat(gl_LOGFILE, &statbuf) == 0 && | |
710 | (statbuf.st_mode & S_IFMT) == S_IFLNK) { | |
711 | fprintf(stderr, "Logfile must not be a symlink.\n"); | |
712 | goto error; | |
713 | } | |
714 | tmpfile = fopen(gl_LOGFILE, "a"); // do not truncate | |
715 | if (tmpfile == NULL) { | |
716 | fprintf(stderr, "Cannot write to logfile %s.\n", gl_LOGFILE); | |
717 | goto error; | |
718 | } | |
719 | fclose(tmpfile); | |
720 | } | |
721 | ||
722 | if (access(optsfile, R_OK) != 0) { | |
723 | logerr(gl_LOGFILE, "Need read rights on options file %s for user %s.\n", | |
724 | optsfile, curr_user ? curr_user->pw_name : "<unknown>"); | |
725 | goto error; | |
726 | } | |
727 | ||
728 | if (access(configfile, R_OK) != 0) { | |
729 | logerr(gl_LOGFILE, "Need read rights on config file %s for user %s.\n", | |
730 | configfile, curr_user ? curr_user->pw_name : "<unknown>"); | |
731 | goto error; | |
732 | } | |
733 | ||
734 | if (read_file_lines(configfile, &config_file_lines) != 0) | |
735 | goto error; | |
736 | ||
737 | statedir = get_config_value(config_file_lines, "statedir", NULL); | |
738 | if (statedir == NULL) { | |
739 | logerr(gl_LOGFILE, "Missing 'statedir' config value in config file %s.\n", configfile); | |
740 | goto error; | |
741 | } | |
742 | if (makedir(statedir, "statedir") != 0) | |
743 | goto error; | |
744 | if (access(statedir, W_OK | R_OK) != 0) { | |
745 | logerr(gl_LOGFILE, "Need read/write rights on statedir %s for user %s.\n", | |
746 | statedir, curr_user ? curr_user->pw_name : "<unknown>"); | |
747 | goto error; | |
748 | } | |
749 | ||
750 | lockfile = g_strjoin(G_DIR_SEPARATOR_S, statedir, ".lock.swtpm-localca", NULL); | |
751 | if (stat(lockfile, &statbuf) == 0 && | |
752 | access(lockfile, W_OK | R_OK) != 0) { | |
753 | logerr(gl_LOGFILE, "Need read/write rights on %s for user %s.\n", | |
754 | lockfile, curr_user ? curr_user->pw_name : "<unknown>"); | |
755 | goto error; | |
756 | } | |
757 | ||
758 | signkey = get_config_value(config_file_lines, "signingkey", NULL); | |
759 | if (signkey == NULL) { | |
760 | logerr(gl_LOGFILE, "Missing 'signingkey' config value in config file %s.\n", | |
761 | configfile); | |
762 | goto error; | |
763 | } | |
764 | ||
765 | if (!g_str_has_prefix(signkey, "tpmkey:file=") && | |
766 | !g_str_has_prefix(signkey, "tpmkey:uuid=") && | |
767 | !g_str_has_prefix(signkey, "pkcs11:")) { | |
768 | g_autofree gchar *d = g_path_get_dirname(signkey); | |
769 | if (makedir(d, "signkey") != 0) | |
770 | goto error; | |
771 | } | |
772 | ||
773 | signkey_password = get_config_value(config_file_lines, "signingkey_password", NULL); | |
774 | parentkey_password = get_config_value(config_file_lines, "parentkey_password", NULL); | |
775 | ||
776 | issuercert = get_config_value(config_file_lines, "issuercert", NULL); | |
777 | if (issuercert == NULL) { | |
778 | logerr(gl_LOGFILE, "Missing 'issuercert' config value in config file %s.\n", configfile); | |
779 | goto error; | |
780 | } | |
781 | { | |
782 | g_autofree gchar *d = g_path_get_dirname(issuercert); | |
783 | if (makedir(d, "issuercert") != 0) | |
784 | goto error; | |
785 | } | |
786 | ||
787 | swtpm_cert_env = g_get_environ(); | |
788 | ||
789 | // TPM keys are GNUTLS URIs... | |
790 | if (g_str_has_prefix(signkey, "tpmkey:file=") || g_str_has_prefix(signkey, "tpmkey:uuid=")) { | |
2bf7bd18 SB |
791 | g_autofree gchar *tss_tcsd_hostname = NULL; |
792 | g_autofree gchar *tss_tcsd_port = NULL; | |
793 | ||
794 | tss_tcsd_hostname = get_config_value(config_file_lines, | |
795 | "TSS_TCSD_HOSTNAME", "localhost"); | |
796 | tss_tcsd_port = get_config_value(config_file_lines, | |
797 | "TSS_TCSD_PORT", "30003"); | |
e689684c SB |
798 | swtpm_cert_env = g_environ_setenv(swtpm_cert_env, |
799 | "TSS_TCSD_HOSTNAME", tss_tcsd_hostname, TRUE); | |
800 | swtpm_cert_env = g_environ_setenv(swtpm_cert_env, | |
801 | "TSS_TCSD_PORT", tss_tcsd_port, TRUE); | |
802 | ||
803 | logit(gl_LOGFILE, "CA uses a GnuTLS TPM key; using TSS_TCSD_HOSTNAME=%s " \ | |
804 | "TSS_TCSD_PORT=%s\n", tss_tcsd_hostname, tss_tcsd_port); | |
805 | } else if (g_str_has_prefix(signkey, "pkcs11:")) { | |
806 | gchar *tmp = str_replace(signkey, "\\;", ";"); /* historical reasons ... */ | |
807 | g_free(signkey); | |
808 | signkey = tmp; | |
809 | ||
810 | if (signkey_password != NULL) { | |
811 | swtpm_cert_env = g_environ_setenv(swtpm_cert_env, | |
812 | "SWTPM_PKCS11_PIN", g_strdup(signkey_password), TRUE); | |
2c270f04 SB |
813 | logit(gl_LOGFILE, "CA uses a PKCS#11 key; using SWTPM_PKCS11_PIN\n"); |
814 | } else { | |
815 | g_autofree gchar *swtpm_pkcs11_pin = NULL; | |
816 | ||
817 | swtpm_pkcs11_pin = get_config_value(config_file_lines, | |
818 | "SWTPM_PKCS11_PIN", "swtpm-tpmca"); | |
819 | swtpm_cert_env = g_environ_setenv(swtpm_cert_env, | |
820 | "SWTPM_PKCS11_PIN", swtpm_pkcs11_pin, TRUE); | |
821 | logit(gl_LOGFILE, "CA uses a PKCS#11 key; using SWTPM_PKCS11_PIN\n"); | |
e689684c SB |
822 | } |
823 | ret = get_config_envvars(config_file_lines, &swtpm_cert_env); | |
824 | if (ret != 0) | |
825 | goto error; | |
826 | } else { | |
827 | if (access(signkey, R_OK) != 0) { | |
828 | if (stat(signkey, &statbuf) == 0) { | |
829 | logerr(gl_LOGFILE, "Need read rights on signing key %s for user %s.\n", | |
830 | signkey, curr_user ? curr_user->pw_name : "<unknown>"); | |
831 | goto error; | |
832 | } | |
833 | ||
834 | logit(gl_LOGFILE, "Creating root CA and a local CA's signing key and issuer cert.\n"); | |
835 | if (create_localca_cert(lockfile, statedir, signkey, signkey_password, | |
836 | issuercert) != 0) { | |
837 | logerr(gl_LOGFILE, "Error creating local CA's signing key and cert.\n"); | |
838 | goto error; | |
839 | } | |
840 | ||
841 | if (access(signkey, R_OK) != 0) { | |
842 | logerr(gl_LOGFILE, "Need read rights on signing key %s for user %s.\n", | |
843 | signkey, curr_user ? curr_user->pw_name : "<unknown>"); | |
844 | goto error; | |
845 | } | |
846 | } | |
847 | } | |
848 | ||
849 | if (access(issuercert, R_OK) != 0) { | |
850 | logerr(gl_LOGFILE, "Need read rights on issuer certificate %s for user %s.\n", | |
851 | issuercert, curr_user ? curr_user->pw_name : "<unknown>"); | |
852 | goto error; | |
853 | } | |
854 | ||
855 | { | |
856 | g_autofree gchar *d = NULL; | |
857 | g_autofree gchar *p = g_strjoin(G_DIR_SEPARATOR_S, statedir, "certserial", NULL); | |
858 | ||
859 | certserial = get_config_value(config_file_lines, "certserial", p); | |
860 | d = g_path_get_dirname(certserial); | |
861 | if (makedir(d, "certserial") != 0) | |
862 | goto error; | |
863 | } | |
864 | ||
865 | ret = create_cert(flags, typ, directory, ekparams, vmid, tpm_spec_params, tpm_attr_params, | |
866 | signkey, signkey_password, issuercert, parentkey_password, swtpm_cert_env, | |
867 | certserial, lockfile, optsfile); | |
868 | ||
869 | out: | |
870 | error: | |
871 | g_strfreev(tpm_attr_params); | |
872 | g_strfreev(tpm_spec_params); | |
873 | ||
1c3417f7 | 874 | return ret; |
e689684c | 875 | } |