Implement workarounds for poor system CA pools

This commit is contained in:
Vladislav Yarmak 2021-04-28 17:56:53 +03:00
parent 67e4886843
commit c42142f5d6
2 changed files with 87 additions and 25 deletions

23
main.go
View file

@ -3,10 +3,12 @@ package main
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/csv" "encoding/csv"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -56,6 +58,8 @@ type CLIArgs struct {
bootstrapDNS string bootstrapDNS string
refresh time.Duration refresh time.Duration
refreshRetry time.Duration refreshRetry time.Duration
certChainWorkaround bool
caFile string
} }
func parse_args() CLIArgs { func parse_args() CLIArgs {
@ -80,6 +84,9 @@ func parse_args() CLIArgs {
"Examples: https://1.1.1.1/dns-query, quic://dns.adguard.com") "Examples: https://1.1.1.1/dns-query, quic://dns.adguard.com")
flag.DurationVar(&args.refresh, "refresh", 4*time.Hour, "login refresh interval") flag.DurationVar(&args.refresh, "refresh", 4*time.Hour, "login refresh interval")
flag.DurationVar(&args.refreshRetry, "refresh-retry", 5*time.Second, "login refresh retry interval") flag.DurationVar(&args.refreshRetry, "refresh-retry", 5*time.Second, "login refresh retry interval")
flag.BoolVar(&args.certChainWorkaround, "certchain-workaround", true,
"add bundled cross-signed intermediate cert to certchain to make it check out on old systems")
flag.StringVar(&args.caFile, "cafile", "", "use custom CA certificate bundle file")
flag.Parse() flag.Parse()
if args.country == "" { if args.country == "" {
arg_fail("Country can't be empty string.") arg_fail("Country can't be empty string.")
@ -259,7 +266,21 @@ func run() int {
return basic_auth_header(seclient.GetProxyCredentials()) return basic_auth_header(seclient.GetProxyCredentials())
} }
handlerDialer := NewProxyDialer(endpoint.NetAddr(), fmt.Sprintf("%s0.%s", args.country, PROXY_SUFFIX), auth, dialer) var caPool *x509.CertPool
if args.caFile != "" {
caPool = x509.NewCertPool()
certs, err := ioutil.ReadFile(args.caFile)
if err != nil {
mainLogger.Error("Can't load CA file: %v", err)
return 15
}
if ok := caPool.AppendCertsFromPEM(certs); !ok {
mainLogger.Error("Can't load certificates from CA file")
return 15
}
}
handlerDialer := NewProxyDialer(endpoint.NetAddr(), fmt.Sprintf("%s0.%s", args.country, PROXY_SUFFIX), auth, args.certChainWorkaround, caPool, dialer)
mainLogger.Info("Endpoint: %s", endpoint.NetAddr()) mainLogger.Info("Endpoint: %s", endpoint.NetAddr())
mainLogger.Info("Starting proxy server...") mainLogger.Info("Starting proxy server...")
handler := NewProxyHandler(handlerDialer, proxyLogger) handler := NewProxyHandler(handlerDialer, proxyLogger)

View file

@ -6,6 +6,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -20,10 +21,37 @@ const (
PROXY_CONNECT_METHOD = "CONNECT" PROXY_CONNECT_METHOD = "CONNECT"
PROXY_HOST_HEADER = "Host" PROXY_HOST_HEADER = "Host"
PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization" PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization"
MISSING_CHAIN_CERT = `-----BEGIN CERTIFICATE-----
MIID0zCCArugAwIBAgIQVmcdBOpPmUxvEIFHWdJ1lDANBgkqhkiG9w0BAQwFADB7
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEGqxUWqn5aCPnetUkb1PGWthL
q8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8Znc
JZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5o4HyMIHvMB8GA1UdIwQYMBaA
FKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1
xmNjmjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAI
MAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5j
b20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQG
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEM
BQADggEBABns652JLCALBIAdGN5CmXKZFjK9Dpx1WywV4ilAbe7/ctvbq5AfjJXy
ij0IckKJUAfiORVsAYfZFhr1wHUrxeZWEQff2Ji8fJ8ZOd+LygBkc7xGEJuTI42+
FsMuCIKchjN0djsoTI0DQoWz4rIjQtUfenVqGtF8qmchxDM6OW1TyaLtYiKou+JV
bJlsQ2uRl9EMC5MCHdK8aXdJ5htN978UeAOwproLtOGFfy/cQjutdAFI3tZs4RmY
CV4Ks2dH/hzg1cEo70qLRDEmBDeNiXQ2Lu+lIg+DdEmSx/cQwgwp+7e9un/jX9Wf
8qn0dNW44bOwgeThpWOjzOoEeJBuv/c=
-----END CERTIFICATE-----
`
) )
var UpstreamBlockedError = errors.New("blocked by upstream") var UpstreamBlockedError = errors.New("blocked by upstream")
var missingLinkDER, _ = pem.Decode([]byte(MISSING_CHAIN_CERT))
var missingLink, _ = x509.ParseCertificate(missingLinkDER.Bytes)
type Dialer interface { type Dialer interface {
Dial(network, address string) (net.Conn, error) Dial(network, address string) (net.Conn, error)
} }
@ -38,14 +66,18 @@ type ProxyDialer struct {
tlsServerName string tlsServerName string
auth AuthProvider auth AuthProvider
next ContextDialer next ContextDialer
intermediateWorkaround bool
caPool *x509.CertPool
} }
func NewProxyDialer(address, tlsServerName string, auth AuthProvider, nextDialer ContextDialer) *ProxyDialer { func NewProxyDialer(address, tlsServerName string, auth AuthProvider, intermediateWorkaround bool, caPool *x509.CertPool, nextDialer ContextDialer) *ProxyDialer {
return &ProxyDialer{ return &ProxyDialer{
address: address, address: address,
tlsServerName: tlsServerName, tlsServerName: tlsServerName,
auth: auth, auth: auth,
next: nextDialer, next: nextDialer,
intermediateWorkaround: intermediateWorkaround,
caPool: caPool,
} }
} }
@ -79,7 +111,7 @@ func ProxyDialerFromURL(u *url.URL, next ContextDialer) (*ProxyDialer, error) {
return authHeader return authHeader
} }
} }
return NewProxyDialer(address, tlsServerName, auth, next), nil return NewProxyDialer(address, tlsServerName, auth, false, nil, next), nil
} }
func (d *ProxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { func (d *ProxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
@ -105,9 +137,18 @@ func (d *ProxyDialer) DialContext(ctx context.Context, network, address string)
opts := x509.VerifyOptions{ opts := x509.VerifyOptions{
DNSName: d.tlsServerName, DNSName: d.tlsServerName,
Intermediates: x509.NewCertPool(), Intermediates: x509.NewCertPool(),
Roots: d.caPool,
} }
waRequired := false
for _, cert := range cs.PeerCertificates[1:] { for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert) opts.Intermediates.AddCert(cert)
if d.intermediateWorkaround && !waRequired &&
bytes.Compare(cert.AuthorityKeyId, missingLink.SubjectKeyId) == 0 {
waRequired = true
}
}
if waRequired {
opts.Intermediates.AddCert(missingLink)
} }
_, err := cs.PeerCertificates[0].Verify(opts) _, err := cs.PeerCertificates[0].Verify(opts)
return err return err