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 /* client certificate */
58 #define CERT_TEMP 0x01
59 #define CERT_TEMP_DEL 0x02
63 static struct cstore {
70 static size_t id_len, id_cap;
73 * Default number of bits when creating a new RSA key.
78 identities_cmp(const void *a, const void *b)
80 return (strcmp(a, b));
84 push_identity(const char *n)
90 for (i = 0; i < id_len; ++i) {
91 if (!strcmp(identities[i], n))
95 /* id_cap is initilized to 8 in certs_init() */
96 if (id_len >= id_cap - 1) {
98 t = recallocarray(identities, id_cap, newcap,
106 if ((name = strdup(n)) == NULL)
109 identities[id_len++] = name;
114 certs_cmp(const void *a, const void *b)
116 const struct ccert *ca = a, *cb = b;
119 if ((r = strcmp(ca->host, cb->host)) != 0)
121 if ((r = strcmp(ca->port, cb->port)) != 0)
123 if ((r = strcmp(ca->path, cb->path)) != 0)
125 if ((r = strcmp(ca->cert, cb->cert)) != 0)
128 if (ca->flags > cb->flags)
130 if (ca->flags < cb->flags)
136 certs_store_add(struct cstore *cstore, const char *host, const char *port,
137 const char *path, const char *cert, int flags)
143 if (cstore->len == cstore->cap) {
144 newcap = cstore->cap + 8;
145 t = recallocarray(cstore->certs, cstore->cap, newcap,
146 sizeof(*cstore->certs));
150 cstore->cap = newcap;
153 c = &cstore->certs[cstore->len];
155 if ((c->host = strdup(host)) == NULL ||
156 (c->port = strdup(port)) == NULL ||
157 (c->path = strdup(path)) == NULL ||
158 (c->cert = strdup(cert)) == NULL) {
163 memset(c, 0, sizeof(*c));
171 certs_store_parse_line(struct cstore *cstore, char *line)
173 char *host, *port, *path, *cert;
175 while (isspace((unsigned char)*line))
177 if (*line == '#' || *line == '\0')
182 port = host + strcspn(host, " \t");
186 while (isspace((unsigned char)*port))
189 path = port + strcspn(port, " \t");
193 while (isspace((unsigned char)*path))
196 cert = path + strcspn(path, " \t");
200 while (isspace((unsigned char)*cert))
206 return (certs_store_add(cstore, host, port, path, cert, CERT_OK));
210 certs_init(const char *certfile)
220 if ((identities = calloc(id_cap, sizeof(*identities))) == NULL)
223 if ((certdir = opendir(cert_dir)) == NULL)
226 while ((dp = readdir(certdir)) != NULL) {
227 if (dp->d_type != DT_REG)
229 if (push_identity(dp->d_name) == -1) {
235 qsort(identities, id_len, sizeof(*identities), identities_cmp);
237 if ((fp = fopen(certfile, "r")) == NULL) {
243 while ((linelen = getline(&line, &linesize, fp)) != -1) {
244 if (line[linelen - 1] == '\n')
245 line[--linelen] = '\0';
247 if (certs_store_parse_line(&cert_store, line) == -1) {
260 qsort(cert_store.certs, cert_store.len, sizeof(*cert_store.certs),
269 ccert(const char *name)
273 for (i = 0; i < id_len; ++i) {
274 if (!strcmp(name, identities[i]))
275 return (identities[i]);
282 * Test whether the test path is under the certificate path.
285 path_under(const char *cpath, const char *tpath)
290 while (*cpath != '\0') {
294 if (*cpath++ != *tpath++)
298 if (*tpath == '\0' || *tpath == '/')
301 return (cpath[-1] == '/');
304 static struct ccert *
305 find_cert_for(struct cstore *cstore, struct iri *iri, size_t *n)
310 for (i = 0; i < cstore->len; ++i) {
311 c = &cstore->certs[i];
313 if (!strcmp(c->host, iri->iri_host) &&
314 !strcmp(c->port, iri->iri_portstr) &&
315 path_under(c->path, iri->iri_path)) {
326 cert_for(struct iri *iri, int *temporary)
332 if ((c = find_cert_for(&cert_store, iri, NULL)) == NULL)
334 if (c->flags & CERT_TEMP_DEL)
337 *temporary = !!(c->flags & CERT_TEMP);
342 write_cert_file(void)
350 strlcpy(sfn, certs_file_tmp, sizeof(sfn));
351 if ((fd = mkstemp(sfn)) == -1 ||
352 (fp = fdopen(fd, "w")) == NULL) {
360 for (i = 0; i < cert_store.len; ++i) {
361 c = &cert_store.certs[i];
362 if (c->flags & CERT_TEMP)
365 r = fprintf(fp, "%s\t%s\t%s\t%s\n", c->host, c->port,
380 if (fclose(fp) == EOF) {
385 if (rename(sfn, certs_file) == -1) {
394 certs_delete(struct cstore *cstore, size_t n)
398 c = &cstore->certs[n];
406 if (n == cstore->len) {
407 memset(&cstore->certs[n], 0, sizeof(*cstore->certs));
411 memmove(&cstore->certs[n], &cstore->certs[n + 1],
412 sizeof(*cstore->certs) * (cstore->len - n));
413 memset(&cstore->certs[cstore->len], 0, sizeof(*cstore->certs));
417 cert_save_for(const char *cert, struct iri *i, int persist)
423 flags = persist ? 0 : CERT_TEMP;
425 if ((c = find_cert_for(&cert_store, i, NULL)) != NULL) {
426 if ((d = strdup(cert)) == NULL)
433 if (certs_store_add(&cert_store, i->iri_host,
434 i->iri_portstr, i->iri_path, cert, flags) == -1)
437 qsort(cert_store.certs, cert_store.len,
438 sizeof(*cert_store.certs), certs_cmp);
441 if (persist && write_cert_file() == -1)
448 cert_delete_for(const char *cert, struct iri *iri, int persist)
453 if ((c = find_cert_for(&cert_store, iri, &i)) == NULL)
457 c->flags |= CERT_TEMP_DEL;
461 certs_delete(&cert_store, i);
462 return (write_cert_file());
466 cert_open(const char *cert)
472 strlcpy(path, cert_dir, sizeof(path));
473 strlcat(path, "/", sizeof(path));
474 strlcat(path, cert, sizeof(path));
476 if ((fd = open(path, O_RDONLY)) == -1)
479 if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
488 rsa_key_create(FILE *f, const char *fname)
490 EVP_PKEY_CTX *ctx = NULL;
491 EVP_PKEY *pkey = NULL;
494 /* First, create the context and the key. */
496 if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL)) == NULL)
499 if (EVP_PKEY_keygen_init(ctx) <= 0)
502 if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KBITS) <= 0)
505 if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
508 /* Serialize the key to the disc. */
509 if (!PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL))
518 EVP_PKEY_CTX_free(ctx);
523 ec_key_create(FILE *f, const char *fname)
525 EC_KEY *eckey = NULL;
526 EVP_PKEY *pkey = NULL;
529 if ((eckey = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL)
532 if (!EC_KEY_generate_key(eckey))
535 /* Serialise the key to the disc in EC format */
536 if (!PEM_write_ECPrivateKey(f, eckey, NULL, NULL, 0, NULL, NULL))
539 /* Convert the EC key into a PKEY structure */
540 if ((pkey = EVP_PKEY_new()) == NULL)
543 if (!EVP_PKEY_set1_EC_KEY(pkey, eckey))
557 cert_new(const char *common_name, const char *certpath, const char *keypath,
560 EVP_PKEY *pkey = NULL;
562 X509_NAME *name = NULL;
565 const unsigned char *cn = (const unsigned char*)common_name;
567 if ((fp = fopen(keypath, "w")) == NULL)
571 pkey = ec_key_create(fp, keypath);
573 pkey = rsa_key_create(fp, keypath);
577 if (fflush(fp) == EOF || fclose(fp) == EOF)
581 if ((x509 = X509_new()) == NULL)
584 ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
585 X509_gmtime_adj(X509_get_notBefore(x509), 0);
586 X509_gmtime_adj(X509_get_notAfter(x509), 315360000L); /* 10 years */
587 X509_set_version(x509, 2); // v3
589 if (!X509_set_pubkey(x509, pkey))
592 if ((name = X509_NAME_new()) == NULL)
595 if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, cn,
599 X509_set_subject_name(x509, name);
600 X509_set_issuer_name(x509, name);
602 if (!X509_sign(x509, pkey, EVP_sha256()))
605 if ((fp = fopen(certpath, "w")) == NULL)
608 if (!PEM_write_X509(fp, x509))
611 if (fflush(fp) == EOF)
621 X509_NAME_free(name);
626 (void) unlink(certpath);
627 (void) unlink(keypath);