From 6258396dc721ac91fc6b3bb74002e58530866404 Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 9 Oct 2018 12:56:25 -0700 Subject: [PATCH] check current onion descriptors on old versions of tor to see if they're out-of-sync --- cmd_onion.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++-- listener.go | 2 +- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/cmd_onion.go b/cmd_onion.go index ab11663..ddac1fa 100644 --- a/cmd_onion.go +++ b/cmd_onion.go @@ -14,7 +14,11 @@ import ( "fmt" "strings" + "errors" "github.com/yawning/bulb/utils/pkcs1" + "golang.org/x/net/proxy" + "log" + "strconv" ) // OnionInfo is the result of the AddOnion command. @@ -39,6 +43,7 @@ type OnionPortSpec struct { // NewOnionConfig is a configuration for NewOnion command. type NewOnionConfig struct { + Onion string PortSpecs []OnionPortSpec PrivateKey crypto.PrivateKey DiscardPK bool @@ -49,7 +54,7 @@ type NewOnionConfig struct { // NewOnion issues an ADD_ONION command using configuration config and // returns the parsed response. -func (c *Conn) NewOnion(config *NewOnionConfig) (*OnionInfo, error) { +func (c *Conn) NewOnion(config *NewOnionConfig, dontCheckDescriptor bool) (*OnionInfo, error) { const keyTypeRSA = "RSA1024" var err error @@ -109,9 +114,41 @@ func (c *Conn) NewOnion(config *NewOnionConfig) (*OnionInfo, error) { flagsStr = " Flags=" flagsStr += strings.Join(flags, ",") } + request := fmt.Sprintf("ADD_ONION %s:%s%s%s", hsKeyType, hsKeyStr, portStr, flagsStr) resp, err := c.Request(request) - if err != nil && fmt.Sprintf("%v", err) != "550 Unspecified Tor error: Onion address collision" { + if err == nil && !dontCheckDescriptor { + pi, _ := c.ProtocolInfo() + // 0.3.5.1 + torversion := strings.Split(pi.TorVersion, ".") + tva, _ := strconv.Atoi(torversion[0]) + tvb, _ := strconv.Atoi(torversion[1]) + tvc, _ := strconv.Atoi(torversion[2]) + if tva == 0 && (tvb < 3 || (tvb == 3 && tvc < 5)) { + // here we need to check if the descriptor upload succeeded + log.Println("running a descriptor check because you are on tor version < 0.3.5.1-alpha. this will take a little while but only happens once per tor process--onion service pair") + HSFetch(config.Onion) + crc, err := c.GetClientRevisionCounter(config.Onion) + if err != nil { + log.Printf("%v\n", err) + } + src, err := c.GetServiceRevisionCounter(config.Onion) + if err != nil { + log.Printf("%v\n", err) + } + if crc > src { + log.Printf("uh oh: your tor process is using a service descriptor revision counter of %d\n", src) + log.Printf("whereas the HSDirs have revision %d. this is a bug in tor < 0.3.5.1-alpha\n", crc) + log.Printf("your have two options to fix it:\n") + log.Printf("- upgrade to tor >= 0.3.5.1-alpha [recommended]\n") + log.Printf("- run this listener %d more times [not a great idea but it'll work]\n", crc-src) + log.Printf("the revision counter is only reset when the tor process restarts, so try not to do that in order to prevent this problem from reoccurring\n") + return nil, errors.New("client descriptor is newer than service descriptor") + } + fmt.Printf("crc: %v src: %v\n", crc, src) + } + } else if fmt.Sprintf("%v", err) != "550 Unspecified Tor error: Onion address collision" { + // (don't worry if there is an address collision -- it's a good thing and means we're using the cached circuits) return nil, err } @@ -171,7 +208,7 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo cfg.PrivateKey = key } cfg.DiscardPK = oneshot - return c.NewOnion(cfg) + return c.NewOnion(cfg, false) } // DeleteOnion issues a DEL_ONION command and returns the parsed response. @@ -179,3 +216,51 @@ func (c *Conn) DeleteOnion(serviceID string) error { _, err := c.Request("DEL_ONION %s", serviceID) return err } + +// run HSFETCH for an onion, in order to put its onion service descriptor into the client cache +// unfortunately we don't actually do this, because HSFETCH doesn't support v3 yet +// instead we make a shortlived connection to it, which has a side effect of putting the descriptor into cache +// see https://trac.torproject.org/projects/tor/ticket/25417 for details +func HSFetch(onion string) error { + torDialer, err := proxy.SOCKS5("tcp", "127.0.0.1:9050", nil, proxy.Direct) + if err != nil { + return err + } + conn, err := torDialer.Dial("tcp", onion+".onion:9878") + if err != nil { + return err + } + conn.Close() + return nil +} + +func (c *Conn) GetClientRevisionCounter(onion string) (uint64, error) { + return c.getRevisionCounterHelper(onion, "client") +} + +func (c *Conn) GetServiceRevisionCounter(onion string) (uint64, error) { + return c.getRevisionCounterHelper(onion, "service") +} + +func (c *Conn) getRevisionCounterHelper(onion string, cachetype string) (uint64, error) { + _, err := c.conn.Cmd("GETINFO hs/%v/desc/id/%v", cachetype, onion) + if err != nil { + return 0, err + } + + var rc uint64 + reterr := errors.New("could not find revision counter in onion descriptor") + for { + line, err := c.conn.ReadLine() + if err != nil { + return 0, err + } + if strings.HasPrefix(line, "250 OK") { + return rc, reterr + } else if strings.HasPrefix(line, "revision-counter ") { + rc, reterr = strconv.ParseUint(line[17:], 10, 64) + } + } + + return rc, reterr +} diff --git a/listener.go b/listener.go index d8bba8f..7b6116b 100644 --- a/listener.go +++ b/listener.go @@ -114,7 +114,7 @@ func (c *Conn) RecoverListener(config *NewOnionConfig, onion string, vports ...u } cfg.PortSpecs = portSpecs // Create the onion. - oi, err := c.NewOnion(&cfg) + oi, err := c.NewOnion(&cfg, false) if err != nil { tcpListener.Close() return nil, err