mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-05 18:57:30 +03:00
136 lines
4.0 KiB
Go
136 lines
4.0 KiB
Go
package tls
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"os"
|
|
"strings"
|
|
|
|
C "github.com/sagernet/sing-box/constant"
|
|
"github.com/sagernet/sing-box/option"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
"github.com/sagernet/sing/common/logger"
|
|
)
|
|
|
|
func NewOpenVPNClient(ctx context.Context, logger logger.ContextLogger, options option.OpenVPNTLSOptions) (Config, error) {
|
|
ca := options.CA
|
|
if ca == "" && options.CAPath != "" {
|
|
data, err := os.ReadFile(options.CAPath)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "read ca_path")
|
|
}
|
|
ca = string(data)
|
|
}
|
|
certificate := options.Certificate
|
|
if certificate == "" && options.CertificatePath != "" {
|
|
data, err := os.ReadFile(options.CertificatePath)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "read certificate_path")
|
|
}
|
|
certificate = string(data)
|
|
}
|
|
key := options.Key
|
|
if key == "" && options.KeyPath != "" {
|
|
data, err := os.ReadFile(options.KeyPath)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "read key_path")
|
|
}
|
|
key = string(data)
|
|
}
|
|
if strings.TrimSpace(ca) == "" {
|
|
return nil, E.New("openvpn: missing ca certificate")
|
|
}
|
|
if block, _ := pem.Decode([]byte(ca)); block == nil {
|
|
return nil, E.New("openvpn: ca is not valid PEM")
|
|
}
|
|
hasCert := strings.TrimSpace(certificate) != "" || strings.TrimSpace(key) != ""
|
|
if hasCert {
|
|
if strings.TrimSpace(certificate) == "" || strings.TrimSpace(key) == "" {
|
|
return nil, E.New("openvpn: certificate and key must both be set")
|
|
}
|
|
if block, _ := pem.Decode([]byte(certificate)); block == nil {
|
|
return nil, E.New("openvpn: certificate is not valid PEM")
|
|
}
|
|
if block, _ := pem.Decode([]byte(key)); block == nil {
|
|
return nil, E.New("openvpn: key is not valid PEM")
|
|
}
|
|
}
|
|
roots := x509.NewCertPool()
|
|
if !roots.AppendCertsFromPEM([]byte(ca)) {
|
|
return nil, E.New("openvpn: failed to parse ca certificate")
|
|
}
|
|
var tlsConfig tls.Config
|
|
tlsConfig.RootCAs = roots
|
|
tlsConfig.InsecureSkipVerify = true
|
|
if options.CipherSuites != nil {
|
|
find:
|
|
for _, cipherSuite := range options.CipherSuites {
|
|
for _, tlsCipherSuite := range tls.CipherSuites() {
|
|
if cipherSuite == tlsCipherSuite.Name {
|
|
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
|
continue find
|
|
}
|
|
}
|
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
|
}
|
|
}
|
|
tlsConfig.VerifyConnection = func(cs tls.ConnectionState) error {
|
|
if len(cs.PeerCertificates) == 0 {
|
|
return E.New("openvpn: server did not provide certificate")
|
|
}
|
|
cert := cs.PeerCertificates[0]
|
|
intermediates := x509.NewCertPool()
|
|
for _, intermediate := range cs.PeerCertificates[1:] {
|
|
intermediates.AddCert(intermediate)
|
|
}
|
|
_, err := cert.Verify(x509.VerifyOptions{
|
|
Roots: roots,
|
|
Intermediates: intermediates,
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if options.VerifyX509Name != "" {
|
|
cn := cert.Subject.CommonName
|
|
switch options.VerifyX509NameMode {
|
|
case "name-prefix":
|
|
if !strings.HasPrefix(cn, options.VerifyX509Name) {
|
|
return E.New("openvpn: server CN ", cn, " does not match prefix ", options.VerifyX509Name)
|
|
}
|
|
case "name-suffix":
|
|
if !strings.HasSuffix(cn, options.VerifyX509Name) {
|
|
return E.New("openvpn: server CN ", cn, " does not match suffix ", options.VerifyX509Name)
|
|
}
|
|
default:
|
|
if cn != options.VerifyX509Name {
|
|
return E.New("openvpn: server CN ", cn, " does not match ", options.VerifyX509Name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
if hasCert {
|
|
cert, err := tls.X509KeyPair([]byte(certificate), []byte(key))
|
|
if err != nil {
|
|
return nil, E.Cause(err, "openvpn: parse client certificate/key")
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
}
|
|
var config Config = &STDClientConfig{ctx, &tlsConfig, false, 0, false}
|
|
if options.KernelRx || options.KernelTx {
|
|
if !C.IsLinux {
|
|
return nil, E.New("kTLS is only supported on Linux")
|
|
}
|
|
config = &KTLSClientConfig{
|
|
Config: config,
|
|
logger: logger,
|
|
kernelTx: options.KernelTx,
|
|
kernelRx: options.KernelRx,
|
|
}
|
|
}
|
|
return config, nil
|
|
}
|