@ldap_plugin_dir@ \
plugins/preauth/otp \
plugins/preauth/pkinit \
+ plugins/tls/k5tls \
kdc kadmin slave clients appl tests \
config-files build-tools man doc @po@
WINSUBDIRS=include util lib ccapi windows clients appl
$(KRB5_LIBDIR) $(KRB5_INCDIR) \
$(KRB5_DB_MODULE_DIR) $(KRB5_PA_MODULE_DIR) \
$(KRB5_AD_MODULE_DIR) \
- $(KRB5_LIBKRB5_MODULE_DIR) \
+ $(KRB5_LIBKRB5_MODULE_DIR) $(KRB5_TLS_MODULE_DIR) \
@localstatedir@ @localstatedir@/krb5kdc \
@runstatedir@ @runstatedir@/krb5kdc \
$(KRB5_INCSUBDIRS) $(datadir) $(EXAMPLEDIR) \
KRB5_PA_MODULE_DIR = $(MODULE_DIR)/preauth
KRB5_AD_MODULE_DIR = $(MODULE_DIR)/authdata
KRB5_LIBKRB5_MODULE_DIR = $(MODULE_DIR)/libkrb5
+KRB5_TLS_MODULE_DIR = $(MODULE_DIR)/tls
KRB5_LOCALEDIR = @localedir@
GSS_MODULE_DIR = @libdir@/gss
KRB5_INCSUBDIRS = \
;;
esac
+if test "$PROXY_TLS_IMPL" = no; then
+ AC_DEFINE(PROXY_TLS_IMPL_NONE,1,
+ [Define if no HTTP TLS implementation is selected])
+fi
+
AC_SUBST(PROXY_TLS_IMPL)
AC_SUBST(PROXY_TLS_IMPL_CFLAGS)
AC_SUBST(PROXY_TLS_IMPL_LIBS)
plugins/authdata/greet
plugins/authdata/greet_client
plugins/authdata/greet_server
+ plugins/tls/k5tls
clients clients/klist clients/kinit clients/kvno
clients/kdestroy clients/kpasswd clients/ksu clients/kswitch
#define PLUGIN_INTERFACE_LOCALAUTH 5
#define PLUGIN_INTERFACE_HOSTREALM 6
#define PLUGIN_INTERFACE_AUDIT 7
-#define PLUGIN_NUM_INTERFACES 8
+#define PLUGIN_INTERFACE_TLS 8
+#define PLUGIN_NUM_INTERFACES 9
/* Retrieve the plugin module of type interface_id and name modname,
* storing the result into module. */
struct ccselect_module_handle;
struct localauth_module_handle;
struct hostrealm_module_handle;
+struct k5_tls_vtable_st;
struct _krb5_context {
krb5_magic magic;
krb5_enctype *in_tkt_etypes;
/* hostrealm module stuff */
struct hostrealm_module_handle **hostrealm_handles;
+ /* TLS module vtable (if loaded) */
+ struct k5_tls_vtable_st *tls;
+
/* error detail info */
struct errinfo err;
--- /dev/null
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* include/k5-tls.h - internal pluggable interface for TLS */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * 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 HOLDER 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.
+ */
+
+/*
+ * This internal pluggable interface allows libkrb5 to load an in-tree module
+ * providing TLS support at runtime. It is currently tailored for the needs of
+ * the OpenSSL module as used for HTTP proxy support. As an internal
+ * interface, it can be changed to fit different implementations and consumers
+ * without regard for backward compatibility.
+ */
+
+#ifndef K5_TLS_H
+#define K5_TLS_H
+
+#include "k5-int.h"
+
+/* An abstract type for localauth module data. */
+typedef struct k5_tls_handle_st *k5_tls_handle;
+
+typedef enum {
+ DATA_READ, DONE, WANT_READ, WANT_WRITE, ERROR_TLS
+} k5_tls_status;
+
+/*
+ * Create a handle for fd, where the server certificate must match servername
+ * and be trusted according to anchors. anchors is a null-terminated list
+ * using the DIR:/FILE:/ENV: syntax borrowed from PKINIT. If anchors is null,
+ * use the system default trust anchors.
+ */
+typedef krb5_error_code
+(*k5_tls_setup_fn)(krb5_context context, SOCKET fd, const char *servername,
+ char **anchors, k5_tls_handle *handle_out);
+
+/*
+ * Write len bytes of data using TLS. Return DONE if writing is complete,
+ * WANT_READ or WANT_WRITE if the underlying socket must be readable or
+ * writable to continue, and ERROR_TLS if the TLS channel or underlying socket
+ * experienced an error. After WANT_READ or WANT_WRITE, the operation will be
+ * retried with the same arguments even if some data has already been written.
+ * (OpenSSL makes this contract easy to fulfill. For other implementations we
+ * might want to change it.)
+ */
+typedef k5_tls_status
+(*k5_tls_write_fn)(krb5_context context, k5_tls_handle handle,
+ const void *data, size_t len);
+
+/*
+ * Read up to data_size bytes of data using TLS. Return DATA_READ and set
+ * *len_out if any data is read. Return DONE if there is no more data to be
+ * read on the connection, WANT_READ or WANT_WRITE if the underlying socket
+ * must be readable or writable to continue, and ERROR_TLS if the TLS channel
+ * or underlying socket experienced an error.
+ *
+ * After DATA_READ, there may still be pending buffered data to read. The
+ * caller must call this method again with additional buffer space before
+ * selecting for reading on the underlying socket.
+ */
+typedef k5_tls_status
+(*k5_tls_read_fn)(krb5_context context, k5_tls_handle handle, void *data,
+ size_t data_size, size_t *len_out);
+
+/* Release a handle. Do not pass a null pointer. */
+typedef void
+(*k5_tls_free_handle_fn)(krb5_context context, k5_tls_handle handle);
+
+/* All functions are mandatory unless they are all null, in which case the
+ * caller should assume that TLS is unsupported. */
+typedef struct k5_tls_vtable_st {
+ k5_tls_setup_fn setup;
+ k5_tls_write_fn write;
+ k5_tls_read_fn read;
+ k5_tls_free_handle_fn free_handle;
+} *k5_tls_vtable;
+
+#endif /* K5_TLS_H */
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, \
- 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) \
+#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) \
- TRACE(c, "HTTPS error receiving from {raddr}: {errno}", raddr, err)
-#define TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(c, raddr) \
+#define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr) \
+ TRACE(c, "HTTPS error receiving from {raddr}", raddr)
+#define TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(c, raddr) \
TRACE(c, "HTTPS error sending to {raddr}", raddr)
#define TRACE_SENDTO_KDC_HTTPS_SEND(c, raddr) \
TRACE(c, "Sending HTTPS request to {raddr}", raddr)
TRACE(c, "TGS reply didn't decode with subkey; trying session key " \
"({keyblock)}", keyblock)
+#define TRACE_TLS_ERROR(c, errs) \
+ TRACE(c, "TLS error: {str}", errs)
+#define TRACE_TLS_NO_REMOTE_CERTIFICATE(c) \
+ TRACE(c, "TLS server certificate not received")
+#define TRACE_TLS_CERT_ERROR(c, depth, namelen, name, err, errs) \
+ TRACE(c, "TLS certificate error at {int} ({lenstr}): {int} ({str})", \
+ depth, namelen, name, err, errs)
+#define TRACE_TLS_SERVER_NAME_MISMATCH(c, hostname) \
+ TRACE(c, "TLS certificate name mismatch: server certificate is " \
+ "not for \"{str}\"", hostname)
+#define TRACE_TLS_SERVER_NAME_MATCH(c, hostname) \
+ TRACE(c, "TLS certificate name matched \"{str}\"", hostname)
+
#define TRACE_TKT_CREDS(c, creds, cache) \
TRACE(c, "Getting credentials {creds} using ccache {ccache}", \
creds, cache)
SHLIB_EXPDEPS = \
$(TOPLIBD)/libk5crypto$(SHLIBEXT) \
$(COM_ERR_DEPLIB) $(SUPPORT_DEPLIB)
-SHLIB_EXPLIBS=-lk5crypto -lcom_err $(PROXY_TLS_IMPL_LIBS) $(SUPPORT_LIB) \
- @GEN_LIB@ $(LIBS)
+SHLIB_EXPLIBS=-lk5crypto -lcom_err $(SUPPORT_LIB) @GEN_LIB@ $(LIBS)
all-unix:: all-liblinks
nctx->ccselect_handles = NULL;
nctx->localauth_handles = NULL;
nctx->hostrealm_handles = NULL;
+ nctx->tls = NULL;
nctx->kdblog_context = NULL;
nctx->trace_callback = NULL;
nctx->trace_callback_data = NULL;
k5_localauth_free_context(ctx);
k5_plugin_free_context(ctx);
free(ctx->plugin_base_dir);
+ free(ctx->tls);
ctx->magic = 0;
free(ctx);
"ccselect",
"localauth",
"hostrealm",
- "audit"
+ "audit",
+ "tls"
};
/* Return the context's interface structure for id, or NULL if invalid. */
if (err)
return err;
- k5_sendto_kdc_initialize();
-
return 0;
}
BUILDTOP=$(REL)..$(S)..$(S)..
DEFINES=-DLIBDIR=\"$(KRB5_LIBDIR)\" -DBINDIR=\"$(CLIENT_BINDIR)\" \
-DSBINDIR=\"$(ADMIN_BINDIR)\"
-LOCALINCLUDES= $(PROXY_TLS_IMPL_CFLAGS) -I$(top_srcdir)/util/profile
+LOCALINCLUDES= -I$(top_srcdir)/util/profile
##DOS##BUILDTOP = ..\..\..
##DOS##PREFIXDIR=os
c_ustime.o \
ccdefname.o \
changepw.o \
- checkhost.o \
dnsglue.o \
dnssrv.o \
expand_path.o \
+++ /dev/null
-/* -*- 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.
- */
-
-#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 int
-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;
- int name_length;
- struct in_addr sin;
- struct in6_addr sin6;
-
- /* Parse the IP address into an octet string. */
- ip = M_ASN1_OCTET_STRING_new();
- if (ip == NULL)
- return FALSE;
-
- if (inet_pton(AF_INET, text, &sin)) {
- M_ASN1_OCTET_STRING_set(ip, &sin, sizeof(sin));
- } else if (inet_pton(AF_INET6, text, &sin6)) {
- M_ASN1_OCTET_STRING_set(ip, &sin6, sizeof(sin6));
- } else {
- ASN1_OCTET_STRING_free(ip);
- return FALSE;
- }
-
- /* 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 (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) == (size_t)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
+++ /dev/null
-/* -*- 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__ */
$(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 \
$(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 checkhost.h os-proto.h \
+ $(top_srcdir)/include/socket-utils.h os-proto.h \
sendto_kdc.c
sn2princ.so sn2princ.po $(OUTPRE)sn2princ.$(OBJEXT): \
$(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
return ENOMEM;
}
-#ifdef PROXY_TLS_IMPL_OPENSSL
static void
parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host,
char **uri_path)
}
}
}
-#else
-static void
-parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host,
- char **uri)
-{
-}
-#endif
/* Return true if server is identical to an entry in list. */
static krb5_boolean
krb5_plugin_vtable vtable);
krb5_error_code localauth_an2ln_initvt(krb5_context context, int maj_ver,
int min_ver, krb5_plugin_vtable vtable);
-void k5_sendto_kdc_initialize(void);
#endif /* KRB5_LIBOS_INT_PROTO__ */
* as necessary. */
#include "k5-int.h"
+#include "k5-tls.h"
#include "fake-addrinfo.h"
#include "os-proto.h"
#endif
#endif
-#ifdef PROXY_TLS_IMPL_OPENSSL
-#include <openssl/err.h>
-#include <openssl/ssl.h>
-#include <openssl/x509.h>
-#include <openssl/x509v3.h>
-#include <dirent.h>
-#include "checkhost.h"
-#endif
-
#define MAX_PASS 3
#define DEFAULT_UDP_PREF_LIMIT 1465
#define HARD_UDP_LIMIT 32700 /* could probably do 64K-epsilon ? */
const char *uri_path;
const char *servername;
char *https_request;
-#ifdef PROXY_TLS_IMPL_OPENSSL
- SSL *ssl;
-#endif
+ k5_tls_handle tls;
} http;
};
-#ifdef PROXY_TLS_IMPL_OPENSSL
-/* Extra-data identifier, used to pass context into the verify callback. */
-static int ssl_ex_context_id = -1;
-static int ssl_ex_conn_id = -1;
-#endif
-
-void
-k5_sendto_kdc_initialize(void)
+/* Set up context->tls. On allocation failure, return ENOMEM. On plugin load
+ * failure, set context->tls to point to a nulled vtable and return 0. */
+static krb5_error_code
+init_tls_vtable(krb5_context context)
{
-#ifdef PROXY_TLS_IMPL_OPENSSL
- SSL_library_init();
- SSL_load_error_strings();
- OpenSSL_add_all_algorithms();
+ krb5_plugin_initvt_fn initfn;
- ssl_ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
- ssl_ex_conn_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-#endif
+ if (context->tls != NULL)
+ return 0;
+
+ context->tls = calloc(1, sizeof(*context->tls));
+ if (context->tls == NULL)
+ return ENOMEM;
+
+ /* Attempt to load the module; just let it stay nulled out on failure. */
+ k5_plugin_register_dyn(context, PLUGIN_INTERFACE_TLS, "k5tls", "tls");
+ if (k5_plugin_load(context, PLUGIN_INTERFACE_TLS, "k5tls", &initfn) == 0)
+ (*initfn)(context, 0, 0, (krb5_plugin_vtable)context->tls);
+
+ return 0;
}
/* Get current time in milliseconds. */
return 0;
}
-#ifdef PROXY_TLS_IMPL_OPENSSL
static void
-free_http_ssl_data(struct conn_state *state)
+free_http_tls_data(krb5_context context, struct conn_state *state)
{
- SSL_free(state->http.ssl);
- state->http.ssl = NULL;
+ if (state->http.tls != NULL)
+ context->tls->free_handle(context, state->http.tls);
+ state->http.tls = NULL;
free(state->http.https_request);
state->http.https_request = NULL;
}
-#else
-static void
-free_http_ssl_data(struct conn_state *state)
-{
-}
-#endif
#ifdef USE_POLL
static fd_handler_fn service_https_write;
static fd_handler_fn service_https_read;
-#ifdef PROXY_TLS_IMPL_OPENSSL
static krb5_error_code
make_proxy_request(struct conn_state *state, const krb5_data *realm,
const krb5_data *message, char **req_out, size_t *len_out)
krb5_free_data(NULL, encoded_pm);
return ret;
}
-#else
-static krb5_error_code
-make_proxy_request(struct conn_state *state, const krb5_data *realm,
- const krb5_data *message, char **req_out, size_t *len_out)
-{
- abort();
-}
-#endif
/* Set up the actual message we will send across the underlying transport to
* communicate the payload message, using one or both of state->out.sgbuf. */
kill_conn(krb5_context context, struct conn_state *conn,
struct select_state *selstate)
{
- free_http_ssl_data(conn);
+ free_http_tls_data(context, conn);
if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM)
TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr);
return TRUE;
}
-#ifdef PROXY_TLS_IMPL_OPENSSL
-/* Output any error strings that OpenSSL's accumulated as tracing messages. */
-static void
-flush_ssl_errors(krb5_context context)
-{
- unsigned long err;
- char buf[128];
-
- while ((err = ERR_get_error()) != 0) {
- ERR_error_string_n(err, buf, sizeof(buf));
- TRACE_SENDTO_KDC_HTTPS_ERROR(context, buf);
- }
-}
-
-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)
+/* Set up conn->http.tls. Return true on success. */
+static krb5_boolean
+setup_tls(krb5_context context, const krb5_data *realm,
+ struct conn_state *conn, struct select_state *selstate)
{
- 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;
-}
+ krb5_error_code ret;
+ krb5_boolean ok = FALSE;
+ char **anchors = NULL, *realmstr = NULL;
+ const char *names[4];
-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;
+ if (init_tls_vtable(context) != 0 || context->tls->setup == NULL)
+ return FALSE;
- realmz = k5memdup0(realm->data, realm->length, &err);
- if (realmz == NULL)
+ realmstr = k5memdup0(realm->data, realm->length, &ret);
+ if (realmstr == 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 krb5_boolean
-ssl_check_name_or_ip(X509 *x, const char *expected_name)
-{
- struct in_addr in;
- struct in6_addr in6;
-
- if (inet_aton(expected_name, &in) != 0 ||
- inet_pton(AF_INET6, expected_name, &in6) != 0) {
- return k5_check_cert_address(x, expected_name);
- } else {
- return k5_check_cert_servername(x, expected_name);
- }
-}
+ names[0] = KRB5_CONF_REALMS;
+ names[1] = realmstr;
+ names[2] = KRB5_CONF_HTTP_ANCHORS;
+ names[3] = NULL;
+ ret = profile_get_values(context->profile, names, &anchors);
+ if (ret != 0 && ret != PROF_NO_RELATION)
+ goto cleanup;
-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;
- struct conn_state *conn = NULL;
- const char *cert = NULL, *errstr, *expected_name;
- 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);
- conn = SSL_get_ex_data(ssl, ssl_ex_conn_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;
- }
- /* If we're not looking at the peer, we're done and everything's ok. */
- if (depth != 0)
- return 1;
- /* Check if the name we expect to find is in the certificate. */
- expected_name = conn->http.servername;
- if (ssl_check_name_or_ip(x, expected_name)) {
- TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(context, expected_name);
- return 1;
- } else {
- TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MISMATCH(context, expected_name);
+ if (context->tls->setup(context, conn->fd, conn->http.servername, anchors,
+ &conn->http.tls) != 0) {
+ TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr);
+ goto cleanup;
}
- /* The name didn't match. */
- return 0;
-}
-/*
- * 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
- * anything goes wrong while we're doing that; return true otherwise.
- */
-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;
+ ok = TRUE;
- if (ssl_ex_context_id == -1 || ssl_ex_conn_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, 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;
- if (!SSL_set_ex_data(ssl, ssl_ex_conn_id, conn))
- goto kill_conn;
-
- /* Tell the SSL library about the socket. */
- if (!SSL_set_fd(ssl, conn->fd))
- goto kill_conn;
- SSL_set_connect_state(ssl);
-
- SSL_CTX_free(ctx);
- conn->http.ssl = ssl;
-
- return TRUE;
-
-kill_conn:
- TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr);
- flush_ssl_errors(context);
- SSL_free(ssl);
- SSL_CTX_free(ctx);
- kill_conn(context, conn, selstate);
- return FALSE;
+cleanup:
+ free(realmstr);
+ profile_free_list(anchors);
+ return ok;
}
/* Set conn->state to READING when done; otherwise, call a cm_set_. */
service_https_write(krb5_context context, const krb5_data *realm,
struct conn_state *conn, struct select_state *selstate)
{
- ssize_t nwritten;
- int e;
+ k5_tls_status st;
/* If this is our first time in here, set up the SSL context. */
- if (conn->http.ssl == NULL && !setup_ssl(context, realm, conn, selstate))
+ if (conn->http.tls == NULL && !setup_tls(context, realm, conn, selstate)) {
+ kill_conn(context, conn, selstate);
return FALSE;
+ }
/* Try to transmit our request to the server. */
- nwritten = SSL_write(conn->http.ssl, SG_BUF(conn->out.sgp),
- SG_LEN(conn->out.sgbuf));
- if (nwritten <= 0) {
- e = SSL_get_error(conn->http.ssl, nwritten);
- if (e == SSL_ERROR_WANT_READ) {
- cm_read(selstate, conn->fd);
- return FALSE;
- } else if (e == SSL_ERROR_WANT_WRITE) {
- cm_write(selstate, conn->fd);
- return FALSE;
- }
+ st = context->tls->write(context, conn->http.tls, SG_BUF(conn->out.sgp),
+ SG_LEN(conn->out.sgbuf));
+ if (st == DONE) {
+ TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr);
+ cm_read(selstate, conn->fd);
+ conn->state = READING;
+ } else if (st == WANT_READ) {
+ cm_read(selstate, conn->fd);
+ } else if (st == WANT_WRITE) {
+ cm_write(selstate, conn->fd);
+ } else if (st == ERROR_TLS) {
TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(context, &conn->addr);
- flush_ssl_errors(context);
kill_conn(context, conn, selstate);
- return FALSE;
}
- /* Done writing, switch to reading. */
- TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr);
- cm_read(selstate, conn->fd);
- conn->state = READING;
return FALSE;
}
-/*
- * Return true on readable data, call a cm_read/write function and return
- * false if the SSL layer needs it, kill the connection otherwise.
- */
+/* Return true on finished data. Call a cm_read/write function and return
+ * false if the TLS layer needs it. Kill the connection on error. */
static krb5_boolean
https_read_bytes(krb5_context context, struct conn_state *conn,
struct select_state *selstate)
{
- size_t bufsize;
- ssize_t nread;
- krb5_boolean readbytes = FALSE;
- int e = 0;
+ size_t bufsize, nread;
+ k5_tls_status st;
char *tmp;
struct incoming_message *in = &conn->in;
in->bufsize = bufsize;
}
- nread = SSL_read(conn->http.ssl, &in->buf[in->pos],
- in->bufsize - in->pos - 1);
- if (nread <= 0)
+ st = context->tls->read(context, conn->http.tls, &in->buf[in->pos],
+ in->bufsize - in->pos - 1, &nread);
+ if (st != DATA_READ)
break;
+
in->pos += nread;
in->buf[in->pos] = '\0';
- readbytes = TRUE;
}
- e = SSL_get_error(conn->http.ssl, nread);
- if (e == SSL_ERROR_WANT_READ) {
+ if (st == DONE)
+ return TRUE;
+
+ if (st == WANT_READ) {
cm_read(selstate, conn->fd);
- return FALSE;
- } else if (e == SSL_ERROR_WANT_WRITE) {
+ } else if (st == WANT_WRITE) {
cm_write(selstate, conn->fd);
- return FALSE;
- } else if ((e == SSL_ERROR_ZERO_RETURN) ||
- (e == SSL_ERROR_SYSCALL && nread == 0 && readbytes)) {
- return TRUE;
+ } else if (st == ERROR_TLS) {
+ TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr);
+ kill_conn(context, conn, selstate);
}
-
- e = readbytes ? SOCKET_ERRNO : ECONNRESET;
- TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr, e);
- flush_ssl_errors(context);
- kill_conn(context, conn, selstate);
return FALSE;
}
kill_conn(context, conn, selstate);
return FALSE;
}
-#else
-static krb5_boolean
-service_https_write(krb5_context context, const krb5_data *realm,
- struct conn_state *conn, struct select_state *selstate)
-{
- abort();
-}
-static krb5_boolean
-service_https_read(krb5_context context, const krb5_data *realm,
- struct conn_state *conn, struct select_state *selstate)
-{
- abort();
-}
-#endif
/* Return the maximum of endtime and the endtime fields of all currently active
* TCP connections. */
if (socktype_for_transport(state->addr.transport) == SOCK_STREAM)
TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &state->addr);
closesocket(state->fd);
- free_http_ssl_data(state);
+ free_http_tls_data(context, state);
}
if (state->state == READING && state->in.buf != udpbuf)
free(state->in.buf);
--- /dev/null
+mydir=plugins$(S)tls$(S)k5tls
+BUILDTOP=$(REL)..$(S)..$(S)..
+MODULE_INSTALL_DIR = $(KRB5_TLS_MODULE_DIR)
+LOCALINCLUDES= $(PROXY_TLS_IMPL_CFLAGS)
+
+LIBBASE=k5tls
+LIBMAJOR=0
+LIBMINOR=0
+RELDIR=../plugins/tls/k5tls
+SHLIB_EXPDEPS= $(KRB5_DEPLIB) $(SUPPORT_DEPLIB)
+SHLIB_EXPLIBS= $(KRB5_LIB) $(SUPPORT_LIB) $(PROXY_TLS_IMPL_LIBS)
+
+STLIBOBJS=openssl.o notls.o
+
+SRCS=$(srcdir)/openssl.c $(srcdir)/notls.c
+
+all-unix:: all-liblinks
+install-unix:: install-libs
+clean-unix:: clean-libs clean-libobjs
+
+@libnover_frag@
+@libobj_frag@
--- /dev/null
+#
+# Generated makefile dependencies follow.
+#
+openssl.so openssl.po $(OUTPRE)openssl.$(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-tls.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 openssl.c
+notls.so notls.po $(OUTPRE)notls.$(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-tls.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 notls.c
--- /dev/null
+tls_k5tls_initvt
--- /dev/null
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plus/tls/k5tls/none.c - Stub TLS module implementation */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * 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 HOLDER 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.
+ */
+
+/* This dummy module is used if no TLS implemented is selected. */
+
+#include "k5-int.h"
+#include "k5-utf8.h"
+#include "k5-tls.h"
+
+#ifdef PROXY_TLS_IMPL_NONE
+
+krb5_error_code
+tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
+krb5_error_code
+tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ /* Leave all vtable functions nulled. */
+ return 0;
+}
+
+#endif /* PROXY_TLS_IMPL_NONE */
--- /dev/null
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/tls/k5tls/openssl.c - OpenSSL TLS module implementation */
+/*
+ * Copyright 2013,2014 Red Hat, Inc.
+ *
+ * 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"
+#include "k5-tls.h"
+
+#ifdef PROXY_TLS_IMPL_OPENSSL
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <dirent.h>
+
+struct k5_tls_handle_st {
+ SSL *ssl;
+ char *servername;
+};
+
+static int ex_context_id = -1;
+static int ex_handle_id = -1;
+
+MAKE_INIT_FUNCTION(init_openssl);
+
+int
+init_openssl()
+{
+ SSL_library_init();
+ SSL_load_error_strings();
+ OpenSSL_add_all_algorithms();
+ ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ ex_handle_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ return 0;
+}
+
+static void
+flush_errors(krb5_context context)
+{
+ unsigned long err;
+ char buf[128];
+
+ while ((err = ERR_get_error()) != 0) {
+ ERR_error_string_n(err, buf, sizeof(buf));
+ TRACE_TLS_ERROR(context, buf);
+ }
+}
+
+/* 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 int
+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 text matches any of the addresses we can recover from x. */
+static krb5_boolean
+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;
+ int name_length;
+ struct in_addr sin;
+ struct in6_addr sin6;
+
+ /* Parse the IP address into an octet string. */
+ ip = M_ASN1_OCTET_STRING_new();
+ if (ip == NULL)
+ return FALSE;
+ if (inet_pton(AF_INET, text, &sin)) {
+ M_ASN1_OCTET_STRING_set(ip, &sin, sizeof(sin));
+ } else if (inet_pton(AF_INET6, text, &sin6)) {
+ M_ASN1_OCTET_STRING_set(ip, &sin6, sizeof(sin6));
+ } else {
+ ASN1_OCTET_STRING_free(ip);
+ return FALSE;
+ }
+
+ /* 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 (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) == (size_t)name_length &&
+ strncmp(text, buf, name_length) == 0;
+ }
+
+ /* We didn't find a match. */
+ return FALSE;
+}
+
+/* Return true if expected matches any of the names we can recover from x. */
+static krb5_boolean
+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;
+}
+
+static krb5_boolean
+check_cert_name_or_ip(X509 *x, const char *expected_name)
+{
+ struct in_addr in;
+ struct in6_addr in6;
+
+ if (inet_pton(AF_INET, expected_name, &in) != 0 ||
+ inet_pton(AF_INET6, expected_name, &in6) != 0) {
+ return check_cert_address(x, expected_name);
+ } else {
+ return check_cert_servername(x, expected_name);
+ }
+}
+
+static int
+verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx)
+{
+ X509 *x;
+ SSL *ssl;
+ BIO *bio;
+ krb5_context context;
+ int err, depth;
+ k5_tls_handle handle;
+ const char *cert = NULL, *errstr, *expected_name;
+ 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, ex_context_id);
+ handle = SSL_get_ex_data(ssl, ex_handle_id);
+ assert(context != NULL && handle != NULL);
+ /* We do have the peer's cert, right? */
+ x = X509_STORE_CTX_get_current_cert(store_ctx);
+ if (x == NULL) {
+ TRACE_TLS_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_TLS_CERT_ERROR(context, depth, count, cert, err, errstr);
+ BIO_free(bio);
+ }
+ return 0;
+ }
+ /* If we're not looking at the peer, we're done and everything's ok. */
+ if (depth != 0)
+ return 1;
+ /* Check if the name we expect to find is in the certificate. */
+ expected_name = handle->servername;
+ if (check_cert_name_or_ip(x, expected_name)) {
+ TRACE_TLS_SERVER_NAME_MATCH(context, expected_name);
+ return 1;
+ } else {
+ TRACE_TLS_SERVER_NAME_MISMATCH(context, expected_name);
+ }
+ /* The name didn't match. */
+ return 0;
+}
+
+static krb5_error_code
+load_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_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_anchor_file(store, filename) == 0)
+ found_any = TRUE;
+ }
+ }
+ closedir(d);
+ return found_any ? 0 : ENOENT;
+}
+
+static krb5_error_code
+load_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_anchor_file(store, location + 5);
+ } else if (strncmp(location, "DIR:", 4) == 0) {
+ return load_anchor_dir(store, location + 4);
+ } else if (strncmp(location, "ENV:", 4) == 0) {
+ envloc = getenv(location + 4);
+ if (envloc == NULL)
+ return ENOENT;
+ return load_anchor(ctx, envloc);
+ }
+ return EINVAL;
+}
+
+static krb5_error_code
+load_anchors(krb5_context context, char **anchors, SSL_CTX *sctx)
+{
+ unsigned int i;
+ krb5_error_code ret;
+
+ if (anchors != NULL) {
+ for (i = 0; anchors[i] != NULL; i++) {
+ ret = load_anchor(sctx, anchors[i]);
+ if (ret)
+ return ret;
+ }
+ } else {
+ /* Use the library defaults. */
+ if (SSL_CTX_set_default_verify_paths(sctx) != 1)
+ return ENOENT;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+setup(krb5_context context, SOCKET fd, const char *servername,
+ char **anchors, k5_tls_handle *handle_out)
+{
+ int e;
+ long options;
+ SSL_CTX *ctx = NULL;
+ SSL *ssl = NULL;
+ k5_tls_handle handle = NULL;
+
+ *handle_out = NULL;
+
+ (void)CALL_INIT_FUNCTION(init_openssl);
+ if (ex_context_id == -1 || ex_handle_id == -1)
+ return KRB5_PLUGIN_OP_NOTSUPP;
+
+ /* Do general SSL library setup. */
+ ctx = SSL_CTX_new(SSLv23_client_method());
+ if (ctx == NULL)
+ goto error;
+ options = SSL_CTX_get_options(ctx);
+ SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2);
+
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
+ X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0);
+ e = load_anchors(context, anchors, ctx);
+ if (e != 0)
+ goto error;
+
+ ssl = SSL_new(ctx);
+ if (ssl == NULL)
+ goto error;
+
+ if (!SSL_set_fd(ssl, fd))
+ goto error;
+ SSL_set_connect_state(ssl);
+
+ /* Create a handle and allow verify_callback to access it. */
+ handle = malloc(sizeof(*handle));
+ if (handle == NULL || !SSL_set_ex_data(ssl, ex_handle_id, handle))
+ goto error;
+
+ handle->ssl = ssl;
+ handle->servername = strdup(servername);
+ if (handle->servername == NULL)
+ goto error;
+ *handle_out = handle;
+ SSL_CTX_free(ctx);
+ return 0;
+
+error:
+ flush_errors(context);
+ free(handle);
+ SSL_free(ssl);
+ SSL_CTX_free(ctx);
+ return KRB5_PLUGIN_OP_NOTSUPP;
+}
+
+static k5_tls_status
+write_tls(krb5_context context, k5_tls_handle handle, const void *data,
+ size_t len)
+{
+ int nwritten, e;
+
+ /* Try to transmit our request; allow verify_callback to access context. */
+ if (!SSL_set_ex_data(handle->ssl, ex_context_id, context))
+ return ERROR_TLS;
+ nwritten = SSL_write(handle->ssl, data, len);
+ (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL);
+ if (nwritten > 0)
+ return DONE;
+
+ e = SSL_get_error(handle->ssl, nwritten);
+ if (e == SSL_ERROR_WANT_READ)
+ return WANT_READ;
+ else if (e == SSL_ERROR_WANT_WRITE)
+ return WANT_WRITE;
+ flush_errors(context);
+ return ERROR_TLS;
+}
+
+static k5_tls_status
+read_tls(krb5_context context, k5_tls_handle handle, void *data,
+ size_t data_size, size_t *len_out)
+{
+ ssize_t nread;
+ int e;
+
+ *len_out = 0;
+
+ /* Try to read response data; allow verify_callback to access context. */
+ if (!SSL_set_ex_data(handle->ssl, ex_context_id, context))
+ return ERROR_TLS;
+ nread = SSL_read(handle->ssl, data, data_size);
+ (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL);
+ if (nread > 0) {
+ *len_out = nread;
+ return DATA_READ;
+ }
+
+ e = SSL_get_error(handle->ssl, nread);
+ if (e == SSL_ERROR_WANT_READ)
+ return WANT_READ;
+ else if (e == SSL_ERROR_WANT_WRITE)
+ return WANT_WRITE;
+
+ if (e == SSL_ERROR_ZERO_RETURN || (e == SSL_ERROR_SYSCALL && nread == 0))
+ return DONE;
+
+ flush_errors(context);
+ return ERROR_TLS;
+}
+
+static void
+free_handle(krb5_context context, k5_tls_handle handle)
+{
+ SSL_free(handle->ssl);
+ free(handle->servername);
+ free(handle);
+}
+
+krb5_error_code
+tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
+krb5_error_code
+tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ k5_tls_vtable vt;
+
+ vt = (k5_tls_vtable)vtable;
+ vt->setup = setup;
+ vt->write = write_tls;
+ vt->read = read_tls;
+ vt->free_handle = free_handle;
+ return 0;
+}
+
+#endif /* PROXY_TLS_IMPL_OPENSSL */