diff options
author | Adriaan de Jong <dejong@fox-it.com> | 2011-06-30 10:48:18 +0200 |
---|---|---|
committer | David Sommerseth <davids@redhat.com> | 2011-10-21 14:51:45 +0200 |
commit | d0811e643cddd796722fb1d0050ad57168da29d4 (patch) | |
tree | e2711207953d65fe05e6bb7d9635f9536aca299a | |
parent | 530af3efa38bd4e1044e5982f1970f5d772dbb48 (diff) | |
download | openvpn-d0811e643cddd796722fb1d0050ad57168da29d4.tar.gz openvpn-d0811e643cddd796722fb1d0050ad57168da29d4.tar.xz openvpn-d0811e643cddd796722fb1d0050ad57168da29d4.zip |
Refactored username and password authentication code
Signed-off-by: Adriaan de Jong <dejong@fox-it.com>
Acked-by: James Yonan <james@openvpn.net>
Signed-off-by: David Sommerseth <davids@redhat.com>
-rw-r--r-- | ssl.c | 527 | ||||
-rw-r--r-- | ssl.h | 91 | ||||
-rw-r--r-- | ssl_common.h | 59 | ||||
-rw-r--r-- | ssl_verify.c | 553 | ||||
-rw-r--r-- | ssl_verify.h | 89 |
5 files changed, 713 insertions, 606 deletions
@@ -1086,45 +1086,6 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) /** @} name Function for authenticating a new connection from a remote OpenVPN peer */ - -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"; -} - #ifdef ENABLE_X509_TRACK void @@ -1150,234 +1111,6 @@ x509_track_add (const struct x509_track **ll_head, const char *name, int msgleve #endif -#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 -static 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)) - 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 PLUGIN_DEF_AUTH - -/* - * auth_control_file functions - */ - -static void -key_state_rm_auth_control_file (struct key_state *ks) -{ - if (ks && ks->auth_control_file) - { - delete_file (ks->auth_control_file); - free (ks->auth_control_file); - ks->auth_control_file = NULL; - } -} - -static void -key_state_gen_auth_control_file (struct key_state *ks, const struct tls_options *opt) -{ - struct gc_arena gc = gc_new (); - const char *acf; - - key_state_rm_auth_control_file (ks); - 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); - } /* FIXME: Should have better error handling? */ - gc_free (&gc); -} - -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 - -/* - * 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 - - 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 - - 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 -#ifdef MANAGEMENT_DEF_AUTH - s2 = man_def_auth_test (ks); -#endif - 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 - success = true; -#endif - } - } - } - } - -#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 - -void -tls_deauthenticate (struct tls_multi *multi) -{ - if (multi) - { - int i, j; - for (i = 0; i < TM_SIZE; ++i) - for (j = 0; j < KS_SIZE; ++j) - multi->session[i].key[j].authenticated = false; - } -} /* * Initialize SSL context. @@ -2659,173 +2392,6 @@ read_string_discard (struct buffer *buf) } /* - * Authenticate a client using username/password. - * Runs on server. - * - * If you want to add new authentication methods, - * this is the place to start. - */ - -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_printf (&argv, "%sc %s", session->opt->auth_user_pass_verify_script, 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) - delete_file (tmp_file); - - argv_reset (&argv); - gc_free (&gc); - return ret; -} - -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; - 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); - 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 */ - key_state_gen_auth_control_file (ks, session->opt); -#endif - - /* call command */ - retval = plugin_call (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es, -1, NULL); - -#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"); - 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"); - } - - return retval; -} - -/* - * MANAGEMENT_DEF_AUTH internal ssl.c status codes - */ -#define KMDA_ERROR 0 -#define KMDA_SUCCESS 1 -#define KMDA_UNDEF 2 -#define KMDA_DEF 3 - -#ifdef MANAGEMENT_DEF_AUTH -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); - 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"); - 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 - -/* * Handle the reading and writing of key data to and from * the TLS control channel (cleartext). */ @@ -3091,23 +2657,18 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi { struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */ struct key_state *ks_lame = &session->key[KS_LAME_DUCK]; /* retiring key */ - struct gc_arena gc = gc_new (); + int key_method_flags; - char *options; - struct user_pass *up; + bool username_status, password_status; - bool man_def_auth = KMDA_UNDEF; + struct gc_arena gc = gc_new (); + char *options; -#ifdef MANAGEMENT_DEF_AUTH - if (management_enable_def_auth (management)) - man_def_auth = KMDA_DEF; -#endif + /* allocate temporary objects */ + ALLOC_ARRAY_CLEAR_GC (options, char, TLS_OPTIONS_LEN, &gc); ASSERT (session->opt->key_method == 2); - /* allocate temporary objects */ - ALLOC_ARRAY_CLEAR_GC (options, char, TLS_OPTIONS_LEN, &gc); - /* discard leading uint32 */ ASSERT (buf_advance (buf, 4)); @@ -3135,21 +2696,17 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi goto error; } - /* should we check username/password? */ ks->authenticated = false; - if (session->opt->auth_user_pass_verify_script - || plugin_defined (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY) - || man_def_auth == KMDA_DEF) + + if (verify_user_pass_enabled(session)) { - int s1 = OPENVPN_PLUGIN_FUNC_SUCCESS; - bool s2 = true; - char *raw_username; - bool username_status, password_status; + /* Perform username/password authentication */ + struct user_pass *up; - /* get username/password from plaintext buffer */ ALLOC_OBJ_CLEAR_GC (up, struct user_pass, &gc); username_status = read_string (buf, up->username, USER_PASS_LEN); password_status = read_string (buf, up->password, USER_PASS_LEN); + if (!username_status || !password_status) { CLEAR (*up); @@ -3160,76 +2717,18 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi } } - /* preserve raw username before string_mod remapping, for plugins */ - 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_sslname (up->username, COMMON_NAME_CHAR_CLASS, session->opt->ssl_flags); - string_mod (up->password, CC_PRINT, CC_CRLF, '_'); - - /* call plugin(s) and/or script */ #ifdef MANAGEMENT_DEF_AUTH /* get peer info from control channel */ free (multi->peer_info); multi->peer_info = read_string_alloc (buf); - - 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 && man_def_auth != KMDA_ERROR - && 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->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"); - } + verify_user_pass(up, multi, session); CLEAR (*up); } else { + /* Session verification should have occurred during TLS negotiation*/ if (!session->verified) { msg (D_TLS_ERRORS, @@ -77,79 +77,6 @@ #define P_FIRST_OPCODE 1 #define P_LAST_OPCODE 8 -/** @addtogroup control_processor - * @{ */ -/** - * @name Control channel negotiation states - * - * These states represent the different phases of control channel - * negotiation between OpenVPN peers. OpenVPN servers and clients - * progress through the states in a different order, because of their - * different roles during exchange of random material. The references to - * the \c key_source2 structure in the list below is only valid if %key - * method 2 is being used. See the \link key_generation data channel key - * generation\endlink related page for more information. - * - * Clients follow this order: - * -# \c S_INITIAL, ready to begin three-way handshake and control - * channel negotiation. - * -# \c S_PRE_START, have started three-way handshake, waiting for - * acknowledgment from remote. - * -# \c S_START, initial three-way handshake complete. - * -# \c S_SENT_KEY, have sent local part of \c key_source2 random - * material. - * -# \c S_GOT_KEY, have received remote part of \c key_source2 random - * material. - * -# \c S_ACTIVE, normal operation during remaining handshake window. - * -# \c S_NORMAL_OP, normal operation. - * - * Servers follow the same order, except for \c S_SENT_KEY and \c - * S_GOT_KEY being reversed, because the server first receives the - * client's \c key_source2 random material before generating and sending - * its own. - * - * @{ - */ -#define S_ERROR -1 /**< Error state. */ -#define S_UNDEF 0 /**< Undefined state, used after a \c - * key_state is cleaned up. */ -#define S_INITIAL 1 /**< Initial \c key_state state after - * initialization by \c key_state_init() - * before start of three-way handshake. */ -#define S_PRE_START 2 /**< Waiting for the remote OpenVPN peer - * to acknowledge during the initial - * three-way handshake. */ -#define S_START 3 /**< Three-way handshake is complete, - * start of key exchange. */ -#define S_SENT_KEY 4 /**< Local OpenVPN process has sent its - * part of the key material. */ -#define S_GOT_KEY 5 /**< Local OpenVPN process has received - * the remote's part of the key - * material. */ -#define S_ACTIVE 6 /**< Operational \c key_state state - * immediately after negotiation has - * completed while still within the - * handshake window. */ -/* ready to exchange data channel packets */ -#define S_NORMAL_OP 7 /**< Normal operational \c key_state - * state. */ -/** @} name Control channel negotiation states */ -/** @} addtogroup control_processor */ - - -#define DECRYPT_KEY_ENABLED(multi, ks) ((ks)->state >= (S_GOT_KEY - (multi)->opt.server)) - /**< Check whether the \a ks \c key_state - * is ready to receive data channel - * packets. - * @ingroup data_crypto - * - * If true, it is safe to assume that - * this session has been authenticated - * by TLS. - * - * @note This macro only works if - * S_SENT_KEY + 1 == S_GOT_KEY. */ - /* Should we aggregate TLS * acknowledgements, and tack them onto * control packets? */ @@ -521,15 +448,7 @@ bool tls_send_payload (struct tls_multi *multi, bool tls_rec_payload (struct tls_multi *multi, struct buffer *buf); -#define TLS_AUTHENTICATION_SUCCEEDED 0 -#define TLS_AUTHENTICATION_FAILED 1 -#define TLS_AUTHENTICATION_DEFERRED 2 -#define TLS_AUTHENTICATION_UNDEFINED 3 -int tls_authentication_status (struct tls_multi *multi, const int latency); - #ifdef MANAGEMENT_DEF_AUTH -bool tls_authenticate_key (struct tls_multi *multi, const unsigned int mda_key_id, const bool auth, const char *client_reason); - static inline char * tls_get_peer_info(const struct tls_multi *multi) { @@ -577,16 +496,6 @@ tls_set_single_session (struct tls_multi *multi) multi->opt.single_session = true; } -static inline const char * -tls_client_reason (struct tls_multi *multi) -{ -#ifdef ENABLE_DEF_AUTH - return multi->client_reason; -#else - return NULL; -#endif -} - /* * protocol_dump() flags */ diff --git a/ssl_common.h b/ssl_common.h index 408744c..525a1da 100644 --- a/ssl_common.h +++ b/ssl_common.h @@ -42,6 +42,65 @@ #define UP_TYPE_AUTH "Auth" #define UP_TYPE_PRIVATE_KEY "Private Key" +/** @addtogroup control_processor + * @{ */ +/** + * @name Control channel negotiation states + * + * These states represent the different phases of control channel + * negotiation between OpenVPN peers. OpenVPN servers and clients + * progress through the states in a different order, because of their + * different roles during exchange of random material. The references to + * the \c key_source2 structure in the list below is only valid if %key + * method 2 is being used. See the \link key_generation data channel key + * generation\endlink related page for more information. + * + * Clients follow this order: + * -# \c S_INITIAL, ready to begin three-way handshake and control + * channel negotiation. + * -# \c S_PRE_START, have started three-way handshake, waiting for + * acknowledgment from remote. + * -# \c S_START, initial three-way handshake complete. + * -# \c S_SENT_KEY, have sent local part of \c key_source2 random + * material. + * -# \c S_GOT_KEY, have received remote part of \c key_source2 random + * material. + * -# \c S_ACTIVE, normal operation during remaining handshake window. + * -# \c S_NORMAL_OP, normal operation. + * + * Servers follow the same order, except for \c S_SENT_KEY and \c + * S_GOT_KEY being reversed, because the server first receives the + * client's \c key_source2 random material before generating and sending + * its own. + * + * @{ + */ +#define S_ERROR -1 /**< Error state. */ +#define S_UNDEF 0 /**< Undefined state, used after a \c + * key_state is cleaned up. */ +#define S_INITIAL 1 /**< Initial \c key_state state after + * initialization by \c key_state_init() + * before start of three-way handshake. */ +#define S_PRE_START 2 /**< Waiting for the remote OpenVPN peer + * to acknowledge during the initial + * three-way handshake. */ +#define S_START 3 /**< Three-way handshake is complete, + * start of key exchange. */ +#define S_SENT_KEY 4 /**< Local OpenVPN process has sent its + * part of the key material. */ +#define S_GOT_KEY 5 /**< Local OpenVPN process has received + * the remote's part of the key + * material. */ +#define S_ACTIVE 6 /**< Operational \c key_state state + * immediately after negotiation has + * completed while still within the + * handshake window. */ +/* ready to exchange data channel packets */ +#define S_NORMAL_OP 7 /**< Normal operational \c key_state + * state. */ +/** @} name Control channel negotiation states */ +/** @} addtogroup control_processor */ + /** * Container for one half of random material to be used in %key method 2 * \ref key_generation "data channel key generation". diff --git a/ssl_verify.c b/ssl_verify.c index b8f66f7..16d294d 100644 --- a/ssl_verify.c +++ b/ssl_verify.c @@ -37,6 +37,33 @@ #include "ssl_verify_openssl.h" #endif +/** Legal characters in a common name */ +#define COMMON_NAME_CHAR_CLASS (CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT|CC_SLASH) + +/** Maximum length of common name */ +#define TLS_USERNAME_LEN 64 + +static void +string_mod_sslname (char *str, const unsigned int restrictive_flags, const unsigned int ssl_flags) +{ + if (ssl_flags & SSLF_NO_NAME_REMAPPING) + string_mod (str, CC_PRINT, CC_CRLF, '_'); + else + string_mod (str, restrictive_flags, 0, '_'); +} + +/* + * 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); +} + +/* + * Remove authenticated state from all sessions in the given tunnel + */ static void tls_deauthenticate (struct tls_multi *multi) { @@ -97,6 +124,43 @@ tls_lock_common_name (struct tls_multi *multi) multi->locked_cn = string_alloc (cn, NULL); } +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 unsigned char *sha1_hash) @@ -201,6 +265,495 @@ tls_lock_cert_hash_set (struct tls_multi *multi) } +/* *************************************************************************** + * 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)) + 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 PLUGIN_DEF_AUTH + +/* + * auth_control_file functions + */ + +void +key_state_rm_auth_control_file (struct key_state *ks) +{ + if (ks && ks->auth_control_file) + { + delete_file (ks->auth_control_file); + free (ks->auth_control_file); + ks->auth_control_file = NULL; + } +} + +static void +key_state_gen_auth_control_file (struct key_state *ks, const struct tls_options *opt) +{ + struct gc_arena gc = gc_new (); + const char *acf; + + key_state_rm_auth_control_file (ks); + 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); + } /* FIXME: Should have better error handling? */ + + gc_free (&gc); +} + +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 + +/* + * 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 + + +/* **************************************************************************** + * 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_printf (&argv, "%sc %s", session->opt->auth_user_pass_verify_script, 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) + delete_file (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; + 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); + 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 */ + key_state_gen_auth_control_file (ks, session->opt); +#endif + + /* call command */ + retval = plugin_call (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es, -1, NULL); + +#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"); + 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"); + } + + 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); + 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"); + 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 + +/* + * 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; + +#ifdef MANAGEMENT_DEF_AUTH + int man_def_auth = KMDA_UNDEF; + + if (management_enable_def_auth (management)) + man_def_auth = KMDA_DEF; +#endif + + /* preserve raw username before string_mod remapping, for plugins */ + 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_sslname (up->username, COMMON_NAME_CHAR_CLASS, session->opt->ssl_flags); + string_mod (up->password, CC_PRINT, CC_CRLF, '_'); + + /* 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->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"); + } + + gc_free (&gc); +} + void verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session) { diff --git a/ssl_verify.h b/ssl_verify.h index b76a16a..ad94bc6 100644 --- a/ssl_verify.h +++ b/ssl_verify.h @@ -58,6 +58,36 @@ struct cert_hash_set { }; +#define TLS_AUTHENTICATION_SUCCEEDED 0 +#define TLS_AUTHENTICATION_FAILED 1 +#define TLS_AUTHENTICATION_DEFERRED 2 +#define TLS_AUTHENTICATION_UNDEFINED 3 + +/* + * Return current session authentication state. Return + * value is TLS_AUTHENTICATION_x. + * + * TODO: document this function + */ +int tls_authentication_status (struct tls_multi *multi, const int latency); + +/** Check whether the \a ks \c key_state is ready to receive data channel + * packets. + * @ingroup data_crypto + * + * If true, it is safe to assume that this session has been authenticated + * by TLS. + * + * @note This macro only works if S_SENT_KEY + 1 == S_GOT_KEY. */ +#define DECRYPT_KEY_ENABLED(multi, ks) ((ks)->state >= (S_GOT_KEY - (multi)->opt.server)) + +/** + * Remove the given key state's auth control file, if it exists. + * + * @param ks The key state the remove the file for + */ +void key_state_rm_auth_control_file (struct key_state *ks); + /** * Frees the given set of certificate hashes. * @@ -87,7 +117,13 @@ void tls_lock_common_name (struct tls_multi *multi); */ const char *tls_common_name (const struct tls_multi* multi, const bool null); -void tls_set_common_name (struct tls_multi *multi, const char *common_name); +/** + * Returns the username field for the given tunnel + * + * @param multi The tunnel to return the username for + * @param null Whether null may be returned. If not, "UNDEF" will be returned. + */ +const char *tls_username (const struct tls_multi *multi, const bool null); #ifdef ENABLE_PF @@ -119,6 +155,38 @@ tls_common_name_hash (const struct tls_multi *multi, const char **cn, uint32_t * #endif /** + * Returns whether or not the server should check for username/password + * + * @param session The current TLS session + * + * @return true if username and password verification is enabled, + * false if not. + * + */ +static inline bool verify_user_pass_enabled(struct tls_session *session) +{ + return (session->opt->auth_user_pass_verify_script + || plugin_defined (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY) + || management_enable_def_auth (management)); +} + +/** + * Verify the given username and password, using either an external script, a + * plugin, or the management interface. + * + * If authentication succeeds, the appropriate state is filled into the + * session's primary key state's authenticated field. Authentication may also + * be deferred, in which case the key state's auth_deferred field is filled in. + * + * @param up The username and password to verify. + * @param multi The TLS multi structure to verify usernames against. + * @param session The current TLS session + * + */ +void verify_user_pass(struct user_pass *up, struct tls_multi *multi, + struct tls_session *session); + +/** * Perform final authentication checks, including locking of the cn, the allowed * certificate hashes, and whether a client config entry exists in the * client config directory. @@ -129,5 +197,24 @@ tls_common_name_hash (const struct tls_multi *multi, const char **cn, uint32_t * */ void verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session); +/* + * TODO: document + */ +#ifdef MANAGEMENT_DEF_AUTH +bool tls_authenticate_key (struct tls_multi *multi, const unsigned int mda_key_id, const bool auth, const char *client_reason); +void man_def_auth_set_client_reason (struct tls_multi *multi, const char *client_reason); +#endif + +static inline const char * +tls_client_reason (struct tls_multi *multi) +{ +#ifdef ENABLE_DEF_AUTH + return multi->client_reason; +#else + return NULL; +#endif +} + #endif /* SSL_VERIFY_H_ */ + |