This repository has been archived on 2019-09-10. You can view files and clone it, but cannot push or open issues or pull requests.
asaur/cmd_onion.go

265 lines
7.9 KiB
Go

// 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
// <http://creativecommons.org/publicdomain/zero/1.0/> 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
}