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>
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.
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.
22 * The routines to generate a certificate were derived from acme-client.
27 #include <sys/types.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>
50 struct cstore cert_store;
53 static size_t id_len, id_cap;
56 * Default number of bits when creating a new RSA key.
61 identities_cmp(const void *a, const void *b)
63 return (strcmp(a, b));
67 push_identity(const char *n)
73 for (i = 0; i < id_len; ++i) {
74 if (!strcmp(identities[i], n))
78 /* id_cap is initialized to 8 in certs_init() */
79 if (id_len >= id_cap - 1) {
81 t = recallocarray(identities, id_cap, newcap,
89 if ((name = strdup(n)) == NULL)
92 identities[id_len++] = name;
97 certs_cmp(const void *a, const void *b)
99 const struct ccert *ca = a, *cb = b;
102 if ((r = strcmp(ca->host, cb->host)) != 0)
104 if ((r = strcmp(ca->port, cb->port)) != 0)
106 if ((r = strcmp(ca->path, cb->path)) != 0)
108 if ((r = strcmp(ca->cert, cb->cert)) != 0)
111 if (ca->flags > cb->flags)
113 if (ca->flags < cb->flags)
119 certs_store_add(struct cstore *cstore, const char *host, const char *port,
120 const char *path, const char *cert, int flags)
126 if (cstore->len == cstore->cap) {
127 newcap = cstore->cap + 8;
128 t = recallocarray(cstore->certs, cstore->cap, newcap,
129 sizeof(*cstore->certs));
133 cstore->cap = newcap;
136 c = &cstore->certs[cstore->len];
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) {
146 memset(c, 0, sizeof(*c));
154 certs_store_parse_line(struct cstore *cstore, char *line)
156 char *host, *port, *path, *cert;
158 while (isspace((unsigned char)*line))
160 if (*line == '#' || *line == '\0')
165 port = host + strcspn(host, " \t");
169 while (isspace((unsigned char)*port))
172 path = port + strcspn(port, " \t");
176 while (isspace((unsigned char)*path))
179 cert = path + strcspn(path, " \t");
183 while (isspace((unsigned char)*cert))
189 return (certs_store_add(cstore, host, port, path, cert, CERT_OK));
193 certs_init(const char *certfile)
203 if ((identities = calloc(id_cap, sizeof(*identities))) == NULL)
206 if ((certdir = opendir(cert_dir)) == NULL)
209 while ((dp = readdir(certdir)) != NULL) {
210 if (dp->d_type != DT_REG)
212 if (push_identity(dp->d_name) == -1) {
218 qsort(identities, id_len, sizeof(*identities), identities_cmp);
220 if ((fp = fopen(certfile, "r")) == NULL) {
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) {
243 qsort(cert_store.certs, cert_store.len, sizeof(*cert_store.certs),
252 ccert(const char *name)
256 for (i = 0; i < id_len; ++i) {
257 if (!strcmp(name, identities[i]))
258 return (identities[i]);
265 * Test whether the test path is under the certificate path.
268 path_under(const char *cpath, const char *tpath)
273 while (*cpath != '\0') {
277 if (*cpath++ != *tpath++)
281 if (*tpath == '\0' || *tpath == '/')
284 return (cpath[-1] == '/');
287 static struct ccert *
288 find_cert_for(struct cstore *cstore, struct iri *iri, size_t *n)
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)) {
309 cert_for(struct iri *iri, int *temporary)
315 if ((c = find_cert_for(&cert_store, iri, NULL)) == NULL)
317 if (c->flags & CERT_TEMP_DEL)
320 *temporary = !!(c->flags & CERT_TEMP);
325 write_cert_file(void)
333 strlcpy(sfn, certs_file_tmp, sizeof(sfn));
334 if ((fd = mkstemp(sfn)) == -1 ||
335 (fp = fdopen(fd, "w")) == NULL) {
343 for (i = 0; i < cert_store.len; ++i) {
344 c = &cert_store.certs[i];
345 if (c->flags & CERT_TEMP)
348 r = fprintf(fp, "%s\t%s\t%s\t%s\n", c->host, c->port,
363 if (fclose(fp) == EOF) {
368 if (rename(sfn, certs_file) == -1) {
377 certs_delete(struct cstore *cstore, size_t n)
381 c = &cstore->certs[n];
389 if (n == cstore->len) {
390 memset(&cstore->certs[n], 0, sizeof(*cstore->certs));
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));
400 cert_save_for(const char *cert, struct iri *i, int persist)
406 flags = persist ? 0 : CERT_TEMP;
408 if ((c = find_cert_for(&cert_store, i, NULL)) != NULL) {
409 if ((d = strdup(cert)) == NULL)
416 if (certs_store_add(&cert_store, i->iri_host,
417 i->iri_portstr, i->iri_path, cert, flags) == -1)
420 qsort(cert_store.certs, cert_store.len,
421 sizeof(*cert_store.certs), certs_cmp);
424 if (persist && write_cert_file() == -1)
431 cert_delete_for(const char *cert, struct iri *iri, int persist)
436 if ((c = find_cert_for(&cert_store, iri, &i)) == NULL)
440 c->flags |= CERT_TEMP_DEL;
444 certs_delete(&cert_store, i);
445 return (write_cert_file());
449 cert_open(const char *cert)
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)
462 if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
471 rsa_key_create(FILE *f)
473 EVP_PKEY_CTX *ctx = NULL;
474 EVP_PKEY *pkey = NULL;
477 /* First, create the context and the key. */
479 if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL)) == NULL)
482 if (EVP_PKEY_keygen_init(ctx) <= 0)
485 if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KBITS) <= 0)
488 if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
491 /* Serialize the key to the disc. */
492 if (!PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL))
501 EVP_PKEY_CTX_free(ctx);
506 ec_key_create(FILE *f)
508 EC_KEY *eckey = NULL;
509 EVP_PKEY *pkey = NULL;
512 if ((eckey = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL)
515 if (!EC_KEY_generate_key(eckey))
518 /* Serialise the key to the disc in EC format */
519 if (!PEM_write_ECPrivateKey(f, eckey, NULL, NULL, 0, NULL, NULL))
522 /* Convert the EC key into a PKEY structure */
523 if ((pkey = EVP_PKEY_new()) == NULL)
526 if (!EVP_PKEY_set1_EC_KEY(pkey, eckey))
540 cert_new(const char *common_name, const char *path, int eckey)
542 EVP_PKEY *pkey = NULL;
544 X509_NAME *name = NULL;
547 const unsigned char *cn = (const unsigned char*)common_name;
549 if ((fp = fopen(path, "wx")) == NULL)
553 pkey = ec_key_create(fp);
555 pkey = rsa_key_create(fp);
559 if ((x509 = X509_new()) == NULL)
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))
570 if ((name = X509_NAME_new()) == NULL)
573 if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, cn,
577 X509_set_subject_name(x509, name);
578 X509_set_issuer_name(x509, name);
580 if (!X509_sign(x509, pkey, EVP_sha256()))
583 if (!PEM_write_X509(fp, x509))
586 if (fflush(fp) == EOF)
596 X509_NAME_free(name);