krb5 commit: Load custom anchors when using KKDCP
Greg Hudson
ghudson at MIT.EDU
Mon Jun 2 18:53:32 EDT 2014
https://github.com/krb5/krb5/commit/f220067c2969aab107bd1300ad1cb8d4855389a7
commit f220067c2969aab107bd1300ad1cb8d4855389a7
Author: Nalin Dahyabhai <nalin at dahyabhai.net>
Date: Thu Apr 17 17:17:13 2014 -0400
Load custom anchors when using KKDCP
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
doc/admin/conf_files/krb5_conf.rst | 26 ++++++
src/include/k5-int.h | 1 +
src/include/k5-trace.h | 7 ++
src/lib/krb5/os/sendto_kdc.c | 169 +++++++++++++++++++++++++++++++++++-
4 files changed, 201 insertions(+), 2 deletions(-)
diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst
index 19ea9c9..c069327 100644
--- a/doc/admin/conf_files/krb5_conf.rst
+++ b/doc/admin/conf_files/krb5_conf.rst
@@ -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
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 8f039ee..187d16d 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -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"
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
index f0d79f1..046bc95 100644
--- a/src/include/k5-trace.h
+++ b/src/include/k5-trace.h
@@ -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) \
diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c
index a4727c4..4bd8698 100644
--- a/src/lib/krb5/os/sendto_kdc.c
+++ b/src/lib/krb5/os/sendto_kdc.c
@@ -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
@@ -152,6 +155,11 @@ 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)
{
@@ -159,6 +167,8 @@ k5_sendto_kdc_initialize(void)
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
}
@@ -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
@@ -1156,10 +1312,14 @@ 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)
@@ -1167,14 +1327,19 @@ setup_ssl(krb5_context context, const krb5_data *realm,
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;
More information about the cvs-krb5
mailing list