mirror of
https://github.com/irssi/irssi.git
synced 2025-02-02 15:08:01 -05:00
Check if an SSL certificate matches the hostname of the server we are connecting to
git-svn-id: file:///var/www/svn.irssi.org/SVN/irssi/trunk@5104 dbcabf3a-b0e7-0310-adc4-f8d773084564
This commit is contained in:
parent
0f47bb8197
commit
bb4ce4562b
@ -26,6 +26,7 @@
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
@ -39,6 +40,7 @@ typedef struct
|
||||
SSL *ssl;
|
||||
SSL_CTX *ctx;
|
||||
unsigned int verify:1;
|
||||
const char *hostname;
|
||||
} GIOSSLChannel;
|
||||
|
||||
static SSL_CTX *ssl_ctx = NULL;
|
||||
@ -53,7 +55,149 @@ static void irssi_ssl_free(GIOChannel *handle)
|
||||
g_free(chan);
|
||||
}
|
||||
|
||||
static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, X509 *cert)
|
||||
/* Checks if the given string has internal NUL characters. */
|
||||
static gboolean has_internal_nul(const char* str, int len) {
|
||||
/* Remove trailing nul characters. They would give false alarms */
|
||||
while (len > 0 && str[len-1] == 0)
|
||||
len--;
|
||||
return strlen(str) != len;
|
||||
}
|
||||
|
||||
/* tls_dns_name - Extract valid DNS name from subjectAltName value */
|
||||
static const char *tls_dns_name(const GENERAL_NAME * gn)
|
||||
{
|
||||
const char *dnsname;
|
||||
|
||||
/* We expect the OpenSSL library to construct GEN_DNS extension objects as
|
||||
ASN1_IA5STRING values. Check we got the right union member. */
|
||||
if (ASN1_STRING_type(gn->d.ia5) != V_ASN1_IA5STRING) {
|
||||
g_warning("Invalid ASN1 value type in subjectAltName");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Safe to treat as an ASCII string possibly holding a DNS name */
|
||||
dnsname = (char *) ASN1_STRING_data(gn->d.ia5);
|
||||
|
||||
if (has_internal_nul(dnsname, ASN1_STRING_length(gn->d.ia5))) {
|
||||
g_warning("Internal NUL in subjectAltName");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dnsname;
|
||||
}
|
||||
|
||||
/* tls_text_name - extract certificate property value by name */
|
||||
static char *tls_text_name(X509_NAME *name, int nid)
|
||||
{
|
||||
int pos;
|
||||
X509_NAME_ENTRY *entry;
|
||||
ASN1_STRING *entry_str;
|
||||
int utf8_length;
|
||||
unsigned char *utf8_value;
|
||||
char *result;
|
||||
|
||||
if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry = X509_NAME_get_entry(name, pos);
|
||||
g_return_val_if_fail(entry != NULL, NULL);
|
||||
entry_str = X509_NAME_ENTRY_get_data(entry);
|
||||
g_return_val_if_fail(entry_str != NULL, NULL);
|
||||
|
||||
/* Convert everything into UTF-8. It's up to OpenSSL to do something
|
||||
reasonable when converting ASCII formats that contain non-ASCII
|
||||
content. */
|
||||
if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) {
|
||||
g_warning("Error decoding ASN.1 type=%d", ASN1_STRING_type(entry_str));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (has_internal_nul((char *)utf8_value, utf8_length)) {
|
||||
g_warning("NUL character in hostname in certificate");
|
||||
OPENSSL_free(utf8_value);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = g_strdup((char *) utf8_value);
|
||||
OPENSSL_free(utf8_value);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** check if a hostname in the certificate matches the hostname we used for the connection */
|
||||
static gboolean match_hostname(const char *cert_hostname, const char *hostname)
|
||||
{
|
||||
const char *hostname_left;
|
||||
|
||||
if (!strcasecmp(cert_hostname, hostname)) { /* exact match */
|
||||
return TRUE;
|
||||
} else if (cert_hostname[0] == '*' && cert_hostname[1] == '.' && cert_hostname[2] != 0) { /* wildcard match */
|
||||
/* The initial '*' matches exactly one hostname component */
|
||||
hostname_left = strchr(hostname, '.');
|
||||
if (hostname_left != NULL && ! strcasecmp(hostname_left + 1, cert_hostname + 2)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* based on verify_extract_name from tls_client.c in postfix */
|
||||
static gboolean irssi_ssl_verify_hostname(X509 *cert, const char *hostname)
|
||||
{
|
||||
int gen_index, gen_count;
|
||||
gboolean matched = FALSE, has_dns_name = FALSE;
|
||||
const char *cert_dns_name;
|
||||
char *cert_subject_cn;
|
||||
const GENERAL_NAME *gn;
|
||||
STACK_OF(GENERAL_NAME) * gens;
|
||||
|
||||
/* Verify the dNSName(s) in the peer certificate against the hostname. */
|
||||
gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
|
||||
if (gens) {
|
||||
gen_count = sk_GENERAL_NAME_num(gens);
|
||||
for (gen_index = 0; gen_index < gen_count && !matched; ++gen_index) {
|
||||
gn = sk_GENERAL_NAME_value(gens, gen_index);
|
||||
if (gn->type != GEN_DNS)
|
||||
continue;
|
||||
|
||||
/* Even if we have an invalid DNS name, we still ultimately
|
||||
ignore the CommonName, because subjectAltName:DNS is
|
||||
present (though malformed). */
|
||||
has_dns_name = TRUE;
|
||||
cert_dns_name = tls_dns_name(gn);
|
||||
if (cert_dns_name && *cert_dns_name) {
|
||||
matched = match_hostname(cert_dns_name, hostname);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free stack *and* member GENERAL_NAME objects */
|
||||
sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
|
||||
}
|
||||
|
||||
if (has_dns_name) {
|
||||
if (! matched) {
|
||||
/* The CommonName in the issuer DN is obsolete when SubjectAltName is available. */
|
||||
g_warning("None of the Subject Alt Names in the certificate match hostname '%s'", hostname);
|
||||
}
|
||||
return matched;
|
||||
} else { /* No subjectAltNames, look at CommonName */
|
||||
cert_subject_cn = tls_text_name(X509_get_subject_name(cert), NID_commonName);
|
||||
if (cert_subject_cn && *cert_subject_cn) {
|
||||
matched = match_hostname(cert_subject_cn, hostname);
|
||||
if (! matched) {
|
||||
g_warning("SSL certificate common name '%s' doesn't match host name '%s'", cert_subject_cn, hostname);
|
||||
}
|
||||
} else {
|
||||
g_warning("No subjectAltNames and no valid common name in certificate");
|
||||
}
|
||||
free(cert_subject_cn);
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, const char* hostname, X509 *cert)
|
||||
{
|
||||
if (SSL_get_verify_result(ssl) != X509_V_OK) {
|
||||
unsigned char md[EVP_MAX_MD_SIZE];
|
||||
@ -89,6 +233,8 @@ static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, X509 *cert)
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
} else if (! irssi_ssl_verify_hostname(cert, hostname)){
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@ -241,7 +387,7 @@ static gboolean irssi_ssl_init(void)
|
||||
|
||||
}
|
||||
|
||||
static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *mycert, const char *mypkey, const char *cafile, const char *capath, gboolean verify)
|
||||
static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *hostname, const char *mycert, const char *mypkey, const char *cafile, const char *capath, gboolean verify)
|
||||
{
|
||||
GIOSSLChannel *chan;
|
||||
GIOChannel *gchan;
|
||||
@ -326,6 +472,7 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *mycer
|
||||
chan->ssl = ssl;
|
||||
chan->ctx = ctx;
|
||||
chan->verify = verify;
|
||||
chan->hostname = hostname;
|
||||
|
||||
gchan = (GIOChannel *)chan;
|
||||
gchan->funcs = &irssi_ssl_channel_funcs;
|
||||
@ -336,14 +483,14 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *mycer
|
||||
return gchan;
|
||||
}
|
||||
|
||||
GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify)
|
||||
GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, const char* hostname, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify)
|
||||
{
|
||||
GIOChannel *handle, *ssl_handle;
|
||||
|
||||
handle = net_connect_ip(ip, port, my_ip);
|
||||
if (handle == NULL)
|
||||
return NULL;
|
||||
ssl_handle = irssi_ssl_get_iochannel(handle, cert, pkey, cafile, capath, verify);
|
||||
ssl_handle = irssi_ssl_get_iochannel(handle, hostname, cert, pkey, cafile, capath, verify);
|
||||
if (ssl_handle == NULL)
|
||||
g_io_channel_unref(handle);
|
||||
return ssl_handle;
|
||||
@ -385,7 +532,7 @@ int irssi_ssl_handshake(GIOChannel *handle)
|
||||
g_warning("SSL server supplied no certificate");
|
||||
return -1;
|
||||
}
|
||||
ret = !chan->verify || irssi_ssl_verify(chan->ssl, chan->ctx, cert);
|
||||
ret = !chan->verify || irssi_ssl_verify(chan->ssl, chan->ctx, chan->hostname, cert);
|
||||
X509_free(cert);
|
||||
return ret ? 0 : -1;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ int net_ip_compare(IPADDR *ip1, IPADDR *ip2);
|
||||
/* Connect to socket */
|
||||
GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip);
|
||||
/* Connect to socket with ip address and SSL*/
|
||||
GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify);
|
||||
GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, const char* hostname, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify);
|
||||
int irssi_ssl_handshake(GIOChannel *handle);
|
||||
/* Connect to socket with ip address */
|
||||
GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip);
|
||||
|
@ -224,7 +224,7 @@ static void server_real_connect(SERVER_REC *server, IPADDR *ip,
|
||||
port = server->connrec->proxy != NULL ?
|
||||
server->connrec->proxy_port : server->connrec->port;
|
||||
handle = server->connrec->use_ssl ?
|
||||
net_connect_ip_ssl(ip, port, own_ip, server->connrec->ssl_cert, server->connrec->ssl_pkey,
|
||||
net_connect_ip_ssl(ip, port, server->connrec->address, own_ip, server->connrec->ssl_cert, server->connrec->ssl_pkey,
|
||||
server->connrec->ssl_cafile, server->connrec->ssl_capath, server->connrec->ssl_verify) :
|
||||
net_connect_ip(ip, port, own_ip);
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user