mirror of
https://github.com/Snawoot/opera-proxy.git
synced 2025-09-02 10:42:07 +00:00
Implement workarounds for poor system CA pools
This commit is contained in:
parent
67e4886843
commit
c42142f5d6
2 changed files with 87 additions and 25 deletions
51
main.go
51
main.go
|
@ -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"
|
||||||
|
@ -42,20 +44,22 @@ func arg_fail(msg string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CLIArgs struct {
|
type CLIArgs struct {
|
||||||
country string
|
country string
|
||||||
listCountries bool
|
listCountries bool
|
||||||
listProxies bool
|
listProxies bool
|
||||||
bindAddress string
|
bindAddress string
|
||||||
verbosity int
|
verbosity int
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
showVersion bool
|
showVersion bool
|
||||||
proxy string
|
proxy string
|
||||||
apiLogin string
|
apiLogin string
|
||||||
apiPassword string
|
apiPassword string
|
||||||
apiAddress string
|
apiAddress string
|
||||||
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)
|
||||||
|
|
61
upstream.go
61
upstream.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -34,18 +62,22 @@ type ContextDialer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyDialer struct {
|
type ProxyDialer struct {
|
||||||
address string
|
address string
|
||||||
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
|
||||||
|
|
Loading…
Add table
Reference in a new issue