From e58c790b2beec673703eab0640de7cb3b72c2255 Mon Sep 17 00:00:00 2001 From: George Aristy Date: Tue, 4 Feb 2025 19:05:03 -0500 Subject: [PATCH] DOT+DOH: support for root-cas file --- .gitignore | 1 + cmd/doggo/cli.go | 23 +++++++++++++++++++++++ cmd/doggo/help.go | 1 + pkg/models/models.go | 1 + pkg/resolvers/classic.go | 1 + pkg/resolvers/doh.go | 2 ++ pkg/resolvers/doq.go | 1 + pkg/resolvers/resolver.go | 2 ++ 8 files changed, 32 insertions(+) diff --git a/.gitignore b/.gitignore index 4d06b6f..67d3157 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.so *.dylib dist/ +.idea/ # Test binary, built with `go test -c` *.test diff --git a/cmd/doggo/cli.go b/cmd/doggo/cli.go index 212e12f..9d8edda 100644 --- a/cmd/doggo/cli.go +++ b/cmd/doggo/cli.go @@ -1,6 +1,7 @@ package main import ( + "crypto/x509" "fmt" "net/http" "os" @@ -67,6 +68,7 @@ func main() { f.String("tls-hostname", "", "Provide a hostname for doing verification of the certificate if the provided DoT nameserver is an IP") f.Bool("skip-hostname-verification", false, "Skip TLS Hostname Verification") f.Var(&headerFlags, "header", "Headers to supply to DoH resolvers") + f.String("root-cas", "", "Root CAs file to use for TLS verification") // Output Options f.BoolP("json", "J", false, "Set the output format as JSON") @@ -118,6 +120,8 @@ func main() { app.QueryFlags.QClasses = append(app.QueryFlags.QClasses, qc...) app.QueryFlags.QNames = append(app.QueryFlags.QNames, qn...) + app.Logger.WithField("query_flags", fmt.Sprintf("%+v", app.QueryFlags)).Debug("Loaded config") + // Check if reverse flag is passed. If it is, then set // query type as PTR and query class as IN. // Modify query name like 94.2.0.192.in-addr.arpa if it's an IPv4 address. @@ -151,6 +155,24 @@ func main() { } } + var certPool *x509.CertPool + + if app.QueryFlags.RootCAs != "" { + certPool = x509.NewCertPool() + + rootCAs, err := os.ReadFile(app.QueryFlags.RootCAs) + if err != nil { + app.Logger.WithError(err).Error("error reading root CAs") + app.Logger.Exit(2) + } + + ok := certPool.AppendCertsFromPEM(rootCAs) + if !ok { + app.Logger.Error("error loading root CAs") + app.Logger.Exit(2) + } + } + // Load Resolvers. rslvrs, err := resolvers.LoadResolvers(resolvers.Options{ Nameservers: app.Nameservers, @@ -164,6 +186,7 @@ func main() { InsecureSkipVerify: app.QueryFlags.InsecureSkipVerify, TLSHostname: app.QueryFlags.TLSHostname, Headers: resolverHeaders, + RootCAs: certPool, }) if err != nil { app.Logger.WithError(err).Error("error loading resolver") diff --git a/cmd/doggo/help.go b/cmd/doggo/help.go index f71c8c9..40f0f22 100644 --- a/cmd/doggo/help.go +++ b/cmd/doggo/help.go @@ -57,6 +57,7 @@ var appHelpTextTemplate = `{{ "NAME" | color "" "heading" }}: {{"--tls-hostname=HOSTNAME" | color "yellow" ""}} Provide a hostname for doing verification of the certificate if the provided DoT nameserver is an IP. {{"--skip-hostname-verification" | color "yellow" ""}} Skip TLS Hostname Verification in case of DOT Lookups. {{"--header" | color "yellow" ""}} HTTP headers to supply to the resolver. Can supply multiple times. + {{"--root-cas=FILE" | color "yellow" ""}} Root CAs file to use for TLS verification. {{ "Output Options" | color "" "heading" }}: {{"-J, --json " | color "yellow" ""}} Format the output as JSON. diff --git a/pkg/models/models.go b/pkg/models/models.go index eb21021..109d065 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -40,6 +40,7 @@ type QueryFlags struct { Strategy string `koanf:"strategy" strategy:"-"` InsecureSkipVerify bool `koanf:"skip-hostname-verification" skip-hostname-verification:"-"` TLSHostname string `koanf:"tls-hostname" tls-hostname:"-"` + RootCAs string `koanf:"root-cas"` } // Nameserver represents the type of Nameserver diff --git a/pkg/resolvers/classic.go b/pkg/resolvers/classic.go index 366e1d0..5a8ee1f 100644 --- a/pkg/resolvers/classic.go +++ b/pkg/resolvers/classic.go @@ -46,6 +46,7 @@ func NewClassicResolver(server string, classicOpts ClassicResolverOpts, resolver client.TLSConfig = &tls.Config{ ServerName: resolverOpts.TLSHostname, InsecureSkipVerify: resolverOpts.InsecureSkipVerify, + RootCAs: resolverOpts.RootCAs, } } diff --git a/pkg/resolvers/doh.go b/pkg/resolvers/doh.go index 3031b8e..87beb1d 100644 --- a/pkg/resolvers/doh.go +++ b/pkg/resolvers/doh.go @@ -41,6 +41,7 @@ func NewDOHResolver(server string, resolverOpts Options, doh3 bool) (Resolver, e Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: resolverOpts.InsecureSkipVerify, + RootCAs: resolverOpts.RootCAs, }, }, } @@ -49,6 +50,7 @@ func NewDOHResolver(server string, resolverOpts Options, doh3 bool) (Resolver, e httpClient.Transport = &http3.RoundTripper{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: resolverOpts.InsecureSkipVerify, + RootCAs: resolverOpts.RootCAs, }, } } diff --git a/pkg/resolvers/doq.go b/pkg/resolvers/doq.go index 8aa1446..37f82ba 100644 --- a/pkg/resolvers/doq.go +++ b/pkg/resolvers/doq.go @@ -28,6 +28,7 @@ func NewDOQResolver(server string, resolverOpts Options) (Resolver, error) { return &DOQResolver{ tls: &tls.Config{ NextProtos: []string{"doq"}, + RootCAs: resolverOpts.RootCAs, }, server: server, resolverOptions: resolverOpts, diff --git a/pkg/resolvers/resolver.go b/pkg/resolvers/resolver.go index e016cdb..6e60d91 100644 --- a/pkg/resolvers/resolver.go +++ b/pkg/resolvers/resolver.go @@ -1,6 +1,7 @@ package resolvers import ( + "crypto/x509" "net/http" "time" @@ -24,6 +25,7 @@ type Options struct { InsecureSkipVerify bool TLSHostname string Headers http.Header + RootCAs *x509.CertPool } // Resolver implements the configuration for a DNS