diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 1ca7ee2..46a94d3 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -2,11 +2,26 @@
"ImportPath": "github.com/s-rah/go-ricochet",
"GoVersion": "go1.7",
"GodepVersion": "v79",
+ "Packages": [
+ "./..."
+ ],
"Deps": [
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "8ee79997227bf9b34611aee7946ae64735e6fd93"
},
+ {
+ "ImportPath": "github.com/yawning/bulb",
+ "Rev": "85d80d893c3d4a478b8c0abbc43f0ea13e1ce4f9"
+ },
+ {
+ "ImportPath": "github.com/yawning/bulb/utils",
+ "Rev": "85d80d893c3d4a478b8c0abbc43f0ea13e1ce4f9"
+ },
+ {
+ "ImportPath": "github.com/yawning/bulb/utils/pkcs1",
+ "Rev": "85d80d893c3d4a478b8c0abbc43f0ea13e1ce4f9"
+ },
{
"ImportPath": "golang.org/x/net/proxy",
"Rev": "60c41d1de8da134c05b7b40154a9a82bf5b7edb9"
diff --git a/application/application.go b/application/application.go
index ac73cfb..f891608 100644
--- a/application/application.go
+++ b/application/application.go
@@ -17,6 +17,7 @@ type RicochetApplication struct {
privateKey *rsa.PrivateKey
chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
chatMessageAckHandler func(*RicochetApplicationInstance, uint32)
+ l net.Listener
}
type RicochetApplicationInstance struct {
@@ -120,15 +121,25 @@ func (ra *RicochetApplication) handleConnection(conn net.Conn) {
rc.Process(rai)
}
+func (ra *RicochetApplication) Shutdown () {
+ log.Printf("Closing")
+ ra.l.Close()
+ log.Printf("Closed")
+}
+
func (ra *RicochetApplication) Run(l net.Listener) {
if ra.privateKey == nil || ra.contactManager == nil {
return
}
-
- for {
- conn, err := l.Accept()
+ ra.l = l
+ var err error
+ for err == nil {
+ conn, err := ra.l.Accept()
if err == nil {
go ra.handleConnection(conn)
+ } else {
+ log.Printf("Closing")
+ return
}
}
}
diff --git a/vendor/github.com/yawning/bulb/.gitignore b/vendor/github.com/yawning/bulb/.gitignore
new file mode 100644
index 0000000..2c540ff
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/.gitignore
@@ -0,0 +1,5 @@
+*.swp
+*~
+examples/basic/basic
+examples/listener/listener
+examples/dialer/dialer
diff --git a/vendor/github.com/yawning/bulb/LICENSE b/vendor/github.com/yawning/bulb/LICENSE
new file mode 100644
index 0000000..6ca207e
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/LICENSE
@@ -0,0 +1,122 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
+
diff --git a/vendor/github.com/yawning/bulb/README.md b/vendor/github.com/yawning/bulb/README.md
new file mode 100644
index 0000000..9e1a7ad
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/README.md
@@ -0,0 +1,18 @@
+## bulb - Is not stem
+### Yawning Angel (yawning at torproject dot org)
+
+bulb is a Go language interface to the Tor control port. It is considerably
+lighter in functionality than stem and other controller libraries, and is
+intended to be used in combination with`control-spec.txt`.
+
+It was written primarily as a not-invented-here hack, and the base interface is
+more than likely to stay fairly low level, though useful helpers will be added
+as I need them.
+
+Things you should probably use instead:
+ * [stem](https://stem.torproject.org)
+ * [txtorcon](https://pypi.python.org/pypi/txtorcon)
+ * [orc](https://github.com/sycamoreone/orc)
+
+Bugs:
+ * bulb does not send the 'QUIT' command before closing the connection.
diff --git a/vendor/github.com/yawning/bulb/cmd_authenticate.go b/vendor/github.com/yawning/bulb/cmd_authenticate.go
new file mode 100644
index 0000000..aba7bd6
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/cmd_authenticate.go
@@ -0,0 +1,137 @@
+// cmd_authenticate.go - AUTHENTICATE/AUTHCHALLENGE commands.
+//
+// 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
+// for full details.
+
+package bulb
+
+import (
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+ "io/ioutil"
+ "strings"
+)
+
+// Authenticate authenticates with the Tor instance using the "best" possible
+// authentication method. The password argument is entirely optional, and will
+// only be used if the "SAFECOOKE" and "NULL" authentication methods are not
+// available and "HASHEDPASSWORD" is.
+func (c *Conn) Authenticate(password string) error {
+ if c.isAuthenticated {
+ return nil
+ }
+
+ // Determine the supported authentication methods, and the cookie path.
+ pi, err := c.ProtocolInfo()
+ if err != nil {
+ return err
+ }
+
+ // "COOKIE" authentication exists, but anything modern supports
+ // "SAFECOOKIE".
+ const (
+ cmdAuthenticate = "AUTHENTICATE"
+ authMethodNull = "NULL"
+ authMethodPassword = "HASHEDPASSWORD"
+ authMethodSafeCookie = "SAFECOOKIE"
+ )
+ if pi.AuthMethods[authMethodNull] {
+ _, err = c.Request(cmdAuthenticate)
+ c.isAuthenticated = err == nil
+ return err
+ } else if pi.AuthMethods[authMethodSafeCookie] {
+ const (
+ authCookieLength = 32
+ authNonceLength = 32
+ authHashLength = 32
+
+ authServerHashKey = "Tor safe cookie authentication server-to-controller hash"
+ authClientHashKey = "Tor safe cookie authentication controller-to-server hash"
+ )
+
+ if pi.CookieFile == "" {
+ return newProtocolError("invalid (empty) COOKIEFILE")
+ }
+ cookie, err := ioutil.ReadFile(pi.CookieFile)
+ if err != nil {
+ return newProtocolError("failed to read COOKIEFILE: %v", err)
+ } else if len(cookie) != authCookieLength {
+ return newProtocolError("invalid cookie file length: %d", len(cookie))
+ }
+
+ // Send an AUTHCHALLENGE command, and parse the response.
+ var clientNonce [authNonceLength]byte
+ if _, err := rand.Read(clientNonce[:]); err != nil {
+ return newProtocolError("failed to generate clientNonce: %v", err)
+ }
+ clientNonceStr := hex.EncodeToString(clientNonce[:])
+ resp, err := c.Request("AUTHCHALLENGE %s %s", authMethodSafeCookie, clientNonceStr)
+ if err != nil {
+ return err
+ }
+ splitResp := strings.Split(resp.Reply, " ")
+ if len(splitResp) != 3 {
+ return newProtocolError("invalid AUTHCHALLENGE response")
+ }
+ serverHashStr := strings.TrimPrefix(splitResp[1], "SERVERHASH=")
+ if serverHashStr == splitResp[1] {
+ return newProtocolError("missing SERVERHASH")
+ }
+ serverHash, err := hex.DecodeString(serverHashStr)
+ if err != nil {
+ return newProtocolError("failed to decode ServerHash: %v", err)
+ }
+ if len(serverHash) != authHashLength {
+ return newProtocolError("invalid ServerHash length: %d", len(serverHash))
+ }
+ serverNonceStr := strings.TrimPrefix(splitResp[2], "SERVERNONCE=")
+ if serverNonceStr == splitResp[2] {
+ return newProtocolError("missing SERVERNONCE")
+ }
+ serverNonce, err := hex.DecodeString(serverNonceStr)
+ if err != nil {
+ return newProtocolError("failed to decode ServerNonce: %v", err)
+ }
+ if len(serverNonce) != authNonceLength {
+ return newProtocolError("invalid ServerNonce length: %d", len(serverNonce))
+ }
+
+ // Validate the ServerHash.
+ m := hmac.New(sha256.New, []byte(authServerHashKey))
+ m.Write(cookie)
+ m.Write(clientNonce[:])
+ m.Write(serverNonce)
+ dervServerHash := m.Sum(nil)
+ if !hmac.Equal(serverHash, dervServerHash) {
+ return newProtocolError("invalid ServerHash: mismatch")
+ }
+
+ // Calculate the ClientHash, and issue the AUTHENTICATE.
+ m = hmac.New(sha256.New, []byte(authClientHashKey))
+ m.Write(cookie)
+ m.Write(clientNonce[:])
+ m.Write(serverNonce)
+ clientHash := m.Sum(nil)
+ clientHashStr := hex.EncodeToString(clientHash)
+
+ _, err = c.Request("%s %s", cmdAuthenticate, clientHashStr)
+ c.isAuthenticated = err == nil
+ return err
+ } else if pi.AuthMethods[authMethodPassword] {
+ // Despite the name HASHEDPASSWORD, the raw password is actually sent.
+ // According to the code, this can either be a QuotedString, or base16
+ // encoded, so go with the later since it's easier to handle.
+ if password == "" {
+ return newProtocolError("password auth needs a password")
+ }
+ passwordStr := hex.EncodeToString([]byte(password))
+ _, err = c.Request("%s %s", cmdAuthenticate, passwordStr)
+ c.isAuthenticated = err == nil
+ return err
+ }
+ return newProtocolError("no supported authentication methods")
+}
diff --git a/vendor/github.com/yawning/bulb/cmd_onion.go b/vendor/github.com/yawning/bulb/cmd_onion.go
new file mode 100644
index 0000000..b616c02
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/cmd_onion.go
@@ -0,0 +1,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
+// 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
+}
diff --git a/vendor/github.com/yawning/bulb/cmd_protocolinfo.go b/vendor/github.com/yawning/bulb/cmd_protocolinfo.go
new file mode 100644
index 0000000..44685b0
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/cmd_protocolinfo.go
@@ -0,0 +1,95 @@
+// cmd_protocolinfo.go - PROTOCOLINFO command.
+//
+// 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
+// for full details.
+
+package bulb
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/yawning/bulb/utils"
+)
+
+// ProtocolInfo is the result of the ProtocolInfo command.
+type ProtocolInfo struct {
+ AuthMethods map[string]bool
+ CookieFile string
+ TorVersion string
+
+ RawResponse *Response
+}
+
+// ProtocolInfo issues a PROTOCOLINFO command and returns the parsed response.
+func (c *Conn) ProtocolInfo() (*ProtocolInfo, error) {
+ // In the pre-authentication state, only one PROTOCOLINFO command
+ // may be issued. Cache the value returned so that subsequent
+ // calls continue to work.
+ if !c.isAuthenticated && c.cachedPI != nil {
+ return c.cachedPI, nil
+ }
+
+ resp, err := c.Request("PROTOCOLINFO")
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse out the PIVERSION to make sure it speaks something we understand.
+ if len(resp.Data) < 1 {
+ return nil, newProtocolError("missing PIVERSION")
+ }
+ switch resp.Data[0] {
+ case "1":
+ return nil, newProtocolError("invalid PIVERSION: '%s'", resp.Reply)
+ default:
+ }
+
+ // Parse out the rest of the lines.
+ pi := new(ProtocolInfo)
+ pi.RawResponse = resp
+ pi.AuthMethods = make(map[string]bool)
+ for i := 1; i < len(resp.Data); i++ {
+ splitLine := utils.SplitQuoted(resp.Data[i], '"', ' ')
+ switch splitLine[0] {
+ case "AUTH":
+ // Parse an AuthLine detailing how to authenticate.
+ if len(splitLine) < 2 {
+ continue
+ }
+ methods := strings.TrimPrefix(splitLine[1], "METHODS=")
+ if methods == splitLine[1] {
+ continue
+ }
+ for _, meth := range strings.Split(methods, ",") {
+ pi.AuthMethods[meth] = true
+ }
+
+ if len(splitLine) < 3 {
+ continue
+ }
+ cookiePath := strings.TrimPrefix(splitLine[2], "COOKIEFILE=")
+ if cookiePath == splitLine[2] {
+ continue
+ }
+ pi.CookieFile, _ = strconv.Unquote(cookiePath)
+ case "VERSION":
+ // Parse a VersionLine detailing the Tor version.
+ if len(splitLine) < 2 {
+ continue
+ }
+ torVersion := strings.TrimPrefix(splitLine[1], "Tor=")
+ if torVersion == splitLine[1] {
+ continue
+ }
+ pi.TorVersion, _ = strconv.Unquote(torVersion)
+ default: // MUST ignore unsupported InfoLines.
+ }
+ }
+ if !c.isAuthenticated {
+ c.cachedPI = pi
+ }
+ return pi, nil
+}
diff --git a/vendor/github.com/yawning/bulb/conn.go b/vendor/github.com/yawning/bulb/conn.go
new file mode 100644
index 0000000..a2613a4
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/conn.go
@@ -0,0 +1,233 @@
+// conn.go - Controller connection instance.
+//
+// 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
+// for full details.
+
+// Package bulb is a Go language interface to a Tor control port.
+package bulb
+
+import (
+ "errors"
+ gofmt "fmt"
+ "io"
+ "log"
+ "net"
+ "net/textproto"
+ "sync"
+)
+
+const (
+ maxEventBacklog = 16
+ maxResponseBacklog = 16
+)
+
+// ErrNoAsyncReader is the error returned when the asynchronous event handling
+// is requested, but the helper go routine has not been started.
+var ErrNoAsyncReader = errors.New("event requested without an async reader")
+
+// Conn is a control port connection instance.
+type Conn struct {
+ conn *textproto.Conn
+ isAuthenticated bool
+ debugLog bool
+ cachedPI *ProtocolInfo
+
+ asyncReaderLock sync.Mutex
+ asyncReaderRunning bool
+ eventChan chan *Response
+ respChan chan *Response
+ closeWg sync.WaitGroup
+
+ rdErrLock sync.Mutex
+ rdErr error
+}
+
+func (c *Conn) setRdErr(err error, force bool) {
+ c.rdErrLock.Lock()
+ defer c.rdErrLock.Unlock()
+ if c.rdErr == nil || force {
+ c.rdErr = err
+ }
+}
+
+func (c *Conn) getRdErr() error {
+ c.rdErrLock.Lock()
+ defer c.rdErrLock.Unlock()
+ return c.rdErr
+}
+
+func (c *Conn) isAsyncReaderRunning() bool {
+ c.asyncReaderLock.Lock()
+ defer c.asyncReaderLock.Unlock()
+ return c.asyncReaderRunning
+}
+
+func (c *Conn) asyncReader() {
+ for {
+ resp, err := c.ReadResponse()
+ if err != nil {
+ c.setRdErr(err, false)
+ break
+ }
+ if resp.IsAsync() {
+ c.eventChan <- resp
+ } else {
+ c.respChan <- resp
+ }
+ }
+ close(c.eventChan)
+ close(c.respChan)
+ c.closeWg.Done()
+
+ // In theory, we would lock and set asyncReaderRunning to false here, but
+ // once it's started, the only way it returns is if there is a catastrophic
+ // failure, or a graceful shutdown. Changing this will require redoing how
+ // Close() works.
+}
+
+// Debug enables/disables debug logging of control port chatter.
+func (c *Conn) Debug(enable bool) {
+ c.debugLog = enable
+}
+
+// Close closes the connection.
+func (c *Conn) Close() error {
+ c.asyncReaderLock.Lock()
+ defer c.asyncReaderLock.Unlock()
+
+ err := c.conn.Close()
+ if err != nil && c.asyncReaderRunning {
+ c.closeWg.Wait()
+ }
+ c.setRdErr(io.ErrClosedPipe, true)
+ return err
+}
+
+// StartAsyncReader starts the asynchronous reader go routine that allows
+// asynchronous events to be handled. It must not be called simultaniously
+// with Read, Request, or ReadResponse or undefined behavior will occur.
+func (c *Conn) StartAsyncReader() {
+ c.asyncReaderLock.Lock()
+ defer c.asyncReaderLock.Unlock()
+ if c.asyncReaderRunning {
+ return
+ }
+
+ // Allocate the channels and kick off the read worker.
+ c.eventChan = make(chan *Response, maxEventBacklog)
+ c.respChan = make(chan *Response, maxResponseBacklog)
+ c.closeWg.Add(1)
+ go c.asyncReader()
+ c.asyncReaderRunning = true
+}
+
+// NextEvent returns the next asynchronous event received, blocking if
+// neccecary. In order to enable asynchronous event handling, StartAsyncReader
+// must be called first.
+func (c *Conn) NextEvent() (*Response, error) {
+ if err := c.getRdErr(); err != nil {
+ return nil, err
+ }
+ if !c.isAsyncReaderRunning() {
+ return nil, ErrNoAsyncReader
+ }
+
+ resp, ok := <-c.eventChan
+ if resp != nil {
+ return resp, nil
+ } else if !ok {
+ return nil, io.ErrClosedPipe
+ }
+ panic("BUG: NextEvent() returned a nil response and error")
+}
+
+// Request sends a raw control port request and returns the response.
+// If the async. reader is not currently running, events received while waiting
+// for the response will be silently dropped. Calling Request simultaniously
+// with StartAsyncReader, Read, Write, or ReadResponse will lead to undefined
+// behavior.
+func (c *Conn) Request(fmt string, args ...interface{}) (*Response, error) {
+ if err := c.getRdErr(); err != nil {
+ return nil, err
+ }
+ asyncResp := c.isAsyncReaderRunning()
+
+ if c.debugLog {
+ log.Printf("C: %s", gofmt.Sprintf(fmt, args...))
+ }
+
+ id, err := c.conn.Cmd(fmt, args...)
+ if err != nil {
+ return nil, err
+ }
+
+ c.conn.StartResponse(id)
+ defer c.conn.EndResponse(id)
+ var resp *Response
+ if asyncResp {
+ var ok bool
+ resp, ok = <-c.respChan
+ if resp == nil && !ok {
+ return nil, io.ErrClosedPipe
+ }
+ } else {
+ // Event handing requires the asyncReader() goroutine, try to get a
+ // response, while silently swallowing events.
+ for resp == nil || resp.IsAsync() {
+ resp, err = c.ReadResponse()
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ if resp == nil {
+ panic("BUG: Request() returned a nil response and error")
+ }
+ if resp.IsOk() {
+ return resp, nil
+ }
+ return resp, resp.Err
+}
+
+// Read reads directly from the control port connection. Mixing this call
+// with Request, ReadResponse, or asynchronous events will lead to undefined
+// behavior.
+func (c *Conn) Read(p []byte) (int, error) {
+ return c.conn.R.Read(p)
+}
+
+// Write writes directly from the control port connection. Mixing this call
+// with Request will lead to undefined behavior.
+func (c *Conn) Write(p []byte) (int, error) {
+ n, err := c.conn.W.Write(p)
+ if err == nil {
+ // If the write succeeds, but the flush fails, n will be incorrect...
+ return n, c.conn.W.Flush()
+ }
+ return n, err
+}
+
+// Dial connects to a given network/address and returns a new Conn for the
+// connection.
+func Dial(network, addr string) (*Conn, error) {
+ c, err := net.Dial(network, addr)
+ if err != nil {
+ return nil, err
+ }
+ return NewConn(c), nil
+}
+
+// NewConn returns a new Conn using c for I/O.
+func NewConn(c io.ReadWriteCloser) *Conn {
+ conn := new(Conn)
+ conn.conn = textproto.NewConn(c)
+ return conn
+}
+
+func newProtocolError(fmt string, args ...interface{}) textproto.ProtocolError {
+ return textproto.ProtocolError(gofmt.Sprintf(fmt, args...))
+}
+
+var _ io.ReadWriteCloser = (*Conn)(nil)
diff --git a/vendor/github.com/yawning/bulb/dialer.go b/vendor/github.com/yawning/bulb/dialer.go
new file mode 100644
index 0000000..3b3c0cb
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/dialer.go
@@ -0,0 +1,54 @@
+// dialer.go - Tor backed proxy.Dialer.
+//
+// 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
+// for full details.
+
+package bulb
+
+import (
+ "strconv"
+ "strings"
+
+ "golang.org/x/net/proxy"
+)
+
+// Dialer returns a proxy.Dialer for the given Tor instance.
+func (c *Conn) Dialer(auth *proxy.Auth) (proxy.Dialer, error) {
+ const (
+ cmdGetInfo = "GETINFO"
+ socksListeners = "net/listeners/socks"
+ unixPrefix = "unix:"
+ )
+
+ // Query for the SOCKS listeners via a GETINFO request.
+ resp, err := c.Request("%s %s", cmdGetInfo, socksListeners)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(resp.Data) != 1 {
+ return nil, newProtocolError("no SOCKS listeners configured")
+ }
+ splitResp := strings.Split(resp.Data[0], " ")
+ if len(splitResp) < 1 {
+ return nil, newProtocolError("no SOCKS listeners configured")
+ }
+
+ // The first listener will have a "net/listeners/socks=" prefix, and all
+ // entries are QuotedStrings.
+ laddrStr := strings.TrimPrefix(splitResp[0], socksListeners+"=")
+ if laddrStr == splitResp[0] {
+ return nil, newProtocolError("failed to parse SOCKS listener")
+ }
+ laddrStr, _ = strconv.Unquote(laddrStr)
+
+ // Construct the proxyDialer.
+ if strings.HasPrefix(laddrStr, unixPrefix) {
+ unixPath := strings.TrimPrefix(laddrStr, unixPrefix)
+ return proxy.SOCKS5("unix", unixPath, auth, proxy.Direct)
+ }
+
+ return proxy.SOCKS5("tcp", laddrStr, auth, proxy.Direct)
+}
diff --git a/vendor/github.com/yawning/bulb/listener.go b/vendor/github.com/yawning/bulb/listener.go
new file mode 100644
index 0000000..847b50f
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/listener.go
@@ -0,0 +1,116 @@
+// listener.go - Tor backed net.Listener.
+//
+// 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
+// for full details.
+
+package bulb
+
+import (
+ "crypto"
+ "fmt"
+ "net"
+ "strconv"
+)
+
+type onionAddr struct {
+ info *OnionInfo
+ port uint16
+}
+
+func (a *onionAddr) Network() string {
+ return "tcp"
+}
+
+func (a *onionAddr) String() string {
+ return fmt.Sprintf("%s.onion:%d", a.info.OnionID, a.port)
+}
+
+type onionListener struct {
+ addr *onionAddr
+ ctrlConn *Conn
+ listener net.Listener
+}
+
+func (l *onionListener) Accept() (net.Conn, error) {
+ return l.listener.Accept()
+}
+
+func (l *onionListener) Close() (err error) {
+ if err = l.listener.Close(); err == nil {
+ // Only delete the onion once.
+ err = l.ctrlConn.DeleteOnion(l.addr.info.OnionID)
+ }
+ return err
+}
+
+func (l *onionListener) Addr() net.Addr {
+ return l.addr
+}
+
+// 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)
+ if err != nil {
+ return nil, err
+ }
+ tAddr, ok := tcpListener.Addr().(*net.TCPAddr)
+ if !ok {
+ tcpListener.Close()
+ 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.
+ oi, err := c.NewOnion(&cfg)
+ if err != nil {
+ tcpListener.Close()
+ return nil, err
+ }
+
+ 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)
+}
diff --git a/vendor/github.com/yawning/bulb/response.go b/vendor/github.com/yawning/bulb/response.go
new file mode 100644
index 0000000..568a3ea
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/response.go
@@ -0,0 +1,125 @@
+// response.go - Generic response handler
+//
+// 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
+// for full details.
+
+package bulb
+
+import (
+ "log"
+ "net/textproto"
+ "strconv"
+ "strings"
+)
+
+// Response is a response to a control port command, or an asyncrhonous event.
+type Response struct {
+ // Err is the status code and string representation associated with a
+ // response. Responses that have completed successfully will also have
+ // Err set to indicate such.
+ Err *textproto.Error
+
+ // Reply is the text on the EndReplyLine of the response.
+ Reply string
+
+ // Data is the MidReplyLines/DataReplyLines of the response. Dot encoded
+ // data is "decoded" and presented as a single string (terminal ".CRLF"
+ // removed, all intervening CRs stripped).
+ Data []string
+
+ // RawLines is all of the lines of a response, without CRLFs.
+ RawLines []string
+}
+
+// IsOk returns true if the response status code indicates success or
+// an asynchronous event.
+func (r *Response) IsOk() bool {
+ switch r.Err.Code {
+ case StatusOk, StatusOkUnneccecary, StatusAsyncEvent:
+ return true
+ default:
+ return false
+ }
+}
+
+// IsAsync returns true if the response is an asyncrhonous event.
+func (r *Response) IsAsync() bool {
+ return r.Err.Code == StatusAsyncEvent
+}
+
+// ReadResponse returns the next response object. Calling this
+// simultaniously with Read, Request, or StartAsyncReader will lead to
+// undefined behavior
+func (c *Conn) ReadResponse() (*Response, error) {
+ var resp *Response
+ var statusCode int
+ for {
+ line, err := c.conn.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ if c.debugLog {
+ log.Printf("S: %s", line)
+ }
+
+ // Parse the line that was just read.
+ if len(line) < 4 {
+ return nil, newProtocolError("truncated response: '%s'", line)
+ }
+ if code, err := strconv.Atoi(line[0:3]); err != nil {
+ return nil, newProtocolError("invalid status code: '%s'", line[0:3])
+ } else if code < 100 {
+ return nil, newProtocolError("invalid status code: '%s'", line[0:3])
+ } else if resp == nil {
+ resp = new(Response)
+ statusCode = code
+ } else if code != statusCode {
+ // The status code should stay fixed for all lines of the
+ // response, since events can't be interleaved with response
+ // lines.
+ return nil, newProtocolError("status code changed: %03d != %03d", code, statusCode)
+ }
+ if resp.RawLines == nil {
+ resp.RawLines = make([]string, 0, 1)
+ }
+
+ if line[3] == ' ' {
+ // Final line in the response.
+ resp.Reply = line[4:]
+ resp.Err = statusCodeToError(statusCode, resp.Reply)
+ resp.RawLines = append(resp.RawLines, line)
+ return resp, nil
+ }
+
+ if resp.Data == nil {
+ resp.Data = make([]string, 0, 1)
+ }
+ switch line[3] {
+ case '-':
+ // Continuation, keep reading.
+ resp.Data = append(resp.Data, line[4:])
+ resp.RawLines = append(resp.RawLines, line)
+ case '+':
+ // A "dot-encoded" payload follows.
+ resp.Data = append(resp.Data, line[4:])
+ resp.RawLines = append(resp.RawLines, line)
+ dotBody, err := c.conn.ReadDotBytes()
+ if err != nil {
+ return nil, err
+ }
+ if c.debugLog {
+ log.Printf("S: [dot encoded data]")
+ }
+ resp.Data = append(resp.Data, strings.TrimRight(string(dotBody), "\n\r"))
+ dotLines := strings.Split(string(dotBody), "\n")
+ for _, dotLine := range dotLines[:len(dotLines)-1] {
+ resp.RawLines = append(resp.RawLines, dotLine)
+ }
+ resp.RawLines = append(resp.RawLines, ".")
+ default:
+ return nil, newProtocolError("invalid separator: '%c'", line[3])
+ }
+ }
+}
diff --git a/vendor/github.com/yawning/bulb/status.go b/vendor/github.com/yawning/bulb/status.go
new file mode 100644
index 0000000..c0971c5
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/status.go
@@ -0,0 +1,71 @@
+// status.go - Status codes.
+//
+// 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
+// for full details.
+
+package bulb
+
+import (
+ "fmt"
+ "strings"
+ "net/textproto"
+)
+
+// The various control port StatusCode constants.
+const (
+ StatusOk = 250
+ StatusOkUnneccecary = 251
+
+ StatusErrResourceExhausted = 451
+ StatusErrSyntaxError = 500
+ StatusErrUnrecognizedCmd = 510
+ StatusErrUnimplementedCmd = 511
+ StatusErrSyntaxErrorArg = 512
+ StatusErrUnrecognizedCmdArg = 513
+ StatusErrAuthenticationRequired = 514
+ StatusErrBadAuthentication = 515
+ StatusErrUnspecifiedTorError = 550
+ StatusErrInternalError = 551
+ StatusErrUnrecognizedEntity = 552
+ StatusErrInvalidConfigValue = 553
+ StatusErrInvalidDescriptor = 554
+ StatusErrUnmanagedEntity = 555
+
+ StatusAsyncEvent = 650
+)
+
+var statusCodeStringMap = map[int]string{
+ StatusOk: "OK",
+ StatusOkUnneccecary: "Operation was unnecessary",
+
+ StatusErrResourceExhausted: "Resource exhausted",
+ StatusErrSyntaxError: "Syntax error: protocol",
+ StatusErrUnrecognizedCmd: "Unrecognized command",
+ StatusErrUnimplementedCmd: "Unimplemented command",
+ StatusErrSyntaxErrorArg: "Syntax error in command argument",
+ StatusErrUnrecognizedCmdArg: "Unrecognized command argument",
+ StatusErrAuthenticationRequired: "Authentication required",
+ StatusErrBadAuthentication: "Bad authentication",
+ StatusErrUnspecifiedTorError: "Unspecified Tor error",
+ StatusErrInternalError: "Internal error",
+ StatusErrUnrecognizedEntity: "Unrecognized entity",
+ StatusErrInvalidConfigValue: "Invalid configuration value",
+ StatusErrInvalidDescriptor: "Invalid descriptor",
+ StatusErrUnmanagedEntity: "Unmanaged entity",
+
+ StatusAsyncEvent: "Asynchronous event notification",
+}
+
+func statusCodeToError(code int, reply string) *textproto.Error {
+ err := new(textproto.Error)
+ err.Code = code
+ if msg, ok := statusCodeStringMap[code]; ok {
+ trimmedReply := strings.TrimSpace(strings.TrimPrefix(reply, msg))
+ err.Msg = fmt.Sprintf("%s: %s", msg, trimmedReply)
+ } else {
+ err.Msg = fmt.Sprintf("Unknown status code (%03d): %s", code, reply)
+ }
+ return err
+}
diff --git a/vendor/github.com/yawning/bulb/utils/pkcs1/rsa.go b/vendor/github.com/yawning/bulb/utils/pkcs1/rsa.go
new file mode 100644
index 0000000..beb740e
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/utils/pkcs1/rsa.go
@@ -0,0 +1,101 @@
+//
+// rsa.go - PKCS#1 RSA key related helpers.
+//
+// To the extent possible under law, Yawning Angel has waived all copyright and
+// related or neighboring rights to bulb, using the creative commons
+// "cc0" public domain dedication. See LICENSE or
+// for full details.
+
+// Package pkcs1 implements PKCS#1 RSA key marshalling/unmarshalling,
+// compatibile with Tor's usage.
+package pkcs1
+
+import (
+ "crypto/rsa"
+ "crypto/sha1"
+ "encoding/asn1"
+ "encoding/base32"
+ "math/big"
+ "strings"
+)
+
+type pkcs1RSAPrivKey struct {
+ Version int // version
+ N *big.Int // modulus
+ E int // publicExponent
+ D *big.Int // privateExponent
+ P *big.Int // prime1
+ Q *big.Int // prime2
+ Dp *big.Int // exponent1: d mod (p-1)
+ Dq *big.Int // exponent2: d mod (q-1)
+ Qinv *big.Int // coefficient: (inverse of q) mod p
+}
+
+// EncodePrivateKeyDER returns the PKCS#1 DER encoding of a rsa.PrivateKey.
+func EncodePrivateKeyDER(sk *rsa.PrivateKey) ([]byte, error) {
+ // The crypto.RSA structure has a slightly different layout than PKCS#1
+ // private keys, so directly marshaling does not work. Pull out the values
+ // into a strucuture with the correct layout and marshal.
+ sk.Precompute() // Ensure that the structure is fully populated.
+ k := pkcs1RSAPrivKey{
+ Version: 0,
+ N: sk.N,
+ E: sk.E,
+ D: sk.D,
+ P: sk.Primes[0],
+ Q: sk.Primes[1],
+ Dp: sk.Precomputed.Dp,
+ Dq: sk.Precomputed.Dq,
+ Qinv: sk.Precomputed.Qinv,
+ }
+ return asn1.Marshal(k)
+}
+
+// DecodePrivateKeyDER returns the rsa.PrivateKey decoding of a PKCS#1 DER blob.
+func DecodePrivateKeyDER(b []byte) (*rsa.PrivateKey, []byte, error) {
+ var k pkcs1RSAPrivKey
+ rest, err := asn1.Unmarshal(b, &k)
+ if err == nil {
+ sk := &rsa.PrivateKey{}
+ sk.Primes = make([]*big.Int, 2)
+ sk.N = k.N
+ sk.E = k.E
+ sk.D = k.D
+ sk.Primes[0] = k.P
+ sk.Primes[1] = k.Q
+
+ // Ignore the precomputed values and just rederive them.
+ sk.Precompute()
+ return sk, rest, nil
+ }
+ return nil, rest, err
+}
+
+// EncodePublicKeyDER returns the PKCS#1 DER encoding of a rsa.PublicKey.
+func EncodePublicKeyDER(pk *rsa.PublicKey) ([]byte, error) {
+ // The crypto.RSA structure is exactly the same as the PCKS#1 public keys,
+ // when the encoding/asn.1 marshaller is done with it.
+ //
+ // DER encoding of (SEQUENCE | INTEGER(n) | INTEGER(e))
+ return asn1.Marshal(*pk)
+}
+
+// DecodePublicKeyDER returns the rsa.PublicKey decoding of a PKCS#1 DER blob.
+func DecodePublicKeyDER(b []byte) (*rsa.PublicKey, []byte, error) {
+ pk := &rsa.PublicKey{}
+ rest, err := asn1.Unmarshal(b, pk)
+ return pk, rest, err
+}
+
+// OnionAddr returns the Tor Onion Service address corresponding to a given
+// rsa.PublicKey.
+func OnionAddr(pk *rsa.PublicKey) (string, error) {
+ der, err := EncodePublicKeyDER(pk)
+ if err != nil {
+ return "", err
+ }
+ h := sha1.Sum(der)
+ hb32 := base32.StdEncoding.EncodeToString(h[:10])
+
+ return strings.ToLower(hb32), nil
+}
diff --git a/vendor/github.com/yawning/bulb/utils/utils.go b/vendor/github.com/yawning/bulb/utils/utils.go
new file mode 100644
index 0000000..d741a8b
--- /dev/null
+++ b/vendor/github.com/yawning/bulb/utils/utils.go
@@ -0,0 +1,81 @@
+// utils.go - A grab bag of useful utilitiy functions.
+//
+// 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
+// for full details.
+
+// Package utils implements useful utilities for dealing with Tor and it's
+// control port.
+package utils
+
+import (
+ "net"
+ "net/url"
+ "strconv"
+)
+
+// SplitQuoted splits s by sep if it is found outside substring
+// quoted by quote.
+func SplitQuoted(s string, quote, sep rune) (splitted []string) {
+ quoteFlag := false
+NewSubstring:
+ for i, c := range s {
+ if c == quote {
+ quoteFlag = !quoteFlag
+ }
+ if c == sep && !quoteFlag {
+ splitted = append(splitted, s[:i])
+ s = s[i+1:]
+ goto NewSubstring
+ }
+ }
+ return append(splitted, s)
+}
+
+// ParseControlPortString parses a string representation of a control port
+// address into a network/address string pair suitable for use with "dial".
+//
+// Valid string representations are:
+// * tcp://address:port
+// * unix://path
+// * port (Translates to tcp://127.0.0.1:port)
+func ParseControlPortString(raw string) (network, addr string, err error) {
+ // Try parsing it as a naked port.
+ if _, err = strconv.ParseUint(raw, 10, 16); err == nil {
+ raw = "tcp://127.0.0.1:" + raw
+ }
+
+ // Ok, parse/validate the URI.
+ uri, err := url.Parse(raw)
+ if err != nil {
+ return "", "", err
+ }
+ if uri.Opaque != "" || uri.RawQuery != "" || uri.Fragment != "" {
+ return "", "", net.InvalidAddrError("uri has Opaque/Query/Fragment")
+ }
+ switch uri.Scheme {
+ case "tcp":
+ if uri.Path != "" {
+ return "", "", net.InvalidAddrError("tcp uri has a path")
+ }
+ tcpAddr, err := net.ResolveTCPAddr(uri.Scheme, uri.Host)
+ if err != nil {
+ return "", "", err
+ }
+ if tcpAddr.Port == 0 {
+ return "", "", net.InvalidAddrError("tcp uri is missing a port")
+ }
+ return uri.Scheme, uri.Host, nil
+ case "unix":
+ if uri.Host != "" {
+ return "", "", net.InvalidAddrError("unix uri has a host")
+ }
+ _, err := net.ResolveUnixAddr(uri.Scheme, uri.Path)
+ if err != nil {
+ return "", "", err
+ }
+ return uri.Scheme, uri.Path, nil
+ }
+ return "", "", net.InvalidAddrError("unknown scheme: " + uri.Scheme)
+}