git » debian:dnss » commit da7ed40

resolver: Fix custom CA pool file

author Alberto Bertogli
2020-07-28 00:36:36 UTC
committer Alberto Bertogli
2020-07-28 00:38:12 UTC
parent 31265e77643fdfa02fa25b727d5d98e5c6b24367

resolver: Fix custom CA pool file

The httpresolver supports receiving a custom file to use as root CA set,
via the --https_client_cafile option.

Unfortunately, the code handling this was configuring the TLS transport
incorrectly, and this file wasn't used as root CA.

So in practice the system CA set was used even if this option was passed.

This patch fixes this issue by setting the right internal option, and
also adds tests for this case.

internal/httpresolver/resolver.go +1 -1
tests/external.sh +30 -0
tests/generate_cert.go +123 -0

diff --git a/internal/httpresolver/resolver.go b/internal/httpresolver/resolver.go
index 6bf2cac..665fa92 100644
--- a/internal/httpresolver/resolver.go
+++ b/internal/httpresolver/resolver.go
@@ -73,7 +73,7 @@ func (r *httpsResolver) Init() error {
 		}
 
 		transport.TLSClientConfig = &tls.Config{
-			ClientCAs: pool,
+			RootCAs: pool,
 		}
 	}
 
diff --git a/tests/external.sh b/tests/external.sh
index ccd5f5b..732e3b8 100755
--- a/tests/external.sh
+++ b/tests/external.sh
@@ -87,6 +87,16 @@ function get() {
 	wget -O.wget.out $URL > .wget.log 2>&1
 }
 
+function generate_certs() {
+	mkdir -p .certs/localhost
+	(
+		cd .certs/localhost
+		go run ../../tests/generate_cert.go \
+			-ca -duration=1h --host=localhost
+	)
+}
+
+
 echo "## Misc"
 # Missing arguments (expect it to fail).
 dnss
@@ -137,6 +147,26 @@ kill $PID
 kill $HTTP_PID
 
 
+echo "## HTTPS with custom certificates"
+generate_certs
+dnss -enable_https_to_dns \
+	-https_key .certs/localhost/privkey.pem \
+	-https_cert .certs/localhost/fullchain.pem \
+	-https_server_addr "localhost:1999"
+HTTP_PID=$PID
+mv .dnss.log .dnss.http.log
+wait_until_ready tcp 1999
+
+dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
+	-https_client_cafile .certs/localhost/fullchain.pem \
+	-https_upstream "https://localhost:1999/dns-query"
+
+resolve
+
+kill $PID
+kill $HTTP_PID
+
+
 # DoH integration test against some publicly available servers.
 # https://github.com/curl/curl/wiki/DNS-over-HTTPS#publicly-available-servers
 # Note not all of the ones in the list are actually functional.
diff --git a/tests/generate_cert.go b/tests/generate_cert.go
new file mode 100644
index 0000000..4fbf706
--- /dev/null
+++ b/tests/generate_cert.go
@@ -0,0 +1,123 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+// Generate a self-signed X.509 certificate for a TLS server. Outputs to
+// 'cert.pem' and 'key.pem' and will overwrite existing files.
+
+package main
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"flag"
+	"fmt"
+	"log"
+	"math/big"
+	"net"
+	"os"
+	"strings"
+	"time"
+
+	"golang.org/x/net/idna"
+)
+
+var (
+	host      = flag.String("host", "", "Comma-separated hostnames and IPs to generate a certificate for")
+	validFrom = flag.String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011")
+	validFor  = flag.Duration("duration", 365*24*time.Hour, "Duration that certificate is valid for")
+	isCA      = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority")
+)
+
+func main() {
+	flag.Parse()
+
+	if len(*host) == 0 {
+		log.Fatalf("Missing required --host parameter")
+	}
+
+	priv, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		log.Fatalf("failed to generate private key: %s", err)
+	}
+
+	var notBefore time.Time
+	if len(*validFrom) == 0 {
+		notBefore = time.Now()
+	} else {
+		notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to parse creation date: %s\n", err)
+			os.Exit(1)
+		}
+	}
+
+	notAfter := notBefore.Add(*validFor)
+
+	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+	if err != nil {
+		log.Fatalf("failed to generate serial number: %s", err)
+	}
+
+	template := x509.Certificate{
+		SerialNumber: serialNumber,
+		Subject: pkix.Name{
+			Organization: []string{"Acme Co"},
+		},
+		NotBefore: notBefore,
+		NotAfter:  notAfter,
+
+		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+		BasicConstraintsValid: true,
+	}
+
+	hosts := strings.Split(*host, ",")
+	for _, h := range hosts {
+		if ip := net.ParseIP(h); ip != nil {
+			template.IPAddresses = append(template.IPAddresses, ip)
+		} else {
+			// We use IDNA-encoded DNS names, otherwise the TLS library won't
+			// load the certificates.
+			ih, err := idna.ToASCII(h)
+			if err != nil {
+				log.Fatalf("host %q cannot be IDNA-encoded: %v", h, err)
+			}
+			template.DNSNames = append(template.DNSNames, ih)
+		}
+	}
+
+	if *isCA {
+		template.IsCA = true
+		template.KeyUsage |= x509.KeyUsageCertSign
+	}
+
+	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
+	if err != nil {
+		log.Fatalf("Failed to create certificate: %s", err)
+	}
+
+	certOut, err := os.Create("fullchain.pem")
+	if err != nil {
+		log.Fatalf("failed to open fullchain.pem for writing: %s", err)
+	}
+	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+	certOut.Close()
+
+	keyOut, err := os.OpenFile("privkey.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		log.Fatalf("failed to open privkey.pem for writing: %s", err)
+		return
+	}
+
+	block := &pem.Block{Type: "RSA PRIVATE KEY",
+		Bytes: x509.MarshalPKCS1PrivateKey(priv)}
+	pem.Encode(keyOut, block)
+	keyOut.Close()
+}