cgmnlm

Unnamed repository; edit this file 'description' to name the repository.
git clone git://code.clttr.info/cgmnlm.git
Log | Files | Refs | README | LICENSE

commit ffc89b6cf01e8b68232b3de3f001d2e635936dfa
parent 955f7524b955e19bc89c6e9f76f3f3ecfb7bfb58
Author: Drew DeVault <sir@cmpwn.com>
Date:   Fri,  5 Mar 2021 08:50:50 -0500

Implement basic client certs for gmnlm

Diffstat:
Mconfigure | 2++
Minclude/gmni/certs.h | 2+-
Msrc/gmnlm.c | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
3 files changed, 67 insertions(+), 10 deletions(-)

diff --git a/configure b/configure @@ -15,6 +15,7 @@ gmni() { cgmnlm() { genrules cgmnlm \ + src/certs.c \ src/client.c \ src/escape.c \ src/gmnlm.c \ @@ -26,6 +27,7 @@ cgmnlm() { libgmni_a() { genrules libgmni.a \ + src/certs.c \ src/client.c \ src/escape.c \ src/tofu.c \ diff --git a/include/gmni/certs.h b/include/gmni/certs.h @@ -20,7 +20,7 @@ struct gmni_private_key { unsigned char data[]; }; -// Returns nonzero on failure and sets errno +// Returns nonzero on failure and sets errno. Closes both files. int gmni_ccert_load(struct gmni_client_certificate *cert, FILE *certin, FILE *skin); diff --git a/src/gmnlm.c b/src/gmnlm.c @@ -4,6 +4,7 @@ #include <errno.h> #include <fcntl.h> #include <getopt.h> +#include <gmni/certs.h> #include <gmni/gmni.h> #include <gmni/tofu.h> #include <gmni/url.h> @@ -434,13 +435,50 @@ do_requests(struct browser *browser, struct gemini_response *resp) int nredir = 0; bool requesting = true; enum gemini_result res; - while (requesting) { - char *scheme; + + char *scheme; + CURLUcode uc = curl_url_get(browser->url, + CURLUPART_SCHEME, &scheme, 0); + assert(uc == CURLUE_OK); // Invariant + + char *host = NULL; + struct gmni_client_certificate client_cert = {0}; + const struct pathspec paths[] = { + {.var = "GMNIDATA", .path = "/certs/%s.%s"}, + {.var = "XDG_DATA_HOME", .path = "/gmni/certs/%s.%s"}, + {.var = "HOME", .path = "/.local/share/gmni/certs/%s.%s"} + }; + char *path_fmt = getpath(paths, sizeof(paths) / sizeof(paths[0])); + char certpath[PATH_MAX+1], keypath[PATH_MAX+1]; + size_t n = 0; + + if (strcmp(scheme, "gemini") == 0) { CURLUcode uc = curl_url_get(browser->url, - CURLUPART_SCHEME, &scheme, 0); - assert(uc == CURLUE_OK); // Invariant + CURLUPART_HOST, &host, 0); + assert(uc == CURLUE_OK); + + n = snprintf(certpath, sizeof(certpath), path_fmt, host, "crt"); + assert(n < sizeof(certpath)); + FILE *certin = fopen(certpath, "r"); + if (certin) { + n = snprintf(keypath, sizeof(keypath), path_fmt, host, "key"); + assert(n < sizeof(keypath)); + + FILE *skin = fopen(keypath, "r"); + if (gmni_ccert_load(&client_cert, certin, skin)) { + browser->opts.client_cert = NULL; + fprintf(stderr, "Unable to load client certificate for host %s", host); + } else { + browser->opts.client_cert = &client_cert; + } + } else { + browser->opts.client_cert = NULL; + } + free(host); + } + + while (requesting) { if (strcmp(scheme, "file") == 0) { - free(scheme); requesting = false; char *path; @@ -475,9 +513,9 @@ do_requests(struct browser *browser, struct gemini_response *resp) resp->status = GEMINI_STATUS_SUCCESS; resp->fd = fd; resp->sc = NULL; - return GEMINI_OK; + res = GEMINI_OK; + goto out; } - free(scheme); res = gemini_request(browser->plain_url, &browser->opts, &browser->tofu, resp); @@ -519,7 +557,19 @@ do_requests(struct browser *browser, struct gemini_response *resp) set_url(browser, resp->meta, NULL); break; case GEMINI_STATUS_CLASS_CLIENT_CERTIFICATE_REQUIRED: - assert(0); // TODO + requesting = false; + assert(host); + n = snprintf(certpath, sizeof(certpath), path_fmt, host, "crt"); + assert(n < sizeof(certpath)); + n = snprintf(keypath, sizeof(keypath), path_fmt, host, "key"); + assert(n < sizeof(keypath)); + fprintf(stderr, "The server requested a client certificate.\n" + "Presently, this process is not automated.\n" + "The following OpenSSL command will generate a certificate for this host:\n\n" + "openssl req -x509 -newkey rsa:4096 \\\n\t-keyout %s \\\n\t-out %s \\\n\t-days 36500 -nodes\n\n" + "Use the 'r' command to reload the page after creating this certificate.\n", + keypath, certpath); + break; case GEMINI_STATUS_CLASS_TEMPORARY_FAILURE: case GEMINI_STATUS_CLASS_PERMANENT_FAILURE: requesting = false; @@ -529,7 +579,7 @@ do_requests(struct browser *browser, struct gemini_response *resp) resp->status, resp->meta); break; case GEMINI_STATUS_CLASS_SUCCESS: - return res; + goto out; } if (requesting) { @@ -537,6 +587,11 @@ do_requests(struct browser *browser, struct gemini_response *resp) } } +out: + if (client_cert.key) { + free(client_cert.key); + } + free(scheme); return res; }