crypto: always reload tls-auth/crypt key contexts
PATCH v3 1/2 crypto: always reload tls-auth/crypt key contexts
In preparation to having tls-auth/crypt keys per connection block, it is important to ensure that such material is always reloaded upon SIGUSR1, no matter if persist-key was specified or not.
This is required because when moving from one remote to the other the key may change and thus the key context needs to be refreshed.
To ensure that the persist-key logic will still work as expected, the tls-auth/crypt key is pre-loaded so that the keyfile is not required at runtime.
Trac: #720
---
v2:
- introduce this patch
v3:
- add key per-loading logic to this patch to avoid temporary features breakages
src/openvpn/buffer.c | 29 +++++++++++++++ src/openvpn/buffer.h | 13 +++++++ src/openvpn/crypto.c | 20 ++-------- src/openvpn/init.c | 87 ++++++++++++++++++++++++++++--------------- src/openvpn/options.c | 20 ++++++++++ 5 files changed, 122 insertions(+), 47 deletions(-)
src/openvpn/buffer.c +29 -0
@@ -1332,3 +1332,32 @@ buffer_list_file(const char *fn, int max_line_len) }return bl; } + +struct buffer +keyfile_to_buffer (const char *file,int max_size,struct gc_arena *gc) +{ +size_t size; +struct buffer in =alloc_buf_gc (max_size, gc); +int fd =platform_open (file,O_RDONLY ,0 ); +if (fd ==-1 ) + { +msg (M_ERR ,"Cannot open key file '%s'" , file); + } + + size =read (fd, in.data, in.capacity); +if (size <0 ) + { +msg (M_FATAL ,"Read error on key file ('%s')" , file); + } + +if (size == in.capacity) + { +msg (M_FATAL ,"Key file ('%s') can be a maximum of %d bytes" , file, + (int )in.capacity); + } +close (fd); + + in.len = size; + +return in; +}
src/openvpn/buffer.h +13 -0
@@ -1112,4 +1112,17 @@ void buffer_list_aggregate_separator(struct buffer_list *bl,struct buffer_list *buffer_list_file (const char *fn,int max_line_len); +/** +* keyfile_to_buffer - copy the content of a file into a buffer +* +* @param file path to the file to read +* @param max_size maximum size of the buffer to allocate +* @param gc the garbage collector to use when allocating the buffer. It +* passed to alloc_buf_gc() and therefore can be NULL. +* +* @return the buffer storing the file content +*/ +struct buffer keyfile_to_buffer (const char *file,int max_size, +struct gc_arena *gc); +#endif /* BUFFER_H */
src/openvpn/crypto.c +3 -17
@@ -1224,7 +1224,7 @@ read_key_file(struct key2 *key2, const char *file, const unsigned int flags) {struct gc_arena gc =gc_new ();struct buffer in; -int fd, size; +int size;uint8_t hex_byte[3 ] = {0 ,0 ,0 };const char *error_filename = file;
@@ -1268,22 +1268,8 @@ read_key_file(struct key2 *key2, const char *file, const unsigned int flags) }else /* 'file' is a filename which refers to a file containing the ascii key */ { - in =alloc_buf_gc (2048 , &gc); - fd =platform_open (file,O_RDONLY ,0 ); -if (fd ==-1 ) - { -msg (M_ERR ,"Cannot open key file '%s'" , file); - } - size =read (fd, in.data, in.capacity); -if (size <0 ) - { -msg (M_FATAL ,"Read error on key file ('%s')" , file); - } -if (size == in.capacity) - { -msg (M_FATAL ,"Key file ('%s') can be a maximum of %d bytes" , file, (int )in.capacity); - } -close (fd); + in =keyfile_to_buffer (file,2048 , &gc); + size = in.len; } cp = (unsigned char *)in.data;
src/openvpn/init.c +57 -30
117 lines not on this page · raw message
src/openvpn/options.c +20 -0
@@ -3019,6 +3019,26 @@ options_postprocess_mutate(struct options *o)options_postprocess_mutate_ce (o, o->connection_list->array[i]); } +/* pre-cache tls-auth/crypt key file if persist-key was specified */ +if (o->persist_key) + { +if (o->tls_auth_file && !o->tls_auth_file_inline) + { +struct buffer in =keyfile_to_buffer (o->tls_auth_file,2048 , + &o->gc); + o->tls_auth_file =INLINE_FILE_TAG ; + o->tls_auth_file_inline = (char *)in.data; + } + +if (o->tls_crypt_file && !o->tls_crypt_inline) + { +struct buffer in =keyfile_to_buffer (o->tls_crypt_file,2048 , + &o->gc); + o->tls_crypt_file =INLINE_FILE_TAG ; + o->tls_crypt_inline = (char *)in.data; + } + } +if (o->tls_server) {/* Check that DH file is specified, or explicitly disabled */
Signature · 1 line
2.17.1
PATCH v3 2/2 make tls-auth and tls-crypt per-connection-block options
Different VPN servers may use different tls-auth/crypt keys. For this reason it is convenient to make tls-auth/crypt per-connection-block options so that the user is allowed to specify one key per remote.
If no tls-auth/crypt option is specified in a given connection block, the global settings, if any, are used.
Trac: #720
---
v2:
- convert tls-auth keyfile to inline key if persist-key was specified v3:
- squash 2/3 and 3/3 in one patch to prevent temporary features breakages
- restyle code introduced in options_postprocess_mutate_ce()
doc/openvpn.8 | 3 + src/openvpn/init.c | 18 ++--- src/openvpn/options.c | 161 ++++++++++++++++++++++++++++++------------ src/openvpn/options.h | 9 +++ 4 files changed, 136 insertions(+), 55 deletions(-)
doc/openvpn.8 +3 -0
@@ -361,6 +361,7 @@ block: .B fragment, .B http\-proxy, .B http\-proxy\-option, +.B key\-direction, .B link\-mtu, .B local, .B lport,
@@ -372,6 +373,8 @@ block: .B remote, .B rport, .B socks\-proxy, +.B tls\-auth, +.B tls\-crypt, .B tun\-mtu and .B tun\-mtu\-extra.
src/openvpn/init.c +9 -9
@@ -2504,7 +2504,7 @@ do_init_tls_wrap_key(struct context *c)const struct options *options = &c->options;/* TLS handshake authentication (--tls-auth) */ -if (options->tls_auth_file) +if (options->ce.tls_auth_file) {/* Initialize key_type for tls-auth with auth only */ CLEAR (c->c1.ks.tls_auth_key_type);
@@ -2522,18 +2522,18 @@ do_init_tls_wrap_key(struct context *c)crypto_read_openvpn_key (&c->c1.ks.tls_auth_key_type, &c->c1.ks.tls_wrap_key, - options->tls_auth_file, - options->tls_auth_file_inline, - options->key_direction, + options->ce.tls_auth_file, + options->ce.tls_auth_file_inline, + options->ce.key_direction,"Control Channel Authentication" ,"tls-auth" ); }/* TLS handshake encryption+authentication (--tls-crypt) */ -if (options->tls_crypt_file) +if (options->ce.tls_crypt_file) {tls_crypt_init_key (&c->c1.ks.tls_wrap_key, - options->tls_crypt_file, - options->tls_crypt_inline, options->tls_server); + options->ce.tls_crypt_file, + options->ce.tls_crypt_inline, options->tls_server); } }
@@ -2803,7 +2803,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)#endif /* TLS handshake authentication (--tls-auth) */ -if (options->tls_auth_file) +if (options->ce.tls_auth_file) { to.tls_wrap.mode =TLS_WRAP_AUTH ; to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
@@ -2814,7 +2814,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) }/* TLS handshake encryption (--tls-crypt) */ -if (options->tls_crypt_file) +if (options->ce.tls_crypt_file) { to.tls_wrap.mode =TLS_WRAP_CRYPT ; to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
src/openvpn/options.c +115 -46
233 lines not on this page · raw message
src/openvpn/options.h +9 -0
@@ -130,6 +130,15 @@ struct connection_entry#define CE_MAN_QUERY_REMOTE_MASK (0x07)#define CE_MAN_QUERY_REMOTE_SHIFT (2)unsigned int flags; + +/* Shared secret used for TLS control channel authentication */ +const char *tls_auth_file; +const char *tls_auth_file_inline; +int key_direction; + +/* Shared secret used for TLS control channel authenticated encryption */ +const char *tls_crypt_file; +const char *tls_crypt_inline; };struct remote_entry
Signature · 1 line
2.17.1
Hi,
Quoted 14 lines · Antonio Quartulli
In preparation to having tls-auth/crypt keys per connection block, it is important to ensure that such material is always reloaded upon SIGUSR1, no matter if
persist-keywas specified or not.This is required because when moving from one remote to the other the key may change and thus the key context needs to be refreshed.
To ensure that the
persist-keylogic will still work as expected, the tls-auth/crypt key is pre-loaded so that the keyfile is not required at runtime.Trac: #720
Quoted 8 lines
Cc Steffan Karger <[email protected]...>Signed-off-by Antonio Quartulli <[email protected]...>---
v2:
- introduce this patch
v3:
- add key per-loading logic to this patch to avoid temporary features
^^^^^ this should be "re-loading", sorry.
Quoted 1 line
breakages
Cheers,
Signature · 1 line
Antonio Quartulli
Quoted 4 lines · Antonio Quartulli
In preparation to having tls-auth/crypt keys per connection block, it is important to ensure that such material is always reloaded upon SIGUSR1, no matter if
persist-keywas specified or not.
Has this been tested with --chroot and --user/--group?
Signature · 4 lines
kind regards, David Sommerseth OpenVPN Inc
Hi,
Quoted 7 lines · David Sommerseth
Quoted 4 lines · Antonio Quartulli
In preparation to having tls-auth/crypt keys per connection block, it is important to ensure that such material is always reloaded upon SIGUSR1, no matter if
persist-keywas specified or not.Has this been tested with --chroot and --user/--group?
No, these tests are missing.
Cheers,
Signature · 1 line
Antonio Quartulli
Hi,
Quoted 4 lines · Antonio Quartulli
Hi,
Quoted 1 line · David Sommerseth
On 05/06/18 10:14, Antonio Quartulli wrote:
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines
In preparation to having tls-auth/crypt keys per connection block, it is important to ensure that such material is always reloaded upon SIGUSR1, no matter if
persist-keywas specified or not.Has this been tested with --chroot and --user/--group?
No, these tests are missing.
Tests performed.
There is no change in behaviour: --chroot and --user/--group behave exactly as now.
This was expected as using persist-key will instruct openvpn to cache the key material and thus it can be reused to re-init the various SSL context every time.
Cheers,
Signature · 1 line
Antonio Quartulli
Hi Antonio,
Quoted 58 lines · Antonio Quartulli
In preparation to having tls-auth/crypt keys per connection block, it is important to ensure that such material is always reloaded upon SIGUSR1, no matter if
persist-keywas specified or not.This is required because when moving from one remote to the other the key may change and thus the key context needs to be refreshed.
To ensure that the
persist-keylogic will still work as expected, the tls-auth/crypt key is pre-loaded so that the keyfile is not required at runtime.Trac: #720
Cc Steffan Karger <[email protected]...>Signed-off-by Antonio Quartulli <[email protected]...>---
v2:
- introduce this patch
v3:
- add key per-loading logic to this patch to avoid temporary features breakagessrc/openvpn/buffer.c | 29 +++++++++++++++ src/openvpn/buffer.h | 13 +++++++ src/openvpn/crypto.c | 20 ++-------- src/openvpn/init.c | 87 ++++++++++++++++++++++++++++--------------- src/openvpn/options.c | 20 ++++++++++ 5 files changed, 122 insertions(+), 47 deletions(-)src/openvpn/buffer.c +19
-0@@ -1332,3 +1332,32 @@ buffer_list_file(const char *fn, int max_line_len) }return bl; } + +struct buffer +keyfile_to_buffer (const char *file,int max_size,struct gc_arena *gc) +{ +size_t size; +struct buffer in =alloc_buf_gc (max_size, gc); +int fd =platform_open (file,O_RDONLY ,0 ); +if (fd ==-1 ) + { +msg (M_ERR ,"Cannot open key file '%s'" , file); + } + + size =read (fd, in.data, in.capacity); +if (size <0 ) + { +msg (M_FATAL ,"Read error on key file ('%s')" , file); + } + +if (size == in.capacity)
src/openvpn/buffer.c buffer_list_file(const char *fn, int max_line_len) · line 1363
Quoted 10 lines
src/openvpn/buffer.c +10
-0+ { +msg (M_FATAL ,"Key file ('%s') can be a maximum of %d bytes" , file, + (int )in.capacity); + } +close (fd); + + in.len = size; + +return in; +}
Hm, I don't really like the FATAL errors in a function that looks generic enough for someone to use in non-startup situations. In that case the FATALs might create a DoS vector. Or, think ahead to the connection blocks, a connection starting, then timeing out, then exiting because the tls-auth/crypt key of the next connection block doesn't exist (instead of just trying the next block).
While reading this, I realize I wrote a very similar function in the "tls-crypt-v2: generate client keys" patch. That one uses platform_stat() to figure out the size and get rid of the maxlen argument, and doesn't throw fatal errors. Feel free to use that implementation if you like it, or not if you don't (and I'll use yours in the latter case).
Quoted 208 lines
src/openvpn/buffer.h +13
-0@@ -1112,4 +1112,17 @@ void buffer_list_aggregate_separator(struct buffer_list *bl,struct buffer_list *buffer_list_file (const char *fn,int max_line_len); +/** +* keyfile_to_buffer - copy the content of a file into a buffer +* +* @param file path to the file to read +* @param max_size maximum size of the buffer to allocate +* @param gc the garbage collector to use when allocating the buffer. It +* passed to alloc_buf_gc() and therefore can be NULL. +* +* @return the buffer storing the file content +*/ +struct buffer keyfile_to_buffer (const char *file,int max_size, +struct gc_arena *gc); +#endif /* BUFFER_H */ src/openvpn/crypto.c +3
-17@@ -1224,7 +1224,7 @@ read_key_file(struct key2 *key2, const char *file, const unsigned int flags) {struct gc_arena gc =gc_new ();struct buffer in; -int fd, size; +int size;uint8_t hex_byte[3 ] = {0 ,0 ,0 };const char *error_filename = file;@@ -1268,22 +1268,8 @@ read_key_file(struct key2 *key2, const char *file, const unsigned int flags) }else /* 'file' is a filename which refers to a file containing the ascii key */ { - in =alloc_buf_gc (2048 , &gc); - fd =platform_open (file,O_RDONLY ,0 ); -if (fd ==-1 ) - { -msg (M_ERR ,"Cannot open key file '%s'" , file); - } - size =read (fd, in.data, in.capacity); -if (size <0 ) - { -msg (M_FATAL ,"Read error on key file ('%s')" , file); - } -if (size == in.capacity) - { -msg (M_FATAL ,"Key file ('%s') can be a maximum of %d bytes" , file, (int )in.capacity); - } -close (fd); + in =keyfile_to_buffer (file,2048 , &gc); + size = in.len; } cp = (unsigned char *)in.data;src/openvpn/init.c +57
-30@@ -2406,7 +2406,6 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)if (tls_ctx_initialised (&ks->ssl_ctx) && free_ssl_ctx) {tls_ctx_free (&ks->ssl_ctx); -free_key_ctx_bi (&ks->tls_wrap_key); }CLEAR (*ks); }@@ -2496,6 +2495,48 @@ do_init_crypto_static(struct context *c, const unsigned int flags)check_replay_consistency (&c->c1.ks.key_type, options->replay); } +/* +* Initialize the tls-auth/crypt key context +*/ +static void +do_init_tls_wrap_key (struct context *c) +{ +const struct options *options = &c->options; + +/* TLS handshake authentication (--tls-auth) */ +if (options->tls_auth_file) + { +/* Initialize key_type for tls-auth with auth only */ +CLEAR (c->c1.ks.tls_auth_key_type); +if (!streq (options->authname,"none" )) + { + c->c1.ks.tls_auth_key_type.digest =md_kt_get (options->authname); + c->c1.ks.tls_auth_key_type.hmac_length = +md_kt_size (c->c1.ks.tls_auth_key_type.digest); + } +else + { +msg (M_FATAL ,"ERROR: tls-auth enabled, but no valid --auth " +"algorithm specified ('%s')" , options->authname); + } + +crypto_read_openvpn_key (&c->c1.ks.tls_auth_key_type, + &c->c1.ks.tls_wrap_key, + options->tls_auth_file, + options->tls_auth_file_inline, + options->key_direction, +"Control Channel Authentication" ,"tls-auth" ); + } + +/* TLS handshake encryption+authentication (--tls-crypt) */ +if (options->tls_crypt_file) + { +tls_crypt_init_key (&c->c1.ks.tls_wrap_key, + options->tls_crypt_file, + options->tls_crypt_inline, options->tls_server); + } +} +/* * Initialize the persistent component of OpenVPN's TLS mode, * which is preserved across SIGUSR1 resets. @@ -2545,35 +2586,8 @@ do_init_crypto_tls_c1(struct context *c)/* Initialize PRNG with config-specified digest */ prng_init (options->prng_hash, options->prng_nonce_secret_len); -/* TLS handshake authentication (--tls-auth) */ -if (options->tls_auth_file) - { -/* Initialize key_type for tls-auth with auth only */ -CLEAR (c->c1.ks.tls_auth_key_type); -if (!streq (options->authname,"none" )) - { - c->c1.ks.tls_auth_key_type.digest =md_kt_get (options->authname); - c->c1.ks.tls_auth_key_type.hmac_length = -md_kt_size (c->c1.ks.tls_auth_key_type.digest); - } -else - { -msg (M_FATAL ,"ERROR: tls-auth enabled, but no valid --auth " -"algorithm specified ('%s')" , options->authname); - } - -crypto_read_openvpn_key (&c->c1.ks.tls_auth_key_type, - &c->c1.ks.tls_wrap_key, options->tls_auth_file, - options->tls_auth_file_inline, options->key_direction, -"Control Channel Authentication" ,"tls-auth" ); - } - -/* TLS handshake encryption+authentication (--tls-crypt) */ -if (options->tls_crypt_file) - { -tls_crypt_init_key (&c->c1.ks.tls_wrap_key, options->tls_crypt_file, - options->tls_crypt_inline, options->tls_server); - } +/* initialize tls-auth/crypt key */ +do_init_tls_wrap_key (c); c->c1.ciphername = options->ciphername; c->c1.authname = options->authname;@@ -2595,6 +2609,12 @@ do_init_crypto_tls_c1(struct context *c) c->options.ciphername = c->c1.ciphername; c->options.authname = c->c1.authname; c->options.keysize = c->c1.keysize; + +/* +* tls-auth/crypt key can be configured per connection block, therefore +* we must reload it as it may have changed +*/ +do_init_tls_wrap_key (c); } }@@ -3398,6 +3418,13 @@ do_close_tls(struct context *c)static void do_close_free_key_schedule (struct context *c,bool free_ssl_ctx) { +/* +* always free the tls_auth/crypt key. If persist_key is true, the key will +* be reloaded from memory (pre-cached) +*/ +free_key_ctx_bi (&c->c1.ks.tls_wrap_key); +CLEAR (c->c1.ks.tls_wrap_key); +if (!(c->sig->signal_received ==SIGUSR1 && c->options.persist_key)) {key_schedule_free (&c->c1.ks, free_ssl_ctx);src/openvpn/options.c +13
-0@@ -3019,6 +3019,26 @@ options_postprocess_mutate(struct options *o)options_postprocess_mutate_ce (o, o->connection_list->array[i]); } +/* pre-cache tls-auth/crypt key file if persist-key was specified */ +if (o->persist_key) + { +if (o->tls_auth_file && !o->tls_auth_file_inline) + { +struct buffer in =keyfile_to_buffer (o->tls_auth_file,2048 , + &o->gc); + o->tls_auth_file =INLINE_FILE_TAG ; + o->tls_auth_file_inline = (char *)in.data; + } + +if (o->tls_crypt_file && !o->tls_crypt_inline) + {
src/openvpn/options.c options_postprocess_mutate(struct options *o) · line 3044
Quoted 10 lines
src/openvpn/options.c +7
-0+struct buffer in =keyfile_to_buffer (o->tls_crypt_file,2048 , + &o->gc); + o->tls_crypt_file =INLINE_FILE_TAG ; + o->tls_crypt_inline = (char *)in.data; + } + } +if (o->tls_server) {/* Check that DH file is specified, or explicitly disabled */
Otherwise this looks good and passes initial tests. I want to test this a bit more before a final ACK, but I think you'll want to look into the above comment anyway.
-Steffan
Hi Steffan and thanks for the review,
Quoted 27 lines · Steffan Karger
Quoted 27 lines
src/openvpn/buffer.c +27
-0+struct buffer +keyfile_to_buffer (const char *file,int max_size,struct gc_arena *gc) +{ +size_t size; +struct buffer in =alloc_buf_gc (max_size, gc); +int fd =platform_open (file,O_RDONLY ,0 ); +if (fd ==-1 ) + { +msg (M_ERR ,"Cannot open key file '%s'" , file); + } + + size =read (fd, in.data, in.capacity); +if (size <0 ) + { +msg (M_FATAL ,"Read error on key file ('%s')" , file); + } + +if (size == in.capacity) + { +msg (M_FATAL ,"Key file ('%s') can be a maximum of %d bytes" , file, + (int )in.capacity); + } +close (fd); + + in.len = size; + +return in;
Quoted 9 lines
Quoted 1 line
+}
Hm, I don't really like the FATAL errors in a function that looks generic enough for someone to use in non-startup situations. In that case the FATALs might create a DoS vector. Or, think ahead to the connection blocks, a connection starting, then timeing out, then exiting because the tls-auth/crypt key of the next connection block doesn't exist (instead of just trying the next block).
You're right. I made an assumption about the context where it'll be used, but you're right, I shouldn't do it. I'll remove the FATAL.
Quoted 6 lines
While reading this, I realize I wrote a very similar function in the "tls-crypt-v2: generate client keys" patch. That one uses
platform_stat()to figure out the size and get rid of the maxlen argument, and doesn't throw fatal errors. Feel free to use that implementation if you like it, or not if you don't (and I'll use yours in the latter case).
Oh Right, thanks for pointing this out.
I'll use your implementation as it is relying more on buffer APIs, while I am dealing directly with the internals (and I prefer your approach).
I'll send v4 of this patch soon.
Thanks!
Signature · 1 line
Antonio Quartulli