forked from aniani/gmnisrv
1
0
Fork 0

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.
This commit is contained in:
nytpu 2021-02-10 18:14:41 -07:00 committed by Drew DeVault
parent 6d9dd838e4
commit ae7ca3db39
3 changed files with 47 additions and 1 deletions

View File

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

View File

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

View File

@ -14,6 +14,14 @@
#include "tls.h"
#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