From 573a8888dff7873420d54c4850c517f2f865a417 Mon Sep 17 00:00:00 2001
From: Florian Westphal <fw@strlen.de>
Date: Mon, 18 May 2009 00:29:02 +0200
Subject: [PATCH 1/4] SSL/TLS: Add initial certificate support to OpenSSL
 backend

new config options:

in global section:
SSLCAFile = /the/file/with/trusted/ca/certificates
Filename pointing to the Trusted CA Certificates. Required for
verifying peer certificates.

SSLCRLFile = /the/file/with/revoked/certificates
(not even tested 8-> )

SSLRequireClientCert
Do not accept incoming SSL connections from clients that do not have a valid
certificate

in [server] section:
SSLVerify: (yes|no)
Verify Server Peer Certificate. If this is an active connection (i.e.  ngircd
connects to the peer), the ssl handshake is aborted if the certificate of the
remote server cannot be validated.

If this is a passive connection (ngircd waits for the remote server to connect)
and SSLRequireClientCert is false, the server link will only be established if
the password matches and a valid certificate was received.  (obvioulsy, because
its not possible to know in advance if the incoming connection is from a server
or an irc client, the ssl handshake always completes; the connection will be
shut down again once the SERVER command is received (and the connection was
established without /invalid certificate).
---
 doc/sample-ngircd.conf.tmpl |   10 ++
 man/ngircd.conf.5.tmpl      |   29 ++++
 src/ngircd/conf.c           |   50 ++++++-
 src/ngircd/conf.h           |    4 +
 src/ngircd/conn-ssl.c       |  320 ++++++++++++++++++++++++++++++++++++++++---
 src/ngircd/conn.c           |   32 +++--
 src/ngircd/conn.h           |    4 +-
 src/ngircd/irc-server.c     |   16 +++
 8 files changed, 435 insertions(+), 30 deletions(-)

diff --git a/doc/sample-ngircd.conf.tmpl b/doc/sample-ngircd.conf.tmpl
index 997a983..286d67f 100644
--- a/doc/sample-ngircd.conf.tmpl
+++ b/doc/sample-ngircd.conf.tmpl
@@ -222,9 +222,16 @@
 	# is only available when ngIRCd is compiled with support for SSL!
 	# So don't forget to remove the ";" above if this is the case ...
 
+	# SSL Trusted CA Certificates File (for verifying peer certificates)
+	;CAFile = /etc/ssl/CA/cacert.pem
+
 	# SSL Server Key Certificate
 	;CertFile = :ETCDIR:/ssl/server-cert.pem
 
+	# Certificate Revocation File (for marking otherwise vaild certficates
+	# as revoked/invalid)
+	;CRLFile = /etc/ssl/CA/crl.pem
+
 	# Diffie-Hellman parameters
 	;DHFile = :ETCDIR:/ssl/dhparams.pem
 
@@ -237,6 +244,9 @@
 	# Additional Listen Ports that expect SSL/TLS encrypted connections
 	;Ports = 6697, 9999
 
+	# Enforce Client Certificates? (Default: false)
+	;RequireClientCert = true
+
 [Operator]
 	# [Operator] sections are used to define IRC Operators. There may be
 	# more than one [Operator] block, one for each local operator.
diff --git a/man/ngircd.conf.5.tmpl b/man/ngircd.conf.5.tmpl
index e3f62c8..e59239b 100644
--- a/man/ngircd.conf.5.tmpl
+++ b/man/ngircd.conf.5.tmpl
@@ -104,6 +104,8 @@ command. This information is not required by the server but by RFC!
 \fBInfo\fR (string)
 Info text of the server. This will be shown by WHOIS and LINKS requests for
 example.
+=======
+>>>>>>> SSL/TLS: Add initial certificate support to OpenSSL backend
 .TP
 \fBListen\fR (list of strings)
 A comma separated list of IP address on which the server should listen.
@@ -340,9 +342,16 @@ All SSL-related configuration variables are located in the
 section. Please note that this whole section is only recognized by ngIRCd
 when it is compiled with support for SSL using OpenSSL or GnuTLS!
 .TP
+\fBCAFile\fR
+Filename pointing to the Trusted CA Certificates. This is required for
+verifying peer certificates.
+.TP
 \fBCertFile\fR (string)
 SSL Certificate file of the private server key.
 .TP
+\fBCRLFile\fR
+Filename of Certificate Revocation List.
+.TP
 \fBDHFile\fR (string)
 Name of the Diffie-Hellman Parameter file. Can be created with GnuTLS
 "certtool \-\-generate-dh-params" or "openssl dhparam". If this file is not
@@ -362,6 +371,11 @@ OpenSSL only: Password to decrypt the private key file.
 Same as \fBPorts\fR , except that ngIRCd will expect incoming connections
 to be SSL/TLS encrypted. Common port numbers for SSL-encrypted IRC are 6669
 and 6697. Default: none.
+\fBRequireClientCert\fR
+Do not accept SSL connections from clients that do not have a valid
+certificate. Defaults to false.
+Also see \fBSSLVerify\fR below.
+.TP
 .SH [OPERATOR]
 .I [Operator]
 sections are used to define IRC Operators. There may be more than one
@@ -428,11 +442,26 @@ You can use the IRC Operator command CONNECT later on to create the link.
 \fBSSLConnect\fR (boolean)
 Connect to the remote server using TLS/SSL. Default: false.
 .TP
+<<<<<<< HEAD
 \fBServiceMask\fR (string)
 Define a (case insensitive) list of masks matching nicknames that should be
 treated as IRC services when introduced via this remote server, separated
 by commas (","). REGULAR SERVERS DON'T NEED this parameter, so leave it empty
 (which is the default).
+=======
+\fBSSLVerify\fR
+Verify Server Peer Certificate (Default: false).
+If this is an active connection (i.e. ngircd connects to the peer), the ssl handshake
+is aborted if the certificate of the remote server cannot be validated.
+If this is a passive connection (ngircd waits for the remote server to connect) and
+\fBfBSSLRequireClientCert\fR is false, the server link will only be established if the
+password matches and a valid certificate was received.
+.TP
+\fBServiceMask\fR
+Define a (case insensitive) mask matching nick names that should be treated as
+IRC services when introduced via this remote server. REGULAR SERVERS DON'T NEED
+this parameter, so leave it empty (which is the default).
+>>>>>>> SSL/TLS: Add initial certificate support to OpenSSL backend
 .PP
 .RS
 When you are connecting IRC services which mask as a IRC server and which use
diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c
index bea4d61..23dcd51 100644
--- a/src/ngircd/conf.c
+++ b/src/ngircd/conf.c
@@ -101,8 +101,16 @@ ConfSSL_Init(void)
 	free(Conf_SSLOptions.CertFile);
 	Conf_SSLOptions.CertFile = NULL;
 
+	free(Conf_SSLOptions.CAFile);
+	Conf_SSLOptions.CAFile = NULL;
+
+	free(Conf_SSLOptions.CRLFile);
+	Conf_SSLOptions.CRLFile = NULL;
+
 	free(Conf_SSLOptions.DHFile);
 	Conf_SSLOptions.DHFile = NULL;
+
+	Conf_SSLOptions.RequireClientCert = false;
 	array_free_wipe(&Conf_SSLOptions.KeyFilePassword);
 
 	array_free(&Conf_SSLOptions.ListenPorts);
@@ -132,9 +140,6 @@ CheckFileReadable(const char *Var, const char *Filename)
 			     Filename, Var, strerror(errno));
 }
 
-#endif
-
-
 /**
  * Duplicate string and warn on errors.
  *
@@ -393,8 +398,12 @@ Conf_Test( void )
 
 #ifdef SSL_SUPPORT
 	puts("[SSL]");
+	printf("  CAFile = %s\n", Conf_SSLOptions.CAFile
+					? Conf_SSLOptions.CAFile : "");
 	printf("  CertFile = %s\n", Conf_SSLOptions.CertFile
 					? Conf_SSLOptions.CertFile : "");
+	printf("  CRLFile = %s\n", Conf_SSLOptions.CRLFile
+					? Conf_SSLOptions.CRLFile : "");
 	printf("  DHFile = %s\n", Conf_SSLOptions.DHFile
 					? Conf_SSLOptions.DHFile : "");
 	printf("  KeyFile = %s\n", Conf_SSLOptions.KeyFile
@@ -406,6 +415,8 @@ Conf_Test( void )
 	array_free_wipe(&Conf_SSLOptions.KeyFilePassword);
 	printf("  Ports = ");
 	ports_puts(&Conf_SSLOptions.ListenPorts);
+	printf("  RequireClientCert = %s\n",
+	       yesno_to_str(Conf_SSLOptions.RequireClientCert));
 	puts("");
 #endif
 
@@ -420,7 +431,8 @@ Conf_Test( void )
 		printf( "  Host = %s\n", Conf_Server[i].host );
 		printf( "  Port = %u\n", (unsigned int)Conf_Server[i].port );
 #ifdef SSL_SUPPORT
-		printf( "  SSLConnect = %s\n", Conf_Server[i].SSLConnect?"yes":"no");
+		printf( "  SSLConnect = %s\n", yesno_to_str(Conf_Server[i].SSLConnect));
+		printf( "  SSLVerify = %s\n", yesno_to_str(Conf_Server[i].SSLVerify));
 #endif
 		printf( "  MyPassword = %s\n", Conf_Server[i].pwd_in );
 		printf( "  PeerPassword = %s\n", Conf_Server[i].pwd_out );
@@ -1638,11 +1650,21 @@ Handle_SSL(int Line, char *Var, char *Arg)
 	assert(Var != NULL);
 	assert(Arg != NULL);
 
+	if (strcasecmp(Var, "CAFile") == 0) {
+		assert(Conf_SSLOptions.CAFile == NULL);
+		Conf_SSLOptions.CAFile = strdup_warn(Arg);
+                return;
+	}
 	if (strcasecmp(Var, "CertFile") == 0) {
 		assert(Conf_SSLOptions.CertFile == NULL);
 		Conf_SSLOptions.CertFile = strdup_warn(Arg);
 		return;
 	}
+	if (strcasecmp(Var, "CRLFile") == 0) {
+		assert(Conf_SSLOptions.CRLFile == NULL);
+		Conf_SSLOptions.CRLFile = strdup_warn(Arg);
+                return;
+        }
 	if (strcasecmp(Var, "DHFile") == 0) {
 		assert(Conf_SSLOptions.DHFile == NULL);
 		Conf_SSLOptions.DHFile = strdup_warn(Arg);
@@ -1666,6 +1688,11 @@ Handle_SSL(int Line, char *Var, char *Arg)
 		ports_parse(&Conf_SSLOptions.ListenPorts, Line, Arg);
 		return;
 	}
+	if (strcasecmp(Var, "RequireClientCert") == 0) {
+		Conf_SSLOptions.RequireClientCert = Check_ArgIsTrue(Arg);
+                return;
+	}
+#endif
 
 	Config_Error_Section(Line, Var, "SSL");
 }
@@ -1795,6 +1822,10 @@ Handle_SERVER( int Line, char *Var, char *Arg )
 		New_Server.SSLConnect = Check_ArgIsTrue(Arg);
 		return;
         }
+	if( strcasecmp( Var, "SSLVerify" ) == 0 ) {
+		New_Server.SSLVerify = Check_ArgIsTrue(Arg);
+		return;
+        }
 #endif
 	if( strcasecmp( Var, "Group" ) == 0 ) {
 		/* Server group */
@@ -2028,6 +2059,10 @@ Validate_Config(bool Configtest, bool Rehash)
 	    array_length(&Conf_Channels, sizeof(struct Conf_Channel)));
 #endif
 
+#ifdef SSL_SUPPORT
+	if (Conf_SSLOptions.RequireClientCert && array_bytes(&Conf_ListenPorts))
+		Log(LOG_WARNING, "SSL certificate validation enabled, (SSLRequireClientCert=yes), but non-SSL listening ports are set");
+#endif
 	return config_valid;
 }
 
@@ -2156,6 +2191,11 @@ Init_Server_Struct( CONF_SERVER *Server )
 	Proc_InitStruct(&Server->res_stat);
 	Server->conn_id = NONE;
 	memset(&Server->bind_addr, 0, sizeof(Server->bind_addr));
-}
+	memset(&Server->bind_addr, 0, sizeof(Server->bind_addr));
+#ifdef SSL_SUPPORT
+	Server->SSLVerify = false;
+#endif
+} /* Init_Server_Struct */
+
 
 /* -eof- */
diff --git a/src/ngircd/conf.h b/src/ngircd/conf.h
index f85a25f..de566ab 100644
--- a/src/ngircd/conf.h
+++ b/src/ngircd/conf.h
@@ -61,6 +61,7 @@ typedef struct _Conf_Server
 	ng_ipaddr_t dst_addr[2];	/**< List of addresses to connect to */
 #ifdef SSL_SUPPORT
 	bool SSLConnect;		/**< Establish connection using SSL? */
+	bool SSLVerify;			/**< Verify server cert using CA? */
 #endif
 	char svs_mask[CLIENT_ID_LEN];	/**< Mask of nicknames that should be
 					     treated and counted as services */
@@ -73,6 +74,9 @@ struct SSLOptions {
 	char *KeyFile;			/**< SSL key file */
 	char *CertFile;			/**< SSL certificate file */
 	char *DHFile;			/**< File containing DH parameters */
+	char *CAFile;			/**< CA file */
+	char *CRLFile;			/**< CRL file */
+	bool RequireClientCert;		/**< Client certificate required? */
 	array ListenPorts;		/**< Array of listening SSL ports */
 	array KeyFilePassword;		/**< Key file password */
 };
diff --git a/src/ngircd/conn-ssl.c b/src/ngircd/conn-ssl.c
index 59729e0..898130e 100644
--- a/src/ngircd/conn-ssl.c
+++ b/src/ngircd/conn-ssl.c
@@ -42,8 +42,11 @@ static SSL_CTX * ssl_ctx;
 static DH *dh_params;
 
 static bool ConnSSL_LoadServerKey_openssl PARAMS(( SSL_CTX *c ));
+static bool ConnSSL_SetVerifyProperties_openssl PARAMS(( SSL_CTX *c ));
 #endif
 
+#define MAX_CERT_CHAIN_LENGTH	10 /* XXX: do not hardcode */
+
 #ifdef HAVE_LIBGNUTLS
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -54,15 +57,22 @@ static bool ConnSSL_LoadServerKey_openssl PARAMS(( SSL_CTX *c ));
 #define DH_BITS 2048
 #define DH_BITS_MIN 1024
 
+gnutls_x509_crl_t *crl_list;
+gnutls_x509_crt_t *ca_list;
+size_t crl_list_size;
+size_t ca_list_size;
+
 static gnutls_certificate_credentials_t x509_cred;
 static gnutls_dh_params_t dh_params;
 static bool ConnSSL_LoadServerKey_gnutls PARAMS(( void ));
+static bool ConnSSL_SetVerifyProperties_gnutls PARAMS(( void ));
 #endif
 
 static bool ConnSSL_Init_SSL PARAMS(( CONNECTION *c ));
 static int ConnectAccept PARAMS(( CONNECTION *c, bool connect ));
 static int ConnSSL_HandleError PARAMS(( CONNECTION *c, const int code, const char *fname ));
 
+
 #ifdef HAVE_LIBGNUTLS
 static char * openreadclose(const char *name, size_t *len)
 {
@@ -110,6 +120,34 @@ out:
 
 #ifdef HAVE_LIBSSL
 static void
+LogOpenSSL_CertInfo(X509 *cert, const char *msg)
+{
+	BIO *mem;
+	char *memptr;
+	long len;
+
+	assert(cert);
+	assert(msg);
+
+	if (!cert) return;
+
+	mem = BIO_new(BIO_s_mem());
+	if (!mem) return;
+
+	X509_NAME_print_ex(mem, X509_get_subject_name (cert), 4, XN_FLAG_ONELINE);
+	X509_NAME_print_ex(mem, X509_get_issuer_name (cert), 4, XN_FLAG_ONELINE);
+	if (BIO_write(mem, "", 1) == 1) {
+		len = BIO_get_mem_data(mem, &memptr);
+		assert(memptr);
+		assert(len>0);
+		Log(LOG_INFO, "%s: \"%s\"", msg, memptr);
+	}
+
+	(void)BIO_set_close(mem, BIO_CLOSE);
+	BIO_free(mem);
+}
+
+static void
 LogOpenSSLError( const char *msg, const char *msg2 )
 {
 	unsigned long err = ERR_get_error();
@@ -145,7 +183,38 @@ pem_passwd_cb(char *buf, int size, int rwflag, void *password)
 	memcpy(buf, (char *)(array_start(pass)), size);
 	return size;
 }
-#endif
+
+
+static int
+verify_cb(int preverify_ok, X509_STORE_CTX *ctx)
+{
+	X509 *err_cert;
+	int err, depth;
+
+	err_cert = X509_STORE_CTX_get_current_cert(ctx);
+	err = X509_STORE_CTX_get_error(ctx);
+	depth = X509_STORE_CTX_get_error_depth(ctx);
+
+	LogDebug("preverify_ok %d error:num=%d:%s:depth=%d",
+			preverify_ok, err, X509_verify_cert_error_string(err), depth);
+
+	if (preverify_ok != 1) {
+		/*
+		 * if certificates are not being enforced, ignore any errors.
+		 * its possible to check if a connection has a valid certificate
+		 * by testing the CONN_SSL_PEERCERT_OK flag.
+		 * This can be used to enforce certificates for incoming servers,
+		 * but not irc clients.
+		 */
+		if (!Conf_SSLOptions.RequireClientCert)
+			preverify_ok = 1;
+		else
+			Log(LOG_ERR, "verify error:num=%d:%s:depth=%d", err,
+				X509_verify_cert_error_string(err), depth);
+	}
+	return preverify_ok;
+}
+#endif /* HAVE_LIBSSL */
 
 
 static bool
@@ -250,6 +319,7 @@ ConnSSL_InitLibrary( void )
 	if (!ssl_ctx) {
 		SSL_library_init();
 		SSL_load_error_strings();
+		OpenSSL_add_all_algorithms();
 	}
 
 	if (!RAND_status()) {
@@ -273,6 +343,9 @@ ConnSSL_InitLibrary( void )
 	if (!ConnSSL_LoadServerKey_openssl(newctx))
 		goto out;
 
+	if (!ConnSSL_SetVerifyProperties_openssl(newctx))
+                goto out;
+
 	SSL_CTX_set_options(newctx, SSL_OP_SINGLE_DH_USE|SSL_OP_NO_SSLv2);
 	SSL_CTX_set_mode(newctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
 	SSL_CTX_free(ssl_ctx);
@@ -300,6 +373,8 @@ out:
 		array_free(&Conf_SSLOptions.ListenPorts);
 		return false;
 	}
+	if (!ConnSSL_SetVerifyProperties_gnutls())
+		return false;
 	Log(LOG_INFO, "gnutls %s initialized.", gnutls_check_version(NULL));
 	initialized = true;
 	return true;
@@ -309,6 +384,30 @@ out:
 
 #ifdef HAVE_LIBGNUTLS
 static bool
+ConnSSL_SetVerifyProperties_gnutls(void)
+{
+	int err;
+
+	if (Conf_SSLOptions.CAFile) {
+		err = gnutls_certificate_set_x509_trust_file(x509_cred, Conf_SSLOptions.CAFile, GNUTLS_X509_FMT_PEM);
+		if (err < 0) {
+			Log(LOG_ERR, "gnutls_certificate_set_x509_trust_file %s: %s", Conf_SSLOptions.CAFile, gnutls_strerror(err));
+			return false;
+		}
+	}
+	if (Conf_SSLOptions.CRLFile) {
+		err = gnutls_certificate_set_x509_crl_file(x509_cred, Conf_SSLOptions.CRLFile, GNUTLS_X509_FMT_PEM);
+		if (err < 0) {
+			Log(LOG_ERR, "gnutls_certificate_set_x509_crl_file %s: %s", Conf_SSLOptions.CRLFile, gnutls_strerror(err));
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
+static bool
 ConnSSL_LoadServerKey_gnutls(void)
 {
 	int err;
@@ -340,6 +439,7 @@ ConnSSL_LoadServerKey_gnutls(void)
 				cert_file, Conf_SSLOptions.KeyFile ? Conf_SSLOptions.KeyFile : "(NULL)", gnutls_strerror(err));
 		return false;
 	}
+	gnutls_certificate_set_verify_flags(x509_cred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
 	return true;
 }
 #endif
@@ -390,6 +490,48 @@ ConnSSL_LoadServerKey_openssl(SSL_CTX *ctx)
 }
 
 
+static bool
+ConnSSL_SetVerifyProperties_openssl(SSL_CTX * ctx)
+{
+	X509_STORE *store = NULL;
+	X509_LOOKUP *lookup;
+	int verify_flags = SSL_VERIFY_PEER;
+	bool ret = false;
+
+	if (!Conf_SSLOptions.CAFile)
+		return true;
+
+	if (SSL_CTX_load_verify_locations(ctx, Conf_SSLOptions.CAFile, NULL) != 1) {
+		LogOpenSSLError("SSL_CTX_load_verify_locations", NULL);
+		goto out;
+	}
+
+	store = SSL_CTX_get_cert_store(ctx);
+	assert(store);
+	lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+	if (!lookup) {
+		LogOpenSSLError("X509_STORE_add_lookup", Conf_SSLOptions.CRLFile);
+		goto out;
+	}
+
+	if (1 != X509_load_crl_file(lookup, Conf_SSLOptions.CRLFile, X509_FILETYPE_PEM)) {
+		LogOpenSSLError("X509_load_crl_file", Conf_SSLOptions.CRLFile);
+		goto out;
+	}
+
+	if (Conf_SSLOptions.RequireClientCert)
+		verify_flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+
+	SSL_CTX_set_verify(ctx, verify_flags, verify_cb);
+	SSL_CTX_set_verify_depth(ctx, MAX_CERT_CHAIN_LENGTH);
+	ret = true;
+ out:
+	free(Conf_SSLOptions.CRLFile);
+	Conf_SSLOptions.CRLFile = NULL;
+	return ret;
+}
+
+
 #endif
 static bool
 ConnSSL_Init_SSL(CONNECTION *c)
@@ -444,17 +586,17 @@ ConnSSL_Init_SSL(CONNECTION *c)
 
 
 bool
-ConnSSL_PrepareConnect(CONNECTION *c, UNUSED CONF_SERVER *s)
+ConnSSL_PrepareConnect(CONNECTION *c, CONF_SERVER *s)
 {
 	bool ret;
 #ifdef HAVE_LIBGNUTLS
-	int err;
-
-	err = gnutls_init(&c->ssl_state.gnutls_session, GNUTLS_CLIENT);
+	int err = gnutls_init(&c->ssl_state.gnutls_session, GNUTLS_CLIENT);
 	if (err) {
 		Log(LOG_ERR, "gnutls_init: %s", gnutls_strerror(err));
 		return false;
         }
+	if (s->SSLVerify)
+		Log(LOG_WARNING, "gnutls backend cannot verify peers yet");
 #endif
 	ret = ConnSSL_Init_SSL(c);
 	if (!ret)
@@ -462,7 +604,11 @@ ConnSSL_PrepareConnect(CONNECTION *c, UNUSED CONF_SERVER *s)
 	Conn_OPTION_ADD(c, CONN_SSL_CONNECT);
 #ifdef HAVE_LIBSSL
 	assert(c->ssl_state.ssl);
-	SSL_set_verify(c->ssl_state.ssl, SSL_VERIFY_NONE, NULL);
+
+	if (s->SSLVerify)
+		SSL_set_verify(c->ssl_state.ssl, SSL_VERIFY_PEER, verify_cb);
+	else
+		SSL_set_verify(c->ssl_state.ssl, SSL_VERIFY_NONE, NULL);
 #endif
 	return true;
 }
@@ -550,27 +696,164 @@ ConnSSL_HandleError( CONNECTION *c, const int code, const char *fname )
 }
 
 
+#ifdef HAVE_LIBGNUTLS
+static bool check_verification(unsigned output)
+{
+	char errmsg[256] = "";
+
+	if (output & GNUTLS_CERT_SIGNER_NOT_FOUND)
+		strlcpy(errmsg, "No Issuer found", sizeof errmsg);
+	if (output & GNUTLS_CERT_SIGNER_NOT_CA) {
+		if (errmsg[0])
+			strlcat(errmsg, " ,", sizeof errmsg);
+		strlcat(errmsg, "Issuer is not a CA", sizeof errmsg);
+	}
+	if (output & GNUTLS_CERT_INSECURE_ALGORITHM) {
+		if (errmsg[0])
+			strlcat(errmsg, " ,", sizeof errmsg);
+		strlcat(errmsg, "Insecure Algorithm", sizeof errmsg);
+	}
+	if (output & GNUTLS_CERT_REVOKED) {
+		if (errmsg[0])
+			strlcat(errmsg, " ,", sizeof errmsg);
+		strlcat(errmsg, "Certificate Revoked", sizeof errmsg);
+	}
+#ifdef DEBUG
+	if (output & GNUTLS_CERT_INVALID)
+		assert(errmsg[0]); /* check for unhandled error */
+#endif
+	if (!(output & GNUTLS_CERT_INVALID) && !errmsg[0]) {
+		LogDebug("Certificate verified.");
+		return true;
+	}
+	Log(LOG_ERR, "Certificate Validation failed: %s", errmsg);
+	return false;
+}
+
+static void *LogMalloc(size_t s)
+{
+	void *mem = malloc(s);
+	if (!mem)
+		Log(LOG_ERR, "Out of memory: Could not allocate %lu byte", (unsigned long) s);
+	return mem;
+}
+
 static void
-ConnSSL_LogCertInfo( CONNECTION *c )
+LogGnuTLS_CertInfo(gnutls_x509_crt_t cert, const char *msg)
 {
+	char *dn, *issuer_dn;
+	size_t size = 0;
+	int err = gnutls_x509_crt_get_dn(cert, NULL, &size);
+	if (size == 0 || (err && err != GNUTLS_E_SHORT_MEMORY_BUFFER))
+		goto err_crt_get;
+	dn = LogMalloc(size);
+	if (!dn)
+		return;
+	err = gnutls_x509_crt_get_dn(cert, dn, &size);
+	if (err) {
+ err_crt_get:
+		Log(LOG_ERR, "gnutls_x509_crt_get_dn: %s", err ? gnutls_strerror(err) : "size == 0");
+		return;
+	}
+	gnutls_x509_crt_get_issuer_dn(cert, NULL, &size);
+	assert(size);
+	issuer_dn = LogMalloc(size);
+	if (!issuer_dn) {
+		Log(LOG_INFO, "%s: Distinguished Name: %s", msg, dn);
+		free(dn);
+		return;
+	}
+	gnutls_x509_crt_get_issuer_dn(cert, issuer_dn, &size);
+	Log(LOG_INFO, "%s: Distinguished Name: \"%s\", Issuer \"%s\"", msg, dn, issuer_dn);
+	free(dn);
+	free(issuer_dn);
+}
+#endif
+
+static void
+ConnSSL_CheckCertInfo( CONNECTION *c )
+{
+	const char *comp_alg = "no compression";
+	bool cert_seen = false, cert_ok = false;
+	char msg[128];
 #ifdef HAVE_LIBSSL
+	const void *comp;
+	X509 *client_cert = NULL;
 	SSL *ssl = c->ssl_state.ssl;
 
 	assert(ssl);
 
-	Log(LOG_INFO, "Connection %d: initialized %s using cipher %s.",
-		c->sock, SSL_get_version(ssl), SSL_get_cipher(ssl));
+	comp=SSL_get_current_compression(ssl);
+	if (comp) {
+		Conn_OPTION_ADD(c, CONN_SSL_COMPRESSION);
+		comp_alg = SSL_COMP_get_name(comp);
+	}
+	Log(LOG_INFO, "Connection %d: Initialized %s using cipher %s (%s).",
+	    c->sock, SSL_get_version(ssl), SSL_get_cipher(ssl), comp_alg);
+	client_cert = SSL_get_peer_certificate(ssl);
+	if (client_cert) {
+		int err = SSL_get_verify_result(ssl);
+		if (err == X509_V_OK)
+			cert_ok = true;
+		else
+			Log(LOG_ERR, "Certificate Validation failed: %s",
+			    X509_verify_cert_error_string(err));
+		snprintf(msg, sizeof(msg), "%svalid peer certificate",
+			 cert_ok ? "":"in");
+		LogOpenSSL_CertInfo(client_cert, msg);
+		X509_free(client_cert);
+		cert_seen = true;
+	}
 #endif
 #ifdef HAVE_LIBGNUTLS
+	unsigned int status;
+	int ret;
+	gnutls_credentials_type_t cred;
 	gnutls_session_t sess = c->ssl_state.gnutls_session;
 	gnutls_cipher_algorithm_t cipher = gnutls_cipher_get(sess);
+	gnutls_compression_method_t comp = gnutls_compression_get(sess);
+
+	if (comp != GNUTLS_COMP_NULL)
+		comp_alg = gnutls_compression_get_name(comp);
 
-	Log(LOG_INFO, "Connection %d: initialized %s using cipher %s-%s.",
+	Log(LOG_INFO, "Connection %d: Initialized %s using cipher %s-%s (%s).",
 	    c->sock,
 	    gnutls_protocol_get_name(gnutls_protocol_get_version(sess)),
 	    gnutls_cipher_get_name(cipher),
-	    gnutls_mac_get_name(gnutls_mac_get(sess)));
+	    gnutls_mac_get_name(gnutls_mac_get(sess)), comp_alg);
+
+	ret = gnutls_certificate_verify_peers2(c->ssl_state.gnutls_session, &status);
+	if (ret < 0)
+		Log(LOG_ERR, "gnutls_certificate_verify_peers2 failed: %s", gnutls_strerror(ret));
+	if (ret == 0 && check_verification(status))
+		cert_ok = true;
+
+	cred = gnutls_auth_get_type (c->ssl_state.gnutls_session);
+	if (cred == GNUTLS_CRD_CERTIFICATE) {
+		gnutls_x509_crt_t cert;
+		unsigned cert_list_size;
+		const gnutls_datum_t *cert_list = gnutls_certificate_get_peers(sess, &cert_list_size);
+		if (!cert_list) {
+			Log(LOG_ERR, "gnutls_certificate_get_peers() failed");
+			return;
+		}
+		assert(cert_list_size > 0);
+		gnutls_x509_crt_init(&cert);
+		gnutls_x509_crt_import(cert, cert_list, GNUTLS_X509_FMT_DER);
+		snprintf(msg, sizeof(msg), "%svalid peer certificate", cert_ok ? "":"in");
+		LogGnuTLS_CertInfo(cert, msg);
+		gnutls_x509_crt_deinit(cert);
+		cert_seen = true;
+	}
 #endif
+	/*
+	 * can be used later to check if connection was authenticated, e.g. if inbound connection
+	 * tries to register itself as server. could also restrict /OPER to authenticated connections, etc.
+	 */
+	if (cert_ok)
+		Conn_OPTION_ADD(c, CONN_SSL_PEERCERT_OK);
+	if (!cert_seen)
+		Log(LOG_INFO, "Peer did not present a certificate");
 }
 
 
@@ -587,11 +870,14 @@ ConnSSL_Accept( CONNECTION *c )
 	assert(c != NULL);
 	if (!Conn_OPTION_ISSET(c, CONN_SSL)) {
 #ifdef HAVE_LIBGNUTLS
+		gnutls_certificate_request_t req;
 		int err = gnutls_init(&c->ssl_state.gnutls_session, GNUTLS_SERVER);
 		if (err) {
 			Log(LOG_ERR, "gnutls_init: %s", gnutls_strerror(err));
 			return false;
 		}
+		req = Conf_SSLOptions.RequireClientCert ? GNUTLS_CERT_REQUIRE : GNUTLS_CERT_REQUEST;
+		gnutls_certificate_server_set_request(c->ssl_state.gnutls_session, req);
 #endif
 		LogDebug("ConnSSL_Accept: Initializing SSL data");
 		if (!ConnSSL_Init_SSL(c))
@@ -627,14 +913,16 @@ ConnectAccept( CONNECTION *c, bool connect)
 		return ConnSSL_HandleError(c, ret, connect ? "SSL_connect": "SSL_accept");
 #endif
 #ifdef HAVE_LIBGNUTLS
-	(void) connect;
 	ret = gnutls_handshake(c->ssl_state.gnutls_session);
-	if (ret)
-		return ConnSSL_HandleError(c, ret, "gnutls_handshake");
+	if (ret) {
+		char buf[256];
+		snprintf(buf, sizeof(buf), "gnutls_handshake %s", connect ? "connect" : "accept");
+		return ConnSSL_HandleError(c, ret, buf);
+	}
 #endif /* _GNUTLS */
+	assert(Conn_OPTION_ISSET(c, CONN_SSL));
 	Conn_OPTION_DEL(c, (CONN_SSL_WANT_WRITE|CONN_SSL_WANT_READ|CONN_SSL_CONNECT));
-	ConnSSL_LogCertInfo(c);
-
+	ConnSSL_CheckCertInfo(c);
 	Conn_StartLogin(CONNECTION2ID(c));
 	return 1;
 }
diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c
index 80b085a..f832fae 100644
--- a/src/ngircd/conn.c
+++ b/src/ngircd/conn.c
@@ -269,6 +269,7 @@ static void
 cb_connserver_login_ssl(int sock, short unused)
 {
 	CONN_ID idx = Socket2Index(sock);
+	int serveridx;
 
 	assert(idx >= 0);
 	if (idx < 0) {
@@ -281,15 +282,28 @@ cb_connserver_login_ssl(int sock, short unused)
 	case 0: LogDebug("ConnSSL_Connect: not ready");
 		return;
 	case -1:
-		Log(LOG_ERR, "SSL connection on socket %d failed!", sock);
-		Conn_Close(idx, "Can't connect!", NULL, false);
-		return;
+		goto err;
 	}
 
-	Log( LOG_INFO, "SSL connection %d with \"%s:%d\" established.", idx,
-			My_Connections[idx].host, Conf_Server[Conf_GetServer( idx )].port );
+	serveridx = Conf_GetServer(idx);
+	assert(serveridx >= 0);
+	if (serveridx < 0)
+		goto err;
 
+	Log(LOG_INFO, "SSL connection %d with \"%s:%d\" established.", idx,
+			My_Connections[idx].host, Conf_Server[serveridx].port);
+
+	if (Conf_Server[serveridx].SSLVerify &&
+			!Conn_OPTION_ISSET(&My_Connections[idx], CONN_SSL_PEERCERT_OK))
+	{
+		Log(LOG_ERR, "SSLVerify enabled for %d, but peer certificate check failed", idx);
+		goto err;
+	}
 	server_login(idx);
+	return;
+ err:
+	Log(LOG_ERR, "SSL connection on socket %d failed!", sock);
+	Conn_Close(idx, "Can't connect!", NULL, false);
 }
 #endif
 
@@ -2093,14 +2107,16 @@ New_Server( int Server , ng_ipaddr_t *dest)
 	{
 		Log(LOG_ALERT, "Could not initialize SSL for outgoing connection");
 		Conn_Close( new_sock, "Could not initialize SSL for outgoing connection", NULL, false );
-		Init_Conn_Struct( new_sock );
-		Conf_Server[Server].conn_id = NONE;
-		return;
+		goto errout;
 	}
 #endif
 	LogDebug("Registered new connection %d on socket %d (%ld in total).",
 		 new_sock, My_Connections[new_sock].sock, NumConnections);
 	Conn_OPTION_ADD( &My_Connections[new_sock], CONN_ISCONNECTING );
+	return;
+ errout:
+	Init_Conn_Struct(new_sock);
+	Conf_Server[Server].conn_id = NONE;
 } /* New_Server */
 
 
diff --git a/src/ngircd/conn.h b/src/ngircd/conn.h
index 9236c58..97014c0 100644
--- a/src/ngircd/conn.h
+++ b/src/ngircd/conn.h
@@ -40,7 +40,9 @@
 #define CONN_SSL		32	/* this connection is SSL encrypted */
 #define CONN_SSL_WANT_WRITE	64	/* SSL/TLS library needs to write protocol data */
 #define CONN_SSL_WANT_READ	128	/* SSL/TLS library needs to read protocol data */
-#define CONN_SSL_FLAGS_ALL	(CONN_SSL_CONNECT|CONN_SSL|CONN_SSL_WANT_WRITE|CONN_SSL_WANT_READ)
+#define CONN_SSL_PEERCERT_OK	256	/* peer presented a valid certificate (used to check inbound server auth */
+#define CONN_SSL_COMPRESSION	512	/* SSL/TLS link is compressed */
+#define CONN_SSL_FLAGS_ALL	(CONN_SSL_CONNECT|CONN_SSL|CONN_SSL_WANT_WRITE|CONN_SSL_WANT_READ|CONN_SSL_PEERCERT_OK|CONN_SSL_COMPRESSION)
 #endif
 typedef int CONN_ID;
 
diff --git a/src/ngircd/irc-server.c b/src/ngircd/irc-server.c
index a587c52..61939fd 100644
--- a/src/ngircd/irc-server.c
+++ b/src/ngircd/irc-server.c
@@ -100,6 +100,22 @@ IRC_SERVER( CLIENT *Client, REQUEST *Req )
 			return DISCONNECTED;
 		}
 
+		/*
+		 * This check is only done if SSLRequireClientCert is disabled,
+		 * and this Servers [SERVER] section has "SSLVerify" enabled.
+		 * (if SSLRequireClientCert is set, certificate validation is
+		 * done during SSL/TLS handshake)
+		 */
+		if (Conf_Server[i].SSLVerify &&
+		   !(Conn_Options(Client_Conn(Client)) & CONN_SSL_PEERCERT_OK)) {
+			Log(LOG_ERR,
+			    "Connection %d: SSLVerify is set, and server \"%s\" did not present a valid certificate",
+			    Client_Conn(Client), Req->argv[0]);
+			Conn_Close(Client_Conn(Client), NULL,
+				   "No valid SSL certificate", true);
+			return DISCONNECTED;
+		}
+
 		/* Is there a registered server with this ID? */
 		if (!Client_CheckID(Client, Req->argv[0]))
 			return DISCONNECTED;
-- 
1.7.10.2 (Apple Git-33)

