Hi,

On 16/04/2020 17:26, Arne Schwabe wrote:
> By default OpenSSL 1.1+ only allows signatures and ecdh/ecdhx from the
> default list of X25519:secp256r1:X448:secp521r1:secp384r1. In
> TLS1.3 key exchange is independent from the signature/key of the
> certificates, so allowing all groups per default is not a sensible
> choice anymore and instead a shorter list is reasonable.
> 
> However, when using certificates with exotic curves that are not on
> the group list, the signatures of these certificates will no longer
> be accepted.
> 
> The tls-groups  option allows to modify the group list to account
> for these corner cases.
> 
> Patch V2: Uses local gc_arena instead of malloc/free, reword commit
>           message. Fix other typos/clarify messages
> 
> Signed-off-by: Arne Schwabe <a...@rfc2549.org>
> ---
>  README.ec                 |  7 ++---
>  doc/openvpn.8             | 33 ++++++++++++++++++++++-
>  src/openvpn/options.c     | 10 ++++++-
>  src/openvpn/options.h     |  1 +
>  src/openvpn/ssl.c         |  6 +++++
>  src/openvpn/ssl_backend.h | 10 +++++++
>  src/openvpn/ssl_mbedtls.c | 46 ++++++++++++++++++++++++++++++++
>  src/openvpn/ssl_mbedtls.h |  1 +
>  src/openvpn/ssl_openssl.c | 56 ++++++++++++++++++++++++++++++++++++++-
>  9 files changed, 164 insertions(+), 6 deletions(-)
> 
> diff --git a/README.ec b/README.ec
> index 32938017..2f830972 100644
> --- a/README.ec
> +++ b/README.ec
> @@ -12,14 +12,15 @@ OpenVPN 2.4.0 and newer automatically initialize ECDH 
> parameters. When ECDSA is
>  used for authentication, the curve used for the server certificate will be 
> used
>  for ECDH too. When autodetection fails (e.g. when using RSA certificates)
>  OpenVPN lets the crypto library decide if possible, or falls back to the
> -secp384r1 curve.
> +secp384r1 curve. The list of groups/curves that the crypto library will 
> choose
> +from can be set with the --tls-groups <grouplist> configuration.
                                  I'd use "config option" ^^^^^
>  
>  An administrator can force an OpenVPN/OpenSSL server to use a specific curve
>  using the --ecdh-curve <curvename> option with one of the curves listed as
> -available by the --show-curves option. Clients will use the same curve as
> +available by the --show-groups option. Clients will use the same curve as
>  selected by the server.
>  
> -Note that not all curves listed by --show-curves are available for use with 
> TLS;
> +Note that not all curves listed by --show-groups are available for use with 
> TLS;
>  in that case connecting will fail with a 'no shared cipher' TLS error.
>  
>  Authentication (ECDSA)
> diff --git a/doc/openvpn.8 b/doc/openvpn.8
> index f0796e52..76633900 100644
> --- a/doc/openvpn.8
> +++ b/doc/openvpn.8
> @@ -5098,6 +5098,8 @@ Use
>  to see a list of TLS ciphers supported by your crypto library.
>  
>  Warning!
> +.B \-\-tls\-groups
> +,
>  .B \-\-tls\-cipher
>  and
>  .B \-\-tls\-ciphersuites
> @@ -5113,6 +5115,33 @@ OpenSSL.
>  The default for \-\-tls\-ciphersuites is to use the crypto library's default.
>  .\"*********************************************************
>  .TP
> +.B \-\-tls\-groups l
> +A list
> +.B l
> +of allowable groups/curves in order of preference.
      ^^^ shouldn't this be "allowed" ?
> +
> +Set the allowed elictipic curves/groups for the TLS session.
> +These groups are  allowed to be used in signatures and key exchange.
> +
> +mbed TLS currently allows all known curves per default.
> +
> +OpenSSL 1.1+ restricts the list per default to
> +"X25519:secp256r1:X448:secp521r1:secp384r1".
> +
> +If you use certificates that use non-standard curves, you
> +might need to add them here. If you do not force the ecdh curve
> +by using
> +.B \-\-ecdh\-curve
> +, the groups for ecdh will also be picked from this list.
> +
> +OpenVPN maps the curve name secp256r1 to prime256v1 to allow
> +specifying the tls-groups option for mbed TLS and OpenSSL.
                 ^ add "same" here to make the sentence more clear

> +
> +Warning: this option not only affects eliptic curve certificates
> +but also the key exchange in TLS 1.3 and using this option improperly
> +will disable TLS 1.3.
> +.\"*********************************************************
> +.TP
>  .B \-\-tls\-cert\-profile profile
>  Set the allowed cryptographic algorithms for certificates according to
>  .B profile\fN.
> @@ -5878,8 +5907,10 @@ engines supported by the OpenSSL library.
>  .TP
>  .B \-\-show\-curves
>  (Standalone)
> -Show all available elliptic curves to use with the
> +Show all available elliptic groups/curves to use with the
>  .B \-\-ecdh\-curve
> +and
> +.B \-\-tls\-groups
>  option.
>  .\"*********************************************************
>  .SS Generating key material:
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index 49df8df1..57bc0abb 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -7895,7 +7895,7 @@ add_option(struct options *options,
>          VERIFY_PERMISSION(OPT_P_GENERAL);
>          options->show_tls_ciphers = true;
>      }
> -    else if (streq(p[0], "show-curves") && !p[1])
> +    else if ((streq(p[0], "show-curves") || streq(p[0], "show-groups")) && 
> !p[1])
shouldn't we deprecate --show-curves at this point?
what's the point of keeping both options?

>      {
>          VERIFY_PERMISSION(OPT_P_GENERAL);
>          options->show_curves = true;
> @@ -7903,6 +7903,9 @@ add_option(struct options *options,
>      else if (streq(p[0], "ecdh-curve") && p[1] && !p[2])
>      {
>          VERIFY_PERMISSION(OPT_P_GENERAL);
> +        msg(M_WARN, "Consider setting groups/curves preference with "
> +            "tls-groups instead of forcing a specific curve with "
> +            "ecdh-curve.");
>          options->ecdh_curve = p[1];
>      }
>      else if (streq(p[0], "tls-server") && !p[1])
> @@ -8091,6 +8094,11 @@ add_option(struct options *options,
>          VERIFY_PERMISSION(OPT_P_GENERAL);
>          options->cipher_list_tls13 = p[1];
>      }
> +    else if (streq(p[0], "tls-groups") && p[1] && !p[2])
> +    {
> +        VERIFY_PERMISSION(OPT_P_GENERAL);
> +        options->tls_groups = p[1];
> +    }
>      else if (streq(p[0], "crl-verify") && p[1] && ((p[2] && streq(p[2], 
> "dir"))
>                                                     || (p[2] && streq(p[1], 
> INLINE_FILE_TAG) ) || !p[2]) && !p[3])
>      {
> diff --git a/src/openvpn/options.h b/src/openvpn/options.h
> index 2f1f6faf..3732a3a5 100644
> --- a/src/openvpn/options.h
> +++ b/src/openvpn/options.h
> @@ -537,6 +537,7 @@ struct options
>      const char *pkcs12_file;
>      const char *cipher_list;
>      const char *cipher_list_tls13;
> +    const char *tls_groups;
>      const char *tls_cert_profile;
>      const char *ecdh_curve;
>      const char *tls_verify;
> diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
> index 56d0576a..ef153d37 100644
> --- a/src/openvpn/ssl.c
> +++ b/src/openvpn/ssl.c
> @@ -630,6 +630,12 @@ init_ssl(const struct options *options, struct 
> tls_root_ctx *new_ctx)
>      tls_ctx_restrict_ciphers(new_ctx, options->cipher_list);
>      tls_ctx_restrict_ciphers_tls13(new_ctx, options->cipher_list_tls13);
>  
> +    /* Set the allow groups/curves for TLS if we want to override them */
> +    if (options->tls_groups)
> +    {
> +        tls_ctx_set_tls_groups(new_ctx, options->tls_groups);
> +    }
> +
>      if (!tls_ctx_set_options(new_ctx, options->ssl_flags))
>      {
>          goto err;
> diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
> index 1c244ece..d95e8320 100644
> --- a/src/openvpn/ssl_backend.h
> +++ b/src/openvpn/ssl_backend.h
> @@ -198,6 +198,16 @@ void tls_ctx_restrict_ciphers_tls13(struct tls_root_ctx 
> *ctx, const char *cipher
>   */
>  void tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile);
>  
> +/**
> + * Set the allowed (eliptic curve) group allowed for signatures and
> + * key exchange.
> + *
> + * @param ctx       TLS context to restrict, must be valid.
> + * @param groups    List of groups that will be allowed, in priority,
> + *                  separated by :
> + */
> +void tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups);
> +
>  /**
>   * Check our certificate notBefore and notAfter fields, and warn if the cert 
> is
>   * either not yet valid or has expired.  Note that this is a non-fatal error,
> diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c
> index 51669278..f133ae39 100644
> --- a/src/openvpn/ssl_mbedtls.c
> +++ b/src/openvpn/ssl_mbedtls.c
> @@ -176,6 +176,11 @@ tls_ctx_free(struct tls_root_ctx *ctx)
>              free(ctx->allowed_ciphers);
>          }
>  
> +        if (ctx->groups)
> +        {
> +            free(ctx->groups);
> +        }
> +
>          CLEAR(*ctx);
>  
>          ctx->initialised = false;
> @@ -342,6 +347,42 @@ tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const 
> char *profile)
>      }
>  }
>  
> +void
> +tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups)
> +{
> +    ASSERT(ctx);
> +    struct gc_arena gc = gc_new();
> +
> +    /* Get number of groups and allocate an array in ctx */
> +    int groups_count = get_num_elements(groups, ':');
> +    ALLOC_ARRAY_CLEAR(ctx->groups, mbedtls_ecp_group_id, groups_count + 1)
> +
> +    /* Parse allowed ciphers, getting IDs */
> +    int i = 0;
> +    char *tmp_groups = string_alloc(groups, &gc);
> +
> +    const char *token = strsep(&tmp_groups, ":");
> +    while (token)

Couldn't we avoid having the assignment here and at the end of the loop
by doing:

const char *token;
while ((token = strsep(&tmp_groups, ":"))

?

> +    {
> +        const mbedtls_ecp_curve_info *ci =
> +            mbedtls_ecp_curve_info_from_name(token);
> +        if (!ci)
> +        {
> +            msg(M_WARN, "Warning unknown curve/group specified: %s", token);
> +        }
> +        else
> +        {
> +            ctx->groups[i] = ci->grp_id;
> +            i++;
> +        }
> +        token = strsep(&tmp_groups, ":");
> +    }
> +    ctx->groups[i] = MBEDTLS_ECP_DP_NONE;
> +
> +    gc_free(&gc);
> +}
> +
> +
>  void
>  tls_ctx_check_cert_time(const struct tls_root_ctx *ctx)
>  {
> @@ -1043,6 +1084,11 @@ key_state_ssl_init(struct key_state_ssl *ks_ssl,
>          mbedtls_ssl_conf_ciphersuites(ks_ssl->ssl_config, 
> ssl_ctx->allowed_ciphers);
>      }
>  
> +    if (ssl_ctx->groups)
> +    {
> +        mbedtls_ssl_conf_curves(&ks_ssl->ssl_config, ssl_ctx->groups);

the '&' should not be there I guess.

/usr/include/mbedtls/ssl.h:2925:51: note: expected ‘mbedtls_ssl_config
*’ but argument is of type ‘mbedtls_ssl_config **’


> +    }
> +
>      /* Disable record splitting (for now).  OpenVPN assumes records are sent
>       * unfragmented, and changing that will require thorough review and
>       * testing.  Since OpenVPN is not susceptible to BEAST, we can just
> diff --git a/src/openvpn/ssl_mbedtls.h b/src/openvpn/ssl_mbedtls.h
> index 92381f1a..1dc28313 100644
> --- a/src/openvpn/ssl_mbedtls.h
> +++ b/src/openvpn/ssl_mbedtls.h
> @@ -105,6 +105,7 @@ struct tls_root_ctx {
>  #endif
>      struct external_context external_key; /**< External key context */
>      int *allowed_ciphers;       /**< List of allowed ciphers for this 
> connection */
> +    mbedtls_ecp_group_id *groups;                /**< List of allowed groups 
> for this connection */

Maybe you can reduce the space before the comment? :-D

>      mbedtls_x509_crt_profile cert_profile; /**< Allowed certificate types */
>  };
>  
> diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
> index d7bd6aa2..06971d63 100644
> --- a/src/openvpn/ssl_openssl.c
> +++ b/src/openvpn/ssl_openssl.c
> @@ -557,6 +557,58 @@ tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const 
> char *profile)
>  #endif /* ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL */
>  }
>  
> +void
> +tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups)
> +{
> +    ASSERT(ctx);
> +    struct gc_arena gc = gc_new();
> +    /* This method could be as easy as
> +     *  SSL_CTX_set1_groups_list(ctx->ctx, groups)
> +     * but OpenSSL does not like the name secp256r1 for prime256v1
> +     * This is one of the important curves.
> +     * To support the same name for OpenSSL and mbedTLS, we do
> +     * this dance.
> +     */
> +
> +    int groups_count = get_num_elements(groups, ':');
> +
> +    int *glist;
> +    /* Allocate an array for them */
> +    ALLOC_ARRAY_CLEAR_GC(glist, int, groups_count, &gc);
> +
> +    /* Parse allowed ciphers, getting IDs */
> +    int glistlen = 0;
> +    char *tmp_groups = string_alloc(groups, &gc);
> +
> +    const char *token = strsep(&tmp_groups, ":");
> +    while (token)

same suggestion as above

> +    {
> +        if (streq(token, "secp256r1"))
> +        {
> +            token = "prime256v1";
> +        }
> +        int nid = OBJ_sn2nid(token);
> +
> +        if (nid == 0)
> +        {
> +            msg(M_WARN, "Warning unknown curve/group specified: %s", token);
> +        }
> +        else
> +        {
> +            glist[glistlen] = nid;
> +            glistlen++;
> +        }
> +        token = strsep(&tmp_groups, ":");
> +    }
> +
> +    if (!SSL_CTX_set1_groups(ctx->ctx, glist, glistlen))
> +    {
> +        crypto_msg(M_FATAL, "Failed to set allowed TLS group list: %s",
> +                   groups);
> +    }
> +    gc_free(&gc);
> +}
> +
>  void
>  tls_ctx_check_cert_time(const struct tls_root_ctx *ctx)
>  {
> @@ -2179,6 +2231,8 @@ show_available_tls_ciphers_list(const char *cipher_list,
>  void
>  show_available_curves(void)
>  {
> +    printf("Consider using openssl ecparam -list_curves as\n"
> +           "alternative to running this command.");

How about putting single quotes around the command? At first I thought
that "as" was also part of the openssl options :-D


>  #ifndef OPENSSL_NO_EC
>      EC_builtin_curve *curves = NULL;
>      size_t crv_len = 0;
> @@ -2188,7 +2242,7 @@ show_available_curves(void)
>      ALLOC_ARRAY(curves, EC_builtin_curve, crv_len);
>      if (EC_get_builtin_curves(curves, crv_len))
>      {
> -        printf("Available Elliptic curves:\n");
> +        printf("\nAvailable Elliptic curves/groups:\n");
>          for (n = 0; n < crv_len; n++)
>          {
>              const char *sname;
> 


The rest looks good.

I applied this patch on top of master with git am -3 because there are
some trivial conflicts to fix.


Cheers,

-- 
Antonio Quartulli


_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to