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);
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 );
CheckFileReadable("CertFile", Conf_SSLOptions.CertFile);
CheckFileReadable("DHFile", Conf_SSLOptions.DHFile);
CheckFileReadable("KeyFile", Conf_SSLOptions.KeyFile);
+ if (Conf_SSLOptions.RequireClientCert) {
+ CheckFileReadable("CAFile", Conf_SSLOptions.CAFile);
+ if (Conf_SSLOptions.CRLFile)
+ CheckFileReadable("CRLFile", Conf_SSLOptions.CRLFile);
+ }
/* Set the default ciphers if none were configured */
if (!Conf_SSLOptions.CipherList)
Conf_SSLOptions.CipherList = strdup_warn(Arg);
return;
}
+ if (strcasecmp(Var, "CAFile") == 0) {
+ assert(Conf_SSLOptions.CAFile == NULL);
+ Conf_SSLOptions.CAFile = strdup_warn( Arg );
+ return;
+ }
+ if (strcasecmp(Var, "CRLFile") == 0) {
+ assert(Conf_SSLOptions.CRLFile == NULL);
+ Conf_SSLOptions.CRLFile = strdup_warn( Arg );
+ return;
+ }
+ if (strcasecmp(Var, "RequireClientCert") == 0) {
+ Conf_SSLOptions.RequireClientCert = Check_ArgIsTrue( Arg );
+ return;
+ }
Config_Error_Section(File, Line, Var, "SSL");
}
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 */
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, (RequireClientCert=yes), but non-SSL listening ports are set");
+#endif
return config_valid;
}
Proc_InitStruct(&Server->res_stat);
Server->conn_id = NONE;
memset(&Server->bind_addr, 0, sizeof(Server->bind_addr));
+#ifdef SSL_SUPPORT
+ Server->SSLVerify = false;
+#endif
}
/* -eof- */
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>
#define MAX_HASH_SIZE 64 /* from gnutls-int.h */
+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 gnutls_priority_t priorities_cache;
static bool ConnSSL_LoadServerKey_gnutls PARAMS(( void ));
+static bool ConnSSL_SetVerifyProperties_gnutls PARAMS(( void ));
#endif
#define SHA256_STRING_LEN (32 * 2 + 1)
* @param msg The error message.
* @param info Additional information text or NULL.
*/
+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 *error, const char *info)
{
static int
-Verify_openssl(UNUSED int preverify_ok, UNUSED X509_STORE_CTX *x509_ctx)
+Verify_openssl(int preverify_ok, X509_STORE_CTX *ctx)
{
- return 1;
+ 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
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_set_verify(newctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE,
goto out;
}
+ if (!ConnSSL_SetVerifyProperties_gnutls())
+ goto out;
+
Log(LOG_INFO, "GnuTLS %s initialized.", gnutls_check_version(NULL));
initialized = true;
return true;
#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, "Failed to load 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, "Failed to load x509 crl file %s: %s", Conf_SSLOptions.CRLFile, gnutls_strerror(err));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
static bool
ConnSSL_LoadServerKey_gnutls(void)
{
}
+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_openssl);
+ 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)
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_openssl);
+ else
+ SSL_set_verify(c->ssl_state.ssl, SSL_VERIFY_NONE, NULL);
#endif
return true;
}
}
+#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
+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_LogCertInfo( CONNECTION *c )
{
+ const char *comp_alg = "no compression";
+ bool cert_seen = false, cert_ok = false, cn_match = 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));
+ X509_NAME *subjectName;
+ char cn[256];
+ subjectName = X509_get_subject_name(client_cert);
+ X509_NAME_get_text_by_NID(
+ subjectName, NID_commonName, cn, sizeof(cn));
+ if (strcmp (cn, c->host) == 0)
+ cn_match = true;
+ else
+ Log(LOG_ERR, "CN mismatch! Got \"%s\", expected \"%s\"", cn, c->host);
+
+ 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);
- Log(LOG_INFO, "Connection %d: initialized %s using cipher %s-%s.",
+ if (comp != GNUTLS_COMP_NULL)
+ comp_alg = gnutls_compression_get_name(comp);
+ 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);
+
+ char *cn;
+ size_t size = 0;
+ int err = gnutls_x509_crt_get_dn_by_oid(cert,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, NULL, &size);
+ if (size == 0 || (err && err != GNUTLS_E_SHORT_MEMORY_BUFFER))
+ goto done_cn_validation;
+ cn = LogMalloc(size);
+ if (!cn)
+ goto done_cn_validation;
+ gnutls_x509_crt_get_dn_by_oid (cert,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, cn, &size);
+ if (strcmp (cn, c->host) == 0)
+ cn_match = true;
+ else
+ Log(LOG_ERR, "CN mismatch! Got \"%s\", expected \"%s\"", cn, c->host);
+ free (cn);
+done_cn_validation:
+
+ 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 && cn_match)
+ Conn_OPTION_ADD(c, CONN_SSL_PEERCERT_OK);
+ if (!cert_seen)
+ Log(LOG_INFO, "Peer did not present a certificate");
}
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, "Failed to initialize new SSL session: %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
if (!ConnSSL_Init_SSL(c))
return -1;
gnutls_x509_crt_deinit(cert);
return 0;
}
-
+
if (gnutls_x509_crt_import(cert, &cert_list[0],
GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) {
gnutls_x509_crt_deinit(cert);