diff --git a/README.md b/README.md
index aa9f811a9bfd9cfb7b6654954e33fc62e868f8c4..fa803f99158582d982ef3a01d6b87471fb27f0cb 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
- Page history
- Regex searches
- Bookmarks
+- basic Client Certificate support (no autocreation of client certs currently)
## Non-Features:
@@ -61,6 +62,5 @@
### Dependencies:
- A POSIX-like system and a C11 compiler
-- OpenSSL
+- [BearSSL](https://www.bearssl.org/index.html)
- [scdoc](https://sr.ht/~sircmpwn/scdoc/) (optional)
-
diff --git a/config.sh b/config.sh
index 424cbda346869c7476859d55a600c414c86bbb46..87888929b98a46887dbcb25378385183e4ef2566 100644
--- a/config.sh
+++ b/config.sh
@@ -117,8 +117,8 @@ echo no
fi
done
- find_library OpenSSL libssl
- find_library OpenSSL libcrypto
+ # XXX: Asked the maintainer to provide a .pc file
+ LIBS="$LIBS -lbearssl"
printf "Checking for scdoc... "
if scdoc -v >/dev/null 2>&1
diff --git a/configure b/configure
index 70a19b193d6e33b41b5cc7ef13511bb0f5b4e076..6abccdd18d0b85e4b2d4b527d49727d360dec5c3 100755
--- a/configure
+++ b/configure
@@ -4,6 +4,7 @@ eval ". $srcdir/config.sh"
gmni() {
genrules gmni \
+ src/certs.c \
src/client.c \
src/escape.c \
src/gmni.c \
@@ -14,6 +15,7 @@ }
cgmnlm() {
genrules cgmnlm \
+ src/certs.c \
src/client.c \
src/escape.c \
src/gmnlm.c \
@@ -25,6 +27,7 @@ }
libgmni_a() {
genrules libgmni.a \
+ src/certs.c \
src/client.c \
src/escape.c \
src/tofu.c \
diff --git a/doc/gmni.scd b/doc/gmni.scd
index 1f20672f1fb0c8ff6a4f3a97a3324a25d2a0a01d..866dd418a510ccd141d9c23afb4b321b84a5f9ec 100644
--- a/doc/gmni.scd
+++ b/doc/gmni.scd
@@ -38,11 +38,9 @@ If the server requests user input, _path_ is opened and read, and a
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
diff --git a/include/gmni/certs.h b/include/gmni/certs.h
new file mode 100644
index 0000000000000000000000000000000000000000..53b8aad27fa50649ddfdacbd08724d5d0b033120
--- /dev/null
+++ b/include/gmni/certs.h
@@ -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. Closes both files.
+int gmni_ccert_load(struct gmni_client_certificate *cert,
+ FILE *certin, FILE *skin);
+
+#endif
diff --git a/include/gmni/gmni.h b/include/gmni/gmni.h
index 7e27b489d71fd3a43ca60292b17d56cab3caa5f8..22295e20fb36d492a979c060de8dcdca14df5a34 100644
--- a/include/gmni/gmni.h
+++ b/include/gmni/gmni.h
@@ -1,7 +1,7 @@
#ifndef GEMINI_CLIENT_H
#define GEMINI_CLIENT_H
+#include <bearssl.h>
#include <netdb.h>
-#include <openssl/ssl.h>
#include <stdbool.h>
#include <sys/socket.h>
@@ -52,20 +52,18 @@ struct gemini_response {
enum gemini_status status;
char *meta;
+ // TODO: Make these private
// Response body may be read from here if appropriate:
- BIO *bio;
+ br_sslio_context body;
// Connection state
- SSL_CTX *ssl_ctx;
- SSL *ssl;
+ br_ssl_client_context *sc;
int fd;
};
-struct gemini_options {
- // If NULL, an SSL context will be created. If unset, the ssl field
- // must also be NULL.
- SSL_CTX *ssl_ctx;
+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.
struct addrinfo *addr;
@@ -73,7 +71,13 @@
// 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;
// Requests the specified URL via the gemini protocol. If options is non-NULL,
// it may specify some additional configuration to adjust client behavior.
@@ -84,6 +88,7 @@ // Caller must call gemini_response_finish afterwards to clean up resources
// before exiting or re-using it for another request.
enum gemini_result gemini_request(const char *url,
struct gemini_options *options,
+ struct gemini_tofu *tofu,
struct gemini_response *resp);
// Must be called after gemini_request in order to free up the resources
@@ -137,15 +142,20 @@ };
};
struct gemini_parser {
- BIO *f;
+ int (*read)(void *state, void *buf, size_t nbyte);
+ void *state;
char *buf;
size_t bufsz;
size_t bufln;
bool preformatted;
};
-// Initializes a text/gemini parser which reads from the specified BIO.
-void gemini_parser_init(struct gemini_parser *p, BIO *f);
+// Initializes a text/gemini parser. The provided "read" function will be called
+// with the provided "state" value in order to obtain more gemtext data. The
+// read function should behave like read(3).
+void gemini_parser_init(struct gemini_parser *p,
+ int (*read)(void *state, void *buf, size_t nbyte),
+ void *state);
// Finishes this text/gemini parser and frees up its resources.
void gemini_parser_finish(struct gemini_parser *p);
diff --git a/include/gmni/tofu.h b/include/gmni/tofu.h
index a88167ba0fb6606b2b170e5005c55131f1861972..51d1d60a3719469a1f8cd295b9d85e21d7affb43 100644
--- a/include/gmni/tofu.h
+++ b/include/gmni/tofu.h
@@ -1,9 +1,7 @@
#ifndef GEMINI_TOFU_H
#define GEMINI_TOFU_H
+#include <bearssl.h>
#include <limits.h>
-#include <openssl/ssl.h>
-#include <openssl/x509.h>
-#include <time.h>
enum tofu_error {
TOFU_VALID,
@@ -24,7 +22,6 @@ };
struct known_host {
char *host, *fingerprint;
- time_t expires;
int lineno;
struct known_host *next;
};
@@ -34,7 +31,23 @@ // certificate. Return true to trust this certificate.
typedef enum tofu_action (tofu_callback_t)(enum tofu_error error,
const char *fingerprint, struct known_host *host, void *data);
+struct gemini_tofu;
+
+struct x509_tofu_context {
+ const br_x509_class *vtable;
+ br_x509_decoder_context decoder;
+ br_x509_pkey *pkey;
+ br_sha512_context sha512;
+ unsigned char hash[64];
+ struct gemini_tofu *store;
+ const char *server_name;
+ int err;
+};
+
struct gemini_tofu {
+ struct x509_tofu_context x509_ctx;
+ br_ssl_client_context sc;
+ unsigned char iobuf[BR_SSL_BUFSIZE_BIDI];
char known_hosts_path[PATH_MAX+1];
struct known_host *known_hosts;
int lineno;
@@ -42,8 +55,7 @@ tofu_callback_t *callback;
void *cb_data;
};
-void gemini_tofu_init(struct gemini_tofu *tofu,
- SSL_CTX *ssl_ctx, tofu_callback_t *cb, void *data);
+void gemini_tofu_init(struct gemini_tofu *tofu, tofu_callback_t *cb, void *data);
void gemini_tofu_finish(struct gemini_tofu *tofu);
#endif
diff --git a/include/util.h b/include/util.h
index 6193fdf48c0ac6d313de7f35a1d09ecba62e3283..6c241f41a175c3d27390ecb6ca5b041bd637cb2b 100644
--- a/include/util.h
+++ b/include/util.h
@@ -1,5 +1,7 @@
#ifndef GEMINI_UTIL_H
#define GEMINI_UTIL_H
+#include <stdio.h>
+#include <sys/types.h>
struct pathspec {
const char *var;
@@ -7,6 +9,7 @@ const char *path;
};
char *getpath(const struct pathspec *paths, size_t npaths);
+void posix_dirname(char *path, char *dname);
int mkdirs(char *path, mode_t mode);
int download_resp(FILE *out, struct gemini_response resp, const char *path,
char *url);
diff --git a/src/certs.c b/src/certs.c
new file mode 100644
index 0000000000000000000000000000000000000000..f40bfa7d27925baaa2fe6dbdf0d6c18ce4e10014
--- /dev/null
+++ b/src/certs.c
@@ -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;
+}
diff --git a/src/client.c b/src/client.c
index a8a12f3cfbe1e98fd2045eec257cfaf95c319910..127a56ca59859e645fea6f1e4ac62431d734e4fd 100644
--- a/src/client.c
+++ b/src/client.c
@@ -1,15 +1,16 @@
#include <assert.h>
#include <errno.h>
#include <netdb.h>
-#include <openssl/bio.h>
-#include <openssl/err.h>
-#include <openssl/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>
static enum gemini_result
@@ -88,9 +89,41 @@
#define GEMINI_META_MAXLEN 1024
#define GEMINI_STATUS_MAXLEN 2
+static int
+sock_read(void *ctx, unsigned char *buf, size_t len)
+{
+ for (;;) {
+ ssize_t rlen;
+ rlen = read(*(int *)ctx, buf, len);
+ if (rlen <= 0) {
+ if (rlen < 0 && errno == EINTR) {
+ continue;
+ }
+ return -1;
+ }
+ return (int)rlen;
+ }
+}
+
+static int
+sock_write(void *ctx, const unsigned char *buf, size_t len)
+{
+ for (;;) {
+ ssize_t wlen;
+ wlen = write(*(int *)ctx, buf, len);
+ if (wlen <= 0) {
+ if (wlen < 0 && errno == EINTR) {
+ continue;
+ }
+ return -1;
+ }
+ return (int)wlen;
+ }
+}
+
enum gemini_result
gemini_request(const char *url, struct gemini_options *options,
- struct gemini_response *resp)
+ struct gemini_tofu *tofu, struct gemini_response *resp)
{
assert(url);
assert(resp);
@@ -128,84 +161,69 @@ free(host);
goto cleanup;
}
- if (options && options->ssl_ctx) {
- resp->ssl_ctx = options->ssl_ctx;
- SSL_CTX_up_ref(options->ssl_ctx);
- } else {
- resp->ssl_ctx = SSL_CTX_new(TLS_method());
- assert(resp->ssl_ctx);
- SSL_CTX_set_verify(resp->ssl_ctx, SSL_VERIFY_PEER, NULL);
- }
-
int r;
- BIO *sbio = BIO_new(BIO_f_ssl());
res = gemini_connect(uri, options, resp, &resp->fd);
if (res != GEMINI_OK) {
free(host);
goto cleanup;
}
- resp->ssl = SSL_new(resp->ssl_ctx);
- assert(resp->ssl);
- SSL_set_connect_state(resp->ssl);
- if ((r = SSL_set1_host(resp->ssl, host)) != 1) {
- free(host);
- goto ssl_error;
- }
- if ((r = SSL_set_tlsext_host_name(resp->ssl, host)) != 1) {
- free(host);
- goto ssl_error;
- }
- free(host);
- if ((r = SSL_set_fd(resp->ssl, resp->fd)) != 1) {
- goto ssl_error;
- }
- if ((r = SSL_connect(resp->ssl)) != 1) {
- goto ssl_error;
- }
-
- X509 *cert = SSL_get_peer_certificate(resp->ssl);
- if (!cert) {
- resp->status = X509_V_ERR_UNSPECIFIED;
- res = GEMINI_ERR_SSL_VERIFY;
- goto cleanup;
- }
- X509_free(cert);
-
- long vr = SSL_get_verify_result(resp->ssl);
- if (vr != X509_V_OK) {
- resp->status = vr;
- res = GEMINI_ERR_SSL_VERIFY;
- goto cleanup;
+ // 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);
- BIO_set_ssl(sbio, resp->ssl, 0);
-
- resp->bio = BIO_new(BIO_f_buffer());
- BIO_push(resp->bio, sbio);
+ br_sslio_init(&resp->body, &resp->sc->eng,
+ sock_read, &resp->fd, sock_write, &resp->fd);
char req[1024 + 3];
r = snprintf(req, sizeof(req), "%s\r\n", url);
assert(r > 0);
- r = BIO_puts(sbio, req);
- if (r == -1) {
- res = GEMINI_ERR_IO;
- goto cleanup;
- }
- assert(r == (int)strlen(req));
+ br_sslio_write_all(&resp->body, req, r);
+ br_sslio_flush(&resp->body);
+ // The SSL engine maintains an internal buffer, so this shouldn't be as
+ // inefficient as it looks. It's necessary to do this one byte at a time
+ // to avoid consuming any of the response body buffer.
char buf[GEMINI_META_MAXLEN
+ GEMINI_STATUS_MAXLEN
+ 2 /* CRLF */ + 1 /* NUL */];
- r = BIO_gets(resp->bio, buf, sizeof(buf));
- if (r == -1) {
- res = GEMINI_ERR_IO;
- goto cleanup;
+ memset(buf, 0, sizeof(buf));
+ size_t l;
+ for (l = 0; l < 2 || memcmp(&buf[l-2], "\r\n", 2) != 0; ++l) {
+ r = br_sslio_read(&resp->body, &buf[l], 1);
+ if (r < 0) {
+ break;
+ }
}
- if (r < 3 || strcmp(&buf[r - 2], "\r\n") != 0) {
- fprintf(stderr, "invalid line %d '%s'\n", r, buf);
+ int err = br_ssl_engine_last_error(&resp->sc->eng);
+ if (err != 0) {
+ // TODO: Bubble this up properly
+ fprintf(stderr, "SSL error %d\n", err);
+ goto ssl_error;
+ }
+
+ if (l < 3 || strcmp(&buf[l-2], "\r\n") != 0) {
+ fprintf(stderr, "invalid line '%s'\n", buf);
res = GEMINI_ERR_PROTOCOL;
goto cleanup;
}
@@ -217,9 +235,9 @@ fprintf(stderr, "invalid status\n");
res = GEMINI_ERR_PROTOCOL;
goto cleanup;
}
- resp->meta = calloc(r - 5 /* 2 digits, space, and CRLF */ + 1 /* NUL */, 1);
- strncpy(resp->meta, &endptr[1], r - 5);
- resp->meta[r - 5] = '\0';
+ resp->meta = calloc(l - 5 /* 2 digits, space, and CRLF */ + 1 /* NUL */, 1);
+ strncpy(resp->meta, &endptr[1], l - 5);
+ resp->meta[l - 5] = '\0';
cleanup:
curl_url_cleanup(uri);
@@ -237,26 +255,18 @@ if (!resp) {
return;
}
- if (resp->bio) {
- BIO_free_all(resp->bio);
- resp->bio = NULL;
+ if (resp->fd != -1) {
+ close(resp->fd);
+ resp->fd = -1;
}
- if (resp->ssl) {
- SSL_free(resp->ssl);
- }
- if (resp->ssl_ctx) {
- SSL_CTX_free(resp->ssl_ctx);
- }
free(resp->meta);
- if (resp->fd != -1) {
- close(resp->fd);
- resp->fd = -1;
+ if (resp->sc) {
+ br_sslio_close(&resp->body);
}
- resp->ssl = NULL;
- resp->ssl_ctx = NULL;
+ resp->sc = NULL;
resp->meta = NULL;
}
@@ -277,11 +287,11 @@ return gai_strerror(resp->status);
case GEMINI_ERR_CONNECT:
return strerror(errno);
case GEMINI_ERR_SSL:
- return ERR_error_string(
- SSL_get_error(resp->ssl, resp->status),
- NULL);
+ // TODO: more specific
+ return "SSL error";
case GEMINI_ERR_SSL_VERIFY:
- return X509_verify_cert_error_string(resp->status);
+ // TODO: more specific
+ return "X.509 certificate not trusted";
case GEMINI_ERR_IO:
return "I/O error";
case GEMINI_ERR_PROTOCOL:
diff --git a/src/gmni.c b/src/gmni.c
index 49abb8524a012c27249006dc45f4688a846e63c3..f3015ac679eba77397fb4aac5068f3a63e1cdbab 100644
--- a/src/gmni.c
+++ b/src/gmni.c
@@ -1,9 +1,8 @@
#include <assert.h>
+#include <bearssl.h>
#include <errno.h>
#include <getopt.h>
#include <netdb.h>
-#include <openssl/bio.h>
-#include <openssl/err.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -12,6 +11,7 @@ #include <sys/socket.h>
#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>
@@ -110,6 +110,45 @@
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[])
{
@@ -166,7 +205,7 @@ }
}
break;
case 'E':
- assert(0); // TODO: Client certificates
+ opts.client_cert = load_client_cert(argv[0], optarg);
break;
case 'h':
usage(argv[0]);
@@ -222,15 +261,12 @@ usage(argv[0]);
return 1;
}
- SSL_load_error_strings();
- ERR_load_crypto_strings();
- opts.ssl_ctx = SSL_CTX_new(TLS_method());
- gemini_tofu_init(&cfg.tofu, opts.ssl_ctx, &tofu_callback, &cfg);
+ gemini_tofu_init(&cfg.tofu, &tofu_callback, &cfg);
bool exit = false;
struct Curl_URL *url = curl_url();
- if(curl_url_set(url, CURLUPART_URL, argv[optind], 0) != CURLUE_OK) {
+ if (curl_url_set(url, CURLUPART_URL, argv[optind], 0) != CURLUE_OK) {
// TODO: Better error
fprintf(stderr, "Error: invalid URL\n");
return 1;
@@ -242,7 +278,8 @@ char *buf;
curl_url_get(url, CURLUPART_URL, &buf, 0);
struct gemini_response resp;
- enum gemini_result r = gemini_request(buf, &opts, &resp);
+ enum gemini_result r = gemini_request(buf,
+ &opts, &cfg.tofu, &resp);
free(buf);
@@ -340,11 +377,8 @@
char last = 0;
char buf[BUFSIZ];
for (int n = 1; n > 0;) {
- n = BIO_read(resp.bio, buf, BUFSIZ);
- if (n == -1) {
- fprintf(stderr, "Error: read\n");
- return 1;
- } else if (n != 0) {
+ n = br_sslio_read(&resp.body, buf, BUFSIZ);
+ if (n > 0) {
last = buf[n - 1];
}
ssize_t w = 0;
@@ -370,7 +404,6 @@ next:
gemini_response_finish(&resp);
}
- SSL_CTX_free(opts.ssl_ctx);
curl_url_cleanup(url);
gemini_tofu_finish(&cfg.tofu);
return ret;
diff --git a/src/gmnlm.c b/src/gmnlm.c
index e28bf70afc5d6fd4c5c6eff85d85198d62a127e8..ef4c97950495e369e4fd3b1256c185fdb01f8cf1 100644
--- a/src/gmnlm.c
+++ b/src/gmnlm.c
@@ -1,22 +1,26 @@
#include <assert.h>
+#include <bearssl.h>
#include <ctype.h>
+#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>
#include <libgen.h>
#include <limits.h>
-#include <openssl/bio.h>
-#include <openssl/err.h>
#include <regex.h>
#include <stdbool.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
-#include <gmni/gmni.h>
-#include <gmni/tofu.h>
-#include <gmni/url.h>
#include "util.h"
#define ANSI_COLOR_RED "\x1b[91m"
@@ -400,10 +404,13 @@ close(pfd[0]);
FILE *f = fdopen(pfd[1], "w");
// XXX: may affect history, do we care?
for (int n = 1; n > 0;) {
- n = BIO_read(resp.bio, buf, BUFSIZ);
- if (n == -1) {
- fprintf(stderr, "Error: read\n");
- return;
+ if (resp.sc) {
+ n = br_sslio_read(&resp.body, buf, BUFSIZ);
+ } else {
+ n = read(resp.fd, buf, BUFSIZ);
+ }
+ if (n < 0) {
+ n = 0;
}
ssize_t w = 0;
while (w < (ssize_t)n) {
@@ -430,13 +437,50 @@ {
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;
@@ -447,23 +491,19 @@ resp->status = GEMINI_STATUS_BAD_REQUEST;
break;
}
- FILE *fp = fopen(path, "r");
- if (!fp) {
+ int fd = open(path, O_RDONLY);
+ if (fd < 0) {
resp->status = GEMINI_STATUS_NOT_FOUND;
- /* Make sure members of resp evaluate to false, so that
- gemini_response_finish does not try to free them. */
- resp->bio = NULL;
- resp->ssl = NULL;
- resp->ssl_ctx = NULL;
+ // Make sure members of resp evaluate to false,
+ // so that gemini_response_finish does not try
+ // to free them.
+ resp->sc = NULL;
resp->meta = NULL;
resp->fd = -1;
free(path);
break;
}
- BIO *file = BIO_new_fp(fp, BIO_CLOSE);
- resp->bio = BIO_new(BIO_f_buffer());
- BIO_push(resp->bio, file);
if (has_suffix(path, ".gmi") || has_suffix(path, ".gemini")) {
resp->meta = strdup("text/gemini");
} else if (has_suffix(path, ".txt")) {
@@ -473,14 +513,14 @@ resp->meta = strdup("application/x-octet-stream");
}
free(path);
resp->status = GEMINI_STATUS_SUCCESS;
- resp->fd = -1;
- resp->ssl = NULL;
- resp->ssl_ctx = NULL;
- return GEMINI_OK;
+ resp->fd = fd;
+ resp->sc = NULL;
+ res = GEMINI_OK;
+ goto out;
}
- free(scheme);
- res = gemini_request(browser->plain_url, &browser->opts, resp);
+ res = gemini_request(browser->plain_url, &browser->opts,
+ &browser->tofu, resp);
if (res != GEMINI_OK) {
fprintf(stderr, "Error: %s\n", gemini_strerr(res, resp));
requesting = false;
@@ -519,7 +559,26 @@ }
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");
+ char dname[PATH_MAX + 1];
+ posix_dirname(certpath, dname);
+ if (mkdirs(dname, 0755) != 0) {
+ fprintf(stderr, "Error creating directory %s: %s\n",
+ dname, strerror(errno));
+ break;
+ }
+ 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 +588,7 @@ "TEMPORARY FAILURE" : "PERMANENT FAILURE",
resp->status, resp->meta);
break;
case GEMINI_STATUS_CLASS_SUCCESS:
- return res;
+ goto out;
}
if (requesting) {
@@ -537,6 +596,11 @@ gemini_response_finish(resp);
}
}
+out:
+ if (client_cert.key) {
+ free(client_cert.key);
+ }
+ free(scheme);
return res;
}
@@ -842,12 +906,23 @@ }
return fprintf(f, "%s\n", s) - 1;
}
+static int
+resp_read(void *state, void *buf, size_t nbyte)
+{
+ struct gemini_response *resp = state;
+ if (resp->sc) {
+ return br_sslio_read(&resp->body, buf, nbyte);
+ } else {
+ return read(resp->fd, buf, nbyte);
+ }
+}
+
static bool
display_gemini(struct browser *browser, struct gemini_response *resp)
{
int nlinks = 0;
struct gemini_parser p;
- gemini_parser_init(&p, resp->bio);
+ gemini_parser_init(&p, &resp_read, resp);
free(browser->page_title);
browser->page_title = NULL;
@@ -1036,10 +1111,13 @@ ioctl(fileno(browser->tty), TIOCGWINSZ, &ws);
char buf[BUFSIZ];
for (int n = 1; n > 0;) {
- n = BIO_read(resp->bio, buf, BUFSIZ);
- if (n == -1) {
- fprintf(stderr, "Error: read\n");
- return 1;
+ if (resp->sc) {
+ n = br_sslio_read(&resp->body, buf, BUFSIZ);
+ } else {
+ n = read(resp->fd, buf, BUFSIZ);
+ }
+ if (n < 0) {
+ n = 0;
}
ssize_t w = 0;
while (w < (ssize_t)n) {
@@ -1214,11 +1292,7 @@ } else {
open_bookmarks(&browser);
}
- SSL_load_error_strings();
- ERR_load_crypto_strings();
- browser.opts.ssl_ctx = SSL_CTX_new(TLS_method());
- gemini_tofu_init(&browser.tofu, browser.opts.ssl_ctx,
- &tofu_callback, &browser);
+ gemini_tofu_init(&browser.tofu, &tofu_callback, &browser);
struct gemini_response resp;
browser.running = true;
@@ -1280,7 +1354,6 @@ while (hist && hist->prev) {
hist = hist->prev;
}
history_free(hist);
- SSL_CTX_free(browser.opts.ssl_ctx);
curl_url_cleanup(browser.url);
free(browser.page_title);
free(browser.plain_url);
diff --git a/src/parser.c b/src/parser.c
index ad2c0e6306872f8dde450063ba8815e2ee7e314a..6f12456ddbe5248b59ea146464a1d76365505c56 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1,21 +1,23 @@
#include <assert.h>
#include <ctype.h>
-#include <openssl/bio.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmni/gmni.h>
void
-gemini_parser_init(struct gemini_parser *p, BIO *f)
+gemini_parser_init(struct gemini_parser *p,
+ int (*read)(void *state, void *buf, size_t nbyte),
+ void *state)
{
- p->f = f;
+ p->read = read;
+ p->state = state;
p->bufln = 0;
p->bufsz = BUFSIZ;
p->buf = malloc(p->bufsz + 1);
p->buf[0] = 0;
- BIO_up_ref(p->f);
p->preformatted = false;
}
@@ -25,7 +27,6 @@ {
if (!p) {
return;
}
- BIO_free(p->f);
free(p->buf);
}
@@ -42,7 +43,7 @@ p->buf = realloc(p->buf, p->bufsz);
assert(p->buf);
}
- ssize_t n = BIO_read(p->f, &p->buf[p->bufln], p->bufsz - p->bufln - 1);
+ int n = p->read(p->state, &p->buf[p->bufln], p->bufsz - p->bufln - 1);
if (n == -1) {
return -1;
} else if (n == 0) {
diff --git a/src/tofu.c b/src/tofu.c
index 48395c08cdbf68b31c5defd15b360f1eb2897a3f..54183a79278de45bfbe5a1f8ddbcbf10be1c01c4 100644
--- a/src/tofu.c
+++ b/src/tofu.c
@@ -1,95 +1,92 @@
#include <assert.h>
+#include <bearssl.h>
#include <errno.h>
+#include <gmni/gmni.h>
+#include <gmni/tofu.h>
#include <libgen.h>
#include <limits.h>
-#include <openssl/asn1.h>
-#include <openssl/evp.h>
-#include <openssl/ssl.h>
-#include <openssl/x509.h>
-#include <openssl/x509v3.h>
+#include <stdint.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
-#include <time.h>
-#include <gmni/gmni.h>
-#include <gmni/tofu.h>
#include "util.h"
-static int
-verify_callback(X509_STORE_CTX *ctx, void *data)
+static void
+xt_start_chain(const br_x509_class **ctx, const char *server_name)
{
- // Gemini clients handle TLS verification differently from the rest of
- // the internet. We use a TOFU system, so trust is based on two factors:
- //
- // - Is the certificate valid at the time of the request?
- // - Has the user trusted this certificate yet?
- //
- // If the answer to the latter is "no", then we give the user an
- // opportunity to explicitly agree to trust the certificate before
- // rejecting it.
- //
- // If you're reading this code with the intent to re-use it for
- // something unrelated to Gemini, think twice.
- struct gemini_tofu *tofu = (struct gemini_tofu *)data;
- X509 *cert = X509_STORE_CTX_get0_cert(ctx);
- struct known_host *host = NULL;
+ struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx;
+ cc->server_name = server_name;
+ cc->err = 0;
+ cc->pkey = NULL;
+}
- int rc;
- int day, sec;
- const ASN1_TIME *notBefore = X509_get0_notBefore(cert);
- const ASN1_TIME *notAfter = X509_get0_notAfter(cert);
- if (!ASN1_TIME_diff(&day, &sec, NULL, notBefore)) {
- rc = X509_V_ERR_UNSPECIFIED;
- goto invalid_cert;
+static void
+xt_start_cert(const br_x509_class **ctx, uint32_t length)
+{
+ struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx;
+ if (cc->err != 0 || cc->pkey) {
+ return;
}
- if (day > 0 || sec > 0) {
- rc = X509_V_ERR_CERT_NOT_YET_VALID;
- goto invalid_cert;
+ if (length == 0) {
+ cc->err = BR_ERR_X509_TRUNCATED;
+ return;
}
- if (!ASN1_TIME_diff(&day, &sec, NULL, notAfter)) {
- rc = X509_V_ERR_UNSPECIFIED;
- goto invalid_cert;
+ br_x509_decoder_init(&cc->decoder, NULL, NULL);
+ br_sha512_init(&cc->sha512);
+}
+
+static void
+xt_append(const br_x509_class **ctx, const unsigned char *buf, size_t len)
+{
+ struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx;
+ if (cc->err != 0 || cc->pkey) {
+ return;
}
- if (day < 0 || sec < 0) {
- rc = X509_V_ERR_CERT_HAS_EXPIRED;
- goto invalid_cert;
+ br_x509_decoder_push(&cc->decoder, buf, len);
+ int err = br_x509_decoder_last_error(&cc->decoder);
+ if (err != 0 && err != BR_ERR_X509_TRUNCATED) {
+ cc->err = err;
}
+ br_sha512_update(&cc->sha512, buf, len);
+}
- unsigned char md[512 / 8];
- const EVP_MD *sha512 = EVP_sha512();
- unsigned int len = sizeof(md);
- rc = X509_digest(cert, sha512, md, &len);
- assert(rc == 1);
-
- char fingerprint[512 / 8 * 3];
- for (size_t i = 0; i < sizeof(md); ++i) {
- snprintf(&fingerprint[i * 3], 4, "%02X%s",
- md[i], i + 1 == sizeof(md) ? "" : ":");
+static void
+xt_end_cert(const br_x509_class **ctx)
+{
+ struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx;
+ if (cc->err != 0) {
+ return;
+ }
+ int err = br_x509_decoder_last_error(&cc->decoder);
+ if (err != 0 && err != BR_ERR_X509_TRUNCATED) {
+ cc->err = err;
+ return;
}
+ cc->pkey = br_x509_decoder_get_pkey(&cc->decoder);
+ br_sha512_out(&cc->sha512, &cc->hash);
+}
- SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
- SSL_get_ex_data_X509_STORE_CTX_idx());
- const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
- if (!servername) {
- rc = X509_V_ERR_HOSTNAME_MISMATCH;
- goto invalid_cert;
+static unsigned
+xt_end_chain(const br_x509_class **ctx)
+{
+ struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx;
+ if (cc->err != 0) {
+ return (unsigned)cc->err;
+ }
+ if (!cc->pkey) {
+ return BR_ERR_X509_EMPTY_CHAIN;
}
- rc = X509_check_host(cert, servername, strlen(servername), 0, NULL);
- if (rc != 1) {
- rc = X509_V_ERR_HOSTNAME_MISMATCH;
- goto invalid_cert;
+ char fingerprint[512 / 8 * 3];
+ for (size_t i = 0; i < sizeof(cc->hash); ++i) {
+ snprintf(&fingerprint[i * 3], 4, "%02X%s",
+ cc->hash[i], i + 1 == sizeof(cc->hash) ? "" : ":");
}
- time_t now;
- time(&now);
-
enum tofu_error error = TOFU_UNTRUSTED_CERT;
- host = tofu->known_hosts;
+ struct known_host *host = cc->store->known_hosts;
while (host) {
- if (host->expires < now) {
- goto next;
- }
- if (strcmp(host->host, servername) != 0) {
+ if (strcmp(host->host, cc->server_name) != 0) {
goto next;
}
if (strcmp(host->fingerprint, fingerprint) == 0) {
@@ -102,66 +99,84 @@ next:
host = host->next;
}
- rc = X509_V_ERR_CERT_UNTRUSTED;
-
-callback:
- switch (tofu->callback(error, fingerprint, host, tofu->cb_data)) {
+ switch (cc->store->callback(error, fingerprint,
+ host, cc->store->cb_data)) {
case TOFU_ASK:
assert(0); // Invariant
case TOFU_FAIL:
- X509_STORE_CTX_set_error(ctx, rc);
- break;
+ return BR_ERR_X509_NOT_TRUSTED;
case TOFU_TRUST_ONCE:
// No further action necessary
return 0;
case TOFU_TRUST_ALWAYS:;
- FILE *f = fopen(tofu->known_hosts_path, "a");
+ FILE *f = fopen(cc->store->known_hosts_path, "a");
if (!f) {
fprintf(stderr, "Error opening %s for writing: %s\n",
- tofu->known_hosts_path, strerror(errno));
+ cc->store->known_hosts_path, strerror(errno));
break;
};
- struct tm expires_tm;
- ASN1_TIME_to_tm(notAfter, &expires_tm);
- time_t expires = mktime(&expires_tm);
- fprintf(f, "%s %s %s %jd\n", servername,
- "SHA-512", fingerprint, (intmax_t)expires);
+ fprintf(f, "%s %s %s\n", cc->server_name,
+ "SHA-512", fingerprint);
fclose(f);
host = calloc(1, sizeof(struct known_host));
- host->host = strdup(servername);
+ host->host = strdup(cc->server_name);
host->fingerprint = strdup(fingerprint);
- host->expires = expires;
- host->lineno = ++tofu->lineno;
- host->next = tofu->known_hosts;
- tofu->known_hosts = host;
+ host->lineno = ++cc->store->lineno;
+ host->next = cc->store->known_hosts;
+ cc->store->known_hosts = host;
return 0;
}
- X509_STORE_CTX_set_error(ctx, rc);
- return 0;
+ assert(0); // Unreachable
+}
-invalid_cert:
- error = TOFU_INVALID_CERT;
- goto callback;
+static const br_x509_pkey *
+xt_get_pkey(const br_x509_class *const *ctx, unsigned *usages)
+{
+ struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx;
+ if (cc->err != 0) {
+ return NULL;
+ }
+ if (usages) {
+ // XXX: BearSSL doesn't pull the usages out of the X.509 for us
+ *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN;
+ }
+ return cc->pkey;
}
+const br_x509_class xt_vtable = {
+ sizeof(struct x509_tofu_context),
+ xt_start_chain,
+ xt_start_cert,
+ xt_append,
+ xt_end_cert,
+ xt_end_chain,
+ xt_get_pkey,
+};
+
+static void
+x509_init_tofu(struct x509_tofu_context *ctx, struct gemini_tofu *store)
+{
+ ctx->vtable = &xt_vtable;
+ ctx->store = store;
+}
void
-gemini_tofu_init(struct gemini_tofu *tofu,
- SSL_CTX *ssl_ctx, tofu_callback_t *cb, void *cb_data)
+gemini_tofu_init(struct gemini_tofu *tofu, tofu_callback_t *cb, void *cb_data)
{
const struct pathspec paths[] = {
{.var = "GMNIDATA", .path = "/%s"},
- {.var = "XDG_DATA_HOME", .path = "/gemini/%s"},
- {.var = "HOME", .path = "/.local/share/gemini/%s"}
+ {.var = "XDG_DATA_HOME", .path = "/gmni/%s"},
+ {.var = "HOME", .path = "/.local/share/gmni/%s"}
};
char *path_fmt = getpath(paths, sizeof(paths) / sizeof(paths[0]));
char dname[PATH_MAX+1];
size_t n = 0;
- n = snprintf(tofu->known_hosts_path, sizeof(tofu->known_hosts_path),
- path_fmt, "known_hosts");
+ n = snprintf(tofu->known_hosts_path,
+ sizeof(tofu->known_hosts_path),
+ path_fmt, "known_hosts");
assert(n < sizeof(tofu->known_hosts_path));
strncpy(dname, dirname(tofu->known_hosts_path), sizeof(dname)-1);
@@ -179,10 +194,17 @@ free(path_fmt);
tofu->callback = cb;
tofu->cb_data = cb_data;
- SSL_CTX_set_cert_verify_callback(ssl_ctx, verify_callback, tofu);
tofu->known_hosts = NULL;
+ x509_init_tofu(&tofu->x509_ctx, tofu);
+
+ br_x509_minimal_context _; // Discarded
+ br_ssl_client_init_full(&tofu->sc, &_, NULL, 0);
+ br_ssl_engine_set_x509(&tofu->sc.eng, &tofu->x509_ctx.vtable);
+ br_ssl_engine_set_buffer(&tofu->sc.eng,
+ &tofu->iobuf, sizeof(tofu->iobuf), 1);
+
FILE *f = fopen(tofu->known_hosts_path, "r");
if (!f) {
return;
@@ -191,6 +213,11 @@ n = 0;
int lineno = 1;
char *line = NULL;
while (getline(&line, &n, f) != -1) {
+ int ln = strlen(line);
+ if (line[ln-1] == '\n') {
+ line[ln-1] = 0;
+ }
+
struct known_host *host = calloc(1, sizeof(struct known_host));
char *tok = strtok(line, " ");
assert(tok);
@@ -207,10 +234,6 @@
tok = strtok(NULL, " ");
assert(tok);
host->fingerprint = strdup(tok);
-
- tok = strtok(NULL, " ");
- assert(tok);
- host->expires = strtoul(tok, NULL, 10);
host->lineno = lineno++;
diff --git a/src/util.c b/src/util.c
index 2f62c29ce40ac012993e1d208d65491b56d204aa..8441b584ff199ffd81472539a59acce5d0a18f42 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1,5 +1,7 @@
#include <assert.h>
+#include <bearssl.h>
#include <errno.h>
+#include <gmni/gmni.h>
#include <libgen.h>
#include <limits.h>
#include <stdint.h>
@@ -7,10 +9,9 @@ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
-#include <gmni/gmni.h>
#include "util.h"
-static void
+void
posix_dirname(char *path, char *dname)
{
char p[PATH_MAX+1];
@@ -82,7 +83,7 @@ }
fprintf(out, "Downloading %s to %s\n", url, path);
char buf[BUFSIZ];
for (int n = 1; n > 0;) {
- n = BIO_read(resp.bio, buf, sizeof(buf));
+ n = br_sslio_read(&resp.body, buf, sizeof(buf));
if (n == -1) {
fprintf(stderr, "Error: read\n");
return 1;