summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2015-10-15 17:54:41 +0200
committerSimon Rettberg2015-10-15 17:54:41 +0200
commit46a9f1c87cb36c82fc99b084cda2ae5a7bb91284 (patch)
treeee19a16d7dfbfc2766c5e1b4ceccb5758e0cb568
parentOnly relay whitelisted fields to client on anonymous bind connections (diff)
downloadldadp-46a9f1c87cb36c82fc99b084cda2ae5a7bb91284.tar.gz
ldadp-46a9f1c87cb36c82fc99b084cda2ae5a7bb91284.tar.xz
ldadp-46a9f1c87cb36c82fc99b084cda2ae5a7bb91284.zip
Support certificate verification by ca-bundle and hostname
-rw-r--r--ldadp.c2
-rw-r--r--openssl.c108
-rw-r--r--openssl.h2
-rw-r--r--server.c19
-rw-r--r--server.h2
-rw-r--r--types.h2
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 <string.h>
+#include <openssl/conf.h>
+#include <openssl/x509v3.h>
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 <unistd.h>
#include <errno.h>
#include <socket.h>
+#include <fcntl.h>
#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;