Hi,

On 19/03/2021 15:20, Arne Schwabe wrote:
> From: "Jason A. Donenfeld" <ja...@zx2c4.com>
> 
> OpenVPN traditionally works around CAs. However many TLS-based protocols also
> allow an alternative simpler mode in which rather than verify certificates
> against CAs, the certificate itself is hashed and compared against a
> pre-known set of acceptable hashes. This is usually referred to as
> "fingerprint verification". It's popular across SMTP servers, IRC servers,
> XMPP servers, and even in the context of HTTP with pinning.
> 
>    * Allow not specifying the --ca parameter, to specify that
>      certificates should not be checked against a CA.
> 
> I've included some instructions on how to use all of this.
> 
> Server side:
> ============
> 
> Make self-signed cert:
> $ openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout 
> serverkey.pem -out servercert.pem -nodes -sha256 -days 3650 -subj '/CN=server'
> 
> Record our fingerprint in an environment variable for the client to use later:
> $ server_fingerprint="$(openssl x509 -in servercert.pem -noout -sha256 
> -fingerprint | sed 's/.*=//;s/\(.*\)/\1/')"
> 
> Client side:
> ============
> Make self-signed cert:
> $ openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout 
> clientkey.pem -out clientcert.pem -nodes -sha256 -days 3650 -subj '/CN=client'
> 
> Record our fingerprint in an environment variable for the server to use later:
> $ client_fingerprint="$(openssl x509 -in clientcert.pem -noout -sha256 
> -fingerprint | sed 's/.*=//;s/\(.*\)/\1/')"
> 
> Start server/client
> ===================
> 
> Start openvpn with peer fingerprint verification:
> 
> $ sudo openvpn --server 10.66.0.0 255.255.255.0 --dev tun --dh none --cert 
> servercert.pem --key serverkey.pem --peer-fingerprint "$client_fingerprint"
> 
> $ sudo openvpn --client --remote 127.0.0.1 --dev tun --cert clientcert.pem 
> --key clientkey.pem --peer-fingerprint "$server_fingerprint" --nobind
> 
> Signed-off-by: Jason A. Donenfeld <ja...@zx2c4.com>
> 
> Patch V2: Changes in V2 (by Arne Schwabe):
>           - Only check peer certificates, not all cert levels, if you need
>             multiple levels of certificate you should use a real CA
>           - Use peer-fingerprint instead tls-verify on server side in example.
>           - rename variable ca_file_none to verify_hash_no_ca
>           - do no require --ca none but allow --ca simply
>             to be absent when --peer-fingprint is present
>           - adjust warnings/errors messages to also point to
>             peer-fingerprint as valid verification method.
>           - Fix mbed TLS version of not requiring CA
>             not working
> 
> Signed-off-by: Arne Schwabe <a...@rfc2549.org>
> ---
>  src/openvpn/init.c               |  2 ++
>  src/openvpn/options.c            | 30 +++++++++++++++++++++++-------
>  src/openvpn/options.h            |  1 +
>  src/openvpn/ssl.c                |  2 +-
>  src/openvpn/ssl_common.h         |  1 +
>  src/openvpn/ssl_verify_mbedtls.c | 17 +++++++++++++++++
>  src/openvpn/ssl_verify_openssl.c |  2 +-
>  7 files changed, 46 insertions(+), 9 deletions(-)
> 
> diff --git a/src/openvpn/init.c b/src/openvpn/init.c
> index 731b0cf2..835621cb 100644
> --- a/src/openvpn/init.c
> +++ b/src/openvpn/init.c
> @@ -2928,6 +2928,8 @@ do_init_crypto_tls(struct context *c, const unsigned 
> int flags)
>      to.verify_hash = options->verify_hash;
>      to.verify_hash_algo = options->verify_hash_algo;
>      to.verify_hash_depth = options->verify_hash_depth;
> +    to.verify_hash_no_ca = options->verify_hash_no_ca;
> +
>  #ifdef ENABLE_X509ALTUSERNAME
>      memcpy(to.x509_username_field, options->x509_username_field, 
> sizeof(to.x509_username_field));
>  #else
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index 6b4a2c11..27ed813d 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -2712,18 +2712,23 @@ options_postprocess_verify_ce(const struct options 
> *options,
>          else
>          {
>  #ifdef ENABLE_CRYPTO_MBEDTLS
> -            if (!(options->ca_file))
> +            if (!(options->ca_file || options->verify_hash_no_ca))
>              {
> -                msg(M_USAGE, "You must define CA file (--ca)");
> +                msg(M_USAGE, "You must define CA file (--ca) and/or "
> +                    "peer fingeprint verification "
> +                    "(--peer-fingerprint)");
>              }
>              if (options->ca_path)
>              {
>                  msg(M_USAGE, "Parameter --capath cannot be used with the 
> mbed TLS version version of OpenVPN.");
>              }
>  #else  /* ifdef ENABLE_CRYPTO_MBEDTLS */
> -            if ((!(options->ca_file)) && (!(options->ca_path)))
> +            if ((!(options->ca_file)) && (!(options->ca_path))
> +                && (!(options->verify_hash_no_ca)))
>              {
> -                msg(M_USAGE, "You must define CA file (--ca) or CA path 
> (--capath)");
> +                msg(M_USAGE, "You must define CA file (--ca) or CA path "
> +                    "(--capath) and/or peer fingeprint verification "
> +                    "(--peer-fingerprint)");
>              }
>  #endif
>              if (pull)
> @@ -2742,7 +2747,8 @@ options_postprocess_verify_ce(const struct options 
> *options,
>  #if P2MP
>                      if (!options->auth_user_pass_file)
>  #endif
> -                    msg(M_USAGE, "No client-side authentication method is 
> specified.  You must use either --cert/--key, --pkcs12, or --auth-user-pass");
> +                    msg(M_USAGE, "No client-side authentication method is 
> specified.  You must use either --cert/--key,"
> +                        " --pkcs12, or --auth-user-pass");

This seems an unrelated change?


>                  }
>                  else if (sum == 2)
>                  {
> @@ -3206,6 +3212,13 @@ options_postprocess_mutate(struct options *o)
>          options_postprocess_http_proxy_override(o);
>      }
>  #endif
> +    if (!o->ca_file && !o->ca_path && o->verify_hash
> +        && o->verify_hash_depth == 0)
> +    {
> +        msg(M_INFO, "Using certificate fingerprint to verify peer (no CA "
> +            "option set). ");
> +        o->verify_hash_no_ca = true;
> +    }
>  
>  #if P2MP
>      /*
> @@ -3441,8 +3454,11 @@ options_postprocess_filechecks(struct options *options)
>      errs |= check_file_access_inline(options->dh_file_inline, CHKACC_FILE,
>                                       options->dh_file, R_OK, "--dh");
>  
> -    errs |= check_file_access_inline(options->ca_file_inline, CHKACC_FILE,
> -                                     options->ca_file, R_OK, "--ca");
> +    if (!options->verify_hash_no_ca)
> +    {
> +        errs |= check_file_access_inline(options->ca_file_inline, 
> CHKACC_FILE,
> +                                         options->ca_file, R_OK, "--ca");
> +    }
>  
>      errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE,
>                                       options->ca_path, R_OK, "--capath");
> diff --git a/src/openvpn/options.h b/src/openvpn/options.h
> index 30ec53d6..c68e89d2 100644
> --- a/src/openvpn/options.h
> +++ b/src/openvpn/options.h
> @@ -561,6 +561,7 @@ struct options
>      struct verify_hash_list *verify_hash;
>      hash_algo_type verify_hash_algo;
>      int verify_hash_depth;
> +    bool verify_hash_no_ca;
>      unsigned int ssl_flags; /* set to SSLF_x flags from ssl.h */
>  
>  #ifdef ENABLE_PKCS11
> diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
> index 893e5753..b6bbcc09 100644
> --- a/src/openvpn/ssl.c
> +++ b/src/openvpn/ssl.c
> @@ -682,7 +682,7 @@ init_ssl(const struct options *options, struct 
> tls_root_ctx *new_ctx)
>      }
>  #endif
>  
> -    if (options->ca_file || options->ca_path)
> +    if ((!options->verify_hash_no_ca && options->ca_file) || 
> options->ca_path)

I don't think it is possible to have the following holding true at the
same time:
1) ca_file != NULL
2) options->verify_hash_no_ca == true

If 2 is true, then 1 must be false.
For this reason I believe this if condition does not need to be modified.

Or am I missing some corner case?

>      {
>          tls_ctx_load_ca(new_ctx, options->ca_file, options->ca_file_inline,
>                          options->ca_path, options->tls_server);
> diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
> index 2b1b87fb..4e1ff6c8 100644
> --- a/src/openvpn/ssl_common.h
> +++ b/src/openvpn/ssl_common.h
> @@ -285,6 +285,7 @@ struct tls_options
>      const char *remote_cert_eku;
>      struct verify_hash_list *verify_hash;
>      int verify_hash_depth;
> +    bool verify_hash_no_ca;
>      hash_algo_type verify_hash_algo;
>  #ifdef ENABLE_X509ALTUSERNAME
>      char *x509_username_field[MAX_PARMS];
> diff --git a/src/openvpn/ssl_verify_mbedtls.c 
> b/src/openvpn/ssl_verify_mbedtls.c
> index 93891038..1ed1e0e2 100644
> --- a/src/openvpn/ssl_verify_mbedtls.c
> +++ b/src/openvpn/ssl_verify_mbedtls.c
> @@ -62,6 +62,23 @@ verify_callback(void *session_obj, mbedtls_x509_crt *cert, 
> int cert_depth,
>      struct buffer cert_fingerprint = x509_get_sha256_fingerprint(cert, &gc);
>      cert_hash_remember(session, cert_depth, &cert_fingerprint);
>  
> +
> +    if (session->opt->verify_hash_no_ca)
> +    {
> +        /*
> +         * If we decide to verify the peer certificate based on the 
> fingerprint
> +         * we ignore wrong dates and the certificate not being trusted.
> +         * Any other problem with the certificate (wrong key, bad cert,...)
> +         * will still trigger an error.
> +         * Clearing these flags relies on verify_cert will later rejecting a
> +         * certificate that has no matching fingerprint.
> +         */
> +        uint32_t flags_ignore = MBEDTLS_X509_BADCERT_NOT_TRUSTED
> +                                | MBEDTLS_X509_BADCERT_EXPIRED
> +                                | MBEDTLS_X509_BADCERT_FUTURE;
> +        *flags = *flags & ~flags_ignore;
> +    }
> +
>      /* did peer present cert which was signed by our root cert? */
>      if (*flags != 0)
>      {
> diff --git a/src/openvpn/ssl_verify_openssl.c 
> b/src/openvpn/ssl_verify_openssl.c
> index d063aeda..8b1f1969 100644
> --- a/src/openvpn/ssl_verify_openssl.c
> +++ b/src/openvpn/ssl_verify_openssl.c
> @@ -67,7 +67,7 @@ verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
>      cert_hash_remember(session, X509_STORE_CTX_get_error_depth(ctx), 
> &cert_hash);
>  
>      /* did peer present cert which was signed by our root cert? */
> -    if (!preverify_ok)
> +    if (!preverify_ok && !session->opt->verify_hash_no_ca)
>      {
>          /* get the X509 name */
>          char *subject = x509_get_subject(current_cert, &gc);
> 

The rest looks good.

It's very nice to be able to start openvpn without CA, especially in
very small setups where a couple of certs can just be enough :)

Cheers,

-- 
Antonio Quartulli


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

Reply via email to