forked from Yawning/bulb
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcmd_onion.go
186 lines (166 loc) · 4.88 KB
/
cmd_onion.go
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
// cmd_onion.go - various onion service commands: ADD_ONION, DEL_ONION...
//
// 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
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
PrivateKey crypto.PrivateKey
RawResponse *Response
}
// OnionPrivateKey is a unknown Onion private key (crypto.PublicKey).
type OnionPrivateKey struct {
KeyType string
Key string
}
// OnionPortSpec is a Onion VirtPort/Target pair.
type OnionPortSpec struct {
VirtPort uint16
Target string
}
// 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 config.PortSpecs == nil {
return nil, newProtocolError("invalid port specification")
}
for _, v := range config.PortSpecs {
portStr += fmt.Sprintf(" Port=%d", v.VirtPort)
if v.Target != "" {
portStr += "," + v.Target
}
}
var hsKeyType, hsKeyStr string
if config.PrivateKey != nil {
switch t := config.PrivateKey.(type) {
case *rsa.PrivateKey:
rsaPK, _ := config.PrivateKey.(*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, _ := 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 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
}
// 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) {
if config.DiscardPK || 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")
}
oi := new(OnionInfo)
oi.RawResponse = resp
oi.OnionID = serviceID
oi.PrivateKey = hsPrivateKey
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)
return err
}