feat: add TLS mutual authentication

Introduce a feature to require and verify client certificate to
provide mutual authentication in TLS.
This commit is contained in:
Aximaris 2023-11-26 00:17:10 +08:00
parent 0f643bf414
commit 737f47858a
No known key found for this signature in database
GPG Key ID: FDD8F48521BD09F3
3 changed files with 97 additions and 27 deletions

View File

@ -128,5 +128,40 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
} }
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = certPool
} }
if options.ClientAuth {
// explicitly call mutual verification
var cert []byte
var key []byte
if len(options.ClientCertificate) > 0 {
cert = []byte(strings.Join(options.ClientCertificate, "\n"))
} else if options.ClientCertificatePath != "" {
content, err := os.ReadFile(options.ClientCertificatePath)
if err != nil {
return nil, E.Cause(err, "read client certificate")
}
cert = content
}
if len(options.ClientKey) > 0 {
key = []byte(strings.Join(options.ClientKey, "\n"))
} else if options.ClientKeyPath != "" {
content, err := os.ReadFile(options.ClientKeyPath)
if err != nil {
return nil, E.Cause(err, "read client key")
}
key = content
}
if cert == nil {
return nil, E.New("missing client certificate")
} else if key == nil {
return nil, E.New("missing client key")
}
keyPair, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, E.New(err, "parse x509 key pair")
}
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
return &STDClientConfig{&tlsConfig}, nil return &STDClientConfig{&tlsConfig}, nil
} }

View File

@ -3,6 +3,7 @@ package tls
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"net" "net"
"os" "os"
"strings" "strings"
@ -212,6 +213,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
} }
var certificate []byte var certificate []byte
var key []byte var key []byte
var clientca []byte
if acmeService == nil { if acmeService == nil {
if len(options.Certificate) > 0 { if len(options.Certificate) > 0 {
certificate = []byte(strings.Join(options.Certificate, "\n")) certificate = []byte(strings.Join(options.Certificate, "\n"))
@ -249,6 +251,29 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
tlsConfig.Certificates = []tls.Certificate{keyPair} tlsConfig.Certificates = []tls.Certificate{keyPair}
} }
} }
if options.ClientAuth {
// require client authentication
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
if len(options.ClientCA) > 0 {
clientca = []byte(strings.Join(options.ClientCA, "\n"))
} else if options.ClientCAPath != "" {
content, err := os.ReadFile(options.ClientCAPath)
if err != nil {
return nil, E.Cause(err, "read client CA")
}
clientca = content
}
// add client ca certpool
if len(clientca) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(clientca) {
return nil, E.New("failed to parse client CA:\n\n", clientca)
}
tlsConfig.ClientCAs = certPool
}
} else {
tlsConfig.ClientAuth = tls.NoClientCert
}
return &STDServerConfig{ return &STDServerConfig{
config: tlsConfig, config: tlsConfig,
logger: logger, logger: logger,
@ -257,5 +282,6 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
key: key, key: key,
certificatePath: options.CertificatePath, certificatePath: options.CertificatePath,
keyPath: options.KeyPath, keyPath: options.KeyPath,
watcher: &fsnotify.Watcher{},
}, nil }, nil
} }

View File

@ -12,6 +12,10 @@ type InboundTLSOptions struct {
CertificatePath string `json:"certificate_path,omitempty"` CertificatePath string `json:"certificate_path,omitempty"`
Key Listable[string] `json:"key,omitempty"` Key Listable[string] `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"` KeyPath string `json:"key_path,omitempty"`
ClientAuth bool `json:"clientAuth,omitempty"`
// , Requireandverify
ClientCA Listable[string] `json:"clientCA,omitempty"`
ClientCAPath string `json:"clientCA_path,omitempty"`
ACME *InboundACMEOptions `json:"acme,omitempty"` ACME *InboundACMEOptions `json:"acme,omitempty"`
ECH *InboundECHOptions `json:"ech,omitempty"` ECH *InboundECHOptions `json:"ech,omitempty"`
Reality *InboundRealityOptions `json:"reality,omitempty"` Reality *InboundRealityOptions `json:"reality,omitempty"`
@ -28,6 +32,11 @@ type OutboundTLSOptions struct {
CipherSuites Listable[string] `json:"cipher_suites,omitempty"` CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
Certificate Listable[string] `json:"certificate,omitempty"` Certificate Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"` CertificatePath string `json:"certificate_path,omitempty"`
ClientAuth bool `json:"clientAuth,omitempty"`
ClientKey Listable[string] `json:"client_key,omitempty"`
ClientKeyPath string `json:"client_key_path,omitempty"`
ClientCertificate Listable[string] `json:"client_certificate,omitempty"`
ClientCertificatePath string `json:"client_certificate_path,omitempty"`
ECH *OutboundECHOptions `json:"ech,omitempty"` ECH *OutboundECHOptions `json:"ech,omitempty"`
UTLS *OutboundUTLSOptions `json:"utls,omitempty"` UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
Reality *OutboundRealityOptions `json:"reality,omitempty"` Reality *OutboundRealityOptions `json:"reality,omitempty"`