commit f0e62b859a56026db832e80814a11b27f6a7332f from: Omar Polo date: Thu Feb 15 09:01:37 2024 UTC draft of telescope-identity(1) This is intended to generate and manage certificates outside of telescope. Some operations are already available in the telescope UI, such as use and forget a certificate for a specific page, but generate and removal are hard at the moment to implement inside telescope limited UI, so provide an helper program for that. commit - b8ec3578cd7c8dd902282fa1253fffc74fd615f0 commit + f0e62b859a56026db832e80814a11b27f6a7332f blob - bc278afeb2580ae9e5bb6a01b29dcbdd99e5c878 blob + 3cff0bf9d3a78c2436a08b6ddcf8a9f4c936b008 --- Makefile.am +++ Makefile.am @@ -1,6 +1,6 @@ SUBDIRS = test -bin_PROGRAMS = telescope +bin_PROGRAMS = telescope telescope-identity EXTRA_telescope_SOURCES = compat/ohash.h compat/queue.h compat/imsg.h contrib @@ -58,6 +58,19 @@ telescope_SOURCES = certs.c \ utils.h \ wrap.c +telescope_identity_SOURCES = \ + certs.c \ + certs.h \ + fs.c \ + fs.h \ + hist.c \ + hist.h \ + identity.c \ + parser.c \ + parser.h \ + utils.c \ + utils.h + noinst_PROGRAMS = pagebundler pagebundler_SOURCES = pagebundler.c blob - abc87f62484296eec0be6a445199cd634defbe66 blob + 42bbc8f817d418c422346de15f12da1aae55e947 --- certs.c +++ certs.c @@ -47,25 +47,8 @@ #include "fs.h" #include "iri.h" -/* client certificate */ -struct ccert { - char *host; - char *port; - char *path; - char *cert; - -#define CERT_OK 0x00 -#define CERT_TEMP 0x01 -#define CERT_TEMP_DEL 0x02 - int flags; -}; +struct cstore cert_store; -static struct cstore { - struct ccert *certs; - size_t len; - size_t cap; -} cert_store; - char **identities; static size_t id_len, id_cap; @@ -485,7 +468,7 @@ cert_open(const char *cert) } static EVP_PKEY * -rsa_key_create(FILE *f, const char *fname) +rsa_key_create(FILE *f) { EVP_PKEY_CTX *ctx = NULL; EVP_PKEY *pkey = NULL; @@ -520,7 +503,7 @@ rsa_key_create(FILE *f, const char *fname) } static EVP_PKEY * -ec_key_create(FILE *f, const char *fname) +ec_key_create(FILE *f) { EC_KEY *eckey = NULL; EVP_PKEY *pkey = NULL; @@ -554,8 +537,7 @@ ec_key_create(FILE *f, const char *fname) } int -cert_new(const char *common_name, const char *certpath, const char *keypath, - int eckey) +cert_new(const char *common_name, const char *path, int eckey) { EVP_PKEY *pkey = NULL; X509 *x509 = NULL; @@ -564,20 +546,16 @@ cert_new(const char *common_name, const char *certpath int ret = -1; const unsigned char *cn = (const unsigned char*)common_name; - if ((fp = fopen(keypath, "w")) == NULL) + if ((fp = fopen(path, "wx")) == NULL) goto done; if (eckey) - pkey = ec_key_create(fp, keypath); + pkey = ec_key_create(fp); else - pkey = rsa_key_create(fp, keypath); + pkey = rsa_key_create(fp); if (pkey == NULL) goto done; - if (fflush(fp) == EOF || fclose(fp) == EOF) - goto done; - fp = NULL; - if ((x509 = X509_new()) == NULL) goto done; @@ -602,9 +580,6 @@ cert_new(const char *common_name, const char *certpath if (!X509_sign(x509, pkey, EVP_sha256())) goto done; - if ((fp = fopen(certpath, "w")) == NULL) - goto done; - if (!PEM_write_X509(fp, x509)) goto done; @@ -621,10 +596,7 @@ cert_new(const char *common_name, const char *certpath X509_NAME_free(name); if (fp) fclose(fp); - - if (ret == -1) { - (void) unlink(certpath); - (void) unlink(keypath); - } + if (ret == -1) + (void) unlink(path); return (ret); } blob - 04d0246480ec6f44be8df1d20dcf2aed524374ae blob + 35fa735d83947d0d0a99ca4601f0e581f0a6fce6 --- certs.h +++ certs.h @@ -20,12 +20,32 @@ struct iri; -extern char **identities; +/* client certificate */ +struct ccert { + char *host; + char *port; + char *path; + char *cert; +#define CERT_OK 0x00 +#define CERT_TEMP 0x01 +#define CERT_TEMP_DEL 0x02 + int flags; +}; + +struct cstore { + struct ccert *certs; + size_t len; + size_t cap; +}; + +extern struct cstore cert_store; +extern char **identities; + int certs_init(const char *); const char *ccert(const char *); const char *cert_for(struct iri *, int *); int cert_save_for(const char *, struct iri *, int); int cert_delete_for(const char *, struct iri *, int); int cert_open(const char *); -int cert_new(const char *, const char *, const char *, int); +int cert_new(const char *, const char *, int); blob - f6e777a09dba8c97f071b17670ccadebd40476c7 blob + 958c7ebd4c7fb2e1d37e1dee0a1ef0d7ed146d55 --- fs.c +++ fs.c @@ -66,7 +66,7 @@ char known_hosts_file[PATH_MAX], known_hosts_tmp[PATH char crashed_file[PATH_MAX]; char session_file[PATH_MAX], session_file_tmp[PATH_MAX]; char history_file[PATH_MAX], history_file_tmp[PATH_MAX]; -char cert_dir[PATH_MAX]; +char cert_dir[PATH_MAX], cert_dir_tmp[PATH_MAX]; char certs_file[PATH_MAX], certs_file_tmp[PATH_MAX]; char cwd[PATH_MAX]; @@ -373,6 +373,8 @@ fs_init(void) sizeof(history_file_tmp)); join_path(cert_dir, data_path_base, "/certs/", sizeof(cert_dir)); + join_path(cert_dir_tmp, data_path_base, "/certs/id.XXXXXXXXXX", + sizeof(cert_dir_tmp)); join_path(certs_file, config_path_base, "/certs.conf", sizeof(certs_file)); join_path(certs_file_tmp, config_path_base, "/certs.conf.XXXXXXXXXX", blob - 00b1e9d9f7d432b1a5c0a6bb1ebae4c406eace56 blob + a99190534cfc48dbf9633337fbeb737afc1cd832 --- fs.h +++ fs.h @@ -32,7 +32,7 @@ extern char known_hosts_file[PATH_MAX], known_hosts_tm extern char crashed_file[PATH_MAX]; extern char session_file[PATH_MAX], session_file_tmp[PATH_MAX]; extern char history_file[PATH_MAX], history_file_tmp[PATH_MAX]; -extern char cert_dir[PATH_MAX]; +extern char cert_dir[PATH_MAX], cert_dir_tmp[PATH_MAX]; extern char certs_file[PATH_MAX], certs_file_tmp[PATH_MAX]; extern char cwd[PATH_MAX]; blob - /dev/null blob + c37f393e960ed08f9185909559aa8a0b289e46c5 (mode 644) --- /dev/null +++ identity.c @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2024 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "compat.h" + +#include +#include +#include +#include +#include +#include + +#include "certs.h" +#include "fs.h" +#include "parser.h" +#include "telescope.h" + +#ifndef nitems +#define nitems(x) (sizeof(x) / sizeof((x)[0])) +#endif + +struct cmd; + +static int cmd_generate(const struct cmd *, int, char **); +static int cmd_remove(const struct cmd *, int, char **); +static int cmd_import(const struct cmd *, int, char **); +static int cmd_export(const struct cmd *, int, char **); +static int cmd_list(const struct cmd *, int, char **); +static int cmd_mappings(const struct cmd *, int, char **); +static int cmd_use(const struct cmd *, int, char **); +static int cmd_forget(const struct cmd *, int, char **); + +struct cmd { + const char *name; + int (*fn)(const struct cmd *, int argc, char **argv); + const char *usage; +}; + +static const struct cmd cmds[] = { + { "generate", cmd_generate, "[-t type] name" }, + { "remove", cmd_remove, "name" }, + { "import", cmd_import, "-C cert [-K key] name" }, + { "export", cmd_export, "-C cert name path" }, + { "list", cmd_list, "" }, + { "mappings", cmd_mappings, "" }, + { "use", cmd_use, "name host[:port][/path]" }, + { "forget", cmd_forget, "name host[:port][/path]" }, +}; + +/* + * Provide some symbols so that we can pull in some subsystems without + * their the dependencies. + */ + +const uint8_t *about_about; +size_t about_about_len; +const uint8_t *about_blank; +size_t about_blank_len; +const uint8_t *about_crash; +size_t about_crash_len; +const uint8_t *about_help; +size_t about_help_len; +const uint8_t *about_license; +size_t about_license_len; +const uint8_t *about_new; +size_t about_new_len; +const uint8_t *bookmarks; +size_t bookmarks_len; + +void gemtext_initparser(struct parser *p) { return; } +void textpatch_initparser(struct parser *p) { return; } +void textplain_initparser(struct parser *p) { return; } + +void load_page_from_str(struct tab *tab, const char *page) { return; } +void erase_buffer(struct buffer *buffer) { return; } + +static void __dead +usage(void) +{ + size_t i; + + fprintf(stderr, "usage: %s command [args...]\n", getprogname()); + fprintf(stderr, "Available subcommands are:"); + for (i = 0; i < nitems(cmds); ++i) + fprintf(stderr, " %s", cmds[i].name); + fputs(".\n", stderr); + exit(1); +} + +static void __dead +cmd_usage(const struct cmd *cmd) +{ + fprintf(stderr, "usage: %s %s%s%s\n", getprogname(), cmd->name, + *cmd->usage ? " " : "", cmd->usage); + exit(1); +} + +int +main(int argc, char **argv) +{ + const struct cmd *cmd; + int ch; + size_t i; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage(); + } + } + argc -= optind; + argv += optind; + optind = 0; + optreset = 1; + + if (argc == 0) + usage(); + + for (i = 0; i < nitems(cmds); ++i) { + cmd = &cmds[i]; + + if (strcmp(cmd->name, argv[0]) != 0) + continue; + + fs_init(); + if (certs_init(certs_file) == -1) + errx(1, "failed to initialize the cert store."); + return (cmd->fn(cmd, argc, argv)); + } + + usage(); +} + +static int +cmd_generate(const struct cmd *cmd, int argc, char **argv) +{ + const char *name; + char path[PATH_MAX]; + int ch, r; + int ec = 1; + + while ((ch = getopt(argc, argv, "t:")) != -1) { + switch (ch) { + case 't': + if (!strcasecmp(optarg, "ec")) { + ec = 1; + break; + } + if (!strcasecmp(optarg, "rsa")) { + ec = 0; + break; + } + errx(1, "Unknown key type requested: %s", optarg); + + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + cmd_usage(cmd); + + name = *argv; + + r = snprintf(path, sizeof(path), "%s%s", cert_dir, name); + if (r < 0 || (size_t)r >= sizeof(path)) + errx(1, "path too long"); + + if (cert_new(name, path, ec) == -1) + errx(1, "failure generating the key"); + + return 0; +} + +static int +cmd_remove(const struct cmd *cmd, int argc, char **argv) +{ + const char *name; + char path[PATH_MAX]; + int ch, r; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + cmd_usage(cmd); + + name = *argv; + + r = snprintf(path, sizeof(path), "%s%s", cert_dir, name); + if (r < 0 || (size_t)r >= sizeof(path)) + errx(1, "path too long"); + + if (unlink(path) == -1) + err(1, "unlink %s", path); + return 0; +} + +static int +cmd_import(const struct cmd *cmd, int argc, char **argv) +{ + struct tls_config *conf; + const char *key = NULL, *cert = NULL; + char path[PATH_MAX], sfn[PATH_MAX]; + FILE *fp; + uint8_t *keym, *certm; + size_t keyl, certl; + int ch, r, fd; + int force = 0; + + while ((ch = getopt(argc, argv, "C:K:f")) != -1) { + switch (ch) { + case 'C': + cert = optarg; + break; + case 'K': + key = optarg; + break; + case 'f': + force = 1; + break; + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + cmd_usage(cmd); + + if (key == NULL) + key = cert; + if (cert == NULL) + cmd_usage(cmd); + + if ((keym = tls_load_file(key, &keyl, NULL)) == NULL) + err(1, "can't open %s", key); + if ((certm = tls_load_file(cert, &certl, NULL)) == NULL) + err(1, "can't open %s", cert); + + if ((conf = tls_config_new()) == NULL) + err(1, "tls_config_new"); + + if (tls_config_set_keypair_mem(conf, certm, certl, keym, keyl) == -1) + errx(1, "failed to load the keypair: %s", + tls_config_error(conf)); + + tls_config_free(conf); + + r = snprintf(path, sizeof(path), "%s/%s", cert_dir, *argv); + if (r < 0 || (size_t)r >= sizeof(path)) + err(1, "identity name too long"); + + strlcpy(sfn, cert_dir_tmp, sizeof(sfn)); + if ((fd = mkstemp(sfn)) == -1 || + (fp = fdopen(fd, "w")) == NULL) { + if (fd != -1) { + warn("fdopen"); + unlink(sfn); + close(fd); + } else + warn("mkstamp"); + return 1; + } + + if (fwrite(certm, 1, certl, fp) != certl) { + warn("fwrite"); + unlink(sfn); + fclose(fp); + return 1; + } + if (strcmp(key, cert) != 0 && + fwrite(keym, 1, keyl, fp) != keyl) { + warn("fwrite"); + unlink(sfn); + fclose(fp); + return 1; + } + + if (fflush(fp) == EOF) { + warn("fflush"); + unlink(sfn); + fclose(fp); + return 1; + } + fclose(fp); + + if (!force && access(path, F_OK) == 0) { + warnx("identity %s already exists", *argv); + unlink(sfn); + return 1; + } + + if (rename(sfn, path) == -1) { + warn("can't rename"); + unlink(sfn); + return 1; + } + + return (0); +} + +static int +cmd_export(const struct cmd *cmd, int argc, char **argv) +{ + FILE *fp, *outfp; + const char *cert = NULL; + const char *identity = NULL; + char path[PATH_MAX]; + char buf[BUFSIZ]; + size_t l; + int ch, r; + + while ((ch = getopt(argc, argv, "C:")) != -1) { + switch (ch) { + case 'C': + cert = optarg; + break; + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + if (argc != 1) + cmd_usage(cmd); + identity = argv[0]; + + if (cert == NULL) + cmd_usage(cmd); + + r = snprintf(path, sizeof(path), "%s/%s", cert_dir, identity); + if (r < 0 || (size_t)r >= sizeof(path)) + err(1, "path too long"); + if ((fp = fopen(path, "r")) == NULL) + err(1, "can't open %s", path); + + if ((outfp = fopen(cert, "w")) == NULL) + err(1, "can't open %s", cert); + + for (;;) { + l = fread(buf, 1, sizeof(buf), fp); + if (l == 0) + break; + if (fwrite(buf, 1, l, outfp) != l) + err(1, "fwrite"); + } + if (ferror(fp)) + err(1, "fread"); + + return 0; +} + +static int +cmd_list(const struct cmd *cmd, int argc, char **argv) +{ + char **id; + int ch; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + if (argc != 0) + cmd_usage(cmd); + + for (id = identities; *id; ++id) + puts(*id); + + return (0); +} + +static int +cmd_mappings(const struct cmd *cmd, int argc, char **argv) +{ + struct ccert *ccert; + int ch, defport; + size_t i; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + if (argc != 0) + cmd_usage(cmd); + + for (i = 0; i < cert_store.len; ++i) { + ccert = &cert_store.certs[i]; + + defport = !strcmp(ccert->port, "1965"); + + printf("%s\t%s%s%s%s\n", ccert->host, ccert->host, + defport ? "" : ":", defport ? "" : ccert->port, + ccert->path); + } + + return (0); +} + +static struct iri * +parseiri(char *spec) +{ + static struct iri iri; + const char *errstr; + char *host, *port = NULL, *path = NULL; + + memset(&iri, 0, sizeof(iri)); + + host = spec; + + port = host + strcspn(host, ":/"); + if (*port == ':') { + *port++ = '\0'; + if ((path = strchr(port, '/')) != NULL) + *path++ = '\0'; + } else if (*port == '/') { + *port++ = '\0'; + path = port; + port = NULL; + } else + port = NULL; + + strlcpy(iri.iri_host, host, sizeof(iri.iri_host)); + strlcpy(iri.iri_portstr, port ? port : "1965", sizeof(iri.iri_portstr)); + strlcpy(iri.iri_path, path ? path : "/", sizeof(iri.iri_path)); + + iri.iri_port = strtonum(iri.iri_portstr, 0, UINT16_MAX, &errstr); + if (errstr) + err(1, "port number is %s: %s", errstr, iri.iri_portstr); + + return &iri; +} + +static int +cmd_use(const struct cmd *cmd, int argc, char **argv) +{ + char *cert, *spec; + int ch; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + if (argc != 2) + cmd_usage(cmd); + + cert = argv[0]; + spec = argv[1]; + + if (ccert(cert) == NULL) + err(1, "unknown identity %s", cert); + + if (cert_save_for(cert, parseiri(spec), 1) == -1) + errx(1, "failed to save the certificate"); + + return 0; +} + +static int +cmd_forget(const struct cmd *cmd, int argc, char **argv) +{ + char *cert, *spec; + int ch; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + cmd_usage(cmd); + } + } + argc -= optind; + argv += optind; + if (argc != 2) + cmd_usage(cmd); + + cert = argv[0]; + spec = argv[1]; + + if (ccert(cert) == NULL) + err(1, "unknown identity %s", cert); + + if (cert_delete_for(cert, parseiri(spec), 1) == -1) + errx(1, "failed to save the certificate"); + + return 0; +} +