Skip to content

Commit

Permalink
Load custom anchors when using KKDCP
Browse files Browse the repository at this point in the history
Add an http_anchors per-realm setting which we'll apply when using an
HTTPS proxy, more or less mimicking the syntax of its similarly-named
PKINIT counterpart.  We only check the [realms] section, though.

ticket: 7929
  • Loading branch information
nalind authored and greghudson committed Jun 2, 2014
1 parent d950809 commit f220067
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 2 deletions.
26 changes: 26 additions & 0 deletions doc/admin/conf_files/krb5_conf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,32 @@ following tags may be specified in the realm's subsection:
(for example, when converting ``rcmd.hostname`` to
``host/hostname.domain``).

**http_anchors**
When KDCs and kpasswd servers are accessed through HTTPS proxies, this tag
can be used to specify the location of the CA certificate which should be
trusted to issue the certificate for a proxy server. If left unspecified,
the system-wide default set of CA certificates is used.

The syntax for values is similar to that of values for the
**pkinit_anchors** tag:

**FILE:** *filename*

*filename* is assumed to be the name of an OpenSSL-style ca-bundle file.

**DIR:** *dirname*

*dirname* is assumed to be an directory which contains CA certificates.
All files in the directory will be examined; if they contain certificates
(in PEM format), they will be used.

**ENV:** *envvar*

*envvar* specifies the name of an environment variable which has been set
to a value conforming to one of the previous values. For example,
``ENV:X509_PROXY_CA``, where environment variable ``X509_PROXY_CA`` has
been set to ``FILE:/tmp/my_proxy.pem``.

**kdc**
The name or address of a host running a KDC for that realm. An
optional port number, separated from the hostname by a colon, may
Expand Down
1 change: 1 addition & 0 deletions src/include/k5-int.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ typedef unsigned char u_char;
#define KRB5_CONF_EXTRA_ADDRESSES "extra_addresses"
#define KRB5_CONF_FORWARDABLE "forwardable"
#define KRB5_CONF_HOST_BASED_SERVICES "host_based_services"
#define KRB5_CONF_HTTP_ANCHORS "http_anchors"
#define KRB5_CONF_IGNORE_ACCEPTOR_HOSTNAME "ignore_acceptor_hostname"
#define KRB5_CONF_IPROP_ENABLE "iprop_enable"
#define KRB5_CONF_IPROP_MASTER_ULOGSIZE "iprop_master_ulogsize"
Expand Down
7 changes: 7 additions & 0 deletions src/include/k5-trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,13 @@ 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_NO_REMOTE_CERTIFICATE(c) \
TRACE(c, "HTTPS server certificate not received")
#define TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(c, depth, \
namelen, name, \
err, errs) \
TRACE(c, "HTTPS certificate error at {int} ({lenstr}): " \
"{int} ({str})", depth, namelen, name, err, errs)
#define TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(c, raddr) \
TRACE(c, "HTTPS error connecting to {raddr}", raddr)
#define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr, err) \
Expand Down
169 changes: 167 additions & 2 deletions src/lib/krb5/os/sendto_kdc.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
#ifdef PROXY_TLS_IMPL_OPENSSL
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <dirent.h>
#endif

#define MAX_PASS 3
Expand Down Expand Up @@ -152,13 +155,20 @@ struct conn_state {
} http;
};

#ifdef PROXY_TLS_IMPL_OPENSSL
/* Extra-data identifier, used to pass context into the verify callback. */
static int ssl_ex_context_id = -1;
#endif

void
k5_sendto_kdc_initialize(void)
{
#ifdef PROXY_TLS_IMPL_OPENSSL
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();

ssl_ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
#endif
}

Expand Down Expand Up @@ -1147,6 +1157,152 @@ flush_ssl_errors(krb5_context context)
}
}

static krb5_error_code
load_http_anchor_file(X509_STORE *store, const char *path)
{
FILE *fp;
STACK_OF(X509_INFO) *sk = NULL;
X509_INFO *xi;
int i;

fp = fopen(path, "r");
if (fp == NULL)
return errno;
sk = PEM_X509_INFO_read(fp, NULL, NULL, NULL);
fclose(fp);
if (sk == NULL)
return ENOENT;
for (i = 0; i < sk_X509_INFO_num(sk); i++) {
xi = sk_X509_INFO_value(sk, i);
if (xi->x509 != NULL)
X509_STORE_add_cert(store, xi->x509);
}
sk_X509_INFO_pop_free(sk, X509_INFO_free);
return 0;
}

static krb5_error_code
load_http_anchor_dir(X509_STORE *store, const char *path)
{
DIR *d = NULL;
struct dirent *dentry = NULL;
char filename[1024];
krb5_boolean found_any = FALSE;

d = opendir(path);
if (d == NULL)
return ENOENT;
while ((dentry = readdir(d)) != NULL) {
if (dentry->d_name[0] != '.') {
snprintf(filename, sizeof(filename), "%s/%s",
path, dentry->d_name);
if (load_http_anchor_file(store, filename) == 0)
found_any = TRUE;
}
}
closedir(d);
return found_any ? 0 : ENOENT;
}

static krb5_error_code
load_http_anchor(SSL_CTX *ctx, const char *location)
{
X509_STORE *store;
const char *envloc;

store = SSL_CTX_get_cert_store(ctx);
if (strncmp(location, "FILE:", 5) == 0) {
return load_http_anchor_file(store, location + 5);
} else if (strncmp(location, "DIR:", 4) == 0) {
return load_http_anchor_dir(store, location + 4);
} else if (strncmp(location, "ENV:", 4) == 0) {
envloc = getenv(location + 4);
if (envloc == NULL)
return ENOENT;
return load_http_anchor(ctx, envloc);
}
return EINVAL;
}

static krb5_error_code
load_http_verify_anchors(krb5_context context, const krb5_data *realm,
SSL_CTX *sctx)
{
const char *anchors[4];
char **values = NULL, *realmz;
unsigned int i;
krb5_error_code err;

realmz = k5memdup0(realm->data, realm->length, &err);
if (realmz == NULL)
goto cleanup;

/* Load the configured anchors. */
anchors[0] = KRB5_CONF_REALMS;
anchors[1] = realmz;
anchors[2] = KRB5_CONF_HTTP_ANCHORS;
anchors[3] = NULL;
if (profile_get_values(context->profile, anchors, &values) == 0) {
for (i = 0; values[i] != NULL; i++) {
err = load_http_anchor(sctx, values[i]);
if (err != 0)
break;
}
profile_free_list(values);
} else {
/* Use the library defaults. */
if (SSL_CTX_set_default_verify_paths(sctx) != 1)
err = ENOENT;
}

cleanup:
free(realmz);
return err;
}

static int
ssl_verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx)
{
X509 *x;
SSL *ssl;
BIO *bio;
krb5_context context;
int err, depth;
const char *cert = NULL, *errstr;
size_t count;

ssl = X509_STORE_CTX_get_ex_data(store_ctx,
SSL_get_ex_data_X509_STORE_CTX_idx());
context = SSL_get_ex_data(ssl, ssl_ex_context_id);
/* We do have the peer's cert, right? */
x = X509_STORE_CTX_get_current_cert(store_ctx);
if (x == NULL) {
TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(context);
return 0;
}
/* Figure out where we are. */
depth = X509_STORE_CTX_get_error_depth(store_ctx);
if (depth < 0)
return 0;
/* If there's an error at this level that we're not ignoring, fail. */
err = X509_STORE_CTX_get_error(store_ctx);
if (err != X509_V_OK) {
bio = BIO_new(BIO_s_mem());
if (bio != NULL) {
X509_NAME_print_ex(bio, x->cert_info->subject, 0, 0);
count = BIO_get_mem_data(bio, &cert);
errstr = X509_verify_cert_error_string(err);
TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(context, depth,
count, cert, err,
errstr);
BIO_free(bio);
}
return 0;
}
/* All done. */
return 1;
}

/*
* Set up structures that we use to manage the SSL handling for this connection
* and apply any non-default settings. Kill the connection and return false if
Expand All @@ -1156,25 +1312,34 @@ static krb5_boolean
setup_ssl(krb5_context context, const krb5_data *realm,
struct conn_state *conn, struct select_state *selstate)
{
int e;
long options;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;

if (ssl_ex_context_id == -1)
goto kill_conn;

/* Do general SSL library setup. */
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL)
goto kill_conn;
options = SSL_CTX_get_options(ctx);
SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2);

SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
if (!SSL_CTX_set_default_verify_paths(ctx))
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, ssl_verify_callback);
X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0);
e = load_http_verify_anchors(context, realm, ctx);
if (e != 0)
goto kill_conn;

ssl = SSL_new(ctx);
if (ssl == NULL)
goto kill_conn;

if (!SSL_set_ex_data(ssl, ssl_ex_context_id, context))
goto kill_conn;

/* Tell the SSL library about the socket. */
if (!SSL_set_fd(ssl, conn->fd))
goto kill_conn;
Expand Down

0 comments on commit f220067

Please sign in to comment.