Add expanded interface for ADD_ONION (NewOnion) to use special configuration

structs that implements all ADD_ONION flags.
Corresponding interface for creating onion Listener (NewListener) that also
makes onion Listeners use multiple virtual ports.
Some code simplifications.
This commit is contained in:
Ivan Markin 2016-11-10 18:21:42 +00:00
parent 38b4676028
commit 3cc4ebc379
3 changed files with 109 additions and 39 deletions

View File

@ -1,8 +1,8 @@
// cmd_onion.go - various onion service commands: ADD_ONION, DEL_ONION...
//
// To the extent possible under law, David Stainton waived all copyright
// and related or neighboring rights to this module of bulb, using the creative
// commons "cc0" public domain dedication. See LICENSE or
// 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 bulb
@ -37,16 +37,27 @@ type OnionPortSpec struct {
Target string
}
// AddOnion issues an ADD_ONION command and returns the parsed response.
func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bool) (*OnionInfo, error) {
// NewOnionConfig is a configuration for NewOnion command.
type NewOnionConfig struct {
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) (*OnionInfo, error) {
const keyTypeRSA = "RSA1024"
var err error
var portStr string
if ports == nil {
if config.PortSpecs == nil {
return nil, newProtocolError("invalid port specification")
}
for _, v := range ports {
for _, v := range config.PortSpecs {
portStr += fmt.Sprintf(" Port=%d", v.VirtPort)
if v.Target != "" {
portStr += "," + v.Target
@ -54,10 +65,10 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
}
var hsKeyType, hsKeyStr string
if key != nil {
switch t := key.(type) {
if config.PrivateKey != nil {
switch t := config.PrivateKey.(type) {
case *rsa.PrivateKey:
rsaPK, _ := key.(*rsa.PrivateKey)
rsaPK, _ := config.PrivateKey.(*rsa.PrivateKey)
if rsaPK.N.BitLen() != 1024 {
return nil, newProtocolError("invalid RSA key size")
}
@ -68,24 +79,38 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
hsKeyType = keyTypeRSA
hsKeyStr = base64.StdEncoding.EncodeToString(pkDER)
case *OnionPrivateKey:
genericPK, _ := key.(*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 resp *Response
if hsKeyStr == "" {
flags := " Flags=DiscardPK"
if !oneshot {
flags = ""
}
resp, err = c.Request("ADD_ONION NEW:BEST%s%s", portStr, flags)
} else {
resp, err = c.Request("ADD_ONION %s:%s%s", hsKeyType, hsKeyStr, portStr)
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 {
return nil, err
}
@ -102,7 +127,7 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
if strings.HasPrefix(l, serviceIDPrefix) {
serviceID = strings.TrimPrefix(l, serviceIDPrefix)
} else if strings.HasPrefix(l, privateKeyPrefix) {
if oneshot || hsKeyStr != "" {
if config.DiscardPK || hsKeyStr != "" {
return nil, newProtocolError("received an unexpected private key")
}
hsKeyStr = strings.TrimPrefix(l, privateKeyPrefix)
@ -142,6 +167,18 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
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)
}
// 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)

View File

@ -55,7 +55,11 @@ func main() {
}
log.Printf("Expected ID: %v", id)
l, err := c.Listener(80, pk)
cfg := &bulb.NewOnionConfig{
DiscardPK: true,
PrivateKey: pk,
}
l, err := c.NewListener(cfg, 80)
if err != nil {
log.Fatalf("Failed to get Listener: %v", err)
}

View File

@ -1,8 +1,8 @@
// listener.go - Tor backed net.Listener.
//
// To the extent possible under law, Yawning Angel waived all copyright
// and related or neighboring rights to bulb, using the creative
// commons "cc0" public domain dedication. See LICENSE or
// To the extent possible under law, Yawning Angel and Ivan Markin
// waived all copyright and related or neighboring rights to bulb, using
// the creative commons "cc0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
package bulb
@ -49,17 +49,23 @@ func (l *onionListener) Addr() net.Addr {
return l.addr
}
// Listener returns a net.Listener backed by a Onion Service, optionally
// having Tor generate an ephemeral private key. Regardless of the status of
// the returned Listener, the Onion Service will be torn down when the control
// connection is closed.
//
// WARNING: Only one port can be listened to per PrivateKey if this interface
// is used. To bind to more ports, use the AddOnion call directly.
func (c *Conn) Listener(port uint16, key crypto.PrivateKey) (net.Listener, error) {
const (
loopbackAddr = "127.0.0.1:0"
)
// NewListener returns a net.Listener backed by an Onion Service using configuration
// config, optionally having Tor generate an ephemeral private key (config is nil or
// config.PrivateKey is nil).
// All of virtual ports specified in vports will be mapped to the port to which
// the underlying TCP listener binded. PortSpecs in config will be ignored since
// there is only one mapping for a vports set is possible.
func (c *Conn) NewListener(config *NewOnionConfig, vports ...uint16) (net.Listener, error) {
var cfg NewOnionConfig
if config == nil {
cfg = NewOnionConfig{
DiscardPK: true,
}
} else {
cfg = *config
}
const loopbackAddr = "127.0.0.1:0"
// Listen on the loopback interface.
tcpListener, err := net.Listen("tcp4", loopbackAddr)
@ -72,16 +78,39 @@ func (c *Conn) Listener(port uint16, key crypto.PrivateKey) (net.Listener, error
return nil, newProtocolError("failed to extract local port")
}
if len(vports) < 1 {
return nil, newProtocolError("no virual ports specified")
}
targetPortStr := strconv.FormatUint((uint64)(tAddr.Port), 10)
var portSpecs []OnionPortSpec
for _, vport := range vports {
portSpecs = append(portSpecs, OnionPortSpec{
VirtPort: vport,
Target: targetPortStr,
})
}
cfg.PortSpecs = portSpecs
// Create the onion.
ports := []OnionPortSpec{{port, strconv.FormatUint((uint64)(tAddr.Port), 10)}}
oi, err := c.AddOnion(ports, key, key == nil)
oi, err := c.NewOnion(&cfg)
if err != nil {
tcpListener.Close()
return nil, err
}
oa := &onionAddr{info: oi, port: port}
oa := &onionAddr{info: oi, port: vports[0]}
ol := &onionListener{addr: oa, ctrlConn: c, listener: tcpListener}
return ol, nil
}
// [DEPRECATED] Listener returns a net.Listener backed by an Onion Service.
func (c *Conn) Listener(port uint16, key crypto.PrivateKey) (net.Listener, error) {
cfg := &NewOnionConfig{}
if key != nil {
cfg.PrivateKey = key
cfg.DiscardPK = false
} else {
cfg.DiscardPK = true
}
return c.NewListener(cfg, port)
}