/* * 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 * Copyright (C) 2010-2018 Fox Crypto B.V. * * 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" #include "base64.h" #include "manage.h" #include "otime.h" #include "run_command.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 static void string_mod_remap_name(char *str) { 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 = platform_create_temp_file(tmp_dir, "pcf", gc))) { msg(M_NONFATAL, "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_NONFATAL|M_ERRNO, "Failed to open temporary file: %s", peercert_filename); return NULL; } if (SUCCESS != x509_write_pem(peercert_file, peercert)) { msg(M_NONFATAL, "Error writing PEM file containing certificate"); (void) platform_unlink(peercert_filename); peercert_filename = NULL; } 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); 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); /* 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 = platform_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 = platform_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) { 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", 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"); } 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) { 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", 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"); 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 */ #ifdef MANAGEMENT_DEF_AUTH int man_def_auth = KMDA_UNDEF; if (management_enable_def_auth(management)) { man_def_auth = KMDA_DEF; } #endif /* enforce character class restrictions in username/password */ string_mod_remap_name(up->username); 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; return; } /* 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; return; } /* 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]" : ""); } return; } /* 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); } #endif if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)) { s1 = verify_user_pass_plugin(session, up); } 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"); } } 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 = platform_gen_path(session->opt->client_config_dir_exclusive, cn, &gc); if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_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; } }