// cmd_onion.go - various onion service commands: ADD_ONION, DEL_ONION... // // To the extent possible under law, David Stainton and Ivan Markin waived // all copyright and related or neighboring rights to this module of bulb, // using the creative commons "cc0" public domain dedication. See LICENSE or // for full details. package asaur import ( "crypto" "crypto/rsa" "encoding/base64" "errors" "fmt" "git.openprivacy.ca/openprivacy/asaur/utils/pkcs1" "golang.org/x/net/proxy" "log" "strconv" "strings" ) // OnionInfo is the result of the AddOnion command. type OnionInfo struct { OnionID string PrivateKey crypto.PrivateKey RawResponse *Response } // OnionPrivateKey is a unknown Onion private key (crypto.PublicKey). type OnionPrivateKey struct { KeyType string Key string } // OnionPortSpec is a Onion VirtPort/Target pair. type OnionPortSpec struct { VirtPort uint16 Target string } // NewOnionConfig is a configuration for NewOnion command. type NewOnionConfig struct { Onion string PortSpecs []OnionPortSpec PrivateKey crypto.PrivateKey DiscardPK bool Detach bool BasicAuth bool NonAnonymous bool } // NewOnion issues an ADD_ONION command using configuration config and // returns the parsed response. func (c *Conn) NewOnion(config *NewOnionConfig, dontCheckDescriptor bool) (*OnionInfo, error) { const keyTypeRSA = "RSA1024" var err error var portStr string if config.PortSpecs == nil { return nil, newProtocolError("invalid port specification") } for _, v := range config.PortSpecs { portStr += fmt.Sprintf(" Port=%d", v.VirtPort) if v.Target != "" { portStr += "," + v.Target } } var hsKeyType, hsKeyStr string if config.PrivateKey != nil { switch t := config.PrivateKey.(type) { case *rsa.PrivateKey: rsaPK, _ := config.PrivateKey.(*rsa.PrivateKey) if rsaPK.N.BitLen() != 1024 { return nil, newProtocolError("invalid RSA key size") } pkDER, err := pkcs1.EncodePrivateKeyDER(rsaPK) if err != nil { return nil, newProtocolError("failed to serialize RSA key: %v", err) } hsKeyType = keyTypeRSA hsKeyStr = base64.StdEncoding.EncodeToString(pkDER) case *OnionPrivateKey: genericPK, _ := config.PrivateKey.(*OnionPrivateKey) hsKeyType = genericPK.KeyType hsKeyStr = genericPK.Key default: return nil, newProtocolError("unsupported private key type: %v", t) } } else { hsKeyStr = "BEST" hsKeyType = "NEW" } var flags []string var flagsStr string if config.DiscardPK { flags = append(flags, "DiscardPK") } if config.Detach { flags = append(flags, "Detach") } if config.BasicAuth { flags = append(flags, "BasicAuth") } if config.NonAnonymous { flags = append(flags, "NonAnonymous") } if flags != nil { 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 && !dontCheckDescriptor { pi, _ := c.ProtocolInfo() torversion := strings.Split(pi.TorVersion, ".") //eg: 0.3.4.8 or 0.3.5.1-alpha 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("this onion service will not be visible to others until the counters are synced.\n") 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") } } } 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 } // Parse out the response. var serviceID string var hsPrivateKey crypto.PrivateKey for _, l := range resp.Data { const ( serviceIDPrefix = "ServiceID=" privateKeyPrefix = "PrivateKey=" ) if strings.HasPrefix(l, serviceIDPrefix) { serviceID = strings.TrimPrefix(l, serviceIDPrefix) } else if strings.HasPrefix(l, privateKeyPrefix) { if config.DiscardPK || hsKeyStr != "" { return nil, newProtocolError("received an unexpected private key") } hsKeyStr = strings.TrimPrefix(l, privateKeyPrefix) splitKey := strings.SplitN(hsKeyStr, ":", 2) if len(splitKey) != 2 { return nil, newProtocolError("failed to parse private key type") } switch splitKey[0] { case keyTypeRSA: keyBlob, err := base64.StdEncoding.DecodeString(splitKey[1]) if err != nil { return nil, newProtocolError("failed to base64 decode RSA key: %v", err) } hsPrivateKey, _, err = pkcs1.DecodePrivateKeyDER(keyBlob) if err != nil { return nil, newProtocolError("failed to deserialize RSA key: %v", err) } default: hsPrivateKey := new(OnionPrivateKey) hsPrivateKey.KeyType = splitKey[0] hsPrivateKey.Key = splitKey[1] } } } oi := new(OnionInfo) oi.RawResponse = resp oi.OnionID = serviceID oi.PrivateKey = hsPrivateKey return oi, nil } // [DEPRECATED] AddOnion issues an ADD_ONION command and // returns the parsed response. func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bool) (*OnionInfo, error) { cfg := &NewOnionConfig{} cfg.PortSpecs = ports if key != nil { cfg.PrivateKey = key } cfg.DiscardPK = oneshot return c.NewOnion(cfg, false) } // DeleteOnion issues a DEL_ONION command and returns the parsed response. 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 }