Skip to content
This repository was archived by the owner on Feb 10, 2024. It is now read-only.

Commit c9b63f7

Browse files
committed
ssl: Validate hostnames
Closes #524
1 parent ebaaf46 commit c9b63f7

File tree

3 files changed

+219
-2
lines changed

3 files changed

+219
-2
lines changed

Diff for: src/common/server.c

+15-1
Original file line numberDiff line numberDiff line change
@@ -723,9 +723,22 @@ ssl_do_connect (server * serv)
723723
switch (verify_error)
724724
{
725725
case X509_V_OK:
726+
{
727+
X509 *cert = SSL_get_peer_certificate (serv->ssl);
728+
int hostname_err;
729+
if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) != 0)
730+
{
731+
snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname? (%d)%s",
732+
hostname_err, serv->accept_invalid_cert ? " -- Ignored" : "");
733+
if (serv->accept_invalid_cert)
734+
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
735+
else
736+
goto conn_fail;
737+
}
738+
break;
739+
}
726740
/* snprintf (buf, sizeof (buf), "* Verify OK (?)"); */
727741
/* EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); */
728-
break;
729742
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
730743
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
731744
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
@@ -744,6 +757,7 @@ ssl_do_connect (server * serv)
744757
snprintf (buf, sizeof (buf), "%s.? (%d)",
745758
X509_verify_cert_error_string (verify_error),
746759
verify_error);
760+
conn_fail:
747761
EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL,
748762
NULL, 0);
749763

Diff for: src/common/ssl.c

+203
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "inet.h" /* make it first to avoid macro redefinitions */
2626
#include <openssl/ssl.h> /* SSL_() */
2727
#include <openssl/err.h> /* ERR_() */
28+
#include <openssl/x509v3.h>
2829
#ifdef WIN32
2930
#include <openssl/rand.h> /* RAND_seed() */
3031
#endif
@@ -35,6 +36,7 @@
3536

3637
#include <glib.h>
3738
#include <glib/gprintf.h>
39+
#include <gio/gio.h>
3840
#include "util.h"
3941

4042
/* If openssl was built without ec */
@@ -339,3 +341,204 @@ _SSL_close (SSL * ssl)
339341
SSL_free (ssl);
340342
ERR_remove_state (0); /* free state buffer */
341343
}
344+
345+
/* Hostname validation code based on OpenBSD's libtls. */
346+
347+
static int
348+
_SSL_match_hostname (const char *cert_hostname, const char *hostname)
349+
{
350+
const char *cert_domain, *domain, *next_dot;
351+
352+
if (g_ascii_strcasecmp (cert_hostname, hostname) == 0)
353+
return 0;
354+
355+
/* Wildcard match? */
356+
if (cert_hostname[0] == '*')
357+
{
358+
/*
359+
* Valid wildcards:
360+
* - "*.domain.tld"
361+
* - "*.sub.domain.tld"
362+
* - etc.
363+
* Reject "*.tld".
364+
* No attempt to prevent the use of eg. "*.co.uk".
365+
*/
366+
cert_domain = &cert_hostname[1];
367+
/* Disallow "*" */
368+
if (cert_domain[0] == '\0')
369+
return -1;
370+
/* Disallow "*foo" */
371+
if (cert_domain[0] != '.')
372+
return -1;
373+
/* Disallow "*.." */
374+
if (cert_domain[1] == '.')
375+
return -1;
376+
next_dot = strchr (&cert_domain[1], '.');
377+
/* Disallow "*.bar" */
378+
if (next_dot == NULL)
379+
return -1;
380+
/* Disallow "*.bar.." */
381+
if (next_dot[1] == '.')
382+
return -1;
383+
384+
domain = strchr (hostname, '.');
385+
386+
/* No wildcard match against a hostname with no domain part. */
387+
if (domain == NULL || strlen(domain) == 1)
388+
return -1;
389+
390+
if (g_ascii_strcasecmp (cert_domain, domain) == 0)
391+
return 0;
392+
}
393+
394+
return -1;
395+
}
396+
397+
static int
398+
_SSL_check_subject_altname (X509 *cert, const char *host)
399+
{
400+
STACK_OF(GENERAL_NAME) *altname_stack = NULL;
401+
GInetAddress *addr;
402+
GSocketFamily family;
403+
int type = GEN_DNS;
404+
int count, i;
405+
int rv = -1;
406+
407+
altname_stack = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
408+
if (altname_stack == NULL)
409+
return -1;
410+
411+
addr = g_inet_address_new_from_string (host);
412+
if (addr != NULL)
413+
{
414+
family = g_inet_address_get_family (addr);
415+
if (family == G_SOCKET_FAMILY_IPV4 || family == G_SOCKET_FAMILY_IPV6)
416+
type = GEN_IPADD;
417+
}
418+
419+
count = sk_GENERAL_NAME_num(altname_stack);
420+
for (i = 0; i < count; i++)
421+
{
422+
GENERAL_NAME *altname;
423+
424+
altname = sk_GENERAL_NAME_value (altname_stack, i);
425+
426+
if (altname->type != type)
427+
continue;
428+
429+
if (type == GEN_DNS)
430+
{
431+
unsigned char *data;
432+
int format;
433+
434+
format = ASN1_STRING_type (altname->d.dNSName);
435+
if (format == V_ASN1_IA5STRING)
436+
{
437+
data = ASN1_STRING_data (altname->d.dNSName);
438+
439+
if (ASN1_STRING_length (altname->d.dNSName) != (int)strlen(data))
440+
{
441+
g_warning("NUL byte in subjectAltName, probably a malicious certificate.\n");
442+
rv = -2;
443+
break;
444+
}
445+
446+
if (_SSL_match_hostname (data, host) == 0)
447+
{
448+
rv = 0;
449+
break;
450+
}
451+
}
452+
else
453+
g_warning ("unhandled subjectAltName dNSName encoding (%d)\n", format);
454+
455+
}
456+
else if (type == GEN_IPADD)
457+
{
458+
unsigned char *data;
459+
const guint8 *addr_bytes;
460+
int datalen, addr_len;
461+
462+
datalen = ASN1_STRING_length (altname->d.iPAddress);
463+
data = ASN1_STRING_data (altname->d.iPAddress);
464+
465+
addr_bytes = g_inet_address_to_bytes (addr);
466+
addr_len = (int)g_inet_address_get_native_size (addr);
467+
468+
if (datalen == addr_len && memcmp (data, addr_bytes, addr_len) == 0)
469+
{
470+
rv = 0;
471+
break;
472+
}
473+
}
474+
}
475+
476+
if (addr != NULL)
477+
g_object_unref (addr);
478+
sk_GENERAL_NAME_free (altname_stack);
479+
return rv;
480+
}
481+
482+
static int
483+
_SSL_check_common_name (X509 *cert, const char *host)
484+
{
485+
X509_NAME *name;
486+
char *common_name = NULL;
487+
int common_name_len;
488+
int rv = -1;
489+
GInetAddress *addr;
490+
491+
name = X509_get_subject_name (cert);
492+
if (name == NULL)
493+
return -1;
494+
495+
common_name_len = X509_NAME_get_text_by_NID (name, NID_commonName, NULL, 0);
496+
if (common_name_len < 0)
497+
return -1;
498+
499+
common_name = calloc (common_name_len + 1, 1);
500+
if (common_name == NULL)
501+
return -1;
502+
503+
X509_NAME_get_text_by_NID (name, NID_commonName, common_name, common_name_len + 1);
504+
505+
/* NUL bytes in CN? */
506+
if (common_name_len != (int)strlen(common_name))
507+
{
508+
g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n");
509+
rv = -2;
510+
goto out;
511+
}
512+
513+
if ((addr = g_inet_address_new_from_string (host)) != NULL)
514+
{
515+
/*
516+
* We don't want to attempt wildcard matching against IP
517+
* addresses, so perform a simple comparison here.
518+
*/
519+
if (g_strcmp0 (common_name, host) == 0)
520+
rv = 0;
521+
else
522+
rv = -1;
523+
524+
g_object_unref (addr);
525+
}
526+
else if (_SSL_match_hostname (common_name, host) == 0)
527+
rv = 0;
528+
529+
out:
530+
free(common_name);
531+
return rv;
532+
}
533+
534+
int
535+
_SSL_check_hostname (X509 *cert, const char *host)
536+
{
537+
int rv;
538+
539+
rv = _SSL_check_subject_altname (cert, host);
540+
if (rv == 0 || rv == -2)
541+
return rv;
542+
543+
return _SSL_check_common_name (cert, host);
544+
}

Diff for: src/common/ssl.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert);
5252
int SSL_get_fd(SSL *);
5353
*/
5454
void _SSL_close (SSL * ssl);
55-
55+
int _SSL_check_hostname(X509 *cert, const char *host);
5656
int _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl);
5757
struct chiper_info *_SSL_get_cipher_info (SSL * ssl);
5858

0 commit comments

Comments
 (0)