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 --- openssl.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 10 deletions(-) (limited to 'openssl.c') 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; +} -- cgit v1.2.3-55-g7522