summaryrefslogtreecommitdiff
path: root/vendor/github.com/cretz/bine/control/cmd_onion.go
blob: b298d36868417ba2c75110f9dd686679bdcfb32f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package control

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"strconv"
	"strings"

	"github.com/cretz/bine/torutil"
	"github.com/cretz/bine/torutil/ed25519"
)

// KeyType is a key type for Key in AddOnion.
type KeyType string

const (
	// KeyTypeNew is NEW.
	KeyTypeNew KeyType = "NEW"
	// KeyTypeRSA1024 is RSA1024.
	KeyTypeRSA1024 KeyType = "RSA1024"
	// KeyTypeED25519V3 is ED25519-V3.
	KeyTypeED25519V3 KeyType = "ED25519-V3"
)

// KeyAlgo is a key algorithm for GenKey on AddOnion.
type KeyAlgo string

const (
	// KeyAlgoBest is BEST.
	KeyAlgoBest KeyAlgo = "BEST"
	// KeyAlgoRSA1024 is RSA1024.
	KeyAlgoRSA1024 KeyAlgo = "RSA1024"
	// KeyAlgoED25519V3 is ED25519-V3.
	KeyAlgoED25519V3 KeyAlgo = "ED25519-V3"
)

// Key is a type of key to use for AddOnion. Implementations include GenKey,
// RSAKey, and ED25519Key.
type Key interface {
	// Type is the KeyType for AddOnion.
	Type() KeyType
	// Blob is the serialized key for AddOnion.
	Blob() string
}

// KeyFromString creates a Key for AddOnion based on a response string.
func KeyFromString(str string) (Key, error) {
	typ, blob, _ := torutil.PartitionString(str, ':')
	switch KeyType(typ) {
	case KeyTypeNew:
		return GenKeyFromBlob(blob), nil
	case KeyTypeRSA1024:
		return RSA1024KeyFromBlob(blob)
	case KeyTypeED25519V3:
		return ED25519KeyFromBlob(blob)
	default:
		return nil, fmt.Errorf("Unrecognized key type: %v", typ)
	}
}

// GenKey is a Key for AddOnion that asks Tor to generate a key for the given
// algorithm.
type GenKey KeyAlgo

// GenKeyFromBlob creates a GenKey for the given response blob which is a
// KeyAlgo.
func GenKeyFromBlob(blob string) GenKey { return GenKey(KeyAlgo(blob)) }

// Type implements Key.Type.
func (GenKey) Type() KeyType { return KeyTypeNew }

// Blob implements Key.Blob.
func (g GenKey) Blob() string { return string(g) }

// RSAKey is a Key for AddOnion that is a RSA-1024 key (i.e. v2).
type RSAKey struct{ *rsa.PrivateKey }

// RSA1024KeyFromBlob creates a RSAKey for the given response blob.
func RSA1024KeyFromBlob(blob string) (*RSAKey, error) {
	byts, err := base64.StdEncoding.DecodeString(blob)
	if err != nil {
		return nil, err
	}
	rsaKey, err := x509.ParsePKCS1PrivateKey(byts)
	if err != nil {
		return nil, err
	}
	return &RSAKey{rsaKey}, nil
}

// Type implements Key.Type.
func (*RSAKey) Type() KeyType { return KeyTypeRSA1024 }

// Blob implements Key.Blob.
func (r *RSAKey) Blob() string {
	return base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(r.PrivateKey))
}

// ED25519Key is a Key for AddOnion that is a ed25519 key (i.e. v3).
type ED25519Key struct{ ed25519.KeyPair }

// ED25519KeyFromBlob creates a ED25519Key for the given response blob.
func ED25519KeyFromBlob(blob string) (*ED25519Key, error) {
	byts, err := base64.StdEncoding.DecodeString(blob)
	if err != nil {
		return nil, err
	}
	return &ED25519Key{ed25519.PrivateKey(byts).KeyPair()}, nil
}

// Type implements Key.Type.
func (*ED25519Key) Type() KeyType { return KeyTypeED25519V3 }

// Blob implements Key.Blob.
func (e *ED25519Key) Blob() string { return base64.StdEncoding.EncodeToString(e.PrivateKey()) }

// AddOnionRequest is a set of request params for AddOnion.
type AddOnionRequest struct {
	// Key is the key to use or GenKey if Tor should generate it.
	Key Key
	// Flags are ADD_ONION flags.
	Flags []string
	// MaxStreams is ADD_ONION MaxStreams.
	MaxStreams int
	// Ports are ADD_ONION Port values. Key is virtual port, Val is target
	// port (or can be empty to use virtual port).
	Ports []*KeyVal
	// ClientAuths are ADD_ONION ClientAuth values. If value is empty string,
	// Tor will generate the password.
	ClientAuths map[string]string
}

// AddOnionResponse is the response for AddOnion.
type AddOnionResponse struct {
	// ServiceID is the ADD_ONION response ServiceID value.
	ServiceID string
	// Key is the ADD_ONION response PrivateKey value.
	Key Key
	// ClientAuths are the ADD_ONION response ClientAuth values.
	ClientAuths map[string]string
	// RawResponse is the raw ADD_ONION response.
	RawResponse *Response
}

// AddOnion invokes ADD_ONION and returns its response.
func (c *Conn) AddOnion(req *AddOnionRequest) (*AddOnionResponse, error) {
	// Build command
	if req.Key == nil {
		return nil, c.protoErr("Key required")
	}
	cmd := "ADD_ONION " + string(req.Key.Type()) + ":" + req.Key.Blob()
	if len(req.Flags) > 0 {
		cmd += " Flags=" + strings.Join(req.Flags, ",")
	}
	if req.MaxStreams > 0 {
		cmd += " MaxStreams=" + strconv.Itoa(req.MaxStreams)
	}
	for _, port := range req.Ports {
		cmd += " Port=" + port.Key
		if port.Val != "" {
			cmd += "," + port.Val
		}
	}
	for name, blob := range req.ClientAuths {
		cmd += " ClientAuth=" + name
		if blob != "" {
			cmd += ":" + blob
		}
	}
	// Invoke and read response
	resp, err := c.SendRequest(cmd)
	if err != nil {
		return nil, err
	}
	ret := &AddOnionResponse{RawResponse: resp}
	for _, data := range resp.Data {
		key, val, _ := torutil.PartitionString(data, '=')
		switch key {
		case "ServiceID":
			ret.ServiceID = val
		case "PrivateKey":
			if ret.Key, err = KeyFromString(val); err != nil {
				return nil, err
			}
		case "ClientAuth":
			name, pass, _ := torutil.PartitionString(val, ':')
			if ret.ClientAuths == nil {
				ret.ClientAuths = map[string]string{}
			}
			ret.ClientAuths[name] = pass
		}
	}
	return ret, nil
}

// DelOnion invokes DELONION.
func (c *Conn) DelOnion(serviceID string) error {
	return c.sendRequestIgnoreResponse("DEL_ONION %v", serviceID)
}