Rewrite the ADD_ONION command to be easier(?) to use.
This is more fully featured and integrates better with other Go (in particular crypto.rsa).
This commit is contained in:
parent
ae6d97af37
commit
da6c08c21e
141
cmd_onion.go
141
cmd_onion.go
|
@ -8,55 +8,142 @@
|
||||||
package bulb
|
package bulb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yawning/bulb/utils/pkcs1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OnionInfo is the result of the AddOnion command.
|
// OnionInfo is the result of the AddOnion command.
|
||||||
type OnionInfo struct {
|
type OnionInfo struct {
|
||||||
OnionID string
|
OnionID string
|
||||||
KeyType string
|
PrivateKey crypto.PrivateKey
|
||||||
Key string
|
|
||||||
|
|
||||||
RawResponse *Response
|
RawResponse *Response
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOnion issues an ADD_ONION command and returns the parsed response.
|
// OnionPrivateKey is a unknown Onion private key (crypto.PublicKey).
|
||||||
func (c *Conn) AddOnion(virtPort int, target, keyType, keyContent string, new bool) (*OnionInfo, error) {
|
type OnionPrivateKey struct {
|
||||||
var fields []string
|
KeyType string
|
||||||
request := "ADD_ONION "
|
Key string
|
||||||
onionInfo := OnionInfo{}
|
}
|
||||||
|
|
||||||
if new {
|
// OnionPortSpec is a Onion VirtPort/Target pair.
|
||||||
request += "NEW:BEST"
|
type OnionPortSpec struct {
|
||||||
} else {
|
VirtPort uint16
|
||||||
request += fmt.Sprintf("%s:%s", keyType, keyContent)
|
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) {
|
||||||
|
const keyTypeRSA = "RSA1024"
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var portStr string
|
||||||
|
if ports == nil {
|
||||||
|
return nil, newProtocolError("invalid port specification")
|
||||||
|
}
|
||||||
|
for _, v := range ports {
|
||||||
|
portStr += fmt.Sprintf(" Port=%d", v.VirtPort)
|
||||||
|
if v.Target != "" {
|
||||||
|
portStr += "," + v.Target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hsKeyType, hsKeyStr string
|
||||||
|
if key != nil {
|
||||||
|
switch t := key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
rsaPK, _ := key.(*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, _ := key.(*OnionPrivateKey)
|
||||||
|
hsKeyType = genericPK.KeyType
|
||||||
|
hsKeyStr = genericPK.Key
|
||||||
|
default:
|
||||||
|
return nil, newProtocolError("unsupported private key type: %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
request += fmt.Sprintf(" Port=%d,%s\n", virtPort, target)
|
|
||||||
response, err := c.Request(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
onionInfo.RawResponse = response
|
// Parse out the response.
|
||||||
fields = strings.Split(fmt.Sprintf("%s", response.Data), "ServiceID=")
|
var serviceID string
|
||||||
fields = strings.Split(fields[1], " ")
|
var hsPrivateKey crypto.PrivateKey
|
||||||
onionInfo.OnionID = fields[0]
|
for _, l := range resp.Data {
|
||||||
|
const (
|
||||||
|
serviceIDPrefix = "ServiceID="
|
||||||
|
privateKeyPrefix = "PrivateKey="
|
||||||
|
)
|
||||||
|
|
||||||
if new {
|
if strings.HasPrefix(l, serviceIDPrefix) {
|
||||||
fields = strings.Split(fmt.Sprintf("%s", response.Data), "PrivateKey=")
|
serviceID = strings.TrimPrefix(l, serviceIDPrefix)
|
||||||
fields = strings.Split(fields[1], ":")
|
} else if strings.HasPrefix(l, privateKeyPrefix) {
|
||||||
onionInfo.KeyType = fields[0]
|
if oneshot || hsKeyStr != "" {
|
||||||
fields = strings.Split(fields[1], "\n")
|
return nil, newProtocolError("received an unexpected private key")
|
||||||
onionInfo.Key = fields[0]
|
}
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if serviceID == "" {
|
||||||
|
// This should *NEVER* happen, since the command succeded, and the spec
|
||||||
|
// guarantees that this will always be present.
|
||||||
|
return nil, newProtocolError("failed to determine service ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &onionInfo, nil
|
oi := new(OnionInfo)
|
||||||
|
oi.RawResponse = resp
|
||||||
|
oi.OnionID = serviceID
|
||||||
|
oi.PrivateKey = hsPrivateKey
|
||||||
|
|
||||||
|
return oi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOnion issues a DEL_ONION command and returns the parsed response.
|
// DeleteOnion issues a DEL_ONION command and returns the parsed response.
|
||||||
func (c *Conn) DeleteOnion(serviceID string) error {
|
func (c *Conn) DeleteOnion(serviceID string) error {
|
||||||
var deleteCmd string = fmt.Sprintf("DEL_ONION %s\n", serviceID)
|
_, err := c.Request("DEL_ONION %s", serviceID)
|
||||||
_, err := c.Request(deleteCmd)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue