From 46a9f1c87cb36c82fc99b084cda2ae5a7bb91284 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 15 Oct 2015 17:54:41 +0200 Subject: Support certificate verification by ca-bundle and hostname --- ldadp.c | 2 ++ openssl.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ openssl.h | 2 +- server.c | 19 ++++++++++- server.h | 2 ++ types.h | 2 ++ 6 files changed, 123 insertions(+), 12 deletions(-) diff --git a/ldadp.c b/ldadp.c index b1a5a73..7371af6 100644 --- a/ldadp.c +++ b/ldadp.c @@ -127,6 +127,8 @@ static int loadConfig_handler(void *stuff, const char *section, const char *key, if (value[0] != '\0') server_setHomeTemplate(section, value); } else if (strcmp(key, "fingerprint") == 0) { if (value[0] != '\0') server_setFingerprint(section, value); + } else if (strcmp(key, "cabundle") == 0) { + if (value[0] != '\0') server_setCaBundle(section, value); } else if (strcmp(key, "port") == 0) { server_setPort(section, value); } else if (strcmp(key, "plainldap") == 0) { diff --git a/openssl.c b/openssl.c index 47acd83..a1684cd 100644 --- a/openssl.c +++ b/openssl.c @@ -1,9 +1,14 @@ #include "openssl.h" #include "helper.h" +#include +#include +#include static BOOL initDone = FALSE; static const EVP_MD *sha1 = NULL; +static BOOL spc_verify_cert_hostname(X509 *cert, const char *hostname); + void ssl_printErrors(char *bailMsg) { unsigned long err; @@ -31,8 +36,7 @@ SSL_CTX* ssl_newServerCtx(char *certfile, char *keyfile) if (m == NULL) ssl_printErrors("newServerCtx: method is NULL"); SSL_CTX *ctx = SSL_CTX_new(m); if (ctx == NULL) ssl_printErrors("newServerCtx: ctx is NULL"); - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM); SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM); if (!SSL_CTX_check_private_key(ctx)) ssl_printErrors("Could not load cert/private key"); @@ -40,7 +44,7 @@ SSL_CTX* ssl_newServerCtx(char *certfile, char *keyfile) return ctx; } -SSL_CTX* ssl_newClientCtx() +SSL_CTX* ssl_newClientCtx(const char *cabundle) { const SSL_METHOD *m = SSLv23_client_method(); if (m == NULL) ssl_printErrors("newClientCtx: method is NULL"); @@ -48,6 +52,10 @@ SSL_CTX* ssl_newClientCtx() if (ctx == NULL) ssl_printErrors("newClientCtx: ctx is NULL"); SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + if (cabundle != NULL && cabundle[0] != '\0') { + SSL_CTX_load_verify_locations(ctx, cabundle, NULL); + //SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + } return ctx; } @@ -87,7 +95,7 @@ BOOL ssl_connectServer(epoll_server_t *server) int ret = SSL_connect(server->ssl); if (ret == 1) { if (!ssl_checkCertificateHash(server)) { - printf("Warning: Fingerprint of %s doesn't match value given in config, refusing to talk to server!\n", server->serverData->addr); + printf("Warning: Certificate invalid, refusing to talk to server (%s)\n", server->serverData->addr); return FALSE; } server->sslConnected = TRUE; @@ -99,7 +107,7 @@ BOOL ssl_connectServer(epoll_server_t *server) if (err == SSL_ERROR_SSL) { ssl_printErrors(NULL); } else { - printf("SSL Unknown error %d\n", err); + printf("SSL connect error %d\n", err); } } return FALSE; @@ -111,20 +119,100 @@ BOOL ssl_checkCertificateHash(epoll_server_t *server) printf("Bug: Asked to check certificate of non-SSL connection\n"); return FALSE; } + // Get server cert + X509 *cert = SSL_get_peer_certificate(server->ssl); + if (cert == NULL) { + printf("Warning: Server %s has no certificate!\n", server->serverData->addr); + return FALSE; + } + // Do we have a cabundle set? + if (server->serverData->cabundle[0] != '\0') { + BOOL hostOk = spc_verify_cert_hostname(cert, server->serverData->addr); + X509_free(cert); + if (!hostOk) { + printf("Warning: Server certificate's host name doesn't match '%s'\n", server->serverData->addr); + return FALSE; + } + long res = SSL_get_verify_result(server->ssl); + if(X509_V_OK != res) { + printf("Warning: Server %s's certificate cannot be verified with given cabundle %s\n", + server->serverData->addr, server->serverData->cabundle); + return FALSE; + } + return TRUE; + } + // No cabundle, try fingerprint for (int i = 0; i < FINGERPRINTLEN; ++i) { if (server->serverData->fingerprint[i] != 0) { unsigned char md[EVP_MAX_MD_SIZE]; unsigned int n = 20; - X509 *cert = SSL_get_peer_certificate(server->ssl); - if (cert == NULL) { - printf("Warning: Server %s has no certificate!\n", server->serverData->addr); - return FALSE; - } X509_free(cert); X509_digest(cert, sha1, md, &n); return n == 20 && memcmp(md, server->serverData->fingerprint, n) == 0; } } + X509_free(cert); return TRUE; } +static BOOL wcmatch(const char *pattern, const char *string) +{ + if (pattern[0] != '*') + return strcasecmp(string, pattern) == 0; + if (pattern[1] != '.') + return FALSE; + if (strcasecmp(string, pattern + 2) == 0) + return TRUE; + // Match from back of string + const size_t slen = strlen(string); + const size_t plen = strlen(pattern + 1); + if (slen < plen) + return FALSE; + return strcasecmp(string + (slen - plen), pattern + 1) == 0; +} + +// Based on +// https://wiki.openssl.org/index.php/Hostname_validation +static BOOL spc_verify_cert_hostname(X509 *cert, const char *hostname) +{ + BOOL ok = FALSE; + char name[256]; + X509_NAME *subj; + int i; + int san_names_nb = -1; + STACK_OF(GENERAL_NAME) *san_names = NULL; + + // Try to extract the names within the SAN extension from the certificate + san_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (san_names != NULL) { + san_names_nb = sk_GENERAL_NAME_num(san_names); + + // Check each name within the extension + for (i = 0; i < san_names_nb; i++) { + const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i); + if (current_name->type != GEN_DNS) + continue; + + // Current name is a DNS name, let's check it + char *dns_name = (char*)ASN1_STRING_data(current_name->d.dNSName); + // Make sure there isn't an embedded null character in the DNS name + if ((size_t) ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) + break; + // Compare expected hostname with the DNS name + if (wcmatch(dns_name, hostname)) { + ok = TRUE; + break; + } + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + } + + if (!ok && (subj = X509_get_subject_name(cert))) { + const size_t len = (size_t)X509_NAME_get_text_by_NID(subj, NID_commonName, name, sizeof(name)); + if (len > 0 && strlen(name) == len && wcmatch(name, hostname)) { + ok = TRUE; + } + } + + return ok; +} diff --git a/openssl.h b/openssl.h index bde6ef4..562e36c 100644 --- a/openssl.h +++ b/openssl.h @@ -13,7 +13,7 @@ BOOL ssl_init(); SSL_CTX* ssl_newServerCtx(char *certfile, char *keyfile); -SSL_CTX* ssl_newClientCtx(); +SSL_CTX* ssl_newClientCtx(const char *cabundle); SSL *ssl_new(int clientFd, SSL_CTX *ctx); diff --git a/server.c b/server.c index a3e0893..c4f8eb9 100644 --- a/server.c +++ b/server.c @@ -11,6 +11,7 @@ #include #include #include +#include #define AD_PORT 3268 #define AD_PORT_SSL 636 @@ -83,6 +84,20 @@ void server_setBase(const char *server, const char *base) entry->base[entry->baseLen] = '\0'; } +void server_setCaBundle(const char *server, const char *file) +{ + server_t *entry = server_create(server); + if (entry == NULL) return; + int fh = open(file, O_RDONLY); + if (fh == -1) { + printf("Error: cabundle '%s' not readable.\n", file); + exit(1); + } + close(fh); + if (snprintf(entry->cabundle, MAXPATH, "%s", file) >= MAXPATH) printf("Warning: CaBundle for %s is too long.\n", server); + ssl_init(); +} + void server_setHomeTemplate(const char *server, const char *hometemplate) { server_t *entry = server_create(server); @@ -142,7 +157,6 @@ void server_setFingerprint(const char *server, const char *fingerprint) } printf("%02x for %s\n", (int)entry->fingerprint[FINGERPRINTLEN-1], server); ssl_init(); - entry->sslContext = ssl_newClientCtx(); } BOOL server_initServers() @@ -150,6 +164,9 @@ BOOL server_initServers() int i; printf("%d servers configured.\n", serverCount); for (i = 0; i < serverCount; ++i) { + if (servers[i].cabundle[0] != '\0' || memcmp(servers[i].fingerprint, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) != 0) { + servers[i].sslContext = ssl_newClientCtx(servers[i].cabundle); + } printf("%s:\n Bind: %s\n Base: %s\n", servers[i].addr, servers[i].bind, servers[i].base); if (!server_ensureConnected(&servers[i])) return FALSE; diff --git a/server.h b/server.h index 417fa88..f50a491 100644 --- a/server.h +++ b/server.h @@ -20,6 +20,8 @@ void server_setHomeTemplate(const char *server, const char *hometemplate); void server_setFingerprint(const char *server, const char *fingerprint); +void server_setCaBundle(const char *server, const char *file); + BOOL server_initServers(); void server_free(epoll_server_t *server); diff --git a/types.h b/types.h index d39c3be..23888db 100644 --- a/types.h +++ b/types.h @@ -13,6 +13,7 @@ #define SIDLEN 28 #define MOUNTLEN 100 #define FINGERPRINTLEN 20 +#define MAXPATH 200 #define REQLEN 4000 #define MAXMSGLEN 100000 @@ -102,6 +103,7 @@ struct _server_t_ { char sid[SIDLEN]; char homeTemplate[MOUNTLEN]; unsigned char fingerprint[FINGERPRINTLEN]; + char cabundle[MAXPATH]; BOOL plainLdap; uint16_t port; SSL_CTX *sslContext; -- cgit v1.2.3-55-g7522