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 \