Blob


1 /*
2 * Copyright (c) 2021, 2023, 2024 Omar Polo <op@omarpolo.com>
3 * Copyright (c) 2019 Renaud Allard <renaud@allard.it>
4 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
6 * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
21 /*
22 * The routines to generate a certificate were derived from acme-client.
23 */
25 #include "compat.h"
27 #include <sys/types.h>
28 #include <sys/stat.h>
30 #include <ctype.h>
31 #include <dirent.h>
32 #include <fcntl.h>
33 #include <limits.h>
34 #include <string.h>
35 #include <unistd.h>
37 #include <openssl/ec.h>
38 #include <openssl/err.h>
39 #include <openssl/evp.h>
40 #include <openssl/obj_mac.h>
41 #include <openssl/pem.h>
42 #include <openssl/rsa.h>
43 #include <openssl/x509_vfy.h>
44 #include <openssl/x509v3.h>
46 #include "certs.h"
47 #include "fs.h"
48 #include "iri.h"
50 struct cstore cert_store;
52 char **identities;
53 static size_t id_len, id_cap;
55 /*
56 * Default number of bits when creating a new RSA key.
57 */
58 #define KBITS 4096
60 static int
61 identities_cmp(const void *a, const void *b)
62 {
63 return (strcmp(a, b));
64 }
66 static inline int
67 push_identity(const char *n)
68 {
69 char *name;
70 void *t;
71 size_t newcap, i;
73 for (i = 0; i < id_len; ++i) {
74 if (!strcmp(identities[i], n))
75 return (0);
76 }
78 /* id_cap is initilized to 8 in certs_init() */
79 if (id_len >= id_cap - 1) {
80 newcap = id_cap + 8;
81 t = recallocarray(identities, id_cap, newcap,
82 sizeof(*identities));
83 if (t == NULL)
84 return (-1);
85 identities = t;
86 id_cap = newcap;
87 }
89 if ((name = strdup(n)) == NULL)
90 return (-1);
92 identities[id_len++] = name;
93 return (0);
94 }
96 static int
97 certs_cmp(const void *a, const void *b)
98 {
99 const struct ccert *ca = a, *cb = b;
100 int r;
102 if ((r = strcmp(ca->host, cb->host)) != 0)
103 return (r);
104 if ((r = strcmp(ca->port, cb->port)) != 0)
105 return (r);
106 if ((r = strcmp(ca->path, cb->path)) != 0)
107 return (r);
108 if ((r = strcmp(ca->cert, cb->cert)) != 0)
109 return (r);
111 if (ca->flags > cb->flags)
112 return (+1);
113 if (ca->flags < cb->flags)
114 return (-1);
115 return (0);
118 static int
119 certs_store_add(struct cstore *cstore, const char *host, const char *port,
120 const char *path, const char *cert, int flags)
122 struct ccert *c;
123 void *t;
124 size_t newcap;
126 if (cstore->len == cstore->cap) {
127 newcap = cstore->cap + 8;
128 t = recallocarray(cstore->certs, cstore->cap, newcap,
129 sizeof(*cstore->certs));
130 if (t == NULL)
131 return (-1);
132 cstore->certs = t;
133 cstore->cap = newcap;
136 c = &cstore->certs[cstore->len];
137 c->flags = flags;
138 if ((c->host = strdup(host)) == NULL ||
139 (c->port = strdup(port)) == NULL ||
140 (c->path = strdup(path)) == NULL ||
141 (c->cert = strdup(cert)) == NULL) {
142 free(c->host);
143 free(c->port);
144 free(c->path);
145 free(c->cert);
146 memset(c, 0, sizeof(*c));
148 cstore->len++;
150 return (0);
153 static int
154 certs_store_parse_line(struct cstore *cstore, char *line)
156 char *host, *port, *path, *cert;
158 while (isspace((unsigned char)*line))
159 ++line;
160 if (*line == '#' || *line == '\0')
161 return (0);
163 host = line;
165 port = host + strcspn(host, " \t");
166 if (*port == '\0')
167 return (-1);
168 *port++ = '\0';
169 while (isspace((unsigned char)*port))
170 ++port;
172 path = port + strcspn(port, " \t");
173 if (*path == '\0')
174 return (-1);
175 *path++ = '\0';
176 while (isspace((unsigned char)*path))
177 ++path;
179 cert = path + strcspn(path, " \t");
180 if (*cert == '\0')
181 return (-1);
182 *cert++ = '\0';
183 while (isspace((unsigned char)*cert))
184 ++cert;
186 if (*cert == '\0')
187 return (-1);
189 return (certs_store_add(cstore, host, port, path, cert, CERT_OK));
192 int
193 certs_init(const char *certfile)
195 struct dirent *dp;
196 DIR *certdir;
197 FILE *fp;
198 char *line = NULL;
199 size_t linesize = 0;
200 ssize_t linelen;
202 id_cap = 8;
203 if ((identities = calloc(id_cap, sizeof(*identities))) == NULL)
204 return (-1);
206 if ((certdir = opendir(cert_dir)) == NULL)
207 return (-1);
209 while ((dp = readdir(certdir)) != NULL) {
210 if (dp->d_type != DT_REG)
211 continue;
212 if (push_identity(dp->d_name) == -1) {
213 closedir(certdir);
214 return (-1);
217 closedir(certdir);
218 qsort(identities, id_len, sizeof(*identities), identities_cmp);
220 if ((fp = fopen(certfile, "r")) == NULL) {
221 if (errno == ENOENT)
222 return (0);
223 return (-1);
226 while ((linelen = getline(&line, &linesize, fp)) != -1) {
227 if (line[linelen - 1] == '\n')
228 line[--linelen] = '\0';
230 if (certs_store_parse_line(&cert_store, line) == -1) {
231 fclose(fp);
232 free(line);
233 return (-1);
237 if (ferror(fp)) {
238 fclose(fp);
239 free(line);
240 return (-1);
243 qsort(cert_store.certs, cert_store.len, sizeof(*cert_store.certs),
244 certs_cmp);
246 fclose(fp);
247 free(line);
248 return (0);
251 const char *
252 ccert(const char *name)
254 size_t i;
256 for (i = 0; i < id_len; ++i) {
257 if (!strcmp(name, identities[i]))
258 return (identities[i]);
261 return (NULL);
264 /*
265 * Test whether the test path is under the certificate path.
266 */
267 static inline int
268 path_under(const char *cpath, const char *tpath)
270 if (*cpath == '\0')
271 return (1);
273 while (*cpath != '\0') {
274 if (*tpath == '\0')
275 return (0);
277 if (*cpath++ != *tpath++)
278 return (0);
281 if (*tpath == '\0' || *tpath == '/')
282 return (1);
284 return (cpath[-1] == '/');
287 static struct ccert *
288 find_cert_for(struct cstore *cstore, struct iri *iri, size_t *n)
290 struct ccert *c;
291 size_t i;
293 for (i = 0; i < cstore->len; ++i) {
294 c = &cstore->certs[i];
296 if (!strcmp(c->host, iri->iri_host) &&
297 !strcmp(c->port, iri->iri_portstr) &&
298 path_under(c->path, iri->iri_path)) {
299 if (n)
300 *n = i;
301 return (c);
305 return (NULL);
308 const char *
309 cert_for(struct iri *iri, int *temporary)
311 struct ccert *c;
313 *temporary = 0;
315 if ((c = find_cert_for(&cert_store, iri, NULL)) == NULL)
316 return (NULL);
317 if (c->flags & CERT_TEMP_DEL)
318 return (NULL);
320 *temporary = !!(c->flags & CERT_TEMP);
321 return (c->cert);
324 static int
325 write_cert_file(void)
327 struct ccert *c;
328 FILE *fp;
329 char sfn[PATH_MAX];
330 size_t i;
331 int fd, r;
333 strlcpy(sfn, certs_file_tmp, sizeof(sfn));
334 if ((fd = mkstemp(sfn)) == -1 ||
335 (fp = fdopen(fd, "w")) == NULL) {
336 if (fd != -1) {
337 unlink(sfn);
338 close(fd);
340 return (-1);
343 for (i = 0; i < cert_store.len; ++i) {
344 c = &cert_store.certs[i];
345 if (c->flags & CERT_TEMP)
346 continue;
348 r = fprintf(fp, "%s\t%s\t%s\t%s\n", c->host, c->port,
349 c->path, c->cert);
350 if (r < 0) {
351 fclose(fp);
352 unlink(sfn);
353 return (-1);
357 if (ferror(fp)) {
358 fclose(fp);
359 unlink(sfn);
360 return (-1);
363 if (fclose(fp) == EOF) {
364 unlink(sfn);
365 return (-1);
368 if (rename(sfn, certs_file) == -1) {
369 unlink(sfn);
370 return (-1);
373 return (0);
376 static void
377 certs_delete(struct cstore *cstore, size_t n)
379 struct ccert *c;
381 c = &cstore->certs[n];
382 free(c->host);
383 free(c->port);
384 free(c->path);
385 free(c->cert);
387 cstore->len--;
389 if (n == cstore->len) {
390 memset(&cstore->certs[n], 0, sizeof(*cstore->certs));
391 return;
394 memmove(&cstore->certs[n], &cstore->certs[n + 1],
395 sizeof(*cstore->certs) * (cstore->len - n));
396 memset(&cstore->certs[cstore->len], 0, sizeof(*cstore->certs));
399 int
400 cert_save_for(const char *cert, struct iri *i, int persist)
402 struct ccert *c;
403 char *d;
404 int flags;
406 flags = persist ? 0 : CERT_TEMP;
408 if ((c = find_cert_for(&cert_store, i, NULL)) != NULL) {
409 if ((d = strdup(cert)) == NULL)
410 return (-1);
412 free(c->cert);
413 c->cert = d;
414 c->flags = flags;
415 } else {
416 if (certs_store_add(&cert_store, i->iri_host,
417 i->iri_portstr, i->iri_path, cert, flags) == -1)
418 return (-1);
420 qsort(cert_store.certs, cert_store.len,
421 sizeof(*cert_store.certs), certs_cmp);
424 if (persist && write_cert_file() == -1)
425 return (-1);
427 return (0);
430 int
431 cert_delete_for(const char *cert, struct iri *iri, int persist)
433 struct ccert *c;
434 size_t i;
436 if ((c = find_cert_for(&cert_store, iri, &i)) == NULL)
437 return (-1);
439 if (!persist) {
440 c->flags |= CERT_TEMP_DEL;
441 return (0);
444 certs_delete(&cert_store, i);
445 return (write_cert_file());
448 int
449 cert_open(const char *cert)
451 char path[PATH_MAX];
452 struct stat sb;
453 int fd;
455 strlcpy(path, cert_dir, sizeof(path));
456 strlcat(path, "/", sizeof(path));
457 strlcat(path, cert, sizeof(path));
459 if ((fd = open(path, O_RDONLY)) == -1)
460 return (-1);
462 if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
463 close(fd);
464 return (-1);
467 return (fd);
470 static EVP_PKEY *
471 rsa_key_create(FILE *f)
473 EVP_PKEY_CTX *ctx = NULL;
474 EVP_PKEY *pkey = NULL;
475 int ret = -1;
477 /* First, create the context and the key. */
479 if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL)) == NULL)
480 goto done;
482 if (EVP_PKEY_keygen_init(ctx) <= 0)
483 goto done;
485 if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KBITS) <= 0)
486 goto done;
488 if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
489 goto done;
491 /* Serialize the key to the disc. */
492 if (!PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL))
493 goto done;
495 ret = 0;
496 done:
497 if (ret == -1) {
498 EVP_PKEY_free(pkey);
499 pkey = NULL;
501 EVP_PKEY_CTX_free(ctx);
502 return pkey;
505 static EVP_PKEY *
506 ec_key_create(FILE *f)
508 EC_KEY *eckey = NULL;
509 EVP_PKEY *pkey = NULL;
510 int ret = -1;
512 if ((eckey = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL)
513 goto done;
515 if (!EC_KEY_generate_key(eckey))
516 goto done;
518 /* Serialise the key to the disc in EC format */
519 if (!PEM_write_ECPrivateKey(f, eckey, NULL, NULL, 0, NULL, NULL))
520 goto done;
522 /* Convert the EC key into a PKEY structure */
523 if ((pkey = EVP_PKEY_new()) == NULL)
524 goto done;
526 if (!EVP_PKEY_set1_EC_KEY(pkey, eckey))
527 goto done;
529 ret = 0;
530 done:
531 if (ret == -1) {
532 EVP_PKEY_free(pkey);
533 pkey = NULL;
535 EC_KEY_free(eckey);
536 return pkey;
539 int
540 cert_new(const char *common_name, const char *path, int eckey)
542 EVP_PKEY *pkey = NULL;
543 X509 *x509 = NULL;
544 X509_NAME *name = NULL;
545 FILE *fp = NULL;
546 int ret = -1;
547 const unsigned char *cn = (const unsigned char*)common_name;
549 if ((fp = fopen(path, "wx")) == NULL)
550 goto done;
552 if (eckey)
553 pkey = ec_key_create(fp);
554 else
555 pkey = rsa_key_create(fp);
556 if (pkey == NULL)
557 goto done;
559 if ((x509 = X509_new()) == NULL)
560 goto done;
562 ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
563 X509_gmtime_adj(X509_get_notBefore(x509), 0);
564 X509_gmtime_adj(X509_get_notAfter(x509), 315360000L); /* 10 years */
565 X509_set_version(x509, 2); // v3
567 if (!X509_set_pubkey(x509, pkey))
568 goto done;
570 if ((name = X509_NAME_new()) == NULL)
571 goto done;
573 if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, cn,
574 -1, -1, 0))
575 goto done;
577 X509_set_subject_name(x509, name);
578 X509_set_issuer_name(x509, name);
580 if (!X509_sign(x509, pkey, EVP_sha256()))
581 goto done;
583 if (!PEM_write_X509(fp, x509))
584 goto done;
586 if (fflush(fp) == EOF)
587 goto done;
589 ret = 0;
590 done:
591 if (pkey)
592 EVP_PKEY_free(pkey);
593 if (x509)
594 X509_free(x509);
595 if (name)
596 X509_NAME_free(name);
597 if (fp)
598 fclose(fp);
599 if (ret == -1)
600 (void) unlink(path);
601 return (ret);