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
```
This commit is contained in:
Stanislas
2025-12-13 14:32:38 +01:00
committed by GitHub
parent 2c53bc0f83
commit 3561d13389
4 changed files with 107 additions and 19 deletions

View File

@@ -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

View File

@@ -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`)

View File

@@ -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 "<tls-crypt-v2>"
cat "$tls_crypt_v2_tmpfile"
echo "</tls-crypt-v2>"
rm -f "$tls_crypt_v2_tmpfile"
;;
2)
echo "<tls-crypt>"
cat /etc/openvpn/server/tls-crypt.key
echo "</tls-crypt>"
;;
2)
3)
echo "key-direction 1"
echo "<tls-auth>"
cat /etc/openvpn/server/tls-auth.key

View File

@@ -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 \