From fade5f412adf0b2b1bee51860d3eb46a11eb8976 Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Sat, 14 Jul 2018 13:11:00 +0000 Subject: [PATCH] Allow ncdns to automatically sync TLSA records with Firefox cert_override.txt. --- server/server.go | 7 + tlsoverridefirefox/filter.go | 50 +++++++ .../firefoxoverridesync.go | 134 ++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 tlsoverridefirefox/filter.go create mode 100644 tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go diff --git a/server/server.go b/server/server.go index 276ce42..4e909c1 100644 --- a/server/server.go +++ b/server/server.go @@ -14,6 +14,7 @@ import ( "github.com/miekg/dns" "github.com/namecoin/ncdns/backend" "github.com/namecoin/ncdns/namecoin" + "github.com/namecoin/ncdns/tlsoverridefirefox/tlsoverridefirefoxsync" "gopkg.in/hlandau/madns.v1" ) @@ -213,6 +214,12 @@ func (s *Server) Start() error { s.tcpServer = s.runListener("tcp") s.wgStart.Wait() log.Info("Listeners started") + + err := tlsoverridefirefoxsync.Start(s.namecoinConn, s.cfg.CanonicalSuffix) + if err != nil { + return fmt.Errorf("Couldn't start Firefox override sync: %s", err) + } + return nil } diff --git a/tlsoverridefirefox/filter.go b/tlsoverridefirefox/filter.go new file mode 100644 index 0000000..1e41c01 --- /dev/null +++ b/tlsoverridefirefox/filter.go @@ -0,0 +1,50 @@ +package tlsoverridefirefox + +import ( + "fmt" + "net" + "strings" +) + +// FilterOverrides accepts as input the contents of a Firefox cert_override.txt +// and a host suffix to blacklist (usually "bit"). It returns the contents of +// a new Firefox cert_override.txt that contains all overrides as the input +// file except for those that match the host suffix. +func FilterOverrides(overrides, blacklistedHostSuffix string) (string, error) { + result := "" + + overridesSlice := strings.Split(overrides, "\n") + + for _, override := range overridesSlice { + trimmed := strings.TrimSpace(override) + if trimmed == "" { + // This is a blank line; don't try to parse it or + // include it in output. + continue + } + if strings.HasPrefix(trimmed, "#") { + // This is a comment; pass it through verbatim. + result = result + override + "\n" + continue + } + + tabSplit := strings.Split(override, "\t") + hostAndPort := tabSplit[0] + + host, _, err := net.SplitHostPort(hostAndPort) + if err != nil { + // Don't log err since it may contain private data. + return "", fmt.Errorf("Error parsing hostport") + } + + if host == blacklistedHostSuffix || + strings.HasSuffix(host, "."+blacklistedHostSuffix) { + // Host is blacklisted; don't include it in output + continue + } + + result = result + override + "\n" + } + + return result, nil +} diff --git a/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go b/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go new file mode 100644 index 0000000..190a274 --- /dev/null +++ b/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go @@ -0,0 +1,134 @@ +package tlsoverridefirefoxsync + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "sync" + "time" + + "github.com/hlandau/xlog" + "gopkg.in/hlandau/easyconfig.v1/cflag" + + "github.com/namecoin/ncdns/namecoin" + "github.com/namecoin/ncdns/ncdumpzone" + "github.com/namecoin/ncdns/tlsoverridefirefox" +) + +var ( + flagGroup = cflag.NewGroup(nil, "tlsoverridefirefox") + syncEnableFlag = cflag.Bool(flagGroup, "sync", false, + "Synchronize TLSA records from the Namecoin zone to Firefox's "+ + "cert_override.txt") + firefoxProfileDirFlag = cflag.String(flagGroup, "profiledir", "", + "Firefox profile directory") +) + +var log, Log = xlog.New("ncdns.tlsoverridefirefoxsync") + +var zoneData string +var zoneDataReady = false +var zoneDataMux sync.Mutex + +// Note: the reason for the Fatal reaction to errors is that, if we stop +// syncing the override list, Firefox will continue trusting .bit certs that +// might be revoked in Namecoin. Therefore, it is important that, in such a +// situation, .bit domains must stop resolving until the issue is corrected. +// Forcing ncdns to exit is the least complex way to achieve this. + +func watchZone(conn namecoin.Conn) { + for { + var result bytes.Buffer + + err := ncdumpzone.Dump(conn, &result, "firefox-override") + log.Fatale(err, "Couldn't dump zone for Firefox override sync") + + zoneDataMux.Lock() + zoneData = result.String() + zoneDataReady = true + zoneDataMux.Unlock() + + time.Sleep(10 * time.Minute) + } +} + +func watchProfile(suffix string) { + if firefoxProfileDirFlag.Value() == "" { + log.Fatal("Missing required config option tlsoverridefirefox.profiledir") + } + + for { + if profileInUse() { + time.Sleep(1 * time.Second) + continue + } + + // At this point we know that Firefox is not running. + + zoneDataMux.Lock() + zoneDataReadyLocal := zoneDataReady + zoneDataLocal := zoneData + zoneDataMux.Unlock() + + if !zoneDataReadyLocal { + time.Sleep(1 * time.Second) + continue + } + + log.Debug("Syncing zone to cert_override.txt...") + + prevOverrides, err := ioutil.ReadFile( + firefoxProfileDirFlag.Value() + "/cert_override.txt") + if err != nil { + if os.IsNotExist(err) { + // cert_override.txt doesn't exist in a default + // Firefox install; it's only created once the + // first override is configured in the Firefox + // GUI. If it's not there, we can pretend we + // read an empty file. + prevOverrides = []byte(``) + } else { + log.Fatale(err, + "Couldn't read Firefox "+ + "cert_override.txt") + } + } + + filteredPrevOverrides, err := tlsoverridefirefox. + FilterOverrides(string(prevOverrides), suffix) + log.Fatale(err, "Couldn't filter Firefox overrides") + + newOverrides := filteredPrevOverrides + zoneDataLocal + "\n" + + // TODO: Does 0600 match the default behavior of Firefox? + // TODO: maybe instead write to a temp file and then move the file into place? + err = ioutil.WriteFile(firefoxProfileDirFlag.Value()+ + "/cert_override.txt", []byte(newOverrides), 0600) + log.Fatale(err, "Couldn't write Firefox cert_override.txt") + + log.Debug("Finished syncing zone to cert_override.txt") + + time.Sleep(10 * time.Minute) + } +} + +func profileInUse() bool { + // This glob pattern matches the ".sqlite-wal" and ".sqlite-shm" files + // that are only present when Firefox's databases are open. + matches, err := filepath.Glob(firefoxProfileDirFlag.Value() + "/*.sqlite-*") + log.Fatale(err, "Couldn't check if Firefox is running for override sync") + + return matches != nil +} + +// Start starts 2 background threads that synchronize the blockchain's TLSA +// records to a Firefox profile's cert_override.txt. It accepts a connection +// to access Namecoin Core, as well as a host suffix (usually "bit"). +func Start(conn namecoin.Conn, suffix string) error { + if syncEnableFlag.Value() { + go watchZone(conn) + go watchProfile(suffix) + } + return nil +}