summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornytpu <alex@nytpu.com>2021-02-10 18:14:41 -0700
committerDrew DeVault <sir@cmpwn.com>2021-02-11 09:19:16 -0500
commitae7ca3db3983321c0ada8416cc19f17190802f38 (patch)
tree8127b7f7f05cb3372d459bb841fd511b627b9d69
parent6d9dd838e439bbc730d12ce81214046cde9227dd (diff)
downloadgmnisrv-ae7ca3db3983321c0ada8416cc19f17190802f38.tar.gz
gmnisrv-ae7ca3db3983321c0ada8416cc19f17190802f38.tar.xz
gmnisrv-ae7ca3db3983321c0ada8416cc19f17190802f38.zip
Send client certificate hash for CGI scripts.
Set SSL_VERIFY_PEER to request a client certificate from the server, when available. Have to shim the certificate verification function or else it will fail on self-signed client certs. In serve_cgi retrieve client certificate, create a fingerprint, and set proper environment variables. It's pretty barebones, it doesn't parse the certificate to give any other useful info like the common name, but it's acceptable IMO. For most CGI uses the fingerprint is the only thing that is needed anyways.
-rw-r--r--doc/gmnisrvini.scd6
-rw-r--r--src/serve.c31
-rw-r--r--src/tls.c11
3 files changed, 47 insertions, 1 deletions
diff --git a/doc/gmnisrvini.scd b/doc/gmnisrvini.scd
index 69e8129..2c82b53 100644
--- a/doc/gmnisrvini.scd
+++ b/doc/gmnisrvini.scd
@@ -175,6 +175,12 @@ The following environment variables will be set:
| *TLS_VERSION*
: TLSv1.3
: The negotiated TLS version.
+| *AUTH_TYPE*
+: CERTIFICATE
+: Compatibility with RFC 3785.
+| *TLS_CLIENT_HASH*
+: SHA256:BD3A388021A92017B781504A3D24F324BF9DE11CE72606AB445D98A8EB00C5A8
+: Unique fingerprint of the client certificate.
\[1]: gemini://example.org/cgi-bin/foo.sh/bar?hello=world
diff --git a/src/serve.c b/src/serve.c
index 4b63dde..d77c2ff 100644
--- a/src/serve.c
+++ b/src/serve.c
@@ -199,7 +199,36 @@ serve_cgi(struct gmnisrv_client *client, const char *path,
setenv("TLS_CIPHER", SSL_CIPHER_get_name(cipher), 1);
setenv("TLS_VERSION", SSL_CIPHER_get_version(cipher), 1);
- // TODO: Client certificate details
+ // barebones client cert implementation
+ // adapted from openssl(1)'s implementation
+ // TODO: support REMOTE_USER, TLS_CLIENT_NOT_{BEFORE,AFTER},
+ // TLS_CLIENT_SERIAL_NUMBER
+ X509 *client_cert = SSL_get_peer_certificate(client->ssl);
+ if (client_cert != NULL) {
+ // 32 bytes because we're always using SHA256, but
+ // possibly change to EVP_MAX_MD_SIZE to support all
+ // of openssl's hash funcs
+ unsigned char digest[32];
+
+ if (X509_digest(client_cert, EVP_sha256(), digest, NULL)) {
+ setenv("AUTH_TYPE", "CERTIFICATE", 1);
+ // 32*2 because converting to hex doubles length
+ // +7 for "SHA256:" prefix
+ // +1 for null char
+ char hex_digest[32*2 + 7 + 1];
+ strncat(hex_digest, "SHA256:", 8);
+
+ char *cur_pos = hex_digest + 7;
+ for (int i = 0; i < 32; ++i) {
+ cur_pos += sprintf(cur_pos, "%02X", digest[i]);
+ }
+ setenv("TLS_CLIENT_HASH", hex_digest, 1);
+ } else {
+ const char *error = "Out of memory";
+ client_submit_response(client,
+ GEMINI_STATUS_TEMPORARY_FAILURE, error, NULL);
+ }
+ }
execlp(path, path, NULL);
server_error("execlp: %s", strerror(errno));
diff --git a/src/tls.c b/src/tls.c
index 26785a0..284cbef 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -15,6 +15,14 @@
#include "util.h"
static int
+always_true_callback(X509_STORE_CTX *ctx, void *arg)
+{
+ (void)(ctx);
+ (void)(arg);
+ return 1;
+}
+
+static int
tls_host_gencert(struct gmnisrv_tls *tlsconf, struct gmnisrv_host *host,
const char *crtpath, const char *keypath)
{
@@ -185,6 +193,9 @@ tls_init(struct gmnisrv_config *conf)
assert(r == 1);
SSL_CTX_set_tlsext_servername_callback(conf->tls.ssl_ctx, NULL);
+ SSL_CTX_set_verify(conf->tls.ssl_ctx, SSL_VERIFY_PEER, NULL);
+ // use always_true_callback to ignore errors such as self-signed error
+ SSL_CTX_set_cert_verify_callback(conf->tls.ssl_ctx, always_true_callback, NULL);
// TLS re-negotiation is a fucking STUPID idea
// I'm gating this behind an #ifdef based on an optimistic assumption