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