Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLS Client authentication #172

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ dnsproxy.exe
example.crt
example.key
coverage.txt
config.yaml
config.yaml
client.crt
client.key
tlsclient.crt
tlsclient.key
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Application Options:
--version Prints the program version

Help Options:
-h, --help Show this help message
-h, --help Show this help message
```

## Examples
Expand Down
32 changes: 26 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ type Options struct {
// Upstream DNS servers settings
// --

// DoH Upstream Authentication

// Path to the .crt with the client-side certificate for upstream client authentication
TLSClientCertPath string `yaml:"tls-client-crt" long:"tls-client-crt" description:"Path to the file with the TLS certificate used for TLS client authentication (supported by DoH/DoT/DoQ)"`

// Path to the file with the client-side private key for upstream client authentication
TLSClientKeyPath string `yaml:"tls-client-key" long:"tls-client-key" description:"Path to the file with the TLS certificate used for TLS client authentication (supported by DoH/DoT/DoQ)"`

// DNS upstreams
Upstreams []string `yaml:"upstream" short:"u" long:"upstream" description:"An upstream to be used (can be specified multiple times). You can also specify path to a file with the list of servers" optional:"false"`

Expand Down Expand Up @@ -278,6 +286,7 @@ func createProxyConfig(options *Options) proxy.Config {
MaxGoroutines: options.MaxGoRoutines,
}

initTLSClient(&config, options)
initUpstreams(&config, options)
initEDNS(&config, options)
initBogusNXDomain(&config, options)
Expand All @@ -288,14 +297,14 @@ func createProxyConfig(options *Options) proxy.Config {
return config
}

// initUpstreams inits upstream-related config
func initUpstreams(config *proxy.Config, options *Options) {
// Init upstreams
upstreams := loadServersList(options.Upstreams)
upsOpts := &upstream.Options{
InsecureSkipVerify: options.Insecure,
Bootstrap: options.BootstrapDNS,
Timeout: defaultTimeout,
InsecureSkipVerify: options.Insecure,
Bootstrap: options.BootstrapDNS,
Timeout: defaultTimeout,
TLSClientCertificates: config.TLSClientCertificates,
}
upstreamConfig, err := proxy.ParseUpstreamsConfig(upstreams, upsOpts)
if err != nil {
Expand Down Expand Up @@ -374,6 +383,18 @@ func initTLSConfig(config *proxy.Config, options *Options) {
}
}

// initTLSConfig inits the DoH Client Auth TLS config
func initTLSClient(config *proxy.Config, options *Options) {
if options.TLSClientCertPath != "" && options.TLSClientKeyPath != "" {
cert, err := loadX509KeyPair(options.TLSClientCertPath, options.TLSClientKeyPath)
if err != nil {
log.Fatalf("could not load TLS cert for TLS client authentication: %s", err)
return
}
config.TLSClientCertificates = &cert
}
}

// initDNSCryptConfig inits the DNSCrypt config
func initDNSCryptConfig(config *proxy.Config, options *Options) {
if options.DNSCryptConfigPath == "" {
Expand Down Expand Up @@ -539,9 +560,8 @@ func newTLSConfig(options *Options) (*tls.Config, error) {

cert, err := loadX509KeyPair(options.TLSCertPath, options.TLSKeyPath)
if err != nil {
return nil, fmt.Errorf("could not load TLS cert: %s", err)
return nil, fmt.Errorf("could not load TLS cert for TLS server: %s", err)
}

return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: uint16(tlsMinVersion), MaxVersion: uint16(tlsMaxVersion)}, nil
}

Expand Down
7 changes: 4 additions & 3 deletions proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ type Config struct {
// Encryption configuration
// --

TLSConfig *tls.Config // necessary for TLS, HTTPS, QUIC
DNSCryptProviderName string // DNSCrypt provider name
DNSCryptResolverCert *dnscrypt.Cert // DNSCrypt resolver certificate
TLSConfig *tls.Config // necessary for TLS, HTTPS, QUIC
TLSClientCertificates *tls.Certificate // necessary for DoH/DoT/DoQ Client Authentication
DNSCryptProviderName string // DNSCrypt provider name
DNSCryptResolverCert *dnscrypt.Cert // DNSCrypt resolver certificate

// Rate-limiting and anti-DNS amplification measures
// --
Expand Down
8 changes: 5 additions & 3 deletions proxy/upstreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ func ParseUpstreamsConfig(upstreamConfig []string, options *upstream.Options) (*
dnsUpstream, err = upstream.AddressToUpstream(
u,
&upstream.Options{
Bootstrap: options.Bootstrap,
Timeout: options.Timeout,
InsecureSkipVerify: options.InsecureSkipVerify,
Bootstrap: options.Bootstrap,
Timeout: options.Timeout,
InsecureSkipVerify: options.InsecureSkipVerify,
TLSClientCertificates: options.TLSClientCertificates,
})
if err != nil {
err = fmt.Errorf("cannot prepare the upstream %s (%s): %s", l, options.Bootstrap, err)
Expand All @@ -86,6 +87,7 @@ func ParseUpstreamsConfig(upstreamConfig []string, options *upstream.Options) (*
}
}
}

return &UpstreamConfig{
Upstreams: upstreams,
DomainReservedUpstreams: domainReservedUpstreams,
Expand Down
19 changes: 16 additions & 3 deletions upstream/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func newBootstrapperResolved(upsURL *url.URL, options *Options) (*bootstrapper,
}
b.dialContext = b.createDialContext(resolverAddresses)
b.resolvedConfig = b.createTLSConfig(host)

return b, nil
}

Expand Down Expand Up @@ -160,7 +159,6 @@ func (n *bootstrapper) get() (*tls.Config, dialHandler, error) {
} else {
ctx = context.Background()
}

addrs, err := LookupParallel(ctx, n.resolvers, host)
if err != nil {
return nil, nil, fmt.Errorf("lookup %s: %w", host, err)
Expand All @@ -179,12 +177,12 @@ func (n *bootstrapper) get() (*tls.Config, dialHandler, error) {
// couldn't find any suitable IP address
return nil, nil, fmt.Errorf("couldn't find any suitable IP address for host %s", host)
}

n.Lock()
defer n.Unlock()

n.dialContext = n.createDialContext(resolved)
n.resolvedConfig = n.createTLSConfig(host)

return n.resolvedConfig, n.dialContext, nil
}

Expand Down Expand Up @@ -212,6 +210,21 @@ func (n *bootstrapper) createTLSConfig(host string) *tls.Config {
tlsConfig.NextProtos = compatProtoDQ
}

if n.options.TLSClientCertificates != nil {
log.Printf("Passing TLS configuration with client authentication")
tlsConfig.Certificates = []tls.Certificate{*n.options.TLSClientCertificates}
}

// The supported application level protocols should be specified only
// for DNS-over-HTTPS and DNS-over-QUIC connections.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2681.
if n.URL.Scheme != "tls" {
tlsConfig.NextProtos = append([]string{
"http/1.1", http2.NextProtoTLS, NextProtoDQ,
}, compatProtoDQ...)
}

return tlsConfig
}

Expand Down
1 change: 0 additions & 1 deletion upstream/parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ type lookupResult struct {
// Return nil and error if count of errors equals count of resolvers
func LookupParallel(ctx context.Context, resolvers []*Resolver, host string) ([]net.IPAddr, error) {
size := len(resolvers)

if size == 0 {
return nil, errors.Error("no resolvers specified")
}
Expand Down
4 changes: 4 additions & 0 deletions upstream/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package upstream

import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
Expand Down Expand Up @@ -45,6 +46,8 @@ type Options struct {
// VerifyDNSCryptCertificate is callback to which the DNSCrypt server certificate will be passed.
// is called in dnsCrypt.exchangeDNSCrypt; if error != nil then Upstream.Exchange() will return it
VerifyDNSCryptCertificate func(cert *dnscrypt.Cert) error

TLSClientCertificates *tls.Certificate // TLS certificates when DoH/DoT/DoQ Client Authentication is used
}

// Parse "host:port" string and validate port number
Expand Down Expand Up @@ -76,6 +79,7 @@ func AddressToUpstream(address string, options *Options) (Upstream, error) {
}

if strings.Contains(address, "://") {

upstreamURL, err := url.Parse(address)
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", address, err)
Expand Down