#define CF_INIT_TLS_AUTH_STANDALONE (1<<2)
static void do_init_first_time(struct context *c);
+static bool do_deferred_p2p_ncp(struct context *c);
void
context_clear(struct context *c)
return false;
}
}
+ else if (c->mode == MODE_POINT_TO_POINT)
+ {
+ if (!do_deferred_p2p_ncp(c))
+ {
+ msg(D_TLS_ERRORS, "ERROR: Failed to apply P2P negotiated protocol options");
+ return false;
+ }
+ }
/* if --up-delay specified, open tun, do ifconfig, and run up script now */
if (c->options.up_delay || PULL_DEFINED(&c->options))
return flags;
}
+static
+void adjust_mtu_peerid(struct context *c)
+{
+ frame_add_to_extra_frame(&c->c2.frame, 3); /* peer-id overhead */
+ if (!c->options.ce.link_mtu_defined)
+ {
+ frame_add_to_link_mtu(&c->c2.frame, 3);
+ msg(D_PUSH, "OPTIONS IMPORT: adjusting link_mtu to %d",
+ EXPANDED_SIZE(&c->c2.frame));
+ }
+ else
+ {
+ msg(M_WARN, "OPTIONS IMPORT: WARNING: peer-id set, but link-mtu"
+ " fixed by config - reducing tun-mtu to %d, expect"
+ " MTU problems", TUN_MTU_SIZE(&c->c2.frame));
+ }
+}
+
+static bool
+do_deferred_p2p_ncp(struct context *c)
+{
+ if (!c->c2.tls_multi)
+ {
+ return true;
+ }
+
+ if (c->c2.tls_multi->use_peer_id)
+ {
+ adjust_mtu_peerid(c);
+ }
+
+ struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
+
+ const char *ncp_cipher = get_p2p_ncp_cipher(session, c->c2.tls_multi->peer_info,
+ &c->options.gc);
+
+ if (ncp_cipher)
+ {
+ c->options.ciphername = ncp_cipher;
+ }
+ else if (!c->options.enable_ncp_fallback)
+ {
+ msg(D_TLS_ERRORS, "ERROR: failed to negotiate cipher with peer and "
+ "--data-ciphers-fallback not enabled. No usable "
+ "data channel cipher");
+ return false;
+ }
+
+ struct frame *frame_fragment = NULL;
+#ifdef ENABLE_FRAGMENT
+ if (c->options.ce.fragment)
+ {
+ frame_fragment = &c->c2.frame_fragment;
+ }
+#endif
+
+ if (!tls_session_update_crypto_params(session, &c->options, &c->c2.frame,
+ frame_fragment))
+ {
+ msg(D_TLS_ERRORS, "ERROR: failed to set crypto cipher");
+ return false;
+ }
+ return true;
+}
+
/*
* Handle non-tun-related pulled options.
*/
msg(D_PUSH, "OPTIONS IMPORT: peer-id set");
c->c2.tls_multi->use_peer_id = true;
c->c2.tls_multi->peer_id = c->options.peer_id;
- frame_add_to_extra_frame(&c->c2.frame, +3); /* peer-id overhead */
- if (!c->options.ce.link_mtu_defined)
- {
- frame_add_to_link_mtu(&c->c2.frame, +3);
- msg(D_PUSH, "OPTIONS IMPORT: adjusting link_mtu to %d",
- EXPANDED_SIZE(&c->c2.frame));
- }
- else
- {
- msg(M_WARN, "OPTIONS IMPORT: WARNING: peer-id set, but link-mtu"
- " fixed by config - reducing tun-mtu to %d, expect"
- " MTU problems", TUN_MTU_SIZE(&c->c2.frame) );
- }
+ adjust_mtu_peerid(c);
}
/* process (potentially pushed) crypto options */
to.pull = options->pull;
if (options->push_peer_info) /* all there is */
{
- to.push_peer_info_detail = 2;
+ to.push_peer_info_detail = 3;
}
else if (options->pull) /* pull clients send some details */
{
- to.push_peer_info_detail = 1;
+ to.push_peer_info_detail = 2;
}
- else /* default: no peer-info at all */
+ else if (options->mode == MODE_SERVER) /* server: no peer info at all */
{
to.push_peer_info_detail = 0;
}
+ else /* default: minimal info to allow NCP in P2P mode */
+ {
+ to.push_peer_info_detail = 1;
+ }
+
/* should we not xmit any packets until we get an initial
* response from client? */
{
if (!o->pull && !(o->mode == MODE_SERVER))
{
- /* we are in the classic P2P mode */
- msg( M_WARN, "Cipher negotiation is disabled since neither "
- "P2MP client nor server mode is enabled");
-
/* If the cipher is not set, use the old default of BF-CBC. We will
* warn that this is deprecated on cipher initialisation, no need
* to warn here as well */
{
o->ciphername = "BF-CBC";
}
+ else
+ {
+ o->enable_ncp_fallback = true;
+ }
return;
}
}
bool
-tls_session_update_crypto_params(struct tls_session *session,
- struct options *options, struct frame *frame,
+tls_session_update_crypto_params_do_work(struct tls_session *session,
+ struct options* options, struct frame *frame,
struct frame *frame_fragment)
{
if (session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized)
{
/* keys already generated, nothing to do */
return true;
- }
-
- bool cipher_allowed_as_fallback = options->enable_ncp_fallback
- && streq(options->ciphername, session->opt->config_ciphername);
- if (!session->opt->server && !cipher_allowed_as_fallback
- && !tls_item_in_cipher_list(options->ciphername, options->ncp_ciphers))
- {
- msg(D_TLS_ERRORS, "Error: pushed cipher not allowed - %s not in %s",
- options->ciphername, options->ncp_ciphers);
- /* undo cipher push, abort connection setup */
- options->ciphername = session->opt->config_ciphername;
- return false;
}
-
- /* Import crypto settings that might be set by pull/push */
- session->opt->crypto_flags |= options->data_channel_crypto_flags;
-
if (strcmp(options->ciphername, session->opt->config_ciphername))
{
msg(D_HANDSHAKE, "Data Channel: using negotiated cipher '%s'",
return tls_session_generate_data_channel_keys(session);
}
+bool
+tls_session_update_crypto_params(struct tls_session *session,
+ struct options *options, struct frame *frame,
+ struct frame *frame_fragment)
+{
+
+ bool cipher_allowed_as_fallback = options->enable_ncp_fallback
+ && streq(options->ciphername, session->opt->config_ciphername);
+
+ if (!session->opt->server && !cipher_allowed_as_fallback
+ && !tls_item_in_cipher_list(options->ciphername, options->ncp_ciphers))
+ {
+ msg(D_TLS_ERRORS, "Error: negotiated cipher not allowed - %s not in %s",
+ options->ciphername, options->ncp_ciphers);
+ /* undo cipher push, abort connection setup */
+ options->ciphername = session->opt->config_ciphername;
+ return false;
+ }
+
+ /* Import crypto settings that might be set by pull/push */
+ session->opt->crypto_flags |= options->data_channel_crypto_flags;
+
+ return tls_session_update_crypto_params_do_work(session, options, frame, frame_fragment);
+}
+
+
static bool
random_bytes_to_buf(struct buffer *buf,
uint8_t *out,
return str;
}
+/**
+ * Prepares the IV_ and UV_ variables that are part of the
+ * exchange to signal the peer's capabilities. The amount
+ * of variables is determined by session->opt->push_peer_info_detail
+ *
+ * 0 nothing. Used on a TLS P2MP server side to send no information
+ * to the client
+ * 1 minimal info needed for NCP in P2P mode
+ * 2 when --pull is enabled, the "default" set of variables
+ * 3 all information including MAC address and library versions
+ *
+ * @param buf the buffer to write these variables to
+ * @param session the TLS session object
+ * @return true if no error was encountered
+ */
static bool
push_peer_info(struct buffer *buf, struct tls_session *session)
{
struct gc_arena gc = gc_new();
bool ret = false;
+ struct buffer out = alloc_buf_gc(512 * 3, &gc);
- if (session->opt->push_peer_info_detail > 0)
+ if (session->opt->push_peer_info_detail > 1)
{
- struct env_set *es = session->opt->es;
- struct buffer out = alloc_buf_gc(512*3, &gc);
-
/* push version */
buf_printf(&out, "IV_VER=%s\n", PACKAGE_VERSION);
#elif defined(_WIN32)
buf_printf(&out, "IV_PLAT=win\n");
#endif
+ }
+ /* These are the IV variable that are sent to peers in p2p mode */
+ if (session->opt->push_peer_info_detail > 0)
+ {
/* support for P_DATA_V2 */
int iv_proto = IV_PROTO_DATA_V2;
buf_printf(&out, "IV_NCP=2\n");
}
- buf_printf(&out, "IV_CIPHERS=%s\n", session->opt->config_ncp_ciphers);
+ }
+ else
+ {
+ /* We are not using pull or p2mp server, instead do P2P NCP */
+ iv_proto |= IV_PROTO_NCP_P2P;
+ }
+
+ buf_printf(&out, "IV_CIPHERS=%s\n", session->opt->config_ncp_ciphers);
#ifdef HAVE_EXPORT_KEYING_MATERIAL
- iv_proto |= IV_PROTO_TLS_KEY_EXPORT;
+ iv_proto |= IV_PROTO_TLS_KEY_EXPORT;
#endif
- }
buf_printf(&out, "IV_PROTO=%d\n", iv_proto);
- /* push compression status */
+ if (session->opt->push_peer_info_detail > 1)
+ {
+ /* push compression status */
#ifdef USE_COMP
- comp_generate_peer_info_string(&session->opt->comp_options, &out);
+ comp_generate_peer_info_string(&session->opt->comp_options, &out);
#endif
+ }
- if (session->opt->push_peer_info_detail >= 2)
+ if (session->opt->push_peer_info_detail > 2)
{
/* push mac addr */
struct route_gateway_info rgi;
#endif
}
- /* push env vars that begin with UV_, IV_PLAT_VER and IV_GUI_VER */
- for (struct env_item *e = es->list; e != NULL; e = e->next)
+ if (session->opt->push_peer_info_detail > 1)
{
- if (e->string)
+ struct env_set *es = session->opt->es;
+ /* push env vars that begin with UV_, IV_PLAT_VER and IV_GUI_VER */
+ for (struct env_item *e = es->list; e != NULL; e = e->next)
{
- if ((((strncmp(e->string, "UV_", 3)==0
- || strncmp(e->string, "IV_PLAT_VER=", sizeof("IV_PLAT_VER=")-1)==0)
- && session->opt->push_peer_info_detail >= 2)
- || (strncmp(e->string,"IV_GUI_VER=",sizeof("IV_GUI_VER=")-1)==0)
- || (strncmp(e->string,"IV_SSO=",sizeof("IV_SSO=")-1)==0)
- )
- && buf_safe(&out, strlen(e->string)+1))
+ if (e->string)
{
- buf_printf(&out, "%s\n", e->string);
+ if ((((strncmp(e->string, "UV_", 3) == 0
+ || strncmp(e->string, "IV_PLAT_VER=", sizeof("IV_PLAT_VER=") - 1) == 0)
+ && session->opt->push_peer_info_detail >= 2)
+ || (strncmp(e->string, "IV_GUI_VER=", sizeof("IV_GUI_VER=") - 1) == 0)
+ || (strncmp(e->string, "IV_SSO=", sizeof("IV_SSO=") - 1) == 0)
+ )
+ && buf_safe(&out, strlen(e->string) + 1))
+ {
+ buf_printf(&out, "%s\n", e->string);
+ }
}
}
}
goto error;
}
+ if (session->opt->server && session->opt->mode != MODE_SERVER
+ && ks->key_id == 0)
+ {
+ /* tls-server option set and not P2MP server, so we
+ * are a P2P client running in tls-server mode */
+ p2p_mode_ncp(multi, session);
+ }
+
return true;
error:
setenv_del(session->opt->es, "exported_keying_material");
}
+ if (!session->opt->server && !session->opt->pull && ks->key_id == 0)
+ {
+ /* We are a p2p tls-client without pull, enable common
+ * protocol options */
+ p2p_mode_ncp(multi, session);
+ }
+
gc_free(&gc);
return true;
/** Supports signaling keywords with AUTH_PENDING, e.g. timeout=xy */
#define IV_PROTO_AUTH_PENDING_KW (1<<4)
+/** Support doing NCP in P2P mode. This mode works by both peers looking at
+ * each other's IV_ variables and deterministically deciding both on the same
+ * result. */
+#define IV_PROTO_NCP_P2P (1<<5)
+
/* Default field in X509 to be username */
#define X509_USERNAME_FIELD_DEFAULT "CN"
const char *crl_file, bool crl_inline);
#define EXPORT_KEY_DATA_LABEL "EXPORTER-OpenVPN-datakeys"
+#define EXPORT_P2P_PEERID_LABEL "EXPORTER-OpenVPN-p2p-peerid"
/**
* Keying Material Exporters [RFC 5705] allows additional keying material to be
* derived from existing TLS channel. This exported keying material can then be
bool disable_occ;
int mode;
bool pull;
+ /**
+ * The detail of info we push in peer info
+ *
+ * 0 - nothing at all, P2MP server only
+ * 1 - only the most basic information to negotiate cipher and features
+ * for P2P NCP
+ * 2 - normal setting for clients
+ * 3 - full information including "sensitive data" like IV_HWADDR
+ * enabled by --push-peer-info
+ */
int push_peer_info_detail;
int transition_window;
int handshake_window;
return false;
}
}
+
+const char*
+get_p2p_ncp_cipher(struct tls_session *session, const char *peer_info,
+ struct gc_arena *gc)
+{
+ /* we use a local gc arena to keep the temporary strings needed by strsep */
+ struct gc_arena gc_local = gc_new();
+ const char *peer_ciphers = extract_var_peer_info(peer_info, "IV_CIPHERS=", &gc_local);
+
+ if (!peer_ciphers)
+ {
+ gc_free(&gc_local);
+ return NULL;
+ }
+
+ const char* server_ciphers;
+ const char* client_ciphers;
+
+ if (session->opt->server)
+ {
+ server_ciphers = session->opt->config_ncp_ciphers;
+ client_ciphers = peer_ciphers;
+ }
+ else
+ {
+ client_ciphers = session->opt->config_ncp_ciphers;
+ server_ciphers = peer_ciphers;
+ }
+
+ /* Find the first common cipher from TLS server and TLS client. We
+ * use the preference of the server here to make it deterministic */
+ char *tmp_ciphers = string_alloc(server_ciphers, &gc_local);
+
+ const char *token;
+ while ((token = strsep(&tmp_ciphers, ":")))
+ {
+ if (tls_item_in_cipher_list(token, client_ciphers))
+ {
+ break;
+ }
+ }
+
+ const char *ret = NULL;
+ if (token != NULL)
+ {
+ ret = string_alloc(token, gc);
+ }
+ gc_free(&gc_local);
+
+ return ret;
+}
+
+static void
+p2p_ncp_set_options(struct tls_multi *multi, struct tls_session *session)
+{
+ /* will return 0 if peer_info is null */
+ const unsigned int iv_proto_peer = extract_iv_proto(multi->peer_info);
+
+ /* The other peer does not support P2P NCP */
+ if (!(iv_proto_peer & IV_PROTO_NCP_P2P))
+ {
+ return;
+ }
+
+ if (iv_proto_peer & IV_PROTO_DATA_V2)
+ {
+ multi->use_peer_id = true;
+ multi->peer_id = 0x76706e; // 'v' 'p' 'n'
+ }
+
+#if defined(HAVE_EXPORT_KEYING_MATERIAL)
+ if (iv_proto_peer & IV_PROTO_TLS_KEY_EXPORT)
+ {
+ session->opt->crypto_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT;
+
+ if (multi->use_peer_id)
+ {
+ /* Using a non hardcoded peer-id makes a tiny bit harder to
+ * fingerprint packets and also gives each connection a unique
+ * peer-id that can be useful for NAT tracking etc. */
+
+ uint8_t peerid[3];
+ if (!key_state_export_keying_material(session, EXPORT_P2P_PEERID_LABEL,
+ strlen(EXPORT_P2P_PEERID_LABEL),
+ &peerid, 3))
+ {
+ /* Non DCO setup might still work but also this should never
+ * happen or very likely the TLS encryption key exporter will
+ * also fail */
+ msg(M_NONFATAL, "TLS key export for P2P peer id failed. "
+ "Continuing anyway, expect problems");
+ }
+ else
+ {
+ multi->peer_id = (peerid[0] << 16) + (peerid[1] << 8) + peerid[2];
+ }
+
+ }
+ }
+#endif
+}
+
+void
+p2p_mode_ncp(struct tls_multi *multi, struct tls_session *session)
+{
+ /* Set the common options */
+ p2p_ncp_set_options(multi, session);
+
+ struct gc_arena gc = gc_new();
+
+ /* Query the common cipher here to log it as part of our message.
+ * We postpone switching the cipher to do_up */
+ const char* common_cipher = get_p2p_ncp_cipher(session, multi->peer_info, &gc);
+
+ if (!common_cipher)
+ {
+ struct buffer out = alloc_buf_gc(128, &gc);
+ struct key_state *ks = get_key_scan(multi, KS_PRIMARY);
+
+ const cipher_ctx_t *ctx = ks->crypto_options.key_ctx_bi.encrypt.cipher;
+ const cipher_kt_t *cipher = cipher_ctx_get_cipher_kt(ctx);
+ const char *fallback_name = cipher_kt_name(cipher);
+
+ if (!cipher)
+ {
+ /* at this point we do not really know if our fallback is
+ * not enabled or if we use 'none' cipher as fallback, so
+ * keep this ambiguity here and print fallback-cipher: none
+ */
+ fallback_name = "none";
+ }
+
+ buf_printf(&out, "(not negotiated, fallback-cipher: %s)", fallback_name);
+ common_cipher = BSTR(&out);
+ }
+
+ msg(D_TLS_DEBUG_LOW, "P2P mode NCP negotiation result: "
+ "TLS_export=%d, DATA_v2=%d, peer-id %d, cipher=%s",
+ (bool)(session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT),
+ multi->use_peer_id, multi->peer_id, common_cipher);
+
+ gc_free(&gc);
+}
\ No newline at end of file
#include "buffer.h"
#include "options.h"
+#include "ssl_common.h"
/**
* Returns whether the client supports NCP either by
*/
#define MAX_NCP_CIPHERS_LENGTH 127
+/**
+ * Determines if there is common cipher of both peer by looking at the
+ * IV_CIPHER peer info. In contrast of the server mode NCP that tries to
+ * accomandate all kind of corner cases in P2P mode NCP only takes IV_CIPHER
+ * into account and falls back to previous behaviour if this fails.
+ */
+void p2p_mode_ncp(struct tls_multi *multi, struct tls_session *session);
+
+/**
+ * Determines the best common cipher from both peers IV_CIPHER lists. The
+ * first cipher from the tls-server that is also in the tls-client IV_CIPHER
+ * list will be returned. If no common cipher can be found, both peer
+ * will continue to use whatever cipher is their default and NULL will be
+ * returned.
+ *
+ * @param session tls_session
+ * @param peer_info peer info of the peer
+ * @param gc gc arena that will be used to allocate the returned cipher
+ * @return common cipher if one exist.
+ */
+const char *
+get_p2p_ncp_cipher(struct tls_session *session, const char *peer_info,
+ struct gc_arena *gc);
+
#endif /* ifndef OPENVPN_SSL_NCP_H */
const char *bf_chacha = "BF-CBC:CHACHA20-POLY1305";
const char *aes_ciphers = "AES-256-GCM:AES-128-GCM";
+
+/* Define this function here as dummy since including the ssl_*.c files
+ * leads to having to include even more unrelated code */
+bool
+key_state_export_keying_material(struct tls_session *session,
+ const char* label, size_t label_size,
+ void *ekm, size_t ekm_size)
+{
+ ASSERT(0);
+}
+
static void
test_check_ncp_ciphers_list(void **state)
{