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:
Yawning Angel 2015-10-10 14:58:06 +00:00
parent ae6d97af37
commit da6c08c21e
1 changed files with 114 additions and 27 deletions

View File

@ -8,55 +8,142 @@
package bulb
import (
"crypto"
"crypto/rsa"
"encoding/base64"
"fmt"
"strings"
"github.com/yawning/bulb/utils/pkcs1"
)
// OnionInfo is the result of the AddOnion command.
type OnionInfo struct {
OnionID string
KeyType string
Key string
OnionID string
PrivateKey crypto.PrivateKey
RawResponse *Response
}
// AddOnion issues an ADD_ONION command and returns the parsed response.
func (c *Conn) AddOnion(virtPort int, target, keyType, keyContent string, new bool) (*OnionInfo, error) {
var fields []string
request := "ADD_ONION "
onionInfo := OnionInfo{}
// OnionPrivateKey is a unknown Onion private key (crypto.PublicKey).
type OnionPrivateKey struct {
KeyType string
Key string
}
if new {
request += "NEW:BEST"
} else {
request += fmt.Sprintf("%s:%s", keyType, keyContent)
// OnionPortSpec is a Onion VirtPort/Target pair.
type OnionPortSpec struct {
VirtPort uint16
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 {
return nil, err
}
onionInfo.RawResponse = response
fields = strings.Split(fmt.Sprintf("%s", response.Data), "ServiceID=")
fields = strings.Split(fields[1], " ")
onionInfo.OnionID = fields[0]
// Parse out the response.
var serviceID string
var hsPrivateKey crypto.PrivateKey
for _, l := range resp.Data {
const (
serviceIDPrefix = "ServiceID="
privateKeyPrefix = "PrivateKey="
)
if new {
fields = strings.Split(fmt.Sprintf("%s", response.Data), "PrivateKey=")
fields = strings.Split(fields[1], ":")
onionInfo.KeyType = fields[0]
fields = strings.Split(fields[1], "\n")
onionInfo.Key = fields[0]
if strings.HasPrefix(l, serviceIDPrefix) {
serviceID = strings.TrimPrefix(l, serviceIDPrefix)
} else if strings.HasPrefix(l, privateKeyPrefix) {
if oneshot || 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]
}
}
}
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.
func (c *Conn) DeleteOnion(serviceID string) error {
var deleteCmd string = fmt.Sprintf("DEL_ONION %s\n", serviceID)
_, err := c.Request(deleteCmd)
_, err := c.Request("DEL_ONION %s", serviceID)
return err
}