diff --git a/README.md b/README.md index 57e250e..126acf8 100644 --- a/README.md +++ b/README.md @@ -289,9 +289,9 @@ The `install` command supports many options for customization: - `--rsa-bits <2048|3072|4096>` - RSA key size (default: `2048`) - `--hmac ` - HMAC algorithm (default: `SHA256`). Options: `SHA256`, `SHA384`, `SHA512` - `--tls-sig ` - TLS mode (default: `crypt-v2`). Options: `crypt-v2`, `crypt`, `auth` -- `--dh-type ` - DH key exchange type (default: `ecdh`) -- `--dh-curve ` - ECDH curve (default: `prime256v1`). Options: `prime256v1`, `secp384r1`, `secp521r1` -- `--dh-bits <2048|3072|4096>` - DH key size when using `--dh-type dh` (default: `2048`) +- `--tls-version-min <1.2|1.3>` - Minimum TLS version (default: `1.2`) +- `--tls-ciphersuites ` - TLS 1.3 cipher suites, colon-separated (default: `TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256`) +- `--tls-groups ` - Key exchange groups, colon-separated (default: `X25519:prime256v1:secp384r1:secp521r1`) - `--server-cert-days ` - Server cert validity in days (default: `3650`) **Client Options:** @@ -415,9 +415,17 @@ OpenVPN 2.6+ defaults `--allow-compression` to `no`, blocking even server-pushed OpenVPN 2.5 and earlier accepted TLS 1.0 by default, which is nearly [20 years old](https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.0). -With `tls-version-min 1.2` we enforce TLS 1.2, which the best protocol available currently for OpenVPN. +This script defaults to `tls-version-min 1.2` for compatibility with all OpenVPN 2.4+ clients. You can optionally set `tls-version-min 1.3` for environments where all clients support TLS 1.3. -TLS 1.2 is supported since OpenVPN 2.3.3. +**TLS 1.3 support** was added in OpenVPN 2.5 and requires OpenSSL 1.1.1+. TLS 1.3 offers improved security and performance with a simplified handshake. + +The script configures TLS 1.3 cipher suites via `--tls-ciphersuites` (separate from the TLS 1.2 `--tls-cipher` option). The default TLS 1.3 cipher suites are: + +- `TLS_AES_256_GCM_SHA384` +- `TLS_AES_128_GCM_SHA256` +- `TLS_CHACHA20_POLY1305_SHA256` + +TLS 1.2 is supported since OpenVPN 2.3.3. TLS 1.3 is supported since OpenVPN 2.5. ### Certificate @@ -476,6 +484,8 @@ OpenVPN 2.4 added a feature called "NCP": _Negotiable Crypto Parameters_. It mea OpenVPN 2.4 will negotiate the best cipher available by default (e.g ECDHE+AES-256-GCM) +#### TLS 1.2 ciphers (`--tls-cipher`) + The script proposes the following options, depending on the certificate: - ECDSA: @@ -489,20 +499,34 @@ The script proposes the following options, depending on the certificate: It defaults to `TLS-ECDHE-*-WITH-AES-128-GCM-SHA256`. -### Diffie-Hellman key exchange +#### TLS 1.3 ciphers (`--tls-ciphersuites`) -OpenVPN uses a 2048 bits DH key by default. +When TLS 1.3 is negotiated, a separate set of cipher suites is used. These are configured via `--tls-ciphersuites` and use OpenSSL naming conventions: -OpenVPN 2.4 added support for ECDH keys. Elliptic curve cryptography is faster, lighter and more secure. +- `TLS_AES_256_GCM_SHA384` +- `TLS_AES_128_GCM_SHA256` +- `TLS_CHACHA20_POLY1305_SHA256` -Also, generating a classic DH keys can take a long, looong time. ECDH keys are ephemeral: they are generated on-the-fly. +By default, all three cipher suites are enabled. TLS 1.3 cipher suites are simpler because they don't include the key exchange algorithm (which is negotiated separately via key shares). -The script provides the following options: +### Key exchange -- ECDH: `prime256v1`/`secp384r1`/`secp521r1` curves -- DH: `2048`/`3072`/`4096` bits keys +OpenVPN historically defaulted to 2048-bit DH parameters for key exchange. This script used to offer both DH (with configurable key sizes) and ECDH as alternatives. -It defaults to `prime256v1`. +OpenVPN 2.4 added ECDH support, and OpenVPN 2.7 made `dh none` (ECDH) the default, as finite-field DH is being deprecated. Since ECDH is now universally supported and preferred, this script no longer offers traditional DH. + +The script configures `tls-groups` with the following preference list: + +``` +X25519:prime256v1:secp384r1:secp521r1 +``` + +- **X25519**: Fast, modern curve (Curve25519), widely supported +- **prime256v1**: NIST P-256, most compatible +- **secp384r1**: NIST P-384, higher security +- **secp521r1**: NIST P-521, highest security + +You can customize this with `--tls-groups`. ### HMAC digest algorithm diff --git a/openvpn-install.sh b/openvpn-install.sh index 166062b..b6143f1 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -228,9 +228,10 @@ show_install_help() { Curves: prime256v1, secp384r1, secp521r1 --rsa-bits RSA key size: 2048, 3072, 4096 (default: 2048) --cc-cipher Control channel cipher (auto-selected) - --dh-type DH type: ecdh or dh (default: ecdh) - --dh-curve ECDH curve (default: prime256v1) - --dh-bits DH key size: 2048, 3072, 4096 (default: 2048) + --tls-version-min Minimum TLS version: 1.2 or 1.3 (default: 1.2) + --tls-ciphersuites TLS 1.3 cipher suites, colon-separated + --tls-groups Key exchange groups, colon-separated + (default: X25519:prime256v1:secp384r1:secp521r1) --hmac HMAC algorithm: SHA256, SHA384, SHA512 (default: SHA256) --tls-sig TLS mode: crypt-v2, crypt, auth (default: crypt-v2) --server-cert-days Server cert validity in days (default: 3650) @@ -460,8 +461,11 @@ set_default_encryption() { CERT_TYPE="${CERT_TYPE:-1}" # ECDSA CERT_CURVE="${CERT_CURVE:-prime256v1}" CC_CIPHER="${CC_CIPHER:-TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256}" - DH_TYPE="${DH_TYPE:-1}" # ECDH - DH_CURVE="${DH_CURVE:-prime256v1}" + # TLS 1.3 cipher suites (OpenSSL format with underscores) + TLS13_CIPHERSUITES="${TLS13_CIPHERSUITES:-TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256}" + TLS_VERSION_MIN="${TLS_VERSION_MIN:-1.2}" + # TLS key exchange groups (replaces deprecated ecdh-curve) + TLS_GROUPS="${TLS_GROUPS:-X25519:prime256v1:secp384r1:secp521r1}" HMAC_ALG="${HMAC_ALG:-SHA256}" TLS_SIG="${TLS_SIG:-1}" # tls-crypt-v2 } @@ -656,28 +660,24 @@ cmd_install() { CUSTOMIZE_ENC=y shift 2 ;; - --dh-type) - [[ -z "${2:-}" ]] && log_fatal "--dh-type requires an argument" + --tls-ciphersuites) + [[ -z "${2:-}" ]] && log_fatal "--tls-ciphersuites requires an argument" + TLS13_CIPHERSUITES="$2" + CUSTOMIZE_ENC=y + shift 2 + ;; + --tls-version-min) + [[ -z "${2:-}" ]] && log_fatal "--tls-version-min requires an argument" case "$2" in - ecdh) DH_TYPE=1 ;; - dh) DH_TYPE=2 ;; - *) log_fatal "Invalid dh-type: $2. Use 'ecdh' or 'dh'." ;; + 1.2 | 1.3) TLS_VERSION_MIN="$2" ;; + *) log_fatal "Invalid TLS version: $2. Use '1.2' or '1.3'." ;; esac CUSTOMIZE_ENC=y shift 2 ;; - --dh-curve) - [[ -z "${2:-}" ]] && log_fatal "--dh-curve requires an argument" - DH_CURVE=$(parse_curve "$2") - CUSTOMIZE_ENC=y - shift 2 - ;; - --dh-bits) - [[ -z "${2:-}" ]] && log_fatal "--dh-bits requires an argument" - case "$2" in - 2048 | 3072 | 4096) DH_KEY_SIZE="$2" ;; - *) log_fatal "Invalid DH key size: $2. Use 2048, 3072, or 4096." ;; - esac + --tls-groups) + [[ -z "${2:-}" ]] && log_fatal "--tls-groups requires an argument" + TLS_GROUPS="$2" CUSTOMIZE_ENC=y shift 2 ;; @@ -781,11 +781,9 @@ cmd_install() { # Multi-client MULTI_CLIENT=${MULTI_CLIENT:-n} - # Encryption + # Encryption - always set defaults for any missing values CUSTOMIZE_ENC=${CUSTOMIZE_ENC:-n} - if [[ $CUSTOMIZE_ENC == "n" ]]; then - set_default_encryption - fi + set_default_encryption # Client setup if [[ $no_client == true ]]; then @@ -1856,8 +1854,9 @@ function installQuestions() { CERT_TYPE="1" # ECDSA CERT_CURVE="prime256v1" CC_CIPHER="TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256" - DH_TYPE="1" # ECDH - DH_CURVE="prime256v1" + TLS13_CIPHERSUITES="TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256" + TLS_VERSION_MIN="1.2" + TLS_GROUPS="X25519:prime256v1:secp384r1:secp521r1" HMAC_ALG="SHA256" TLS_SIG="1" # tls-crypt-v2 else @@ -1990,54 +1989,60 @@ function installQuestions() { ;; esac log_menu "" - log_prompt "Choose what kind of Diffie-Hellman key you want to use:" - log_menu " 1) ECDH (recommended)" - log_menu " 2) DH" - until [[ $DH_TYPE =~ [1-2] ]]; do - read -rp "DH key type [1-2]: " -e -i 1 DH_TYPE + log_prompt "Choose the minimum TLS version:" + log_menu " 1) TLS 1.2 (recommended, compatible with all clients)" + log_menu " 2) TLS 1.3 (more secure, requires OpenVPN 2.5+ clients)" + until [[ $TLS_VERSION_MIN_CHOICE =~ ^[1-2]$ ]]; do + read -rp "Minimum TLS version [1-2]: " -e -i 1 TLS_VERSION_MIN_CHOICE done - case $DH_TYPE in + case $TLS_VERSION_MIN_CHOICE in 1) - log_menu "" - log_prompt "Choose which curve you want to use for the ECDH key:" - log_menu " 1) prime256v1 (recommended)" - log_menu " 2) secp384r1" - log_menu " 3) secp521r1" - while [[ $DH_CURVE_CHOICE != "1" && $DH_CURVE_CHOICE != "2" && $DH_CURVE_CHOICE != "3" ]]; do - read -rp "Curve [1-3]: " -e -i 1 DH_CURVE_CHOICE - done - case $DH_CURVE_CHOICE in - 1) - DH_CURVE="prime256v1" - ;; - 2) - DH_CURVE="secp384r1" - ;; - 3) - DH_CURVE="secp521r1" - ;; - esac + TLS_VERSION_MIN="1.2" ;; 2) - log_menu "" - log_prompt "Choose what size of Diffie-Hellman key you want to use:" - log_menu " 1) 2048 bits (recommended)" - log_menu " 2) 3072 bits" - log_menu " 3) 4096 bits" - until [[ $DH_KEY_SIZE_CHOICE =~ ^[1-3]$ ]]; do - read -rp "DH key size [1-3]: " -e -i 1 DH_KEY_SIZE_CHOICE - done - case $DH_KEY_SIZE_CHOICE in - 1) - DH_KEY_SIZE="2048" - ;; - 2) - DH_KEY_SIZE="3072" - ;; - 3) - DH_KEY_SIZE="4096" - ;; - esac + TLS_VERSION_MIN="1.3" + ;; + esac + log_menu "" + log_prompt "Choose TLS 1.3 cipher suites (used when TLS 1.3 is negotiated):" + log_menu " 1) All secure ciphers (recommended)" + log_menu " 2) AES-256-GCM only" + log_menu " 3) AES-128-GCM only" + log_menu " 4) ChaCha20-Poly1305 only" + until [[ $TLS13_CIPHER_CHOICE =~ ^[1-4]$ ]]; do + read -rp "TLS 1.3 cipher suite [1-4]: " -e -i 1 TLS13_CIPHER_CHOICE + done + case $TLS13_CIPHER_CHOICE in + 1) + TLS13_CIPHERSUITES="TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256" + ;; + 2) + TLS13_CIPHERSUITES="TLS_AES_256_GCM_SHA384" + ;; + 3) + TLS13_CIPHERSUITES="TLS_AES_128_GCM_SHA256" + ;; + 4) + TLS13_CIPHERSUITES="TLS_CHACHA20_POLY1305_SHA256" + ;; + esac + log_menu "" + log_prompt "Choose TLS key exchange groups (for ECDH key exchange):" + log_menu " 1) All modern curves (recommended)" + log_menu " 2) X25519 only (most secure, may have compatibility issues)" + log_menu " 3) NIST curves only (prime256v1, secp384r1, secp521r1)" + until [[ $TLS_GROUPS_CHOICE =~ ^[1-3]$ ]]; do + read -rp "TLS groups [1-3]: " -e -i 1 TLS_GROUPS_CHOICE + done + case $TLS_GROUPS_CHOICE in + 1) + TLS_GROUPS="X25519:prime256v1:secp384r1:secp521r1" + ;; + 2) + TLS_GROUPS="X25519" + ;; + 3) + TLS_GROUPS="prime256v1:secp384r1:secp521r1" ;; esac log_menu "" @@ -2271,11 +2276,6 @@ function installOpenVPN() { log_info "Building CA..." run_cmd_fatal "Building CA" ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass - if [[ $DH_TYPE == "2" ]]; then - # ECDH keys are generated on-the-fly so we don't need to generate them beforehand - run_cmd_fatal "Generating DH parameters (this may take a while)" openssl dhparam -out dh.pem "$DH_KEY_SIZE" - fi - export EASYRSA_CERT_EXPIRE=${SERVER_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS} log_info "Building server certificate..." run_cmd_fatal "Building server certificate" ./easyrsa --batch build-server-full "$SERVER_NAME" nopass @@ -2307,9 +2307,6 @@ function installOpenVPN() { # Move all the generated files log_info "Copying certificates..." run_cmd_fatal "Copying certificates to /etc/openvpn/server" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server - if [[ $DH_TYPE == "2" ]]; then - run_cmd_fatal "Copying DH parameters" cp dh.pem /etc/openvpn/server - fi # Make cert revocation list readable for non-root run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/server/crl.pem @@ -2430,12 +2427,9 @@ push "redirect-gateway ipv6"' >>/etc/openvpn/server/server.conf echo "tun-mtu $MTU" >>/etc/openvpn/server/server.conf fi - if [[ $DH_TYPE == "1" ]]; then - echo "dh none" >>/etc/openvpn/server/server.conf - echo "ecdh-curve $DH_CURVE" >>/etc/openvpn/server/server.conf - elif [[ $DH_TYPE == "2" ]]; then - echo "dh dh.pem" >>/etc/openvpn/server/server.conf - fi + # Use ECDH key exchange (dh none) with tls-groups for curve negotiation + echo "dh none" >>/etc/openvpn/server/server.conf + echo "tls-groups $TLS_GROUPS" >>/etc/openvpn/server/server.conf case $TLS_SIG in 1) @@ -2459,9 +2453,10 @@ ignore-unknown-option data-ciphers data-ciphers $CIPHER ncp-ciphers $CIPHER tls-server -tls-version-min 1.2 +tls-version-min $TLS_VERSION_MIN remote-cert-tls client tls-cipher $CC_CIPHER +tls-ciphersuites $TLS13_CIPHERSUITES client-config-dir ccd status /var/log/openvpn/status.log verb 3" >>/etc/openvpn/server/server.conf @@ -2689,8 +2684,9 @@ ignore-unknown-option data-ciphers data-ciphers $CIPHER ncp-ciphers $CIPHER tls-client -tls-version-min 1.2 +tls-version-min $TLS_VERSION_MIN tls-cipher $CC_CIPHER +tls-ciphersuites $TLS13_CIPHERSUITES ignore-unknown-option block-outside-dns setenv opt block-outside-dns # Prevent Windows 10 DNS leak verb 3" >>/etc/openvpn/server/client-template.txt diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index a4e0b5e..7985841 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -26,6 +26,12 @@ export VPN_GATEWAY TLS_SIG="${TLS_SIG:-crypt-v2}" TLS_KEY_FILE="${TLS_KEY_FILE:-tls-crypt-v2.key}" +# TLS 1.3 configuration +# TLS_VERSION_MIN: 1.2 or 1.3 +# TLS13_CIPHERSUITES: colon-separated list of TLS 1.3 cipher suites +TLS_VERSION_MIN="${TLS_VERSION_MIN:-1.2}" +TLS13_CIPHERSUITES="${TLS13_CIPHERSUITES:-TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256}" + # Build install command with CLI flags (using array for proper quoting) INSTALL_CMD=(/opt/openvpn-install.sh install) INSTALL_CMD+=(--endpoint openvpn-server) @@ -40,6 +46,18 @@ if [ "$TLS_SIG" != "crypt-v2" ]; then echo "Testing TLS key type: $TLS_SIG (key file: $TLS_KEY_FILE)" fi +# Add TLS version if non-default +if [ "$TLS_VERSION_MIN" != "1.2" ]; then + INSTALL_CMD+=(--tls-version-min "$TLS_VERSION_MIN") + echo "Testing TLS version min: $TLS_VERSION_MIN" +fi + +# Add TLS 1.3 ciphersuites if non-default +if [ "$TLS13_CIPHERSUITES" != "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256" ]; then + INSTALL_CMD+=(--tls-ciphersuites "$TLS13_CIPHERSUITES") + echo "Testing TLS 1.3 ciphersuites: $TLS13_CIPHERSUITES" +fi + echo "Running OpenVPN install script..." echo "Command: ${INSTALL_CMD[*]}" # Run in subshell because the script calls 'exit 0' after generating client config @@ -219,6 +237,47 @@ echo "" echo "Server config:" cat /etc/openvpn/server/server.conf +# ===================================================== +# Verify TLS 1.3 configuration +# ===================================================== +echo "" +echo "=== Verifying TLS 1.3 Configuration ===" + +# Verify tls-version-min is set correctly +if grep -q "tls-version-min $TLS_VERSION_MIN" /etc/openvpn/server/server.conf; then + echo "PASS: tls-version-min is set to $TLS_VERSION_MIN" +else + echo "FAIL: tls-version-min is not set correctly" + grep "tls-version-min" /etc/openvpn/server/server.conf || echo "tls-version-min not found" + exit 1 +fi + +# Verify tls-ciphersuites is set +if grep -q "tls-ciphersuites $TLS13_CIPHERSUITES" /etc/openvpn/server/server.conf; then + echo "PASS: tls-ciphersuites is configured correctly" +else + echo "FAIL: tls-ciphersuites is not configured correctly" + grep "tls-ciphersuites" /etc/openvpn/server/server.conf || echo "tls-ciphersuites not found" + exit 1 +fi + +# Verify client template also has TLS 1.3 settings +if grep -q "tls-version-min $TLS_VERSION_MIN" /etc/openvpn/server/client-template.txt; then + echo "PASS: Client template has correct tls-version-min" +else + echo "FAIL: Client template missing tls-version-min" + exit 1 +fi + +if grep -q "tls-ciphersuites $TLS13_CIPHERSUITES" /etc/openvpn/server/client-template.txt; then + echo "PASS: Client template has correct tls-ciphersuites" +else + echo "FAIL: Client template missing tls-ciphersuites" + exit 1 +fi + +echo "=== TLS 1.3 Configuration Verified ===" + # ===================================================== # Test certificate renewal functionality # =====================================================