265 lines
7.9 KiB
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
|
|
}
|