1
0
mirror of https://git.sr.ht/~sircmpwn/gmni synced 2024-11-22 20:32:03 +01:00

Implement basic client certs for gmnlm

This commit is contained in:
Drew DeVault 2021-03-05 08:50:50 -05:00
parent 1ed4f09532
commit 925d9e321d
3 changed files with 67 additions and 10 deletions

2
configure vendored

@ -15,6 +15,7 @@ gmni() {
gmnlm() {
genrules gmnlm \
src/certs.c \
src/client.c \
src/escape.c \
src/gmnlm.c \
@ -26,6 +27,7 @@ gmnlm() {
libgmni_a() {
genrules libgmni.a \
src/certs.c \
src/client.c \
src/escape.c \
src/tofu.c \

@ -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);

@ -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>
@ -382,13 +383,50 @@ do_requests(struct browser *browser, struct gemini_response *resp)
int nredir = 0;
bool requesting = true;
enum gemini_result res;
while (requesting) {
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_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;
@ -423,9 +461,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);
@ -467,7 +505,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;
@ -477,7 +527,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) {
@ -485,6 +535,11 @@ do_requests(struct browser *browser, struct gemini_response *resp)
}
}
out:
if (client_cert.key) {
free(client_cert.key);
}
free(scheme);
return res;
}