generate OpenVPN StaticKey

This commit is contained in:
Alireza Ahmadi 2026-09-10 01:43:43 +02:00
parent d8625970e4
commit cbce1fef0f
4 changed files with 74 additions and 1 deletions

View file

@ -269,7 +269,10 @@ func writeStaticKey(t *testing.T) string {
if _, err := rand.Read(material); err != nil {
t.Fatal(err)
}
// Same shape as ServerService.generateOpenVPNStaticKey, comment header and
// all, so this covers what the panel's generate button hands over.
var body strings.Builder
body.WriteString("#\n# 2048 bit OpenVPN static key\n#\n")
body.WriteString("-----BEGIN OpenVPN Static key V1-----\n")
encoded := hex.EncodeToString(material)
for offset := 0; offset < len(encoded); offset += 32 {

@ -1 +1 @@
Subproject commit faad00676a658c10930719f4c866d98510db2329
Subproject commit fa41add99f48a0b84a214e653c5ac36599feb0f8

View file

@ -1,7 +1,9 @@
package service
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"os"
"runtime"
"strconv"
@ -212,6 +214,8 @@ func (s *ServerService) GenKeypair(keyType string, options string) []string {
return s.generateRealityKeyPair()
case "wireguard":
return s.generateWireGuardKey(options)
case "openvpn":
return s.generateOpenVPNStaticKey()
}
return []string{"Failed to generate keypair"}
@ -233,6 +237,29 @@ func (s *ServerService) generateTLSKeyPair(serverName string) []string {
return append(strings.Split(string(privateKeyPem), "\n"), strings.Split(string(publicKeyPem), "\n")...)
}
// generateOpenVPNStaticKey produces what `openvpn --genkey secret` writes: 256
// random bytes as hex between OpenVPN's own markers. tls-auth and tls-crypt
// take one of these rather than a PEM, and both ends of a tunnel have to carry
// the same one.
func (s *ServerService) generateOpenVPNStaticKey() []string {
material := make([]byte, 256)
if _, err := rand.Read(material); err != nil {
return []string{"Failed to generate OpenVPN static key: ", err.Error()}
}
encoded := hex.EncodeToString(material)
lines := []string{
"#",
"# 2048 bit OpenVPN static key",
"#",
"-----BEGIN OpenVPN Static key V1-----",
}
for offset := 0; offset < len(encoded); offset += 32 {
lines = append(lines, encoded[offset:offset+32])
}
return append(lines, "-----END OpenVPN Static key V1-----")
}
func (s *ServerService) generateRealityKeyPair() []string {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {

View file

@ -0,0 +1,43 @@
package service
import (
"strings"
"testing"
)
// The static key sing-openvpn accepts is 256 bytes of hex between its own
// markers, with anything before the begin marker ignored. TestOpenVPNStaticKey
// checks the shape; core.TestEndpointInlineTLS proves sing-box takes it.
func TestGenerateOpenVPNStaticKey(t *testing.T) {
s := &ServerService{}
lines := s.GenKeypair("openvpn", "")
begin, end := -1, -1
for index, line := range lines {
switch line {
case "-----BEGIN OpenVPN Static key V1-----":
begin = index
case "-----END OpenVPN Static key V1-----":
end = index
}
}
if begin == -1 || end <= begin {
t.Fatalf("markers missing or out of order: %v", lines)
}
body := strings.Join(lines[begin+1:end], "")
if len(body) != 512 {
t.Errorf("key body is %d hex characters, want 512 (256 bytes)", len(body))
}
for _, r := range body {
if !strings.ContainsRune("0123456789abcdef", r) {
t.Fatalf("key body is not lowercase hex: %q", body)
}
}
// Two calls must not produce the same key, or every tunnel built through
// the panel would share one.
if strings.Join(s.GenKeypair("openvpn", ""), "") == strings.Join(lines, "") {
t.Error("two generated keys are identical")
}
}