blob: c7e595e462286d3db2d9442ea8c398da9988f371 [file] [log] [blame]
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
* Copyright (C) 2010-2018 Fox Crypto B.V. <openvpn@fox-it.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* @file Control Channel Verification Module
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#ifdef ENABLE_CRYPTO
#include "misc.h"
#include "manage.h"
#include "otime.h"
#include "base64.h"
#include "ssl_verify.h"
#include "ssl_verify_backend.h"
#ifdef ENABLE_CRYPTO_OPENSSL
#include "ssl_verify_openssl.h"
#endif
/** Maximum length of common name */
#define TLS_USERNAME_LEN 64
/** Legal characters in an X509 name with --compat-names */
#define X509_NAME_CHAR_CLASS (CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT|CC_SLASH|CC_COLON|CC_EQUAL)
/** Legal characters in a common name with --compat-names */
#define COMMON_NAME_CHAR_CLASS (CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT|CC_SLASH)
static void
string_mod_remap_name(char *str, const unsigned int restrictive_flags)
{
if (compat_flag(COMPAT_FLAG_QUERY | COMPAT_NAMES)
&& !compat_flag(COMPAT_FLAG_QUERY | COMPAT_NO_NAME_REMAPPING))
{
string_mod(str, restrictive_flags, 0, '_');
}
else
{
string_mod(str, CC_PRINT, CC_CRLF, '_');
}
}
/*
* Export the untrusted IP address and port to the environment
*/
static void
setenv_untrusted(struct tls_session *session)
{
setenv_link_socket_actual(session->opt->es, "untrusted", &session->untrusted_addr, SA_IP_PORT);
}
/**
* Wipes the authentication token out of the memory, frees and cleans up related buffers and flags
*
* @param multi Pointer to a multi object holding the auth_token variables
*/
static void
wipe_auth_token(struct tls_multi *multi)
{
if(multi)
{
if (multi->auth_token)
{
secure_memzero(multi->auth_token, AUTH_TOKEN_SIZE);
free(multi->auth_token);
}
multi->auth_token = NULL;
multi->auth_token_sent = false;
}
}
/*
* Remove authenticated state from all sessions in the given tunnel
*/
static void
tls_deauthenticate(struct tls_multi *multi)
{
if (multi)
{
wipe_auth_token(multi);
for (int i = 0; i < TM_SIZE; ++i)
{
for (int j = 0; j < KS_SIZE; ++j)
{
multi->session[i].key[j].authenticated = false;
}
}
}
}
/*
* Set the given session's common_name
*/
static void
set_common_name(struct tls_session *session, const char *common_name)
{
if (session->common_name)
{
free(session->common_name);
session->common_name = NULL;
#ifdef ENABLE_PF
session->common_name_hashval = 0;
#endif
}
if (common_name)
{
/* FIXME: Last alloc will never be freed */
session->common_name = string_alloc(common_name, NULL);
#ifdef ENABLE_PF
{
const uint32_t len = (uint32_t) strlen(common_name);
if (len)
{
session->common_name_hashval = hash_func((const uint8_t *)common_name, len+1, 0);
}
else
{
session->common_name_hashval = 0;
}
}
#endif
}
}
/*
* Retrieve the common name for the given tunnel's active session. If the
* common name is NULL or empty, return NULL if null is true, or "UNDEF" if
* null is false.
*/
const char *
tls_common_name(const struct tls_multi *multi, const bool null)
{
const char *ret = NULL;
if (multi)
{
ret = multi->session[TM_ACTIVE].common_name;
}
if (ret && strlen(ret))
{
return ret;
}
else if (null)
{
return NULL;
}
else
{
return "UNDEF";
}
}
/*
* Lock the common name for the given tunnel.
*/
void
tls_lock_common_name(struct tls_multi *multi)
{
const char *cn = multi->session[TM_ACTIVE].common_name;
if (cn && !multi->locked_cn)
{
multi->locked_cn = string_alloc(cn, NULL);
}
}
/*
* Lock the username for the given tunnel
*/
static bool
tls_lock_username(struct tls_multi *multi, const char *username)
{
if (multi->locked_username)
{
if (!username || strcmp(username, multi->locked_username))
{
msg(D_TLS_ERRORS, "TLS Auth Error: username attempted to change from '%s' to '%s' -- tunnel disabled",
multi->locked_username,
np(username));
/* disable the tunnel */
tls_deauthenticate(multi);
return false;
}
}
else
{
if (username)
{
multi->locked_username = string_alloc(username, NULL);
}
}
return true;
}
const char *
tls_username(const struct tls_multi *multi, const bool null)
{
const char *ret = NULL;
if (multi)
{
ret = multi->locked_username;
}
if (ret && strlen(ret))
{
return ret;
}
else if (null)
{
return NULL;
}
else
{
return "UNDEF";
}
}
void
cert_hash_remember(struct tls_session *session, const int error_depth,
const struct buffer *cert_hash)
{
if (error_depth >= 0 && error_depth < MAX_CERT_DEPTH)
{
if (!session->cert_hash_set)
{
ALLOC_OBJ_CLEAR(session->cert_hash_set, struct cert_hash_set);
}
if (!session->cert_hash_set->ch[error_depth])
{
ALLOC_OBJ(session->cert_hash_set->ch[error_depth], struct cert_hash);
}
struct cert_hash *ch = session->cert_hash_set->ch[error_depth];
ASSERT(sizeof(ch->sha256_hash) == BLEN(cert_hash));
memcpy(ch->sha256_hash, BPTR(cert_hash), sizeof(ch->sha256_hash));
}
}
void
cert_hash_free(struct cert_hash_set *chs)
{
if (chs)
{
int i;
for (i = 0; i < MAX_CERT_DEPTH; ++i)
{
free(chs->ch[i]);
}
free(chs);
}
}
bool
cert_hash_compare(const struct cert_hash_set *chs1, const struct cert_hash_set *chs2)
{
if (chs1 && chs2)
{
int i;
for (i = 0; i < MAX_CERT_DEPTH; ++i)
{
const struct cert_hash *ch1 = chs1->ch[i];
const struct cert_hash *ch2 = chs2->ch[i];
if (!ch1 && !ch2)
{
continue;
}
else if (ch1 && ch2 && !memcmp(ch1->sha256_hash, ch2->sha256_hash,
sizeof(ch1->sha256_hash)))
{
continue;
}
else
{
return false;
}
}
return true;
}
else if (!chs1 && !chs2)
{
return true;
}
else
{
return false;
}
}
static struct cert_hash_set *
cert_hash_copy(const struct cert_hash_set *chs)
{
struct cert_hash_set *dest = NULL;
if (chs)
{
int i;
ALLOC_OBJ_CLEAR(dest, struct cert_hash_set);
for (i = 0; i < MAX_CERT_DEPTH; ++i)
{
const struct cert_hash *ch = chs->ch[i];
if (ch)
{
ALLOC_OBJ(dest->ch[i], struct cert_hash);
memcpy(dest->ch[i]->sha256_hash, ch->sha256_hash,
sizeof(dest->ch[i]->sha256_hash));
}
}
}
return dest;
}
void
tls_lock_cert_hash_set(struct tls_multi *multi)
{
const struct cert_hash_set *chs = multi->session[TM_ACTIVE].cert_hash_set;
if (chs && !multi->locked_cert_hash_set)
{
multi->locked_cert_hash_set = cert_hash_copy(chs);
}
}
/*
* Returns the string associated with the given certificate type.
*/
static const char *
print_nsCertType(int type)
{
switch (type)
{
case NS_CERT_CHECK_SERVER:
return "SERVER";
case NS_CERT_CHECK_CLIENT:
return "CLIENT";
default:
return "?";
}
}
/*
* Verify the peer's certificate fields.
*
* @param opt the tls options to verify against
* @param peer_cert the peer's certificate
* @param subject the peer's extracted subject name
* @param subject the peer's extracted common name
*/
static result_t
verify_peer_cert(const struct tls_options *opt, openvpn_x509_cert_t *peer_cert,
const char *subject, const char *common_name)
{
/* verify certificate nsCertType */
if (opt->ns_cert_type != NS_CERT_CHECK_NONE)
{
if (SUCCESS == x509_verify_ns_cert_type(peer_cert, opt->ns_cert_type))
{
msg(D_HANDSHAKE, "VERIFY OK: nsCertType=%s",
print_nsCertType(opt->ns_cert_type));
}
else
{
msg(D_HANDSHAKE, "VERIFY nsCertType ERROR: %s, require nsCertType=%s",
subject, print_nsCertType(opt->ns_cert_type));
return FAILURE; /* Reject connection */
}
}
/* verify certificate ku */
if (opt->remote_cert_ku[0] != 0)
{
if (SUCCESS == x509_verify_cert_ku(peer_cert, opt->remote_cert_ku, MAX_PARMS))
{
msg(D_HANDSHAKE, "VERIFY KU OK");
}
else
{
msg(D_HANDSHAKE, "VERIFY KU ERROR");
return FAILURE; /* Reject connection */
}
}
/* verify certificate eku */
if (opt->remote_cert_eku != NULL)
{
if (SUCCESS == x509_verify_cert_eku(peer_cert, opt->remote_cert_eku))
{
msg(D_HANDSHAKE, "VERIFY EKU OK");
}
else
{
msg(D_HANDSHAKE, "VERIFY EKU ERROR");
return FAILURE; /* Reject connection */
}
}
/* verify X509 name or username against --verify-x509-[user]name */
if (opt->verify_x509_type != VERIFY_X509_NONE)
{
if ( (opt->verify_x509_type == VERIFY_X509_SUBJECT_DN
&& strcmp(opt->verify_x509_name, subject) == 0)
|| (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN
&& strcmp(opt->verify_x509_name, common_name) == 0)
|| (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN_PREFIX
&& strncmp(opt->verify_x509_name, common_name,
strlen(opt->verify_x509_name)) == 0) )
{
msg(D_HANDSHAKE, "VERIFY X509NAME OK: %s", subject);
}
else
{
msg(D_HANDSHAKE, "VERIFY X509NAME ERROR: %s, must be %s",
subject, opt->verify_x509_name);
return FAILURE; /* Reject connection */
}
}
return SUCCESS;
}
/*
* Export the subject, common_name, and raw certificate fields to the
* environment for later verification by scripts and plugins.
*/
static void
verify_cert_set_env(struct env_set *es, openvpn_x509_cert_t *peer_cert, int cert_depth,
const char *subject, const char *common_name,
const struct x509_track *x509_track)
{
char envname[64];
char *serial = NULL;
struct gc_arena gc = gc_new();
/* Save X509 fields in environment */
if (x509_track)
{
x509_setenv_track(x509_track, es, cert_depth, peer_cert);
}
else
{
x509_setenv(es, cert_depth, peer_cert);
}
/* export subject name string as environmental variable */
openvpn_snprintf(envname, sizeof(envname), "tls_id_%d", cert_depth);
setenv_str(es, envname, subject);
#if 0
/* export common name string as environmental variable */
openvpn_snprintf(envname, sizeof(envname), "tls_common_name_%d", cert_depth);
setenv_str(es, envname, common_name);
#endif
/* export X509 cert fingerprints */
{
struct buffer sha1 = x509_get_sha1_fingerprint(peer_cert, &gc);
struct buffer sha256 = x509_get_sha256_fingerprint(peer_cert, &gc);
openvpn_snprintf(envname, sizeof(envname), "tls_digest_%d", cert_depth);
setenv_str(es, envname,
format_hex_ex(BPTR(&sha1), BLEN(&sha1), 0, 1, ":", &gc));
openvpn_snprintf(envname, sizeof(envname), "tls_digest_sha256_%d",
cert_depth);
setenv_str(es, envname,
format_hex_ex(BPTR(&sha256), BLEN(&sha256), 0, 1, ":", &gc));
}
/* export serial number as environmental variable */
serial = backend_x509_get_serial(peer_cert, &gc);
openvpn_snprintf(envname, sizeof(envname), "tls_serial_%d", cert_depth);
setenv_str(es, envname, serial);
/* export serial number in hex as environmental variable */
serial = backend_x509_get_serial_hex(peer_cert, &gc);
openvpn_snprintf(envname, sizeof(envname), "tls_serial_hex_%d", cert_depth);
setenv_str(es, envname, serial);
gc_free(&gc);
}
/*
* call --tls-verify plug-in(s)
*/
static result_t
verify_cert_call_plugin(const struct plugin_list *plugins, struct env_set *es,
int cert_depth, openvpn_x509_cert_t *cert, char *subject)
{
if (plugin_defined(plugins, OPENVPN_PLUGIN_TLS_VERIFY))
{
int ret;
struct argv argv = argv_new();
argv_printf(&argv, "%d %s", cert_depth, subject);
ret = plugin_call_ssl(plugins, OPENVPN_PLUGIN_TLS_VERIFY, &argv, NULL, es, cert_depth, cert);
argv_reset(&argv);
if (ret == OPENVPN_PLUGIN_FUNC_SUCCESS)
{
msg(D_HANDSHAKE, "VERIFY PLUGIN OK: depth=%d, %s",
cert_depth, subject);
}
else
{
msg(D_HANDSHAKE, "VERIFY PLUGIN ERROR: depth=%d, %s",
cert_depth, subject);
return FAILURE; /* Reject connection */
}
}
return SUCCESS;
}
static const char *
verify_cert_export_cert(openvpn_x509_cert_t *peercert, const char *tmp_dir, struct gc_arena *gc)
{
FILE *peercert_file;
const char *peercert_filename = "";
/* create tmp file to store peer cert */
if (!tmp_dir
|| !(peercert_filename = create_temp_file(tmp_dir, "pcf", gc)))
{
msg (M_WARN, "Failed to create peer cert file");
return NULL;
}
/* write peer-cert in tmp-file */
peercert_file = fopen(peercert_filename, "w+");
if (!peercert_file)
{
msg(M_ERR, "Failed to open temporary file : %s", peercert_filename);
return NULL;
}
if (SUCCESS != x509_write_pem(peercert_file, peercert))
{
msg(M_ERR, "Error writing PEM file containing certificate");
}
fclose(peercert_file);
return peercert_filename;
}
/*
* run --tls-verify script
*/
static result_t
verify_cert_call_command(const char *verify_command, struct env_set *es,
int cert_depth, openvpn_x509_cert_t *cert, char *subject, const char *verify_export_cert)
{
const char *tmp_file = NULL;
int ret;
struct gc_arena gc = gc_new();
struct argv argv = argv_new();
setenv_str(es, "script_type", "tls-verify");
if (verify_export_cert)
{
tmp_file = verify_cert_export_cert(cert, verify_export_cert, &gc);
if (!tmp_file)
{
ret = false;
goto cleanup;
}
setenv_str(es, "peer_cert", tmp_file);
}
argv_parse_cmd(&argv, verify_command);
argv_printf_cat(&argv, "%d %s", cert_depth, subject);
argv_msg_prefix(D_TLS_DEBUG, &argv, "TLS: executing verify command");
ret = openvpn_run_script(&argv, es, 0, "--tls-verify script");
if (verify_export_cert)
{
if (tmp_file)
{
platform_unlink(tmp_file);
}
}
cleanup:
gc_free(&gc);
argv_reset(&argv);
if (ret)
{
msg(D_HANDSHAKE, "VERIFY SCRIPT OK: depth=%d, %s",
cert_depth, subject);
return SUCCESS;
}
msg(D_HANDSHAKE, "VERIFY SCRIPT ERROR: depth=%d, %s",
cert_depth, subject);
return FAILURE; /* Reject connection */
}
/*
* check peer cert against CRL directory
*/
static result_t
verify_check_crl_dir(const char *crl_dir, openvpn_x509_cert_t *cert)
{
result_t ret = FAILURE;
char fn[256];
int fd = -1;
struct gc_arena gc = gc_new();
char *serial = backend_x509_get_serial(cert, &gc);
if (!openvpn_snprintf(fn, sizeof(fn), "%s%c%s", crl_dir, OS_SPECIFIC_DIRSEP, serial))
{
msg(D_HANDSHAKE, "VERIFY CRL: filename overflow");
goto cleanup;
}
fd = platform_open(fn, O_RDONLY, 0);
if (fd >= 0)
{
msg(D_HANDSHAKE, "VERIFY CRL: certificate serial number %s is revoked", serial);
goto cleanup;
}
ret = SUCCESS;
cleanup:
if (fd != -1)
{
close(fd);
}
gc_free(&gc);
return ret;
}
result_t
verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_depth)
{
result_t ret = FAILURE;
char *subject = NULL;
char common_name[TLS_USERNAME_LEN+1] = {0}; /* null-terminated */
const struct tls_options *opt;
struct gc_arena gc = gc_new();
opt = session->opt;
ASSERT(opt);
session->verified = false;
/* get the X509 name */
subject = x509_get_subject(cert, &gc);
if (!subject)
{
msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, could not extract X509 "
"subject string from certificate", cert_depth);
goto cleanup;
}
/* enforce character class restrictions in X509 name */
string_mod_remap_name(subject, X509_NAME_CHAR_CLASS);
string_replace_leading(subject, '-', '_');
/* extract the username (default is CN) */
if (SUCCESS != backend_x509_get_username(common_name, sizeof(common_name),
opt->x509_username_field, cert))
{
if (!cert_depth)
{
msg(D_TLS_ERRORS, "VERIFY ERROR: could not extract %s from X509 "
"subject string ('%s') -- note that the username length is "
"limited to %d characters",
opt->x509_username_field,
subject,
TLS_USERNAME_LEN);
goto cleanup;
}
}
/* enforce character class restrictions in common name */
string_mod_remap_name(common_name, COMMON_NAME_CHAR_CLASS);
/* warn if cert chain is too deep */
if (cert_depth >= MAX_CERT_DEPTH)
{
msg(D_TLS_ERRORS, "TLS Error: Convoluted certificate chain detected with depth [%d] greater than %d", cert_depth, MAX_CERT_DEPTH);
goto cleanup; /* Reject connection */
}
/* verify level 1 cert, i.e. the CA that signed our leaf cert */
if (cert_depth == 1 && opt->verify_hash)
{
struct buffer ca_hash = {0};
switch (opt->verify_hash_algo)
{
case MD_SHA1:
ca_hash = x509_get_sha1_fingerprint(cert, &gc);
break;
case MD_SHA256:
ca_hash = x509_get_sha256_fingerprint(cert, &gc);
break;
default:
/* This should normally not happen at all; the algorithm used
* is parsed by add_option() [options.c] and set to a predefined
* value in an enumerated type. So if this unlikely scenario
* happens, consider this a failure
*/
msg(M_WARN, "Unexpected invalid algorithm used with "
"--verify-hash (%i)", opt->verify_hash_algo);
ret = FAILURE;
goto cleanup;
}
if (memcmp(BPTR(&ca_hash), opt->verify_hash, BLEN(&ca_hash)))
{
msg(D_TLS_ERRORS, "TLS Error: level-1 certificate hash verification failed");
goto cleanup;
}
}
/* save common name in session object */
if (cert_depth == 0)
{
set_common_name(session, common_name);
}
session->verify_maxlevel = max_int(session->verify_maxlevel, cert_depth);
/* export certificate values to the environment */
verify_cert_set_env(opt->es, cert, cert_depth, subject, common_name,
opt->x509_track);
/* export current untrusted IP */
setenv_untrusted(session);
/* If this is the peer's own certificate, verify it */
if (cert_depth == 0 && SUCCESS != verify_peer_cert(opt, cert, subject, common_name))
{
goto cleanup;
}
/* call --tls-verify plug-in(s), if registered */
if (SUCCESS != verify_cert_call_plugin(opt->plugins, opt->es, cert_depth, cert, subject))
{
goto cleanup;
}
/* run --tls-verify script */
if (opt->verify_command && SUCCESS != verify_cert_call_command(opt->verify_command,
opt->es, cert_depth, cert, subject, opt->verify_export_cert))
{
goto cleanup;
}
/* check peer cert against CRL */
if (opt->crl_file)
{
if (opt->ssl_flags & SSLF_CRL_VERIFY_DIR)
{
if (SUCCESS != verify_check_crl_dir(opt->crl_file, cert))
{
goto cleanup;
}
}
else
{
if (tls_verify_crl_missing(opt))
{
msg(D_TLS_ERRORS, "VERIFY ERROR: CRL not loaded");
goto cleanup;
}
}
}
msg(D_HANDSHAKE, "VERIFY OK: depth=%d, %s", cert_depth, subject);
session->verified = true;
ret = SUCCESS;
cleanup:
if (ret != SUCCESS)
{
tls_clear_error(); /* always? */
session->verified = false; /* double sure? */
}
gc_free(&gc);
return ret;
}
/* ***************************************************************************
* Functions for the management of deferred authentication when using
* user/password authentication.
*************************************************************************** */
#ifdef ENABLE_DEF_AUTH
/* key_state_test_auth_control_file return values,
* NOTE: acf_merge indexing depends on these values */
#define ACF_UNDEFINED 0
#define ACF_SUCCEEDED 1
#define ACF_DISABLED 2
#define ACF_FAILED 3
#endif
#ifdef MANAGEMENT_DEF_AUTH
void
man_def_auth_set_client_reason(struct tls_multi *multi, const char *client_reason)
{
if (multi->client_reason)
{
free(multi->client_reason);
multi->client_reason = NULL;
}
if (client_reason && strlen(client_reason))
{
/* FIXME: Last alloc will never be freed */
multi->client_reason = string_alloc(client_reason, NULL);
}
}
static inline unsigned int
man_def_auth_test(const struct key_state *ks)
{
if (management_enable_def_auth(management))
{
return ks->mda_status;
}
else
{
return ACF_DISABLED;
}
}
#endif /* ifdef MANAGEMENT_DEF_AUTH */
#ifdef PLUGIN_DEF_AUTH
/*
* auth_control_file functions
*/
void
key_state_rm_auth_control_file(struct key_state *ks)
{
if (ks && ks->auth_control_file)
{
platform_unlink(ks->auth_control_file);
free(ks->auth_control_file);
ks->auth_control_file = NULL;
}
}
static bool
key_state_gen_auth_control_file(struct key_state *ks, const struct tls_options *opt)
{
struct gc_arena gc = gc_new();
key_state_rm_auth_control_file(ks);
const char *acf = create_temp_file(opt->tmp_dir, "acf", &gc);
if (acf)
{
ks->auth_control_file = string_alloc(acf, NULL);
setenv_str(opt->es, "auth_control_file", ks->auth_control_file);
}
gc_free(&gc);
return acf;
}
static unsigned int
key_state_test_auth_control_file(struct key_state *ks)
{
if (ks && ks->auth_control_file)
{
unsigned int ret = ks->auth_control_status;
if (ret == ACF_UNDEFINED)
{
FILE *fp = fopen(ks->auth_control_file, "r");
if (fp)
{
const int c = fgetc(fp);
if (c == '1')
{
ret = ACF_SUCCEEDED;
}
else if (c == '0')
{
ret = ACF_FAILED;
}
fclose(fp);
ks->auth_control_status = ret;
}
}
return ret;
}
return ACF_DISABLED;
}
#endif /* ifdef PLUGIN_DEF_AUTH */
/*
* Return current session authentication state. Return
* value is TLS_AUTHENTICATION_x.
*/
int
tls_authentication_status(struct tls_multi *multi, const int latency)
{
bool deferred = false;
bool success = false;
bool active = false;
#ifdef ENABLE_DEF_AUTH
static const unsigned char acf_merge[] =
{
ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_UNDEFINED */
ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_SUCCEEDED */
ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_DISABLED */
ACF_FAILED, /* s1=ACF_UNDEFINED s2=ACF_FAILED */
ACF_UNDEFINED, /* s1=ACF_SUCCEEDED s2=ACF_UNDEFINED */
ACF_SUCCEEDED, /* s1=ACF_SUCCEEDED s2=ACF_SUCCEEDED */
ACF_SUCCEEDED, /* s1=ACF_SUCCEEDED s2=ACF_DISABLED */
ACF_FAILED, /* s1=ACF_SUCCEEDED s2=ACF_FAILED */
ACF_UNDEFINED, /* s1=ACF_DISABLED s2=ACF_UNDEFINED */
ACF_SUCCEEDED, /* s1=ACF_DISABLED s2=ACF_SUCCEEDED */
ACF_DISABLED, /* s1=ACF_DISABLED s2=ACF_DISABLED */
ACF_FAILED, /* s1=ACF_DISABLED s2=ACF_FAILED */
ACF_FAILED, /* s1=ACF_FAILED s2=ACF_UNDEFINED */
ACF_FAILED, /* s1=ACF_FAILED s2=ACF_SUCCEEDED */
ACF_FAILED, /* s1=ACF_FAILED s2=ACF_DISABLED */
ACF_FAILED /* s1=ACF_FAILED s2=ACF_FAILED */
};
#endif /* ENABLE_DEF_AUTH */
if (multi)
{
int i;
#ifdef ENABLE_DEF_AUTH
if (latency && multi->tas_last && multi->tas_last + latency >= now)
{
return TLS_AUTHENTICATION_UNDEFINED;
}
multi->tas_last = now;
#endif /* ENABLE_DEF_AUTH */
for (i = 0; i < KEY_SCAN_SIZE; ++i)
{
struct key_state *ks = multi->key_scan[i];
if (DECRYPT_KEY_ENABLED(multi, ks))
{
active = true;
if (ks->authenticated)
{
#ifdef ENABLE_DEF_AUTH
unsigned int s1 = ACF_DISABLED;
unsigned int s2 = ACF_DISABLED;
#ifdef PLUGIN_DEF_AUTH
s1 = key_state_test_auth_control_file(ks);
#endif /* PLUGIN_DEF_AUTH */
#ifdef MANAGEMENT_DEF_AUTH
s2 = man_def_auth_test(ks);
#endif /* MANAGEMENT_DEF_AUTH */
ASSERT(s1 < 4 && s2 < 4);
switch (acf_merge[(s1<<2) + s2])
{
case ACF_SUCCEEDED:
case ACF_DISABLED:
success = true;
ks->auth_deferred = false;
break;
case ACF_UNDEFINED:
if (now < ks->auth_deferred_expire)
{
deferred = true;
}
break;
case ACF_FAILED:
ks->authenticated = false;
break;
default:
ASSERT(0);
}
#else /* !ENABLE_DEF_AUTH */
success = true;
#endif /* ENABLE_DEF_AUTH */
}
}
}
}
#if 0
dmsg(D_TLS_ERRORS, "TAS: a=%d s=%d d=%d", active, success, deferred);
#endif
if (success)
{
return TLS_AUTHENTICATION_SUCCEEDED;
}
else if (!active || deferred)
{
return TLS_AUTHENTICATION_DEFERRED;
}
else
{
return TLS_AUTHENTICATION_FAILED;
}
}
#ifdef MANAGEMENT_DEF_AUTH
/*
* For deferred auth, this is where the management interface calls (on server)
* to indicate auth failure/success.
*/
bool
tls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, const bool auth, const char *client_reason)
{
bool ret = false;
if (multi)
{
int i;
man_def_auth_set_client_reason(multi, client_reason);
for (i = 0; i < KEY_SCAN_SIZE; ++i)
{
struct key_state *ks = multi->key_scan[i];
if (ks->mda_key_id == mda_key_id)
{
ks->mda_status = auth ? ACF_SUCCEEDED : ACF_FAILED;
ret = true;
}
}
}
return ret;
}
#endif /* ifdef MANAGEMENT_DEF_AUTH */
/* ****************************************************************************
* Functions to verify username and password
*
* Authenticate a client using username/password.
* Runs on server.
*
* If you want to add new authentication methods,
* this is the place to start.
*************************************************************************** */
/*
* Verify the user name and password using a script
*/
static bool
verify_user_pass_script(struct tls_session *session, const struct user_pass *up)
{
struct gc_arena gc = gc_new();
struct argv argv = argv_new();
const char *tmp_file = "";
bool ret = false;
/* Is username defined? */
if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
{
/* Set environmental variables prior to calling script */
setenv_str(session->opt->es, "script_type", "user-pass-verify");
if (session->opt->auth_user_pass_verify_script_via_file)
{
struct status_output *so;
tmp_file = create_temp_file(session->opt->tmp_dir, "up", &gc);
if (tmp_file)
{
so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
status_printf(so, "%s", up->username);
status_printf(so, "%s", up->password);
if (!status_close(so))
{
msg(D_TLS_ERRORS, "TLS Auth Error: could not write username/password to file: %s",
tmp_file);
goto done;
}
}
else
{
msg(D_TLS_ERRORS, "TLS Auth Error: could not create write "
"username/password to temp file");
}
}
else
{
setenv_str(session->opt->es, "username", up->username);
setenv_str(session->opt->es, "password", up->password);
}
/* setenv incoming cert common name for script */
setenv_str(session->opt->es, "common_name", session->common_name);
/* setenv client real IP address */
setenv_untrusted(session);
/* format command line */
argv_parse_cmd(&argv, session->opt->auth_user_pass_verify_script);
argv_printf_cat(&argv, "%s", tmp_file);
/* call command */
ret = openvpn_run_script(&argv, session->opt->es, 0,
"--auth-user-pass-verify");
if (!session->opt->auth_user_pass_verify_script_via_file)
{
setenv_del(session->opt->es, "password");
}
}
else
{
msg(D_TLS_ERRORS, "TLS Auth Error: peer provided a blank username");
}
done:
if (tmp_file && strlen(tmp_file) > 0)
{
platform_unlink(tmp_file);
}
argv_reset(&argv);
gc_free(&gc);
return ret;
}
/*
* Verify the username and password using a plugin
*/
static int
verify_user_pass_plugin(struct tls_session *session, const struct user_pass *up, const char *raw_username)
{
int retval = OPENVPN_PLUGIN_FUNC_ERROR;
#ifdef PLUGIN_DEF_AUTH
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
#endif
/* Is username defined? */
if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
{
/* set username/password in private env space */
setenv_str(session->opt->es, "username", (raw_username ? raw_username : up->username));
setenv_str(session->opt->es, "password", up->password);
/* setenv incoming cert common name for script */
setenv_str(session->opt->es, "common_name", session->common_name);
/* setenv client real IP address */
setenv_untrusted(session);
#ifdef PLUGIN_DEF_AUTH
/* generate filename for deferred auth control file */
if (!key_state_gen_auth_control_file(ks, session->opt))
{
msg (D_TLS_ERRORS, "TLS Auth Error (%s): "
"could not create deferred auth control file", __func__);
goto cleanup;
}
#endif
/* call command */
retval = plugin_call(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es);
#ifdef PLUGIN_DEF_AUTH
/* purge auth control filename (and file itself) for non-deferred returns */
if (retval != OPENVPN_PLUGIN_FUNC_DEFERRED)
{
key_state_rm_auth_control_file(ks);
}
#endif
setenv_del(session->opt->es, "password");
if (raw_username)
{
setenv_str(session->opt->es, "username", up->username);
}
}
else
{
msg(D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_plugin): peer provided a blank username");
}
cleanup:
return retval;
}
#ifdef MANAGEMENT_DEF_AUTH
/*
* MANAGEMENT_DEF_AUTH internal ssl_verify.c status codes
*/
#define KMDA_ERROR 0
#define KMDA_SUCCESS 1
#define KMDA_UNDEF 2
#define KMDA_DEF 3
static int
verify_user_pass_management(struct tls_session *session, const struct user_pass *up, const char *raw_username)
{
int retval = KMDA_ERROR;
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
/* Is username defined? */
if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
{
/* set username/password in private env space */
setenv_str(session->opt->es, "username", (raw_username ? raw_username : up->username));
setenv_str(session->opt->es, "password", up->password);
/* setenv incoming cert common name for script */
setenv_str(session->opt->es, "common_name", session->common_name);
/* setenv client real IP address */
setenv_untrusted(session);
if (management)
{
management_notify_client_needing_auth(management, ks->mda_key_id, session->opt->mda_context, session->opt->es);
}
setenv_del(session->opt->es, "password");
if (raw_username)
{
setenv_str(session->opt->es, "username", up->username);
}
retval = KMDA_SUCCESS;
}
else
{
msg(D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_management): peer provided a blank username");
}
return retval;
}
#endif /* ifdef MANAGEMENT_DEF_AUTH */
/*
* Main username/password verification entry point
*/
void
verify_user_pass(struct user_pass *up, struct tls_multi *multi,
struct tls_session *session)
{
int s1 = OPENVPN_PLUGIN_FUNC_SUCCESS;
bool s2 = true;
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
struct gc_arena gc = gc_new();
char *raw_username = NULL;
#ifdef MANAGEMENT_DEF_AUTH
int man_def_auth = KMDA_UNDEF;
if (management_enable_def_auth(management))
{
man_def_auth = KMDA_DEF;
}
#endif
/*
* Preserve the raw username before string_mod remapping, for plugins
* and management clients when in --compat-names mode
*/
if (compat_flag(COMPAT_FLAG_QUERY | COMPAT_NAMES))
{
ALLOC_ARRAY_CLEAR_GC(raw_username, char, USER_PASS_LEN, &gc);
strcpy(raw_username, up->username);
string_mod(raw_username, CC_PRINT, CC_CRLF, '_');
}
/* enforce character class restrictions in username/password */
string_mod_remap_name(up->username, COMMON_NAME_CHAR_CLASS);
string_mod(up->password, CC_PRINT, CC_CRLF, '_');
/* If server is configured with --auth-gen-token and we have an
* authentication token for this client, this authentication
* round will be done internally using the token instead of
* calling any external authentication modules.
*/
if (session->opt->auth_token_generate && multi->auth_token_sent
&& NULL != multi->auth_token)
{
unsigned int ssl_flags = session->opt->ssl_flags;
/* Ensure that the username has not changed */
if (!tls_lock_username(multi, up->username))
{
/* auth-token cleared in tls_lock_username() on failure */
ks->authenticated = false;
goto done;
}
/* If auth-token lifetime has been enabled,
* ensure the token has not expired
*/
if (session->opt->auth_token_lifetime > 0
&& (multi->auth_token_tstamp + session->opt->auth_token_lifetime) < now)
{
msg(D_HANDSHAKE, "Auth-token for client expired\n");
wipe_auth_token(multi);
ks->authenticated = false;
goto done;
}
/* The core authentication of the token itself */
if (memcmp_constant_time(multi->auth_token, up->password,
strlen(multi->auth_token)) != 0)
{
ks->authenticated = false;
tls_deauthenticate(multi);
msg(D_TLS_ERRORS, "TLS Auth Error: Auth-token verification "
"failed for username '%s' %s", up->username,
(ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
}
else
{
ks->authenticated = true;
if (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)
{
set_common_name(session, up->username);
}
msg(D_HANDSHAKE, "TLS: Username/auth-token authentication "
"succeeded for username '%s' %s",
up->username,
(ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
}
goto done;
}
/* call plugin(s) and/or script */
#ifdef MANAGEMENT_DEF_AUTH
if (man_def_auth == KMDA_DEF)
{
man_def_auth = verify_user_pass_management(session, up, raw_username);
}
#endif
if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY))
{
s1 = verify_user_pass_plugin(session, up, raw_username);
}
if (session->opt->auth_user_pass_verify_script)
{
s2 = verify_user_pass_script(session, up);
}
/* check sizing of username if it will become our common name */
if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) && strlen(up->username) > TLS_USERNAME_LEN)
{
msg(D_TLS_ERRORS, "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters", TLS_USERNAME_LEN);
s1 = OPENVPN_PLUGIN_FUNC_ERROR;
}
/* auth succeeded? */
if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS
#ifdef PLUGIN_DEF_AUTH
|| s1 == OPENVPN_PLUGIN_FUNC_DEFERRED
#endif
) && s2
#ifdef MANAGEMENT_DEF_AUTH
&& man_def_auth != KMDA_ERROR
#endif
&& tls_lock_username(multi, up->username))
{
ks->authenticated = true;
#ifdef PLUGIN_DEF_AUTH
if (s1 == OPENVPN_PLUGIN_FUNC_DEFERRED)
{
ks->auth_deferred = true;
}
#endif
#ifdef MANAGEMENT_DEF_AUTH
if (man_def_auth != KMDA_UNDEF)
{
ks->auth_deferred = true;
}
#endif
if ((session->opt->auth_token_generate) && (NULL == multi->auth_token))
{
/* Server is configured with --auth-gen-token but no token has yet
* been generated for this client. Generate one and save it.
*/
uint8_t tok[AUTH_TOKEN_SIZE];
if (!rand_bytes(tok, AUTH_TOKEN_SIZE))
{
msg( M_FATAL, "Failed to get enough randomness for "
"authentication token");
}
/* The token should be longer than the input when
* being base64 encoded
*/
ASSERT(openvpn_base64_encode(tok, AUTH_TOKEN_SIZE,
&multi->auth_token) > AUTH_TOKEN_SIZE);
multi->auth_token_tstamp = now;
dmsg(D_SHOW_KEYS, "Generated token for client: %s",
multi->auth_token);
}
if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME))
{
set_common_name(session, up->username);
}
#ifdef ENABLE_DEF_AUTH
msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
ks->auth_deferred ? "deferred" : "succeeded",
up->username,
(session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
#else
msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
"succeeded",
up->username,
(session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
#endif
}
else
{
msg(D_TLS_ERRORS, "TLS Auth Error: Auth Username/Password verification failed for peer");
}
done:
gc_free(&gc);
}
void
verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session)
{
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
/* While it shouldn't really happen, don't allow the common name to be NULL */
if (!session->common_name)
{
set_common_name(session, "");
}
/* Don't allow the CN to change once it's been locked */
if (ks->authenticated && multi->locked_cn)
{
const char *cn = session->common_name;
if (cn && strcmp(cn, multi->locked_cn))
{
msg(D_TLS_ERRORS, "TLS Auth Error: TLS object CN attempted to change from '%s' to '%s' -- tunnel disabled",
multi->locked_cn,
cn);
/* change the common name back to its original value and disable the tunnel */
set_common_name(session, multi->locked_cn);
tls_deauthenticate(multi);
}
}
/* Don't allow the cert hashes to change once they have been locked */
if (ks->authenticated && multi->locked_cert_hash_set)
{
const struct cert_hash_set *chs = session->cert_hash_set;
if (chs && !cert_hash_compare(chs, multi->locked_cert_hash_set))
{
msg(D_TLS_ERRORS, "TLS Auth Error: TLS object CN=%s client-provided SSL certs unexpectedly changed during mid-session reauth",
session->common_name);
/* disable the tunnel */
tls_deauthenticate(multi);
}
}
/* verify --client-config-dir based authentication */
if (ks->authenticated && session->opt->client_config_dir_exclusive)
{
struct gc_arena gc = gc_new();
const char *cn = session->common_name;
const char *path = gen_path(session->opt->client_config_dir_exclusive, cn, &gc);
if (!cn || !strcmp(cn, CCD_DEFAULT) || !test_file(path))
{
ks->authenticated = false;
wipe_auth_token(multi);
msg(D_TLS_ERRORS, "TLS Auth Error: --client-config-dir authentication failed for common name '%s' file='%s'",
session->common_name,
path ? path : "UNDEF");
}
gc_free(&gc);
}
}
void
tls_x509_clear_env(struct env_set *es)
{
struct env_item *item = es->list;
while (item)
{
struct env_item *next = item->next;
if (item->string
&& 0 == strncmp("X509_", item->string, strlen("X509_")))
{
env_set_del(es, item->string);
}
item = next;
}
}
#endif /* ENABLE_CRYPTO */