Hi,

On 04-05-17 22:42, David Sommerseth wrote:
> This enhances --verify-hash with an optional algorithm flag.  If not
> provided, it defaults to SHA1 to preserve backwards compatbilitity with
> existing configurations.  The only valid flags are SHA1 and SHA256.
> 
> In addition enhance the layout of the --verify-hash section in the man
> page.
> 
> Signed-off-by: David Sommerseth <dav...@openvpn.net>
> ---
> 
>  v2 - Ensure ca_hash is initialized to a known value
>     - Add a default target to the switch()
>     - Move back to memcmp(), as memcmp_constant_time() isn't really
>       needed here; the result of MD hashes have defined lengths
>       regardless of the certificates an attacker may decide to use.
> 
> Simple test script
> ==================
> 
> * Server:
>   # openvpn --dev tun --reneg-sec 60 --verb 4 \
>             --server 10.8.0.0 255.255.255.0 --topology subnet \
>             --ca sample/sample-keys/ca.crt \
>             --key sample/sample-keys/server.key \
>             --cert sample/sample-keys/server.crt \
>             --dh sample/sample-keys/dh2048.pem \
>             --cipher AES-256-CBC --auth SHA256 \
>             --verify-hash 
> 57:4D:B3:49:7E:FB:9E:FD:DC:BC:A4:ED:BC:B3:C2:8D:C3:D7:2A:E3:0F:6F:76:57:F0:BB:F4:90:56:1C:DC:F8
>  SHA256
> 
> * Client:
>   # openvpn --dev tun --verb 4 \
>             --remote 192.168.122.1 --explicit-exit-notify \
>             --client --ca sample/sample-keys/ca.crt \
>             --key sample/sample-keys/client.key \
>             --cert sample/sample-keys/client.crt \
>             --cipher AES-256-CBC --auth SHA256 \
>             --verify-hash 
> 06:30:DA:EA:77:41:5B:D6:8C:C5:CC:CA:F5:D7:91:87:FA:6D:89:44
> 
>   Alternative --verify-hash variant which must succeed:
>      --verify-hash 
> 06:30:DA:EA:77:41:5B:D6:8C:C5:CC:CA:F5:D7:91:87:FA:6D:89:44 SHA1
> 
>   These --verify-hash variants must fail:
>      --verify-hash 
> 06:30:DA:EA:77:41:5B:D6:8C:C5:CC:CA:F5:D7:91:87:FA:6D:89:44 SHA256
>      --verify-hash 
> 57:4D:B3:49:7E:FB:9E:FD:DC:BC:A4:ED:BC:B3:C2:8D:C3:D7:2A:E3:0F:6F:76:57:F0:BB:F4:90:56:1C:DC:F9
>      --verify-hash 
> 57:4D:B3:49:7E:FB:9E:FD:DC:BC:A4:ED:BC:B3:C2:8D:C3:D7:2A:E3:0F:6F:76:57:F0:BB:F4:90:56:1C:DC:00
>  SHA256
>      --verify-hash 
> 06:30:DA:EA:77:41:5B:D6:8C:C5:CC:CA:F5:D7:91:87:FA:6D:89:00 SHA1
>      --verify-hash 06:30:DA:EA:77:41:5B:D6:8C:C5:CC:CA:F5:D7:91:87:FA:6D:89:00
>      --verify-hash 06:30:DA:EA:77:41:5B:D6:8C:C5:CC:CA:F5:D7:91:87:FA:6D:89:XX
> 
> To retrieve the SHA fingerprints in certificates:
> 
>     $ openssl x509 -noout -fingerprint -in sample/sample-keys/ca.crt
>     $ openssl x509 -noout -fingerprint -sha256 -in sample/sample-keys/ca.crt
> ---
>  Changes.rst                  |  3 +++
>  doc/openvpn.8                | 18 +++++++++++++++---
>  src/openvpn/crypto_backend.h |  6 ++++++
>  src/openvpn/init.c           |  1 +
>  src/openvpn/options.c        | 22 +++++++++++++++++++---
>  src/openvpn/options.h        |  5 +++++
>  src/openvpn/ssl_common.h     |  1 +
>  src/openvpn/ssl_verify.c     | 27 +++++++++++++++++++++++++--
>  8 files changed, 75 insertions(+), 8 deletions(-)
> 
> diff --git a/Changes.rst b/Changes.rst
> index 2a94990e..da93060c 100644
> --- a/Changes.rst
> +++ b/Changes.rst
> @@ -318,3 +318,6 @@ Version 2.4.1
>     ``--remote-cert-tls`` uses the far more common keyUsage and 
> extendedKeyUsage
>     extension instead.  Make sure your certificates carry these to be able to
>     use ``--remote-cert-tls``.
> + - ``--verify-hash`` can now take an optional flag which changes the hashing
> +   algorithm. It can be either SHA1 or SHA256.  The default if not provided 
> is
> +   SHA1 to preserve backwards compatibility with existing configurations.
> diff --git a/doc/openvpn.8 b/doc/openvpn.8
> index c3248fde..fcf825c6 100644
> --- a/doc/openvpn.8
> +++ b/doc/openvpn.8
> @@ -4694,15 +4694,27 @@ and
>  Not available with PolarSSL.
>  .\"*********************************************************
>  .TP
> -.B \-\-verify\-hash hash
> -Specify SHA1 fingerprint for level-1 cert.  The level-1 cert is the
> +.B \-\-verify\-hash hash [algo]
> +Specify SHA1 or SHA256 fingerprint for level-1 cert.  The level-1 cert is the
>  CA (or intermediate cert) that signs the leaf certificate, and is
>  one removed from the leaf certificate in the direction of the root.
>  When accepting a connection from a peer, the level-1 cert
>  fingerprint must match
>  .B hash
>  or certificate verification will fail.  Hash is specified
> -as XX:XX:...  For example: 
> AD:B0:95:D8:09:C8:36:45:12:A9:89:C8:90:09:CB:13:72:A6:AD:16
> +as XX:XX:... For example:
> +
> +.nf
> +.ft 3
> +.in +4
> +AD:B0:95:D8:09:C8:36:45:12:A9:89:C8:90:09:CB:13:72:A6:AD:16
> +.in -4
> +.ft
> +.fi
> +
> +The
> +.B algo
> +flag can be either SHA1 or SHA256.  If not provided, it defaults to SHA1.
>  .\"*********************************************************
>  .TP
>  .B \-\-pkcs11\-cert\-private [0|1]...
> diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h
> index 2c79baa1..9b113d7b 100644
> --- a/src/openvpn/crypto_backend.h
> +++ b/src/openvpn/crypto_backend.h
> @@ -47,6 +47,12 @@
>  /* Maximum HMAC digest size (bytes) */
>  #define OPENVPN_MAX_HMAC_SIZE   64
>  
> +/** Types referencing specific message digest hashing algorithms */
> +typedef enum {
> +    MD_SHA1,
> +    MD_SHA256
> +} hash_algo_type ;
> +
>  /** Struct used in cipher name translation table */
>  typedef struct {
>      const char *openvpn_name;   /**< Cipher name used by OpenVPN */
> diff --git a/src/openvpn/init.c b/src/openvpn/init.c
> index ee14f672..e9455039 100644
> --- a/src/openvpn/init.c
> +++ b/src/openvpn/init.c
> @@ -2619,6 +2619,7 @@ do_init_crypto_tls(struct context *c, const unsigned 
> int flags)
>      memmove(to.remote_cert_ku, options->remote_cert_ku, 
> sizeof(to.remote_cert_ku));
>      to.remote_cert_eku = options->remote_cert_eku;
>      to.verify_hash = options->verify_hash;
> +    to.verify_hash_algo = options->verify_hash_algo;
>  #ifdef ENABLE_X509ALTUSERNAME
>      to.x509_username_field = (char *) options->x509_username_field;
>  #else
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index dcb6ecfa..f38abad7 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -591,7 +591,8 @@ static const char usage_message[] =
>      "--x509-username-field : Field in x509 certificate containing the 
> username.\n"
>      "                        Default is CN in the Subject field.\n"
>  #endif
> -    "--verify-hash   : Specify SHA1 fingerprint for level-1 cert.\n"
> +    "--verify-hash hash [algo] : Specify fingerprint for level-1 
> certificate.\n"
> +    "                            Valid algo flags are SHA1 and SHA256. \n"
>  #ifdef _WIN32
>      "--cryptoapicert select-string : Load the certificate and private key 
> from the\n"
>      "                  Windows Certificate System Store.\n"
> @@ -7687,10 +7688,25 @@ add_option(struct options *options,
>              options->extra_certs_file_inline = p[2];
>          }
>      }
> -    else if (streq(p[0], "verify-hash") && p[1] && !p[2])
> +    else if (streq(p[0], "verify-hash") && p[1] && !p[3])
>      {
>          VERIFY_PERMISSION(OPT_P_GENERAL);
> -        options->verify_hash = parse_hash_fingerprint(p[1], 
> SHA_DIGEST_LENGTH, msglevel, &options->gc);
> +
> +        if (!p[2] || (p[2] && streq(p[2], "SHA1")))
> +        {
> +            options->verify_hash = parse_hash_fingerprint(p[1], 
> SHA_DIGEST_LENGTH, msglevel, &options->gc);
> +            options->verify_hash_algo = MD_SHA1;
> +        }
> +        else if (p[2] && streq(p[2], "SHA256"))
> +        {
> +            options->verify_hash = parse_hash_fingerprint(p[1], 
> SHA256_DIGEST_LENGTH, msglevel, &options->gc);
> +            options->verify_hash_algo = MD_SHA256;
> +        }
> +        else
> +        {
> +            msg(msglevel, "invalid or unsupported hashing algorithm: %s  
> (only SHA1 and SHA256 are valid)", p[2]);
> +            goto err;
> +        }
>      }
>  #ifdef ENABLE_CRYPTOAPI
>      else if (streq(p[0], "cryptoapicert") && p[1] && !p[2])
> diff --git a/src/openvpn/options.h b/src/openvpn/options.h
> index dc63aeda..5ad087e0 100644
> --- a/src/openvpn/options.h
> +++ b/src/openvpn/options.h
> @@ -42,6 +42,10 @@
>  #include "comp.h"
>  #include "pushlist.h"
>  #include "clinat.h"
> +#ifdef ENABLE_CRYPTO
> +#include "crypto_backend.h"
> +#endif
> +
>  
>  /*
>   * Maximum number of parameters associated with an option,
> @@ -518,6 +522,7 @@ struct options
>      unsigned remote_cert_ku[MAX_PARMS];
>      const char *remote_cert_eku;
>      uint8_t *verify_hash;
> +    hash_algo_type verify_hash_algo;
>      unsigned int ssl_flags; /* set to SSLF_x flags from ssl.h */
>  
>  #ifdef ENABLE_PKCS11
> diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
> index 9a16d77e..a99d24d6 100644
> --- a/src/openvpn/ssl_common.h
> +++ b/src/openvpn/ssl_common.h
> @@ -271,6 +271,7 @@ struct tls_options
>      unsigned remote_cert_ku[MAX_PARMS];
>      const char *remote_cert_eku;
>      uint8_t *verify_hash;
> +    hash_algo_type verify_hash_algo;
>      char *x509_username_field;
>  
>      /* allow openvpn config info to be
> diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c
> index ac1e110a..078b65ad 100644
> --- a/src/openvpn/ssl_verify.c
> +++ b/src/openvpn/ssl_verify.c
> @@ -718,8 +718,31 @@ verify_cert(struct tls_session *session, 
> openvpn_x509_cert_t *cert, int cert_dep
>      /* verify level 1 cert, i.e. the CA that signed our leaf cert */
>      if (cert_depth == 1 && opt->verify_hash)
>      {
> -        struct buffer sha1_hash = x509_get_sha1_fingerprint(cert, &gc);
> -        if (memcmp(BPTR(&sha1_hash), opt->verify_hash, BLEN(&sha1_hash)))
> +        struct buffer ca_hash = {0};
> +
> +        switch (opt->verify_hash_algo)
> +        {
> +        case MD_SHA1:
> +            ca_hash = x509_get_sha1_fingerprint(cert, &gc);
> +            break;
> +
> +        case MD_SHA256:
> +            ca_hash = x509_get_sha256_fingerprint(cert, &gc);
> +            break;
> +
> +        default:
> +            /* This should normally not happen at all; the algorithm used
> +             * is parsed by add_option() [options.c] and set to a predefined
> +             * value in an enumerated type.  So if this unlikely scenario
> +             * happens, consider this a failure
> +             */
> +            msg(M_WARN, "Unexpected invalid algorithm used with "
> +                "--verify-hash (%i)", opt->verify_hash_algo);
> +            ret = FAILURE;
> +            goto cleanup;
> +        }
> +
> +        if (memcmp(BPTR(&ca_hash), opt->verify_hash, BLEN(&ca_hash)))
>          {
>              msg(D_TLS_ERRORS, "TLS Error: level-1 certificate hash 
> verification failed");
>              goto cleanup;
> 

ACK

The Changes.rst bit will need some adjustment because of the 2.4.2
release but I you'll manage that on-the-fly.

-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