Message ID | 20200825073643.15920-2-arne@rfc2549.org |
---|---|
State | Superseded |
Headers | show |
Series | [Openvpn-devel,v4,1/2] Move openvpn specific key expansion into its own function | expand |
This sentence is badly constructed, so, use some grammar to help it read correctly: On 25/08/2020 08:36, Arne Schwabe wrote: > + * When the client sends the IV_PROTO_TLS_KEY_EXPORT flag and the server replies > + * with `key-derivation tls-ekm` the RFC5705 key material exporter with the > + * label EXPORTER-OpenVPN-datakeys is used for the key data. > * > + * When the client sends the IV_PROTO_TLS_KEY_EXPORT flag and the server replies > + * with `key-derivation tls-ekm` *then* the RFC5705 key material exporter, with the > + * label EXPORTER-OpenVPN-datakeys, is used for the key data. > * Sorry to be a pain ..
Hi, On 25-08-2020 09:36, Arne Schwabe wrote: > OpenVPN currently uses its own (based on TLS 1.0) key derivation > mechanism to generate the 256 bytes key data in key2 struct that > are then used used to generate encryption/hmac/iv vectors. While > this mechanism is still secure, it is not state of the art. > > Instead of modernising our own approach, this commit implements > key derivation using the Keying Material Exporters API introduced > by RFC 5705. Thanks! I'm glad to see you're picking up the work I haven't been able to find time for. > We also use an opportunistic approach of negotiating the use of > EKM (exported key material) through an IV_PROTO flag and prefer > EKM to our own PRF if both client and server support it. The > use of EKM is pushed to the client as part of NCP as > key-derivation tls-ekm. > > We still exchange the random data (112 bytes from client to server > and 64 byte from server to client) for the OpenVPN PRF but > do not use it. Removing that exchange would break the handshake > and make a key-method 3 or similar necessary. > > As a side effect, this makes a little bit easier to have a FIPS compatible > version of OpenVPN since we do not rely on calling MD5 anymore. > > Side note: this commit breaks the (not yet merged) WolfSSL support as it > claims to support EKM in the OpenSSL compat API but always returns an error > if you try to use it. > > Signed-off-by: Arne Schwabe <arne@rfc2549.org> Signed off two times. > Patch v2: rebase/change to V2 of EKM refactoring > > Patch v3: add Changes.rst > > Signed-off-by: Arne Schwabe <arne@rfc2549.org> > --- > Changes.rst | 11 +++++++ > doc/doxygen/doc_key_generation.h | 14 +++++++-- > src/openvpn/crypto.h | 4 +++ > src/openvpn/init.c | 1 + > src/openvpn/multi.c | 4 +++ > src/openvpn/options.c | 14 +++++++++ > src/openvpn/options.h | 3 ++ > src/openvpn/push.c | 5 ++- > src/openvpn/ssl.c | 54 ++++++++++++++++++++++++++++---- > src/openvpn/ssl.h | 2 ++ > src/openvpn/ssl_backend.h | 2 ++ > src/openvpn/ssl_mbedtls.c | 7 ++--- > 12 files changed, 107 insertions(+), 14 deletions(-) > > diff --git a/Changes.rst b/Changes.rst > index f67e1d76..2a2829e7 100644 > --- a/Changes.rst > +++ b/Changes.rst > @@ -1,3 +1,14 @@ > +Overview of changes in 2.6 > +========================== > + > + > +New features > +------------ > +Keying Material Exporters (RFC 5705) based key generation > + As part of the cipher negotiation OpenVPN will automatically prefer > + the RFC5705 based key material generation to the current custom > + OpenVPN PRF. This feature requires OpenSSL or mbed TLS 2.18+. > + > Overview of changes in 2.5 > ========================== > > diff --git a/doc/doxygen/doc_key_generation.h b/doc/doxygen/doc_key_generation.h > index 4bb9c708..cef773a9 100644 > --- a/doc/doxygen/doc_key_generation.h > +++ b/doc/doxygen/doc_key_generation.h > @@ -58,6 +58,12 @@ > * > * @subsection key_generation_method_2 Key method 2 > * > + * There are two methods for generating key data when using key method 2 > + * the first is OpenVPN's traditional approach that exchanges random > + * data and uses a PRF and the other is using the RFC5705 keying material > + * exporter to generate the key material. For both methods the random > + * data is exchange but only used in the traditional method. > + * > * -# The client generates random material in the following amounts: > * - Pre-master secret: 48 bytes > * - Client's PRF seed for master secret: 32 bytes > @@ -73,8 +79,12 @@ > * server's random material. > * > * %Key method 2 %key expansion is performed by the \c > - * generate_key_expansion() function. Please refer to its source code for > - * details of the %key expansion process. > + * generate_key_expansion_openvpn_prf() function. Please refer to its source > + * code for details of the %key expansion process. > + * > + * When the client sends the IV_PROTO_TLS_KEY_EXPORT flag and the server replies > + * with `key-derivation tls-ekm` the RFC5705 key material exporter with the > + * label EXPORTER-OpenVPN-datakeys is used for the key data. Did you request this label with IANA? Also, a user could now use --keying-material-exporter to export the data channel keys to a plugin. I'm inclined to say we should prevent that, by rejecting our internal label as a user label. > * @subsection key_generation_random Source of random material > * > diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h > index 999f643e..ec935ca5 100644 > --- a/src/openvpn/crypto.h > +++ b/src/openvpn/crypto.h > @@ -254,6 +254,10 @@ struct crypto_options > #define CO_MUTE_REPLAY_WARNINGS (1<<2) > /**< Bit-flag indicating not to display > * replay warnings. */ > +#define CO_USE_TLS_KEY_MATERIAL_EXPORT (1<<3) > + /**< Bit-flag indicating that key derivation Sugestion: "data channel key derivation". > + * is done using TLS keying material export [RFC5705] > + */ > unsigned int flags; /**< Bit-flags determining behavior of > * security operation functions. */ > }; > diff --git a/src/openvpn/init.c b/src/openvpn/init.c > index a785934a..dff090b1 100644 > --- a/src/openvpn/init.c > +++ b/src/openvpn/init.c > @@ -676,6 +676,7 @@ restore_ncp_options(struct context *c) > c->options.ciphername = c->c1.ciphername; > c->options.authname = c->c1.authname; > c->options.keysize = c->c1.keysize; > + c->options.data_channel_use_ekm = false; > } > > void > diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c > index 13738180..a5862020 100644 > --- a/src/openvpn/multi.c > +++ b/src/openvpn/multi.c > @@ -1817,6 +1817,10 @@ multi_client_set_protocol_options(struct context *c) > c->c2.push_request_received = true; > } > > +#ifdef HAVE_EXPORT_KEYING_MATERIAL > + o->data_channel_use_ekm = (proto & IV_PROTO_TLS_KEY_EXPORT); > +#endif > + > /* Select cipher if client supports Negotiable Crypto Parameters */ > if (!o->ncp_enabled) > { > diff --git a/src/openvpn/options.c b/src/openvpn/options.c > index 8bf82c57..90e78a7b 100644 > --- a/src/openvpn/options.c > +++ b/src/openvpn/options.c > @@ -7947,6 +7947,20 @@ add_option(struct options *options, > } > options->ncp_ciphers = p[1]; > } > + else if (streq(p[0], "key-derivation") && p[1]) > + { > + VERIFY_PERMISSION(OPT_P_NCP) > +#ifdef HAVE_EXPORT_KEYING_MATERIAL > + if (streq(p[1], "tls-ekm")) > + { > + options->data_channel_use_ekm = true; > + } > + else > +#endif > + { > + msg(msglevel, "Unknown key-derivation method %s", p[1]); > + } > + } This option is also accepted on the command line or config file, but is not documented in the man page or --help text. It would probably be best to not accept is on the command line or config file, but I'm not sure we have the infra in place for that. > else if (streq(p[0], "ncp-disable") && !p[1]) > { > VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE); > diff --git a/src/openvpn/options.h b/src/openvpn/options.h > index 877e9396..c730c6a7 100644 > --- a/src/openvpn/options.h > +++ b/src/openvpn/options.h > @@ -648,6 +648,9 @@ struct options > /* Useful when packets sent by openvpn itself are not subject > * to the routing tables that would move packets into the tunnel. */ > bool allow_recursive_routing; > + > + /* Use RFC 5705 key export */ Suggestion: "Use RFC5705 key export to generate data channel keys". > + bool data_channel_use_ekm; > }; > > #define streq(x, y) (!strcmp((x), (y))) > diff --git a/src/openvpn/push.c b/src/openvpn/push.c > index e0d2eeaf..17bba948 100644 > --- a/src/openvpn/push.c > +++ b/src/openvpn/push.c > @@ -479,7 +479,10 @@ prepare_push_reply(struct context *c, struct gc_arena *gc, > { > push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername); > } > - > + if (o->data_channel_use_ekm) > + { > + push_option_fmt(gc, push_list, M_USAGE, "key-derivation tls-ekm"); > + } > return true; > } > > diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c > index b4d94b8a..9643fbdd 100644 > --- a/src/openvpn/ssl.c > +++ b/src/openvpn/ssl.c > @@ -1784,6 +1784,29 @@ init_key_contexts(struct key_ctx_bi *key, > } > > > +static bool > +generate_key_expansion_tls_export(struct tls_session *session, struct key2 *key2) > +{ > + struct gc_arena gc = gc_new(); > + unsigned char *key2data; > + > + key2data = key_state_export_keying_material(session, > + EXPORT_KEY_DATA_LABEL, > + strlen(EXPORT_KEY_DATA_LABEL), > + EXPORT_KEY_DATA_EKM_SIZE, Why not sizeof(key2->keys)? That makes it trivial to verify correctness (just read this function) and would make EXPORT_KEY_DATA_EKM_SIZE unneeded. > + &gc); > + if (!key2data) > + { > + return false; > + } > + memcpy(key2->keys, key2data, sizeof(key2->keys)); > + secure_memzero(key2data, sizeof(key2->keys)); > + key2->n = 2; > + > + gc_free(&gc); > + return true; > +} > + > static struct key2 > generate_key_expansion_openvpn_prf(const struct tls_session *session) > { > @@ -1855,7 +1878,7 @@ generate_key_expansion_openvpn_prf(const struct tls_session *session) > */ > static bool > generate_key_expansion(struct key_ctx_bi *key, > - const struct tls_session *session) > + struct tls_session *session) > { > bool ret = false; > struct key2 key2; > @@ -1866,10 +1889,20 @@ generate_key_expansion(struct key_ctx_bi *key, > goto exit; > } > > - > bool server = session->opt->server; > > - key2 = generate_key_expansion_openvpn_prf(session); > + if (session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT) > + { > + if(!generate_key_expansion_tls_export(session, &key2)) Missing space between if and (. > + { > + msg(D_TLS_ERRORS, "TLS Error: Keying material export failed"); > + goto exit; > + } > + } > + else > + { > + key2 = generate_key_expansion_openvpn_prf(session); > + } > > key2_print(&key2, &session->opt->key_type, > "Master Encrypt", "Master Decrypt"); > @@ -1970,6 +2003,11 @@ tls_session_update_crypto_params(struct tls_session *session, > return false; > } > > + if (options->data_channel_use_ekm) > + { > + session->opt->crypto_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT; > + } > + > if (strcmp(options->ciphername, session->opt->config_ciphername)) > { > msg(D_HANDSHAKE, "Data Channel: using negotiated cipher '%s'", > @@ -2254,13 +2292,11 @@ push_peer_info(struct buffer *buf, struct tls_session *session) > * push request, also signal that the client wants > * to get push-reply messages without without requiring a round > * trip for a push request message*/ > - if(session->opt->pull) > + if (session->opt->pull) > { > iv_proto |= IV_PROTO_REQUEST_PUSH; > } > > - buf_printf(&out, "IV_PROTO=%d\n", iv_proto); > - > /* support for Negotiable Crypto Parameters */ > if (session->opt->ncp_enabled > && (session->opt->mode == MODE_SERVER || session->opt->pull)) > @@ -2272,8 +2308,14 @@ push_peer_info(struct buffer *buf, struct tls_session *session) > buf_printf(&out, "IV_NCP=2\n"); > } > buf_printf(&out, "IV_CIPHERS=%s\n", session->opt->config_ncp_ciphers); > + > +#ifdef HAVE_EXPORT_KEYING_MATERIAL > + iv_proto |= IV_PROTO_TLS_KEY_EXPORT; > +#endif > } > > + buf_printf(&out, "IV_PROTO=%d\n", iv_proto); > + > /* push compression status */ > #ifdef USE_COMP > comp_generate_peer_info_string(&session->opt->comp_options, &out); > diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h > index 005628f6..f00f8abd 100644 > --- a/src/openvpn/ssl.h > +++ b/src/openvpn/ssl.h > @@ -116,6 +116,8 @@ > * to wait for a push-request to send a push-reply */ > #define IV_PROTO_REQUEST_PUSH (1<<2) > > +/** Supports key derivation via TLS key material exporter [RFC5705] */ > +#define IV_PROTO_TLS_KEY_EXPORT (1<<3) > > /* Default field in X509 to be username */ > #define X509_USERNAME_FIELD_DEFAULT "CN" > diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h > index cf9fba25..06ced86a 100644 > --- a/src/openvpn/ssl_backend.h > +++ b/src/openvpn/ssl_backend.h > @@ -389,6 +389,8 @@ void key_state_ssl_free(struct key_state_ssl *ks_ssl); > void backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, > const char *crl_file, bool crl_inline); > > +#define EXPORT_KEY_DATA_LABEL "EXPORTER-OpenVPN-datakeys" > +#define EXPORT_KEY_DATA_EKM_SIZE (2 * (MAX_CIPHER_KEY_LENGTH + MAX_HMAC_KEY_LENGTH)) This is probably not needed (see above). Otherwise, I'd like to see a (static) assert somewhere that this indeed matches the size we're going to memcpy() into the keys struct. > /** > * Keying Material Exporters [RFC 5705] allows additional keying material to be > * derived from existing TLS channel. This exported keying material can then be > diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c > index 4ec355a9..f375e957 100644 > --- a/src/openvpn/ssl_mbedtls.c > +++ b/src/openvpn/ssl_mbedtls.c > @@ -1168,11 +1168,8 @@ key_state_ssl_init(struct key_state_ssl *ks_ssl, > > #ifdef HAVE_EXPORT_KEYING_MATERIAL > /* Initialize keying material exporter */ > - if (session->opt->ekm_size) > - { > - mbedtls_ssl_conf_export_keys_ext_cb(ks_ssl->ssl_config, > - mbedtls_ssl_export_keys_cb, session); > - } > + mbedtls_ssl_conf_export_keys_ext_cb(ks_ssl->ssl_config, > + mbedtls_ssl_export_keys_cb, session); > #endif > > /* Initialise SSL context */ > So, looks good in general, but needs a bit more polishing :) -Steffan
Am 09.10.20 um 11:23 schrieb Steffan Karger: > Hi, > > On 25-08-2020 09:36, Arne Schwabe wrote: >> OpenVPN currently uses its own (based on TLS 1.0) key derivation >> mechanism to generate the 256 bytes key data in key2 struct that >> are then used used to generate encryption/hmac/iv vectors. While >> this mechanism is still secure, it is not state of the art. >> >> Instead of modernising our own approach, this commit implements >> key derivation using the Keying Material Exporters API introduced >> by RFC 5705. > > Thanks! I'm glad to see you're picking up the work I haven't been able > to find time for. > >> We also use an opportunistic approach of negotiating the use of >> EKM (exported key material) through an IV_PROTO flag and prefer >> EKM to our own PRF if both client and server support it. The >> use of EKM is pushed to the client as part of NCP as >> key-derivation tls-ekm. >> >> We still exchange the random data (112 bytes from client to server >> and 64 byte from server to client) for the OpenVPN PRF but >> do not use it. Removing that exchange would break the handshake >> and make a key-method 3 or similar necessary. >> >> As a side effect, this makes a little bit easier to have a FIPS compatible >> version of OpenVPN since we do not rely on calling MD5 anymore. >> >> Side note: this commit breaks the (not yet merged) WolfSSL support as it >> claims to support EKM in the OpenSSL compat API but always returns an error >> if you try to use it. >> >> Signed-off-by: Arne Schwabe <arne@rfc2549.org> > > Signed off two times. > >> Patch v2: rebase/change to V2 of EKM refactoring >> >> Patch v3: add Changes.rst >> >> Signed-off-by: Arne Schwabe <arne@rfc2549.org> >> --- >> Changes.rst | 11 +++++++ >> doc/doxygen/doc_key_generation.h | 14 +++++++-- >> src/openvpn/crypto.h | 4 +++ >> src/openvpn/init.c | 1 + >> src/openvpn/multi.c | 4 +++ >> src/openvpn/options.c | 14 +++++++++ >> src/openvpn/options.h | 3 ++ >> src/openvpn/push.c | 5 ++- >> src/openvpn/ssl.c | 54 ++++++++++++++++++++++++++++---- >> src/openvpn/ssl.h | 2 ++ >> src/openvpn/ssl_backend.h | 2 ++ >> src/openvpn/ssl_mbedtls.c | 7 ++--- >> 12 files changed, 107 insertions(+), 14 deletions(-) >> >> diff --git a/Changes.rst b/Changes.rst >> index f67e1d76..2a2829e7 100644 >> --- a/Changes.rst >> +++ b/Changes.rst >> @@ -1,3 +1,14 @@ >> +Overview of changes in 2.6 >> +========================== >> + >> + >> +New features >> +------------ >> +Keying Material Exporters (RFC 5705) based key generation >> + As part of the cipher negotiation OpenVPN will automatically prefer >> + the RFC5705 based key material generation to the current custom >> + OpenVPN PRF. This feature requires OpenSSL or mbed TLS 2.18+. >> + >> Overview of changes in 2.5 >> ========================== >> >> diff --git a/doc/doxygen/doc_key_generation.h b/doc/doxygen/doc_key_generation.h >> index 4bb9c708..cef773a9 100644 >> --- a/doc/doxygen/doc_key_generation.h >> +++ b/doc/doxygen/doc_key_generation.h >> @@ -58,6 +58,12 @@ >> * >> * @subsection key_generation_method_2 Key method 2 >> * >> + * There are two methods for generating key data when using key method 2 >> + * the first is OpenVPN's traditional approach that exchanges random >> + * data and uses a PRF and the other is using the RFC5705 keying material >> + * exporter to generate the key material. For both methods the random >> + * data is exchange but only used in the traditional method. >> + * >> * -# The client generates random material in the following amounts: >> * - Pre-master secret: 48 bytes >> * - Client's PRF seed for master secret: 32 bytes >> @@ -73,8 +79,12 @@ >> * server's random material. >> * >> * %Key method 2 %key expansion is performed by the \c >> - * generate_key_expansion() function. Please refer to its source code for >> - * details of the %key expansion process. >> + * generate_key_expansion_openvpn_prf() function. Please refer to its source >> + * code for details of the %key expansion process. >> + * >> + * When the client sends the IV_PROTO_TLS_KEY_EXPORT flag and the server replies >> + * with `key-derivation tls-ekm` the RFC5705 key material exporter with the >> + * label EXPORTER-OpenVPN-datakeys is used for the key data. > > Did you request this label with IANA? It is not really described well how this actually works, from what I interpreted you need to write to the mailing list, which I did: https://mailarchive.ietf.org/arch/msg/tls/IWFb5hLsqNlCcIMnEDDuPAkzXvg/ but I am lost what the next step would be. > > Also, a user could now use --keying-material-exporter to export the data > channel keys to a plugin. I'm inclined to say we should prevent that, by > rejecting our internal label as a user label. > >> * @subsection key_generation_random Source of random material >> * >> diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h >> index 999f643e..ec935ca5 100644 >> --- a/src/openvpn/crypto.h >> +++ b/src/openvpn/crypto.h >> @@ -254,6 +254,10 @@ struct crypto_options >> #define CO_MUTE_REPLAY_WARNINGS (1<<2) >> /**< Bit-flag indicating not to display >> * replay warnings. */ >> +#define CO_USE_TLS_KEY_MATERIAL_EXPORT (1<<3) >> + /**< Bit-flag indicating that key derivation > > Sugestion: "data channel key derivation". > >> + * is done using TLS keying material export [RFC5705] >> + */ >> unsigned int flags; /**< Bit-flags determining behavior of >> * security operation functions. */ >> }; >> diff --git a/src/openvpn/init.c b/src/openvpn/init.c >> index a785934a..dff090b1 100644 >> --- a/src/openvpn/init.c >> +++ b/src/openvpn/init.c >> @@ -676,6 +676,7 @@ restore_ncp_options(struct context *c) >> c->options.ciphername = c->c1.ciphername; >> c->options.authname = c->c1.authname; >> c->options.keysize = c->c1.keysize; >> + c->options.data_channel_use_ekm = false; >> } >> >> void >> diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c >> index 13738180..a5862020 100644 >> --- a/src/openvpn/multi.c >> +++ b/src/openvpn/multi.c >> @@ -1817,6 +1817,10 @@ multi_client_set_protocol_options(struct context *c) >> c->c2.push_request_received = true; >> } >> >> +#ifdef HAVE_EXPORT_KEYING_MATERIAL >> + o->data_channel_use_ekm = (proto & IV_PROTO_TLS_KEY_EXPORT); >> +#endif >> + >> /* Select cipher if client supports Negotiable Crypto Parameters */ >> if (!o->ncp_enabled) >> { >> diff --git a/src/openvpn/options.c b/src/openvpn/options.c >> index 8bf82c57..90e78a7b 100644 >> --- a/src/openvpn/options.c >> +++ b/src/openvpn/options.c >> @@ -7947,6 +7947,20 @@ add_option(struct options *options, >> } >> options->ncp_ciphers = p[1]; >> } >> + else if (streq(p[0], "key-derivation") && p[1]) >> + { >> + VERIFY_PERMISSION(OPT_P_NCP) >> +#ifdef HAVE_EXPORT_KEYING_MATERIAL >> + if (streq(p[1], "tls-ekm")) >> + { >> + options->data_channel_use_ekm = true; >> + } >> + else >> +#endif >> + { >> + msg(msglevel, "Unknown key-derivation method %s", p[1]); >> + } >> + } > > This option is also accepted on the command line or config file, but is > not documented in the man page or --help text. It would probably be best > to not accept is on the command line or config file, but I'm not sure we > have the infra in place for that. Sadly, we don't. Other options like peer-id or echo are also accepted from command line or config. Or we have it and nobody is aware of how to use it. > > So, looks good in general, but needs a bit more polishing :) > I will do the polishing and send a new version :) Thanks for reviewing. Arne
diff --git a/Changes.rst b/Changes.rst index f67e1d76..2a2829e7 100644 --- a/Changes.rst +++ b/Changes.rst @@ -1,3 +1,14 @@ +Overview of changes in 2.6 +========================== + + +New features +------------ +Keying Material Exporters (RFC 5705) based key generation + As part of the cipher negotiation OpenVPN will automatically prefer + the RFC5705 based key material generation to the current custom + OpenVPN PRF. This feature requires OpenSSL or mbed TLS 2.18+. + Overview of changes in 2.5 ========================== diff --git a/doc/doxygen/doc_key_generation.h b/doc/doxygen/doc_key_generation.h index 4bb9c708..cef773a9 100644 --- a/doc/doxygen/doc_key_generation.h +++ b/doc/doxygen/doc_key_generation.h @@ -58,6 +58,12 @@ * * @subsection key_generation_method_2 Key method 2 * + * There are two methods for generating key data when using key method 2 + * the first is OpenVPN's traditional approach that exchanges random + * data and uses a PRF and the other is using the RFC5705 keying material + * exporter to generate the key material. For both methods the random + * data is exchange but only used in the traditional method. + * * -# The client generates random material in the following amounts: * - Pre-master secret: 48 bytes * - Client's PRF seed for master secret: 32 bytes @@ -73,8 +79,12 @@ * server's random material. * * %Key method 2 %key expansion is performed by the \c - * generate_key_expansion() function. Please refer to its source code for - * details of the %key expansion process. + * generate_key_expansion_openvpn_prf() function. Please refer to its source + * code for details of the %key expansion process. + * + * When the client sends the IV_PROTO_TLS_KEY_EXPORT flag and the server replies + * with `key-derivation tls-ekm` the RFC5705 key material exporter with the + * label EXPORTER-OpenVPN-datakeys is used for the key data. * * @subsection key_generation_random Source of random material * diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 999f643e..ec935ca5 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -254,6 +254,10 @@ struct crypto_options #define CO_MUTE_REPLAY_WARNINGS (1<<2) /**< Bit-flag indicating not to display * replay warnings. */ +#define CO_USE_TLS_KEY_MATERIAL_EXPORT (1<<3) + /**< Bit-flag indicating that key derivation + * is done using TLS keying material export [RFC5705] + */ unsigned int flags; /**< Bit-flags determining behavior of * security operation functions. */ }; diff --git a/src/openvpn/init.c b/src/openvpn/init.c index a785934a..dff090b1 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -676,6 +676,7 @@ restore_ncp_options(struct context *c) c->options.ciphername = c->c1.ciphername; c->options.authname = c->c1.authname; c->options.keysize = c->c1.keysize; + c->options.data_channel_use_ekm = false; } void diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 13738180..a5862020 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -1817,6 +1817,10 @@ multi_client_set_protocol_options(struct context *c) c->c2.push_request_received = true; } +#ifdef HAVE_EXPORT_KEYING_MATERIAL + o->data_channel_use_ekm = (proto & IV_PROTO_TLS_KEY_EXPORT); +#endif + /* Select cipher if client supports Negotiable Crypto Parameters */ if (!o->ncp_enabled) { diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 8bf82c57..90e78a7b 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -7947,6 +7947,20 @@ add_option(struct options *options, } options->ncp_ciphers = p[1]; } + else if (streq(p[0], "key-derivation") && p[1]) + { + VERIFY_PERMISSION(OPT_P_NCP) +#ifdef HAVE_EXPORT_KEYING_MATERIAL + if (streq(p[1], "tls-ekm")) + { + options->data_channel_use_ekm = true; + } + else +#endif + { + msg(msglevel, "Unknown key-derivation method %s", p[1]); + } + } else if (streq(p[0], "ncp-disable") && !p[1]) { VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 877e9396..c730c6a7 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -648,6 +648,9 @@ struct options /* Useful when packets sent by openvpn itself are not subject * to the routing tables that would move packets into the tunnel. */ bool allow_recursive_routing; + + /* Use RFC 5705 key export */ + bool data_channel_use_ekm; }; #define streq(x, y) (!strcmp((x), (y))) diff --git a/src/openvpn/push.c b/src/openvpn/push.c index e0d2eeaf..17bba948 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -479,7 +479,10 @@ prepare_push_reply(struct context *c, struct gc_arena *gc, { push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername); } - + if (o->data_channel_use_ekm) + { + push_option_fmt(gc, push_list, M_USAGE, "key-derivation tls-ekm"); + } return true; } diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index b4d94b8a..9643fbdd 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1784,6 +1784,29 @@ init_key_contexts(struct key_ctx_bi *key, } +static bool +generate_key_expansion_tls_export(struct tls_session *session, struct key2 *key2) +{ + struct gc_arena gc = gc_new(); + unsigned char *key2data; + + key2data = key_state_export_keying_material(session, + EXPORT_KEY_DATA_LABEL, + strlen(EXPORT_KEY_DATA_LABEL), + EXPORT_KEY_DATA_EKM_SIZE, + &gc); + if (!key2data) + { + return false; + } + memcpy(key2->keys, key2data, sizeof(key2->keys)); + secure_memzero(key2data, sizeof(key2->keys)); + key2->n = 2; + + gc_free(&gc); + return true; +} + static struct key2 generate_key_expansion_openvpn_prf(const struct tls_session *session) { @@ -1855,7 +1878,7 @@ generate_key_expansion_openvpn_prf(const struct tls_session *session) */ static bool generate_key_expansion(struct key_ctx_bi *key, - const struct tls_session *session) + struct tls_session *session) { bool ret = false; struct key2 key2; @@ -1866,10 +1889,20 @@ generate_key_expansion(struct key_ctx_bi *key, goto exit; } - bool server = session->opt->server; - key2 = generate_key_expansion_openvpn_prf(session); + if (session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT) + { + if(!generate_key_expansion_tls_export(session, &key2)) + { + msg(D_TLS_ERRORS, "TLS Error: Keying material export failed"); + goto exit; + } + } + else + { + key2 = generate_key_expansion_openvpn_prf(session); + } key2_print(&key2, &session->opt->key_type, "Master Encrypt", "Master Decrypt"); @@ -1970,6 +2003,11 @@ tls_session_update_crypto_params(struct tls_session *session, return false; } + if (options->data_channel_use_ekm) + { + session->opt->crypto_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT; + } + if (strcmp(options->ciphername, session->opt->config_ciphername)) { msg(D_HANDSHAKE, "Data Channel: using negotiated cipher '%s'", @@ -2254,13 +2292,11 @@ push_peer_info(struct buffer *buf, struct tls_session *session) * push request, also signal that the client wants * to get push-reply messages without without requiring a round * trip for a push request message*/ - if(session->opt->pull) + if (session->opt->pull) { iv_proto |= IV_PROTO_REQUEST_PUSH; } - buf_printf(&out, "IV_PROTO=%d\n", iv_proto); - /* support for Negotiable Crypto Parameters */ if (session->opt->ncp_enabled && (session->opt->mode == MODE_SERVER || session->opt->pull)) @@ -2272,8 +2308,14 @@ push_peer_info(struct buffer *buf, struct tls_session *session) buf_printf(&out, "IV_NCP=2\n"); } buf_printf(&out, "IV_CIPHERS=%s\n", session->opt->config_ncp_ciphers); + +#ifdef HAVE_EXPORT_KEYING_MATERIAL + iv_proto |= IV_PROTO_TLS_KEY_EXPORT; +#endif } + buf_printf(&out, "IV_PROTO=%d\n", iv_proto); + /* push compression status */ #ifdef USE_COMP comp_generate_peer_info_string(&session->opt->comp_options, &out); diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index 005628f6..f00f8abd 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -116,6 +116,8 @@ * to wait for a push-request to send a push-reply */ #define IV_PROTO_REQUEST_PUSH (1<<2) +/** Supports key derivation via TLS key material exporter [RFC5705] */ +#define IV_PROTO_TLS_KEY_EXPORT (1<<3) /* Default field in X509 to be username */ #define X509_USERNAME_FIELD_DEFAULT "CN" diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h index cf9fba25..06ced86a 100644 --- a/src/openvpn/ssl_backend.h +++ b/src/openvpn/ssl_backend.h @@ -389,6 +389,8 @@ void key_state_ssl_free(struct key_state_ssl *ks_ssl); void backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, const char *crl_file, bool crl_inline); +#define EXPORT_KEY_DATA_LABEL "EXPORTER-OpenVPN-datakeys" +#define EXPORT_KEY_DATA_EKM_SIZE (2 * (MAX_CIPHER_KEY_LENGTH + MAX_HMAC_KEY_LENGTH)) /** * Keying Material Exporters [RFC 5705] allows additional keying material to be * derived from existing TLS channel. This exported keying material can then be diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c index 4ec355a9..f375e957 100644 --- a/src/openvpn/ssl_mbedtls.c +++ b/src/openvpn/ssl_mbedtls.c @@ -1168,11 +1168,8 @@ key_state_ssl_init(struct key_state_ssl *ks_ssl, #ifdef HAVE_EXPORT_KEYING_MATERIAL /* Initialize keying material exporter */ - if (session->opt->ekm_size) - { - mbedtls_ssl_conf_export_keys_ext_cb(ks_ssl->ssl_config, - mbedtls_ssl_export_keys_cb, session); - } + mbedtls_ssl_conf_export_keys_ext_cb(ks_ssl->ssl_config, + mbedtls_ssl_export_keys_cb, session); #endif /* Initialise SSL context */