1
0
mirror of https://git.sr.ht/~sircmpwn/gmni synced 2024-11-26 10:53:46 +01:00

Initial support for client side certificates

This is only supported with gmni for now - gmnlm support will come
later. A limitation with BearSSL prevents us from doing automated
certificate generation for now, unfortunately.
This commit is contained in:
Drew DeVault 2021-03-04 17:24:57 -05:00
parent 2017d26c41
commit 1ed4f09532
11 changed files with 264 additions and 17 deletions

1
configure vendored

@ -4,6 +4,7 @@ eval ". $srcdir/config.sh"
gmni() {
genrules gmni \
src/certs.c \
src/client.c \
src/escape.c \
src/gmni.c \

@ -38,11 +38,9 @@ performed with the user's input supplied to the server.
second request is performed with the contents of _path_ as the user
input.
*-E* _path_[:_password_]
Sets the path to the client certificate to use (and optionally a
password). If the filename contains ":" but the certificate does not
accept a password, append ":" to the path and it will be intepreted as
an empty password.
*-E* _path_:_key_
Sets the path to the client certificate and private key file to use,
both PEM encoded.
*-l*
For *text/\** responses, *gmni* normally adds a line feed if stdout is a

27
include/gmni/certs.h Normal file

@ -0,0 +1,27 @@
#ifndef GEMINI_CERTS_H
#define GEMINI_CERTS_H
#include <bearssl.h>
#include <stdio.h>
struct gmni_options;
struct gmni_client_certificate {
br_x509_certificate *chain;
size_t nchain;
struct gmni_private_key *key;
};
struct gmni_private_key {
int type;
union {
br_rsa_private_key rsa;
br_ec_private_key ec;
};
unsigned char data[];
};
// Returns nonzero on failure and sets errno
int gmni_ccert_load(struct gmni_client_certificate *cert,
FILE *certin, FILE *skin);
#endif

@ -1,6 +1,6 @@
#ifndef GEMINI_CLIENT_H
#define GEMINI_CLIENT_H
#include <bearssl_ssl.h>
#include <bearssl.h>
#include <netdb.h>
#include <stdbool.h>
#include <sys/socket.h>
@ -61,6 +61,8 @@ struct gemini_response {
int fd;
};
struct gmni_client_certificate;
struct gemini_options {
// If ai_family != AF_UNSPEC (the default value on most systems), the
// client will connect to this address and skip name resolution.
@ -69,6 +71,10 @@ struct gemini_options {
// If non-NULL, these hints are provided to getaddrinfo. Useful, for
// example, to force IPv4/IPv6.
struct addrinfo *hints;
// If non-NULL, this will be used as the client certificate for the
// request. The other fields must be set as well.
struct gmni_client_certificate *client_cert;
};
struct gemini_tofu;

@ -1,6 +1,6 @@
#ifndef GEMINI_TOFU_H
#define GEMINI_TOFU_H
#include <bearssl_x509.h>
#include <bearssl.h>
#include <limits.h>
enum tofu_error {

156
src/certs.c Normal file

@ -0,0 +1,156 @@
#include <assert.h>
#include <bearssl.h>
#include <errno.h>
#include <gmni/certs.h>
#include <gmni/gmni.h>
#include <stdio.h>
#include <stdlib.h>
static void
crt_append(void *ctx, const void *src, size_t len)
{
br_x509_certificate *crt = (br_x509_certificate *)ctx;
crt->data = realloc(crt->data, crt->data_len + len);
assert(crt->data);
memcpy(&crt->data[crt->data_len], src, len);
crt->data_len += len;
}
static void
key_append(void *ctx, const void *src, size_t len)
{
br_skey_decoder_context *skctx = (br_skey_decoder_context *)ctx;
br_skey_decoder_push(skctx, src, len);
}
int
gmni_ccert_load(struct gmni_client_certificate *cert, FILE *certin, FILE *skin)
{
// TODO: Better error propagation to caller
static unsigned char buf[BUFSIZ];
br_pem_decoder_context pemdec;
br_pem_decoder_init(&pemdec);
cert->chain = NULL;
cert->nchain = 0;
static const char *certname = "CERTIFICATE";
while (!feof(certin)) {
size_t n = fread(&buf, 1, sizeof(buf), certin);
if (ferror(certin)) {
goto error;
}
size_t q = 0;
while (q < n) {
q += br_pem_decoder_push(&pemdec, &buf[q], n - q);
switch (br_pem_decoder_event(&pemdec)) {
case BR_PEM_BEGIN_OBJ:
if (strcmp(br_pem_decoder_name(&pemdec), certname) != 0) {
break;
}
cert->chain = realloc(cert->chain,
sizeof(br_x509_certificate) * (cert->nchain + 1));
memset(&cert->chain[cert->nchain], 0, sizeof(*cert->chain));
br_pem_decoder_setdest(&pemdec, &crt_append,
&cert->chain[cert->nchain]);
++cert->nchain;
break;
case BR_PEM_END_OBJ:
break;
case BR_PEM_ERROR:
fprintf(stderr, "Error decoding PEM certificate\n");
errno = EINVAL;
goto error;
}
}
}
if (cert->nchain == 0) {
fprintf(stderr, "No certificates found in provided client certificate file\n");
errno = EINVAL;
goto error;
}
br_skey_decoder_context skdec = {0};
br_skey_decoder_init(&skdec);
br_pem_decoder_init(&pemdec);
// TODO: Better validation of PEM file
while (!feof(skin)) {
size_t n = fread(&buf, 1, sizeof(buf), skin);
if (ferror(skin)) {
goto error;
}
size_t q = 0;
while (q < n) {
q += br_pem_decoder_push(&pemdec, &buf[q], n - q);
switch (br_pem_decoder_event(&pemdec)) {
case BR_PEM_BEGIN_OBJ:
br_pem_decoder_setdest(&pemdec, &key_append, &skdec);
break;
case BR_PEM_END_OBJ:
// no-op
break;
case BR_PEM_ERROR:
fprintf(stderr, "Error decoding PEM private key\n");
errno = EINVAL;
goto error;
}
}
}
int err = br_skey_decoder_last_error(&skdec);
if (err != 0) {
fprintf(stderr, "Error loading private key: %d\n", err);
errno = EINVAL;
goto error;
}
switch (br_skey_decoder_key_type(&skdec)) {
struct gmni_private_key *k;
const br_ec_private_key *ec;
const br_rsa_private_key *rsa;
case BR_KEYTYPE_RSA:
rsa = br_skey_decoder_get_rsa(&skdec);
cert->key = k = malloc(sizeof(*k)
+ rsa->plen + rsa->qlen
+ rsa->dplen + rsa->dqlen
+ rsa->iqlen);
assert(k);
k->type = BR_KEYTYPE_RSA;
k->rsa = *rsa;
k->rsa.p = k->data;
k->rsa.q = k->rsa.p + k->rsa.plen;
k->rsa.dp = k->rsa.q + k->rsa.qlen;
k->rsa.dq = k->rsa.dp + k->rsa.dplen;
k->rsa.iq = k->rsa.dq + k->rsa.dqlen;
memcpy(k->rsa.p, rsa->p, rsa->plen);
memcpy(k->rsa.q, rsa->q, rsa->qlen);
memcpy(k->rsa.dp, rsa->dp, rsa->dplen);
memcpy(k->rsa.dq, rsa->dq, rsa->dqlen);
memcpy(k->rsa.iq, rsa->iq, rsa->iqlen);
break;
case BR_KEYTYPE_EC:
ec = br_skey_decoder_get_ec(&skdec);
cert->key = k = malloc(sizeof(*k) + ec->xlen);
assert(k);
k->type = BR_KEYTYPE_EC;
k->ec.curve = ec->curve;
k->ec.x = k->data;
k->ec.xlen = ec->xlen;
memcpy(k->ec.x, ec->x, ec->xlen);
break;
default:
assert(0);
}
fclose(certin);
fclose(skin);
return 0;
error:
fclose(certin);
fclose(skin);
free(cert->chain);
return 1;
}

@ -1,13 +1,14 @@
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <bearssl_ssl.h>
#include <bearssl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <gmni/certs.h>
#include <gmni/gmni.h>
#include <gmni/tofu.h>
#include <gmni/url.h>
@ -169,7 +170,26 @@ gemini_request(const char *url, struct gemini_options *options,
// TODO: session reuse
resp->sc = &tofu->sc;
if (options->client_cert) {
struct gmni_client_certificate *cert = options->client_cert;
struct gmni_private_key *key = cert->key;
switch (key->type) {
case BR_KEYTYPE_RSA:
br_ssl_client_set_single_rsa(resp->sc,
cert->chain, cert->nchain, &key->rsa,
br_rsa_pkcs1_sign_get_default());
break;
case BR_KEYTYPE_EC:
br_ssl_client_set_single_ec(resp->sc,
cert->chain, cert->nchain, &key->ec,
BR_KEYTYPE_SIGN, 0,
br_ec_get_default(),
br_ecdsa_sign_asn1_get_default());
break;
}
}
br_ssl_client_reset(resp->sc, host, 0);
br_sslio_init(&resp->body, &resp->sc->eng,
sock_read, &resp->fd, sock_write, &resp->fd);

@ -1,5 +1,5 @@
#include <assert.h>
#include <bearssl_ssl.h>
#include <bearssl.h>
#include <errno.h>
#include <getopt.h>
#include <netdb.h>
@ -11,6 +11,7 @@
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <gmni/certs.h>
#include <gmni/gmni.h>
#include <gmni/tofu.h>
#include <gmni/url.h>
@ -109,6 +110,45 @@ tofu_callback(enum tofu_error error, const char *fingerprint,
return action;
}
static struct gmni_client_certificate *
load_client_cert(char *argv_0, char *path)
{
char *certpath = strtok(path, ":");
if (!certpath) {
usage(argv_0);
exit(1);
}
FILE *certf = fopen(certpath, "r");
if (!certf) {
fprintf(stderr, "Failed to open certificate: %s\n",
strerror(errno));
exit(1);
}
char *keypath = strtok(NULL, ":");
if (!keypath) {
usage(argv_0);
exit(1);
}
FILE *keyf = fopen(keypath, "r");
if (!keyf) {
fprintf(stderr, "Failed to open certificate: %s\n",
strerror(errno));
exit(1);
}
struct gmni_client_certificate *cert =
calloc(1, sizeof(struct gmni_client_certificate));
if (gmni_ccert_load(cert, certf, keyf) != 0) {
fprintf(stderr, "Failed to load client certificate: %s\n",
strerror(errno));
exit(1);
}
return cert;
}
int
main(int argc, char *argv[])
{
@ -165,7 +205,7 @@ main(int argc, char *argv[])
}
break;
case 'E':
assert(0); // TODO: Client certificates
opts.client_cert = load_client_cert(argv[0], optarg);
break;
case 'h':
usage(argv[0]);
@ -238,8 +278,8 @@ main(int argc, char *argv[])
curl_url_get(url, CURLUPART_URL, &buf, 0);
struct gemini_response resp;
enum gemini_result r = gemini_request(
buf, &opts, &cfg.tofu, &resp);
enum gemini_result r = gemini_request(buf,
&opts, &cfg.tofu, &resp);
free(buf);

@ -1,5 +1,5 @@
#include <assert.h>
#include <bearssl_ssl.h>
#include <bearssl.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>

@ -1,6 +1,5 @@
#include <assert.h>
#include <bearssl_hash.h>
#include <bearssl_x509.h>
#include <bearssl.h>
#include <errno.h>
#include <gmni/gmni.h>
#include <gmni/tofu.h>

@ -1,5 +1,5 @@
#include <assert.h>
#include <bearssl_ssl.h>
#include <bearssl.h>
#include <errno.h>
#include <gmni/gmni.h>
#include <libgen.h>