Feat: multiple OpenVPN ciphers for negotiation

- Perfect privacy to accept AES-256-CBC and AES-256-GCM
- Cyberghost default cipher set to AES-256-GCM
- `OPENVPN_CIPHER` accept comma separated cipher values
- Use `ncp-ciphers` for OpenVPN 2.4
This commit is contained in:
Quentin McGaw (desktop)
2021-10-05 20:36:23 +00:00
parent e0e3ca3832
commit ca975b1c01
27 changed files with 101 additions and 94 deletions

View File

@@ -19,7 +19,7 @@ type OpenVPN struct {
Flags []string `json:"flags"`
MSSFix uint16 `json:"mssfix"`
Root bool `json:"run_as_root"`
Cipher string `json:"cipher"`
Ciphers []string `json:"ciphers"`
Auth string `json:"auth"`
ConfFile string `json:"conf_file"`
Version string `json:"version"`
@@ -52,8 +52,8 @@ func (settings *OpenVPN) lines() (lines []string) {
lines = append(lines, indent+lastIndent+"Run as root: enabled")
}
if len(settings.Cipher) > 0 {
lines = append(lines, indent+lastIndent+"Custom cipher: "+settings.Cipher)
if len(settings.Ciphers) > 0 {
lines = append(lines, indent+lastIndent+"Custom ciphers: "+commaJoin(settings.Ciphers))
}
if len(settings.Auth) > 0 {
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
@@ -132,7 +132,7 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
return fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
}
settings.Cipher, err = r.env.Get("OPENVPN_CIPHER")
settings.Ciphers, err = r.env.CSV("OPENVPN_CIPHER")
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CIPHER: %w", err)
}

View File

@@ -11,8 +11,9 @@ import (
func Test_OpenVPN_JSON(t *testing.T) {
t.Parallel()
in := OpenVPN{
Root: true,
Flags: []string{},
Root: true,
Flags: []string{},
Ciphers: []string{},
}
data, err := json.MarshalIndent(in, "", " ")
require.NoError(t, err)
@@ -23,7 +24,7 @@ func Test_OpenVPN_JSON(t *testing.T) {
"flags": [],
"mssfix": 0,
"run_as_root": true,
"cipher": "",
"ciphers": [],
"auth": "",
"conf_file": "",
"version": "",

View File

@@ -50,8 +50,8 @@ func modifyConfig(lines []string, connection models.Connection,
strings.HasPrefix(line, "remote "),
strings.HasPrefix(line, "dev "),
// Remove values eventually modified
settings.Cipher != "" && hasPrefixOneOf(line,
"cipher ", "data-ciphers ", "data-ciphers-fallback "),
len(settings.Ciphers) > 0 && hasPrefixOneOf(line,
"cipher ", "ncp-ciphers ", "data-ciphers ", "data-ciphers-fallback "),
settings.Auth != "" && strings.HasPrefix(line, "auth "),
settings.MSSFix > 0 && strings.HasPrefix(line, "mssfix "),
!settings.IPv6 && hasPrefixOneOf(line, "tun-ipv6",
@@ -75,8 +75,8 @@ func modifyConfig(lines []string, connection models.Connection,
modified = append(modified, "auth-user-pass "+constants.OpenVPNAuthConf)
}
modified = append(modified, "verb "+strconv.Itoa(settings.Verbosity))
if settings.Cipher != "" {
modified = append(modified, utils.CipherLines(settings.Cipher, settings.Version)...)
if len(settings.Ciphers) > 0 {
modified = append(modified, utils.CipherLines(settings.Ciphers, settings.Version)...)
}
if settings.Auth != "" {
modified = append(modified, "auth "+settings.Auth)

View File

@@ -32,7 +32,7 @@ func Test_modifyConfig(t *testing.T) {
},
settings: configuration.OpenVPN{
User: "user",
Cipher: "cipher",
Ciphers: []string{"cipher"},
Auth: "auth",
MSSFix: 1000,
ProcUser: "procuser",

View File

@@ -2,7 +2,6 @@ package cyberghost
import (
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
@@ -12,8 +11,12 @@ import (
func (c *Cyberghost) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES128gcm
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{
constants.AES256gcm,
constants.AES256cbc,
constants.AES128gcm,
}
}
if settings.Auth == "" {
@@ -45,16 +48,12 @@ func (c *Cyberghost) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")
}
if strings.HasSuffix(settings.Cipher, "-gcm") {
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
}
if !settings.Root {
lines = append(lines, "user "+settings.ProcUser)
lines = append(lines, "persist-tun")

View File

@@ -11,8 +11,8 @@ import (
func (p *Provider) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
if settings.Auth == "" {
settings.Auth = constants.SHA512
@@ -53,7 +53,7 @@ func (p *Provider) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (f *Fastestvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
if settings.Auth == "" {
settings.Auth = constants.SHA256
@@ -49,7 +49,7 @@ func (f *Fastestvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (h *HideMyAss) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
lines = []string{
@@ -39,7 +39,7 @@ func (h *HideMyAss) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.Auth != "" {
lines = append(lines, "auth "+settings.Auth)

View File

@@ -11,8 +11,8 @@ import (
func (i *Ipvanish) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
if settings.Auth == "" {
settings.Auth = constants.SHA256
@@ -43,7 +43,7 @@ func (i *Ipvanish) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.MSSFix > 0 {
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))

View File

@@ -12,8 +12,8 @@ import (
func (i *Ivpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
namePrefix := strings.Split(connection.Hostname, ".")[0]
@@ -45,7 +45,7 @@ func (i *Ivpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.Auth != "" {
lines = append(lines, "auth "+settings.Auth)

View File

@@ -11,8 +11,8 @@ import (
func (m *Mullvad) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc, constants.AES128gcm}
}
lines = []string{
@@ -42,7 +42,7 @@ func (m *Mullvad) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.Auth != "" {
lines = append(lines, "auth "+settings.Auth)

View File

@@ -11,8 +11,8 @@ import (
func (n *Nordvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
if settings.Auth == "" {
@@ -52,7 +52,7 @@ func (n *Nordvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "fast-io")

View File

@@ -11,9 +11,8 @@ import (
func (p *Perfectprivacy) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
// TODO add AES 256 GCM
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc, constants.AES256gcm}
}
if settings.Auth == "" {
@@ -54,7 +53,7 @@ func (p *Perfectprivacy) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (p *Privado) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
if settings.Auth == "" {
@@ -45,7 +45,7 @@ func (p *Privado) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if !settings.Root {
lines = append(lines, "user "+settings.ProcUser)

View File

@@ -30,8 +30,8 @@ func (p *PIA) BuildConf(connection models.Connection,
certificate = constants.PIACertificateStrong
}
if settings.Cipher == "" {
settings.Cipher = defaultCipher
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{defaultCipher}
}
if settings.Auth == "" {
@@ -62,8 +62,8 @@ func (p *PIA) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
if settings.Cipher != "" {
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
if len(settings.Ciphers) > 0 {
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
}
if settings.Auth != "" {

View File

@@ -11,8 +11,8 @@ import (
func (p *Privatevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES128gcm
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES128gcm}
}
if settings.Auth == "" {
@@ -43,7 +43,7 @@ func (p *Privatevpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "key-direction 1")

View File

@@ -11,8 +11,8 @@ import (
func (p *Protonvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
if settings.Auth == "" {
@@ -52,7 +52,7 @@ func (p *Protonvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "fast-io")

View File

@@ -11,8 +11,8 @@ import (
func (p *Purevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256gcm}
}
lines = []string{
@@ -40,7 +40,7 @@ func (p *Purevpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (s *Surfshark) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256gcm}
}
if settings.Auth == "" {
@@ -53,7 +53,7 @@ func (s *Surfshark) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (t *Torguard) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256gcm}
}
if settings.Auth == "" {
@@ -55,7 +55,7 @@ func (t *Torguard) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if !settings.Root {
lines = append(lines, "user "+settings.ProcUser)

View File

@@ -1,17 +1,22 @@
package utils
import (
"strings"
"github.com/qdm12/gluetun/internal/constants"
)
func CipherLines(cipher, version string) (lines []string) {
func CipherLines(ciphers []string, version string) (lines []string) {
switch version {
case constants.Openvpn24:
return []string{"cipher " + cipher}
return []string{
"cipher " + ciphers[0],
"ncp-ciphers " + strings.Join(ciphers, ":"),
}
default: // 2.5 and above
return []string{
"data-ciphers-fallback " + cipher,
"data-ciphers " + cipher,
"data-ciphers-fallback " + ciphers[0],
"data-ciphers " + strings.Join(ciphers, ":"),
}
}
}

View File

@@ -9,24 +9,31 @@ import (
func Test_CipherLines(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
ciphers []string
version string
lines []string
}{
"empty version": {
ciphers: []string{"AES"},
lines: []string{
"data-ciphers-fallback AES",
"data-ciphers AES",
},
},
"2.4": {
ciphers: []string{"AES", "CBC"},
version: "2.4",
lines: []string{"cipher AES"},
lines: []string{
"cipher AES",
"ncp-ciphers AES:CBC",
},
},
"2.5": {
ciphers: []string{"AES", "CBC"},
version: "2.5",
lines: []string{
"data-ciphers-fallback AES",
"data-ciphers AES",
"data-ciphers AES:CBC",
},
},
}
@@ -35,9 +42,7 @@ func Test_CipherLines(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
const cipher = "AES"
lines := CipherLines(cipher, testCase.version)
lines := CipherLines(testCase.ciphers, testCase.version)
assert.Equal(t, testCase.lines, lines)
})

View File

@@ -36,8 +36,8 @@ func (p *Provider) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
if settings.Cipher != "" {
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
if len(settings.Ciphers) > 0 {
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
}
if settings.Auth != "" {

View File

@@ -11,8 +11,8 @@ import (
func (v *Vyprvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256cbc
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256cbc}
}
if settings.Auth == "" {
@@ -46,7 +46,7 @@ func (v *Vyprvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (w *Wevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{constants.AES256gcm}
}
if settings.Auth == "" {
@@ -50,7 +50,7 @@ func (w *Wevpn) BuildConf(connection models.Connection,
lines = append(lines, "explicit-exit-notify")
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if !settings.Root {
lines = append(lines, "user "+settings.ProcUser)

View File

@@ -2,7 +2,6 @@ package windscribe
import (
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
@@ -12,8 +11,12 @@ import (
func (w *Windscribe) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
if len(settings.Ciphers) == 0 {
settings.Ciphers = []string{
constants.AES256gcm,
constants.AES256cbc,
constants.AES128gcm,
}
}
if settings.Auth == "" {
@@ -48,11 +51,7 @@ func (w *Windscribe) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
if strings.HasSuffix(settings.Cipher, "-gcm") {
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
}
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")

View File

@@ -5,8 +5,6 @@
- Remove duplicate `/gluetun` directory creation
- Remove firewall shadowsocks input port?
- Remove `script-security` option
- `ncp-ciphers` to `data-ciphers`
- Remove `ncp-disable`
## Uniformization
@@ -40,6 +38,7 @@
- `WIREGUARD_ADDRESS` to `WIREGUARD_ADDRESSES`
- `VPNSP` to `VPN_SERVICE_PROVIDER`
- Rename `REGION` (etc.) to `SERVER_REGIONS`
- `OPENVPN_CIPHER` to `OPENVPN_CIPHERS`
- Split servers.json and compress it
## Gluetun V4