Hi,

Thanks!  Great to see the unit tests payed off.  Two minor remarks still:

On 07-07-18 19:13, Antonio Quartulli wrote:
> 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
> Cc: Steffan Karger <stef...@karger.me>
> Signed-off-by: Antonio Quartulli <a...@unstable.cc>
> ---
> v2:
> - introduce this patch
> v3:
> - add key per-loading logic to this patch to avoid temporary features
>   breakages
> v4:
> - rebase on top of master
> - use buffer_read_from_file() implementation from "tls-crypt-v2: generate
>   client keys". Minor change: in case of error, free buffer and remove
>   it from gc (with free_buf_gc()), if provided.
> - don't go FATAL inside buffer_read_from_file() in case of error, but
>   let external logic decide
> v5:
> - fix various bits of pointer logic in free_buf_gc()
> - implement unit test for free_buf_gc()
> - use CLEAR() instead of free_buf() at the end of free_buf_gc() (problem
>   found thanks to the unit-test)
> 
> 
>  src/openvpn/buffer.c                   | 61 ++++++++++++++++++
>  src/openvpn/buffer.h                   | 12 ++++
>  src/openvpn/crypto.c                   | 21 ++-----
>  src/openvpn/init.c                     | 87 +++++++++++++++++---------
>  src/openvpn/options.c                  | 26 ++++++++
>  tests/unit_tests/openvpn/Makefile.am   |  1 -
>  tests/unit_tests/openvpn/test_buffer.c | 45 +++++++++++++
>  7 files changed, 206 insertions(+), 47 deletions(-)
> 
> diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c
> index becfeb93..0972139f 100644
> --- a/src/openvpn/buffer.c
> +++ b/src/openvpn/buffer.c
> @@ -189,6 +189,34 @@ free_buf(struct buffer *buf)
>      CLEAR(*buf);
>  }
>  
> +static void
> +free_buf_gc(struct buffer *buf, struct gc_arena *gc)
> +{
> +    if (gc)
> +    {
> +        struct gc_entry **e = &gc->list;
> +
> +        while (*e)
> +        {
> +            /* check if this object is the one we want to delete */
> +            if ((uint8_t *)(*e + 1) == buf->data)
> +            {
> +                struct gc_entry *to_delete = *e;
> +
> +                /* remove element from linked list and free it */
> +                *e = (*e)->next;
> +                free(to_delete);
> +
> +                break;
> +            }
> +
> +            e = &(*e)->next;
> +        }
> +    }
> +
> +    CLEAR(*buf);
> +}
> +
>  /*
>   * Return a buffer for write that is a subset of another buffer
>   */
> @@ -1332,3 +1360,36 @@ buffer_list_file(const char *fn, int max_line_len)
>      }
>      return bl;
>  }
> +
> +struct buffer
> +buffer_read_from_file(const char *filename, struct gc_arena *gc)
> +{
> +    struct buffer ret = { 0 };
> +
> +    platform_stat_t file_stat = {0};
> +    if (platform_stat(filename, &file_stat) < 0)
> +    {
> +        return ret;
> +    }
> +
> +    FILE *fp = platform_fopen(filename, "r");
> +    if (!fp)
> +    {
> +        return ret;
> +    }
> +
> +    const size_t size = file_stat.st_size;
> +    ret = alloc_buf_gc(size + 1, gc); /* space for trailing \0 */
> +    ssize_t read_size = fread(BPTR(&ret), 1, size, fp);
> +    if (read_size < 0)
> +    {
> +        free_buf_gc(&ret, gc);
> +        goto cleanup;
> +    }
> +    ASSERT(buf_inc_len(&ret, read_size));
> +    buf_null_terminate(&ret);
> +
> +cleanup:
> +    fclose(fp);
> +    return ret;
> +}
> diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h
> index d848490a..a11c8bd9 100644
> --- a/src/openvpn/buffer.h
> +++ b/src/openvpn/buffer.h
> @@ -1112,4 +1112,16 @@ void buffer_list_aggregate_separator(struct 
> buffer_list *bl,
>  
>  struct buffer_list *buffer_list_file(const char *fn, int max_line_len);
>  
> +/**
> + * buffer_read_from_file - copy the content of a file into a buffer
> + *
> + * @param file      path to the file to read
> + * @param gc        the garbage collector to use when allocating the buffer. 
> It
> + *                  passed to alloc_buf_gc() and therefore can be NULL.

Looks like you forgot to fix the typo.

> + *
> + * @return the buffer storing the file content or an invalid buffer in case 
> of
> + * error
> + */
> +struct buffer buffer_read_from_file(const char *filename, struct gc_arena 
> *gc);
> +
>  #endif /* BUFFER_H */
> diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
> index b59c1f73..5381ef2a 100644
> --- a/src/openvpn/crypto.c
> +++ b/src/openvpn/crypto.c
> @@ -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,11 @@ 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)
> -        {
> +        in = buffer_read_from_file(file, &gc);
> +        if (!buf_valid(&in))
>              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);
> +
> +        size = in.len;
>      }
>  
>      cp = (unsigned char *)in.data;
> diff --git a/src/openvpn/init.c b/src/openvpn/init.c
> index b748357d..1b46b364 100644
> --- a/src/openvpn/init.c
> +++ b/src/openvpn/init.c
> @@ -2409,7 +2409,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);
>  }
> @@ -2499,6 +2498,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.
> @@ -2548,35 +2589,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;
> @@ -2598,6 +2612,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);
>      }
>  }
>  
> @@ -3401,6 +3421,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);
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index 426057ab..b2ca2738 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -3019,6 +3019,32 @@ 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 = buffer_read_from_file(o->tls_auth_file, 
> &o->gc);
> +            if (!buf_valid(&in))
> +                msg(M_FATAL, "Cannot pre-load tls-auth keyfile (%s)",
> +                    o->tls_auth_file);
> +
> +            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 = buffer_read_from_file(o->tls_crypt_file, 
> &o->gc);
> +            if (!buf_valid(&in))
> +                msg(M_FATAL, "Cannot pre-load tls-crypt keyfile (%s)",
> +                    o->tls_auth_file);
> +
> +            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 */
> diff --git a/tests/unit_tests/openvpn/Makefile.am 
> b/tests/unit_tests/openvpn/Makefile.am
> index 23d758b7..4395afc1 100644
> --- a/tests/unit_tests/openvpn/Makefile.am
> +++ b/tests/unit_tests/openvpn/Makefile.am
> @@ -26,7 +26,6 @@ argv_testdriver_SOURCES = test_argv.c mock_msg.c \
>  buffer_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(openvpn_srcdir) 
> -I$(compat_srcdir)
>  buffer_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) 
> -Wl,--wrap=parse_line
>  buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
> -     $(openvpn_srcdir)/buffer.c \
>       $(openvpn_srcdir)/platform.c
>  
>  packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
> diff --git a/tests/unit_tests/openvpn/test_buffer.c 
> b/tests/unit_tests/openvpn/test_buffer.c
> index d083b78f..fd64ee59 100644
> --- a/tests/unit_tests/openvpn/test_buffer.c
> +++ b/tests/unit_tests/openvpn/test_buffer.c
> @@ -33,6 +33,7 @@
>  #include <cmocka.h>
>  
>  #include "buffer.h"
> +#include "buffer.c"
>  
>  static void
>  test_buffer_strprefix(void **state)
> @@ -197,6 +198,48 @@ test_buffer_list_aggregate_separator_emptybuffers(void 
> **state)
>      assert_int_equal(BLEN(buf), 0);
>  }
>  
> +static void
> +test_buffer_free_gc_one(void **state)
> +{
> +    struct gc_arena gc = gc_new();
> +    struct buffer buf = alloc_buf_gc(1024, &gc);
> +
> +    assert_ptr_equal(gc.list + 1, buf.data);
> +    free_buf_gc(&buf, &gc);
> +    assert_null(gc.list);
> +
> +    gc_free(&gc);
> +}
> +
> +static void
> +test_buffer_free_gc_two(void **state)
> +{
> +    struct gc_arena gc = gc_new();
> +    struct buffer buf1 = alloc_buf_gc(1024, &gc);
> +    struct buffer buf2 = alloc_buf_gc(1024, &gc);
> +    struct buffer buf3 = alloc_buf_gc(1024, &gc);
> +
> +    struct gc_entry *e;
> +
> +    e = gc.list;
> +
> +    assert_ptr_equal(e + 1, buf3.data);
> +    assert_ptr_equal(e->next + 1, buf2.data);
> +    assert_ptr_equal(e->next->next + 1, buf1.data);
> +
> +    free_buf_gc(&buf2, &gc);
> +
> +    assert_non_null(gc.list);
> +
> +    while (e)
> +    {
> +        assert_ptr_not_equal(e + 1, buf2.data);
> +        e = e->next;
> +    }
> +
> +    gc_free(&gc);
> +}
> +
>  int
>  main(void)
>  {
> @@ -226,6 +269,8 @@ main(void)
>          
> cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_emptybuffers,
>                                          test_buffer_list_setup,
>                                          test_buffer_list_teardown),
> +        cmocka_unit_test_setup_teardown(test_buffer_free_gc_one, NULL, NULL),
> +        cmocka_unit_test_setup_teardown(test_buffer_free_gc_two, NULL, NULL),

You could just call cmocka_unit_test() instead, without the NULL arguments.

>      };
>  
>      return cmocka_run_group_tests_name("buffer", tests, NULL, NULL);
> 

Both are very minor, so either with or without these fixed:

Acked-by: Steffan Karger <stef...@karger.me>

-Steffan

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to