Skip to content

Commit

Permalink
Check names in the server's cert when using KKDCP
Browse files Browse the repository at this point in the history
When we connect to a KDC using an HTTPS proxy, check that the naming
information in the certificate matches the name or address which we
extracted from the server URL in the configuration.

ticket: 7929
  • Loading branch information
nalind authored and greghudson committed Jun 2, 2014
1 parent f220067 commit f7825e8
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 10 deletions.
5 changes: 5 additions & 0 deletions src/include/k5-trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
TRACE(c, "Resolving hostname {str}", hostname)
#define TRACE_SENDTO_KDC_RESPONSE(c, len, raddr) \
TRACE(c, "Received answer ({int} bytes) from {raddr}", len, raddr)
#define TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MISMATCH(c, hostname) \
TRACE(c, "HTTPS certificate name mismatch: server certificate is " \
"not for \"{str}\"", hostname)
#define TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(c, hostname) \
TRACE(c, "HTTPS certificate name matched \"{str}\"", hostname)
#define TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(c) \
TRACE(c, "HTTPS server certificate not received")
#define TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(c, depth, \
Expand Down
3 changes: 3 additions & 0 deletions src/lib/krb5/os/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ STLIBOBJS= \
c_ustime.o \
ccdefname.o \
changepw.o \
checkhost.o \
dnsglue.o \
dnssrv.o \
expand_path.o \
Expand Down Expand Up @@ -59,6 +60,7 @@ OBJS= \
$(OUTPRE)c_ustime.$(OBJEXT) \
$(OUTPRE)ccdefname.$(OBJEXT) \
$(OUTPRE)changepw.$(OBJEXT) \
$(OUTPRE)checkhost.$(OBJEXT) \
$(OUTPRE)dnsglue.$(OBJEXT) \
$(OUTPRE)dnssrv.$(OBJEXT) \
$(OUTPRE)expand_path.$(OBJEXT) \
Expand Down Expand Up @@ -105,6 +107,7 @@ SRCS= \
$(srcdir)/c_ustime.c \
$(srcdir)/ccdefname.c \
$(srcdir)/changepw.c \
$(srcdir)/checkhost.c \
$(srcdir)/dnsglue.c \
$(srcdir)/dnssrv.c \
$(srcdir)/expand_path.c \
Expand Down
251 changes: 251 additions & 0 deletions src/lib/krb5/os/checkhost.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Copyright 2014 Red Hat, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "k5-int.h"
#include "k5-utf8.h"

#ifdef PROXY_TLS_IMPL_OPENSSL
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include "checkhost.h"

/* Return the passed-in character, lower-cased if it's an ASCII character. */
static inline char
ascii_tolower(char p)
{
if (KRB5_UPPER(p))
return p + ('a' - 'A');
return p;
}

/*
* Check a single label. If allow_wildcard is true, and the presented name
* includes a wildcard, return true and note that we matched a wildcard.
* Otherwise, for both the presented and expected values, do a case-insensitive
* comparison of ASCII characters, and a case-sensitive comparison of
* everything else.
*/
static krb5_boolean
label_match(const char *presented, size_t plen, const char *expected,
size_t elen, krb5_boolean allow_wildcard, krb5_boolean *wildcard)
{
unsigned int i;

if (allow_wildcard && plen == 1 && presented[0] == '*') {
*wildcard = TRUE;
return TRUE;
}

if (plen != elen)
return FALSE;

for (i = 0; i < elen; i++) {
if (ascii_tolower(presented[i]) != ascii_tolower(expected[i]))
return FALSE;
}
return TRUE;
}

/* Break up the two names and check them, label by label. */
static krb5_boolean
domain_match(const char *presented, size_t plen, const char *expected)
{
const char *p, *q, *r, *s;
int n_label;
krb5_boolean used_wildcard = FALSE;

n_label = 0;
p = presented;
r = expected;
while (p < presented + plen && *r != '\0') {
q = memchr(p, '.', plen - (p - presented));
if (q == NULL)
q = presented + plen;
s = r + strcspn(r, ".");
if (!label_match(p, q - p, r, s - r, n_label == 0, &used_wildcard))
return FALSE;
p = q < presented + plen ? q + 1 : q;
r = *s ? s + 1 : s;
n_label++;
}
if (used_wildcard && n_label <= 2)
return FALSE;
if (p == presented + plen && *r == '\0')
return TRUE;
return FALSE;
}

/* Fetch the list of subjectAltNames from a certificate. */
static GENERAL_NAMES *
get_cert_sans(X509 *x)
{
int ext;
X509_EXTENSION *san_ext;

ext = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);
if (ext < 0)
return NULL;
san_ext = X509_get_ext(x, ext);
if (san_ext == NULL)
return NULL;
return X509V3_EXT_d2i(san_ext);
}

/* Fetch a CN value from the subjct name field, returning its length, or -1 if
* there is no subject name or it contains no CN value. */
static ssize_t
get_cert_cn(X509 *x, char *buf, size_t bufsize)
{
X509_NAME *name;

name = X509_get_subject_name(x);
if (name == NULL)
return -1;
return X509_NAME_get_text_by_NID(name, NID_commonName, buf, bufsize);
}

/*
* Return true if the passed-in expected IP address matches any of the names we
* can recover from the server certificate, false otherwise.
*/
krb5_boolean
k5_check_cert_address(X509 *x, const char *text)
{
char buf[1024];
GENERAL_NAMES *sans;
GENERAL_NAME *san = NULL;
ASN1_OCTET_STRING *ip;
krb5_boolean found_ip_san = FALSE, matched = FALSE;
int n_sans, i;
size_t name_length;
union {
struct in_addr in;
struct in6_addr in6;
} name;

/* Parse the IP address into an octet string. */
ip = M_ASN1_OCTET_STRING_new();
if (ip == NULL)
return FALSE;

if (inet_aton(text, &name.in) == 1)
name_length = sizeof(name.in);
else if (inet_pton(AF_INET6, text, &name.in6) == 1)
name_length = sizeof(name.in6);
else
name_length = 0;

if (name_length == 0) {
ASN1_OCTET_STRING_free(ip);
return FALSE;
}
M_ASN1_OCTET_STRING_set(ip, &name, name_length);

/* Check for matches in ipaddress subjectAltName values. */
sans = get_cert_sans(x);
if (sans != NULL) {
n_sans = sk_GENERAL_NAME_num(sans);
for (i = 0; i < n_sans; i++) {
san = sk_GENERAL_NAME_value(sans, i);
if (san->type != GEN_IPADD)
continue;
found_ip_san = TRUE;
matched = ASN1_OCTET_STRING_cmp(ip, san->d.iPAddress) == 0;
if (matched)
break;
}
sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free);
}
ASN1_OCTET_STRING_free(ip);

if (matched)
return TRUE;
if (found_ip_san)
return matched;

/* Check for a match against the CN value in the peer's subject name. */
name_length = get_cert_cn(x, buf, sizeof(buf));
if (name_length >= 0) {
/* Do a string compare to check if it's an acceptable value. */
return strlen(text) == name_length &&
strncmp(text, buf, name_length) == 0;
}

/* We didn't find a match. */
return FALSE;
}

/*
* Return true if the passed-in expected name matches any of the names we can
* recover from a server certificate, false otherwise.
*/
krb5_boolean
k5_check_cert_servername(X509 *x, const char *expected)
{
char buf[1024];
GENERAL_NAMES *sans;
GENERAL_NAME *san = NULL;
unsigned char *dnsname;
krb5_boolean found_dns_san = FALSE, matched = FALSE;
int name_length, n_sans, i;

/* Check for matches in dnsname subjectAltName values. */
sans = get_cert_sans(x);
if (sans != NULL) {
n_sans = sk_GENERAL_NAME_num(sans);
for (i = 0; i < n_sans; i++) {
san = sk_GENERAL_NAME_value(sans, i);
if (san->type != GEN_DNS)
continue;
found_dns_san = TRUE;
dnsname = NULL;
name_length = ASN1_STRING_to_UTF8(&dnsname, san->d.dNSName);
if (dnsname == NULL)
continue;
matched = domain_match((char *)dnsname, name_length, expected);
OPENSSL_free(dnsname);
if (matched)
break;
}
sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free);
}

if (matched)
return TRUE;
if (found_dns_san)
return matched;

/* Check for a match against the CN value in the peer's subject name. */
name_length = get_cert_cn(x, buf, sizeof(buf));
if (name_length >= 0)
return domain_match(buf, name_length, expected);

/* We didn't find a match. */
return FALSE;
}
#endif
39 changes: 39 additions & 0 deletions src/lib/krb5/os/checkhost.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2014 Red Hat, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
* Certificate subjectAltName check prototypes.
*/

#ifndef KRB5_LIBOS_CHECKHOST_PROTO__
#define KRB5_LIBOS_CHECKHOST_PROTO__

krb5_boolean k5_check_cert_servername(X509 *x, const char *expected);
krb5_boolean k5_check_cert_address(X509 *x, const char *expected);

#endif /* KRB5_LIBOS_CHECKHOST_PROTO__ */
14 changes: 13 additions & 1 deletion src/lib/krb5/os/deps
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ changepw.so changepw.po $(OUTPRE)changepw.$(OBJEXT): \
$(top_srcdir)/include/krb5/locate_plugin.h $(top_srcdir)/include/krb5/plugin.h \
$(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
changepw.c os-proto.h
checkhost.so checkhost.po $(OUTPRE)checkhost.$(OBJEXT): \
$(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
$(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
$(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
$(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
$(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
$(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
$(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \
$(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
$(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
$(top_srcdir)/include/socket-utils.h checkhost.c checkhost.h
dnsglue.so dnsglue.po $(OUTPRE)dnsglue.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
$(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
$(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
Expand Down Expand Up @@ -418,7 +429,8 @@ sendto_kdc.so sendto_kdc.po $(OUTPRE)sendto_kdc.$(OBJEXT): \
$(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
$(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/locate_plugin.h \
$(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
$(top_srcdir)/include/socket-utils.h os-proto.h sendto_kdc.c
$(top_srcdir)/include/socket-utils.h checkhost.h os-proto.h \
sendto_kdc.c
sn2princ.so sn2princ.po $(OUTPRE)sn2princ.$(OBJEXT): \
$(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
$(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
Expand Down

0 comments on commit f7825e8

Please sign in to comment.