2015-09-30 16:02:47 +00:00
// cmd_onion.go - various onion service commands: ADD_ONION, DEL_ONION...
//
2016-11-10 18:21:42 +00:00
// 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
2015-09-30 16:02:47 +00:00
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
2018-10-09 21:29:46 +00:00
package asaur
2015-09-30 16:02:47 +00:00
import (
2015-10-10 14:58:06 +00:00
"crypto"
"crypto/rsa"
"encoding/base64"
2018-10-09 19:56:25 +00:00
"errors"
2015-09-30 16:02:47 +00:00
"fmt"
2018-10-09 21:29:46 +00:00
"git.openprivacy.ca/openprivacy/asaur/utils/pkcs1"
2018-10-09 19:56:25 +00:00
"golang.org/x/net/proxy"
"log"
"strconv"
2018-10-09 22:00:53 +00:00
"strings"
2015-09-30 16:02:47 +00:00
)
// OnionInfo is the result of the AddOnion command.
type OnionInfo struct {
2015-10-10 14:58:06 +00:00
OnionID string
PrivateKey crypto . PrivateKey
RawResponse * Response
}
// OnionPrivateKey is a unknown Onion private key (crypto.PublicKey).
type OnionPrivateKey struct {
2015-09-30 16:02:47 +00:00
KeyType string
Key string
2015-10-10 14:58:06 +00:00
}
2015-09-30 16:02:47 +00:00
2015-10-10 14:58:06 +00:00
// OnionPortSpec is a Onion VirtPort/Target pair.
type OnionPortSpec struct {
VirtPort uint16
Target string
2015-09-30 16:02:47 +00:00
}
2016-11-10 18:21:42 +00:00
// NewOnionConfig is a configuration for NewOnion command.
type NewOnionConfig struct {
2018-10-09 19:56:25 +00:00
Onion string
2016-11-10 18:21:42 +00:00
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.
2018-10-09 19:56:25 +00:00
func ( c * Conn ) NewOnion ( config * NewOnionConfig , dontCheckDescriptor bool ) ( * OnionInfo , error ) {
2015-10-10 14:58:06 +00:00
const keyTypeRSA = "RSA1024"
var err error
var portStr string
2016-11-10 18:21:42 +00:00
if config . PortSpecs == nil {
2015-10-10 14:58:06 +00:00
return nil , newProtocolError ( "invalid port specification" )
}
2016-11-10 18:21:42 +00:00
for _ , v := range config . PortSpecs {
2015-10-10 14:58:06 +00:00
portStr += fmt . Sprintf ( " Port=%d" , v . VirtPort )
if v . Target != "" {
portStr += "," + v . Target
}
}
var hsKeyType , hsKeyStr string
2016-11-10 18:21:42 +00:00
if config . PrivateKey != nil {
switch t := config . PrivateKey . ( type ) {
2015-10-10 14:58:06 +00:00
case * rsa . PrivateKey :
2016-11-10 18:21:42 +00:00
rsaPK , _ := config . PrivateKey . ( * rsa . PrivateKey )
2015-10-10 14:58:06 +00:00
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 :
2016-11-10 18:21:42 +00:00
genericPK , _ := config . PrivateKey . ( * OnionPrivateKey )
2015-10-10 14:58:06 +00:00
hsKeyType = genericPK . KeyType
hsKeyStr = genericPK . Key
default :
return nil , newProtocolError ( "unsupported private key type: %v" , t )
}
2016-11-10 18:21:42 +00:00
} else {
hsKeyStr = "BEST"
hsKeyType = "NEW"
2015-10-10 14:58:06 +00:00
}
2015-09-30 16:02:47 +00:00
2016-11-10 18:21:42 +00:00
var flags [ ] string
var flagsStr string
if config . DiscardPK {
flags = append ( flags , "DiscardPK" )
}
if config . Detach {
flags = append ( flags , "Detach" )
2015-09-30 16:02:47 +00:00
}
2016-11-10 18:21:42 +00:00
if config . BasicAuth {
flags = append ( flags , "BasicAuth" )
}
if config . NonAnonymous {
flags = append ( flags , "NonAnonymous" )
}
if flags != nil {
flagsStr = " Flags="
flagsStr += strings . Join ( flags , "," )
}
2018-10-09 19:56:25 +00:00
2016-11-10 18:21:42 +00:00
request := fmt . Sprintf ( "ADD_ONION %s:%s%s%s" , hsKeyType , hsKeyStr , portStr , flagsStr )
resp , err := c . Request ( request )
2018-10-09 19:56:25 +00:00
if err == nil && ! dontCheckDescriptor {
pi , _ := c . ProtocolInfo ( )
2018-10-09 20:34:26 +00:00
torversion := strings . Split ( pi . TorVersion , "." ) //eg: 0.3.4.8 or 0.3.5.1-alpha
2018-10-09 19:56:25 +00:00
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 )
2018-10-09 20:34:26 +00:00
log . Printf ( "this onion service will not be visible to others until the counters are synced.\n" )
2018-10-09 19:56:25 +00:00
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)
2015-09-30 16:02:47 +00:00
return nil , err
}
2015-10-10 14:58:06 +00:00
// 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 ) {
2016-11-10 18:21:42 +00:00
if config . DiscardPK || hsKeyStr != "" {
2015-10-10 14:58:06 +00:00
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 ]
}
}
2015-09-30 16:02:47 +00:00
}
2015-10-10 14:58:06 +00:00
oi := new ( OnionInfo )
oi . RawResponse = resp
oi . OnionID = serviceID
oi . PrivateKey = hsPrivateKey
2015-09-30 16:02:47 +00:00
2015-10-10 14:58:06 +00:00
return oi , nil
2015-09-30 16:02:47 +00:00
}
2016-11-10 18:21:42 +00:00
// [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
2018-10-09 19:56:25 +00:00
return c . NewOnion ( cfg , false )
2016-11-10 18:21:42 +00:00
}
2015-10-08 21:25:22 +00:00
// DeleteOnion issues a DEL_ONION command and returns the parsed response.
func ( c * Conn ) DeleteOnion ( serviceID string ) error {
2015-10-10 14:58:06 +00:00
_ , err := c . Request ( "DEL_ONION %s" , serviceID )
2015-09-30 16:02:47 +00:00
return err
}
2018-10-09 19:56:25 +00:00
// 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
}