Adding bulb to godep
This commit is contained in:
		
							parent
							
								
									22cbf5d738
								
							
						
					
					
						commit
						93baafc2f7
					
				| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
*.swp
 | 
			
		||||
*~
 | 
			
		||||
examples/basic/basic
 | 
			
		||||
examples/listener/listener
 | 
			
		||||
examples/dialer/dialer
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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)
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
// <http://creativecommons.org/publicdomain/zero/1.0/> 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)
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue