From 3561d13389a03a6917574f69816b850c837ca85e Mon Sep 17 00:00:00 2001 From: Stanislas Date: Sat, 13 Dec 2025 14:32:38 +0100 Subject: [PATCH] feat: add tls-crypt-v2 support with per-client keys (#1377) ## Summary - Add support for OpenVPN's `tls-crypt-v2` feature (per-client TLS keys) - Set `tls-crypt-v2` as the new recommended default - Add CI tests for all 3 TLS key types Closes #983 Closes #758 Closes https://github.com/angristan/openvpn-install/pull/1257 ## What is tls-crypt-v2? Unlike `tls-crypt` (shared key), `tls-crypt-v2` generates unique keys per client: - **Better security**: Compromised client keys don't affect other clients - **Easier management**: Individual client key revocation without regenerating server key - **Scalability**: Better suited for large deployments Requires OpenVPN 2.5+ (released 2020). ## Menu options ``` 1) tls-crypt-v2 (recommended): Encrypts control channel, unique key per client 2) tls-crypt: Encrypts control channel, shared key for all clients 3) tls-auth: Authenticates control channel, no encryption ``` --- .github/workflows/docker-test.yml | 37 +++++++++++++++++++++-- README.md | 14 +++++++-- openvpn-install.sh | 49 +++++++++++++++++++++++-------- test/server-entrypoint.sh | 26 ++++++++++++++-- 4 files changed, 107 insertions(+), 19 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index d2113bd..f1e83ab 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -68,6 +68,27 @@ jobs: image: oraclelinux:10 - name: amazonlinux-2023 image: amazonlinux:2023 + # Default TLS settings (tls-crypt-v2) + tls: + - name: tls-crypt-v2 + sig: "1" + key_file: tls-crypt-v2.key + # Additional TLS types tested on Ubuntu 24.04 only + include: + - os: + name: ubuntu-24.04-tls-crypt + image: ubuntu:24.04 + tls: + name: tls-crypt + sig: "2" + key_file: tls-crypt.key + - os: + name: ubuntu-24.04-tls-auth + image: ubuntu:24.04 + tls: + name: tls-auth + sig: "3" + key_file: tls-auth.key name: ${{ matrix.os.name }} steps: @@ -110,6 +131,8 @@ jobs: --tmpfs /run \ --tmpfs /run/lock \ --stop-signal SIGRTMIN+3 \ + -e TLS_SIG=${{ matrix.tls.sig }} \ + -e TLS_KEY_FILE=${{ matrix.tls.key_file }} \ openvpn-server - name: Wait for server installation and startup @@ -151,8 +174,18 @@ jobs: sleep 5 done - # Final verification - if ! docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then + # Final verification with retry (handles race condition during cert renewal restart) + OPENVPN_STARTED=false + for retry in {1..5}; do + if docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then + OPENVPN_STARTED=true + break + fi + echo "Waiting for OpenVPN process... (retry $retry/5)" + sleep 2 + done + + if [ "$OPENVPN_STARTED" = false ]; then echo "ERROR: OpenVPN server failed to start" docker exec openvpn-server systemctl status openvpn-server@server 2>&1 || true docker exec openvpn-server journalctl -u openvpn-test.service --no-pager -n 100 2>&1 || true diff --git a/README.md b/README.md index 3ade0dc..fd280c1 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,7 @@ The script provides the following choices: It defaults to `SHA256`. -### `tls-auth` and `tls-crypt` +### `tls-auth`, `tls-crypt`, and `tls-crypt-v2` From the OpenVPN wiki, about `tls-auth`: @@ -381,7 +381,17 @@ So both provide an additional layer of security and mitigate DoS attacks. They a `tls-crypt` is an OpenVPN 2.4 feature that provides encryption in addition to authentication (unlike `tls-auth`). It is more privacy-friendly. -The script supports both and uses `tls-crypt` by default. +`tls-crypt-v2` is an OpenVPN 2.5 feature that builds on `tls-crypt` by using **per-client keys** instead of a shared key. Each client receives a unique key derived from a server key. This provides: + +- **Better security**: If a client key is compromised, other clients are not affected +- **Easier key management**: Client keys can be revoked individually without regenerating the server key +- **Scalability**: Better suited for large deployments with many clients + +The script supports all three options: + +- `tls-crypt-v2` (default): Per-client keys for better security +- `tls-crypt`: Shared key for all clients, compatible with OpenVPN 2.4+ +- `tls-auth`: HMAC authentication only (no encryption), compatible with older clients ### Certificate type verification (`remote-cert-tls`) diff --git a/openvpn-install.sh b/openvpn-install.sh index d68fdd8..bda5b48 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -749,7 +749,7 @@ function installQuestions() { DH_TYPE="1" # ECDH DH_CURVE="prime256v1" HMAC_ALG="SHA256" - TLS_SIG="1" # tls-crypt + TLS_SIG="1" # tls-crypt-v2 else log_menu "" log_prompt "Choose which cipher you want to use for the data channel:" @@ -956,12 +956,12 @@ function installQuestions() { ;; esac log_menu "" - log_prompt "You can add an additional layer of security to the control channel with tls-auth and tls-crypt" - log_prompt "tls-auth authenticates the packets, while tls-crypt authenticate and encrypt them." - log_menu " 1) tls-crypt (recommended)" - log_menu " 2) tls-auth" - until [[ $TLS_SIG =~ [1-2] ]]; do - read -rp "Control channel additional security mechanism [1-2]: " -e -i 1 TLS_SIG + log_prompt "You can add an additional layer of security to the control channel." + log_menu " 1) tls-crypt-v2 (recommended): Encrypts control channel, unique key per client" + log_menu " 2) tls-crypt: Encrypts control channel, shared key for all clients" + log_menu " 3) tls-auth: Authenticates control channel, no encryption" + until [[ $TLS_SIG =~ ^[1-3]$ ]]; do + read -rp "Control channel additional security mechanism [1-3]: " -e -i 1 TLS_SIG done fi log_menu "" @@ -1170,10 +1170,14 @@ function installOpenVPN() { log_info "Generating TLS key..." case $TLS_SIG in 1) + # Generate tls-crypt-v2 server key + run_cmd_fatal "Generating tls-crypt-v2 server key" openvpn --genkey tls-crypt-v2-server /etc/openvpn/server/tls-crypt-v2.key + ;; + 2) # Generate tls-crypt key run_cmd_fatal "Generating tls-crypt key" openvpn --genkey --secret /etc/openvpn/server/tls-crypt.key ;; - 2) + 3) # Generate tls-auth key run_cmd_fatal "Generating tls-auth key" openvpn --genkey --secret /etc/openvpn/server/tls-auth.key ;; @@ -1320,9 +1324,12 @@ push "redirect-gateway ipv6"' >>/etc/openvpn/server/server.conf case $TLS_SIG in 1) - echo "tls-crypt tls-crypt.key" >>/etc/openvpn/server/server.conf + echo "tls-crypt-v2 tls-crypt-v2.key" >>/etc/openvpn/server/server.conf ;; 2) + echo "tls-crypt tls-crypt.key" >>/etc/openvpn/server/server.conf + ;; + 3) echo "tls-auth tls-auth.key 0" >>/etc/openvpn/server/server.conf ;; esac @@ -1550,12 +1557,14 @@ function generateClientConfig() { local client="$1" local home_dir="$2" - # Determine if we use tls-auth or tls-crypt + # Determine if we use tls-crypt-v2, tls-crypt, or tls-auth local tls_sig="" - if grep -qs "^tls-crypt" /etc/openvpn/server/server.conf; then + if grep -qs "^tls-crypt-v2" /etc/openvpn/server/server.conf; then tls_sig="1" - elif grep -qs "^tls-auth" /etc/openvpn/server/server.conf; then + elif grep -qs "^tls-crypt" /etc/openvpn/server/server.conf; then tls_sig="2" + elif grep -qs "^tls-auth" /etc/openvpn/server/server.conf; then + tls_sig="3" fi # Generate the custom client.ovpn @@ -1575,11 +1584,25 @@ function generateClientConfig() { case $tls_sig in 1) + # Generate per-client tls-crypt-v2 key using secure temp file + tls_crypt_v2_tmpfile=$(mktemp) + if ! openvpn --tls-crypt-v2 /etc/openvpn/server/tls-crypt-v2.key \ + --genkey tls-crypt-v2-client "$tls_crypt_v2_tmpfile"; then + rm -f "$tls_crypt_v2_tmpfile" + log_error "Failed to generate tls-crypt-v2 client key" + exit 1 + fi + echo "" + cat "$tls_crypt_v2_tmpfile" + echo "" + rm -f "$tls_crypt_v2_tmpfile" + ;; + 2) echo "" cat /etc/openvpn/server/tls-crypt.key echo "" ;; - 2) + 3) echo "key-direction 1" echo "" cat /etc/openvpn/server/tls-auth.key diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 63260bb..83e5328 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -22,11 +22,33 @@ export PORT_CHOICE=1 export PROTOCOL_CHOICE=1 export DNS=2 # Self-hosted Unbound DNS resolver export COMPRESSION_ENABLED=n -export CUSTOMIZE_ENC=n export CLIENT=testclient export PASS=1 export ENDPOINT=openvpn-server +# TLS key type configuration (default: tls-crypt-v2) +# TLS_SIG: 1=tls-crypt-v2, 2=tls-crypt, 3=tls-auth +# TLS_KEY_FILE: the expected key file name for verification +TLS_SIG="${TLS_SIG:-1}" +TLS_KEY_FILE="${TLS_KEY_FILE:-tls-crypt-v2.key}" +export TLS_SIG + +# If using non-default TLS settings, enable encryption customization +if [ "$TLS_SIG" != "1" ]; then + export CUSTOMIZE_ENC=y + # Set other encryption defaults when customizing + export CIPHER_CHOICE=1 # AES-128-GCM + export CERT_TYPE=1 # ECDSA + export CERT_CURVE_CHOICE=1 # prime256v1 + export CC_CIPHER_CHOICE=1 # ECDHE-ECDSA-AES-128-GCM-SHA256 + export DH_TYPE=1 # ECDH + export DH_CURVE_CHOICE=1 # prime256v1 + export HMAC_ALG_CHOICE=1 # SHA-256 + echo "Testing TLS key type: $TLS_SIG (key file: $TLS_KEY_FILE)" +else + export CUSTOMIZE_ENC=n +fi + echo "Running OpenVPN install script..." # Run in subshell because the script calls 'exit 0' after generating client config # Capture output to validate logging format, while still displaying it @@ -59,7 +81,7 @@ for f in \ /etc/openvpn/server/server.conf \ /etc/openvpn/server/ca.crt \ /etc/openvpn/server/ca.key \ - /etc/openvpn/server/tls-crypt.key \ + "/etc/openvpn/server/$TLS_KEY_FILE" \ /etc/openvpn/server/crl.pem \ /etc/openvpn/server/easy-rsa/pki/ca.crt \ /etc/iptables/add-openvpn-rules.sh \