Compare commits

..

No commits in common. "master" and "new_api-fix_app" have entirely different histories.

36 changed files with 159 additions and 1852 deletions

15
Godeps/Godeps.json generated
View File

@ -2,26 +2,11 @@
"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"

View File

@ -1,23 +0,0 @@
package application
import (
"crypto/rsa"
)
// AcceptAllContactManager implements the contact manager interface an presumes
// all connections are allowed.
type AcceptAllContactManager struct {
}
// LookupContact returns that a contact is known and allowed to communicate for all cases.
func (aacm *AcceptAllContactManager) LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
return true, true
}
func (aacm *AcceptAllContactManager) GetContactDetails() (string, string) {
return "", ""
}
func (aacm *AcceptAllContactManager) ContactRequest(name string, message string) string {
return "Accepted"
}

View File

@ -1,145 +1,36 @@
package application
import (
"crypto/rsa"
"github.com/s-rah/go-ricochet"
"errors"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/connection"
"log"
"net"
"time"
)
// RicochetApplication bundles many useful constructs that are
// likely standard in a ricochet application
type RicochetApplication struct {
contactManager ContactManagerInterface
privateKey *rsa.PrivateKey
chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
chatMessageAckHandler func(*RicochetApplicationInstance, uint32)
l net.Listener
connection *connection.Connection
}
type RicochetApplicationInstance struct {
connection.AutoConnectionHandler
connection *connection.Connection
RemoteHostname string
ChatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
ChatMessageAckHandler func(*RicochetApplicationInstance, uint32)
// NewRicochetApplication ...
func NewRicochetApplication(connection *connection.Connection) *RicochetApplication {
ra := new(RicochetApplication)
ra.connection = connection
return ra
}
func (rai *RicochetApplicationInstance) GetContactDetails() (string, string) {
return "EchoBot", "I LIVE 😈😈!!!!"
}
func (rai *RicochetApplicationInstance) ContactRequest(name string, message string) string {
return "Accepted"
}
func (rai *RicochetApplicationInstance) ContactRequestRejected() {
}
func (rai *RicochetApplicationInstance) ContactRequestAccepted() {
}
func (rai *RicochetApplicationInstance) ContactRequestError() {
}
func (rai *RicochetApplicationInstance) SendChatMessage(message string) {
// Technically this errors afte the second time but we can ignore it.
rai.connection.RequestOpenChannel("im.ricochet.chat", rai)
rai.connection.Do(func() error {
channel := rai.connection.Channel("im.ricochet.chat", channels.Outbound)
// SendMessage ...
func (ra *RicochetApplication) SendChatMessage(message string) error {
return ra.connection.Do(func() error {
channel := ra.connection.Channel("im.ricochet.chat", channels.Outbound)
if channel != nil {
chatchannel, ok := (*channel.Handler).(*channels.ChatChannel)
if ok {
chatchannel.SendMessage(message)
}
} else {
return errors.New("")
}
return nil
})
}
func (rai *RicochetApplicationInstance) ChatMessage(messageID uint32, when time.Time, message string) bool {
go rai.ChatMessageHandler(rai, messageID, when, message)
return true
}
func (rai *RicochetApplicationInstance) ChatMessageAck(messageID uint32) {
rai.ChatMessageAckHandler(rai, messageID)
}
func (ra *RicochetApplication) Init(pk *rsa.PrivateKey, cm ContactManagerInterface) {
ra.privateKey = pk
ra.contactManager = cm
ra.chatMessageHandler = func(*RicochetApplicationInstance, uint32, time.Time, string) {}
ra.chatMessageAckHandler = func(*RicochetApplicationInstance, uint32) {}
}
func (ra *RicochetApplication) OnChatMessage(call func(*RicochetApplicationInstance, uint32, time.Time, string)) {
ra.chatMessageHandler = call
}
func (ra *RicochetApplication) OnChatMessageAck(call func(*RicochetApplicationInstance, uint32)) {
ra.chatMessageAckHandler = call
}
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
rc, err := goricochet.NegotiateVersionInbound(conn)
if err != nil {
log.Printf("There was an error")
conn.Close()
return
}
ich := connection.HandleInboundConnection(rc)
err = ich.ProcessAuthAsServer(ra.privateKey, ra.contactManager.LookupContact)
if err != nil {
log.Printf("There was an error")
conn.Close()
return
}
rai := new(RicochetApplicationInstance)
rai.Init(ra.privateKey, "")
rai.RemoteHostname = rc.RemoteHostname
rai.connection = rc
rai.ChatMessageHandler = ra.chatMessageHandler
rai.ChatMessageAckHandler = ra.chatMessageAckHandler
rai.RegisterChannelHandler("im.ricochet.contact.request", func() channels.Handler {
contact := new(channels.ContactRequestChannel)
contact.Handler = rai
return contact
})
rai.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler {
chat := new(channels.ChatChannel)
chat.Handler = rai
return chat
})
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
}
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
}
}
}

View File

@ -1,11 +0,0 @@
package application
import (
"crypto/rsa"
)
// ContactManagerInterface provides a mechanism for autonous applications
// to make decisions on what connections to accept or reject.
type ContactManagerInterface interface {
LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
}

View File

@ -1,31 +0,0 @@
package main
import (
"github.com/s-rah/go-ricochet/application"
"github.com/s-rah/go-ricochet/utils"
"log"
"time"
)
func main() {
echobot := new(application.RicochetApplication)
pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key")
if err != nil {
log.Fatalf("error reading private key file: %v", err)
}
l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", pk, 9878)
if err != nil {
log.Fatalf("error setting up onion service: %v", err)
}
echobot.Init(pk, new(application.AcceptAllContactManager))
echobot.OnChatMessage(func(rai *application.RicochetApplicationInstance, id uint32, timestamp time.Time, message string) {
log.Printf("message from %v - %v", rai.RemoteHostname, message)
rai.SendChatMessage(message)
})
log.Printf("echobot listening on %s", l.Addr().String())
echobot.Run(l)
}

View File

@ -1,27 +0,0 @@
package application
import (
"crypto/rsa"
"github.com/yawning/bulb"
"net"
)
// "127.0.0.1:9051" "tcp4"
// "/var/run/tor/control" "unix"
func SetupOnion(torControlAddress string, torControlSocketType string, authentication string, pk *rsa.PrivateKey, onionport uint16) (net.Listener, error) {
c, err := bulb.Dial(torControlSocketType, torControlAddress)
if err != nil {
return nil, err
}
if err := c.Authenticate(authentication); err != nil {
return nil, err
}
cfg := &bulb.NewOnionConfig{
DiscardPK: true,
PrivateKey: pk,
}
return c.NewListener(cfg, onionport)
}

View File

@ -12,7 +12,6 @@ const (
// AuthChannelResult captures the result of an authentication flow
type AuthChannelResult struct {
Hostname string
Accepted bool
IsKnownContact bool
}

View File

@ -1,19 +1,13 @@
package channels
import (
"errors"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/contact"
"github.com/s-rah/go-ricochet/wire/control"
)
// Defining Versions
const (
InvalidContactNameError = utils.Error("InvalidContactNameError")
InvalidContactMessageError = utils.Error("InvalidContactMessageError")
InvalidContactRequestError = utils.Error("InvalidContactRequestError")
)
// ContactRequestChannel implements the ChannelHandler interface for a channel of
// type "im.ricochet.contact.request". The channel may be inbound or outbound.
// a ContactRequestChannelHandler implementation to handle chat events.
@ -79,12 +73,12 @@ func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Dat
if len(contactRequest.GetNickname()) > int(Protocol_Data_ContactRequest.Limits_NicknameMaxCharacters) {
// Violation of the Protocol
return nil, InvalidContactNameError
return nil, errors.New("invalid nickname")
}
if len(contactRequest.GetMessageText()) > int(Protocol_Data_ContactRequest.Limits_MessageMaxCharacters) {
// Violation of the Protocol
return nil, InvalidContactMessageError
return nil, errors.New("invalid message")
}
result := crc.Handler.ContactRequest(contactRequest.GetNickname(), contactRequest.GetMessageText())
@ -92,7 +86,7 @@ func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Dat
return messageBuilder.ReplyToContactRequestOnResponse(channel.ID, result), nil
}
}
return nil, InvalidContactRequestError
return nil, errors.New("could not parse contact request extension")
}
// OpenOutbound is the first method called for an outbound channel request.

View File

@ -7,18 +7,16 @@ import (
"crypto/rsa"
"crypto/sha256"
"encoding/asn1"
"errors"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/auth"
"github.com/s-rah/go-ricochet/wire/control"
"io"
"log"
)
const (
InvalidClientCookieError = utils.Error("InvalidClientCookieError")
)
// HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
// HiddenServiceAuthChannel wraps implementation o fim.ricochet.auth.hidden-service"
type HiddenServiceAuthChannel struct {
// Methods of Handler are called for events on this channel
Handler AuthChannelHandler
@ -77,16 +75,11 @@ func (ah *HiddenServiceAuthChannel) Closed(err error) {
// returned, it will be sent as the ChannelResult message.
// Remote -> [Open Authentication Channel] -> Local
func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
if ah.PrivateKey == nil {
return nil, utils.PrivateKeyNotSetError
}
ah.channel = channel
clientCookie, _ := proto.GetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie)
if len(clientCookie.([]byte)[:]) != 16 {
// reutrn without opening channel.
return nil, InvalidClientCookieError
return nil, errors.New("invalid client cookie")
}
ah.AddClientCookie(clientCookie.([]byte)[:])
messageBuilder := new(utils.MessageBuilder)
@ -99,11 +92,6 @@ func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_D
// returned, it will be sent as the OpenChannel message.
// Local -> [Open Authentication Channel] -> Remote
func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) {
if ah.PrivateKey == nil {
return nil, utils.PrivateKeyNotSetError
}
ah.channel = channel
messageBuilder := new(utils.MessageBuilder)
return messageBuilder.OpenAuthenticationChannel(ah.channel.ID, ah.GenClientCookie()), nil
@ -256,7 +244,7 @@ func (ah *HiddenServiceAuthChannel) GenChallenge(clientHostname string, serverHo
key := make([]byte, 32)
copy(key[0:16], ah.clientCookie[:])
copy(key[16:], ah.serverCookie[:])
log.Printf("CHALLENGE: %s %s %v", clientHostname, serverHostname, key)
value := []byte(clientHostname + serverHostname)
mac := hmac.New(sha256.New, key)
mac.Write(value)

View File

@ -71,10 +71,9 @@ func GetOpenAuthenticationChannelMessage() *Protocol_Data_Control.OpenChannel {
}
func TestAuthenticationOpenInbound(t *testing.T) {
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
opm := GetOpenAuthenticationChannelMessage()
authHandler := new(HiddenServiceAuthChannel)
authHandler.PrivateKey = privateKey
channel := Channel{ID: 1}
response, err := authHandler.OpenInbound(&channel, opm)
@ -91,9 +90,7 @@ func TestAuthenticationOpenInbound(t *testing.T) {
}
func TestAuthenticationOpenOutbound(t *testing.T) {
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
authHandler := new(HiddenServiceAuthChannel)
authHandler.PrivateKey = privateKey
channel := Channel{ID: 1}
response, err := authHandler.OpenOutbound(&channel)

View File

@ -4,6 +4,7 @@ import (
"crypto/rsa"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/utils"
"log"
)
// AutoConnectionHandler implements the ConnectionHandler interface on behalf of
@ -23,9 +24,7 @@ type AutoConnectionHandler struct {
}
// Init ...
// TODO: Split this into client and server init
func (ach *AutoConnectionHandler) Init(privateKey *rsa.PrivateKey, serverHostname string) {
ach.handlerMap = make(map[string]func() channels.Handler)
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service", func() channels.Handler {
hsau := new(channels.HiddenServiceAuthChannel)
@ -58,6 +57,7 @@ func (ach *AutoConnectionHandler) WaitForAuthenticationEvent() channels.AuthChan
// ClientAuthResult ...
func (ach *AutoConnectionHandler) ClientAuthResult(accepted bool, isKnownContact bool) {
log.Printf("Got auth result %v %v", accepted, isKnownContact)
ach.authResultChannel <- channels.AuthChannelResult{Accepted: accepted, IsKnownContact: isKnownContact}
}
@ -65,7 +65,7 @@ func (ach *AutoConnectionHandler) ClientAuthResult(accepted bool, isKnownContact
func (ach *AutoConnectionHandler) ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
// Do something
accepted, isKnownContact := ach.sach(hostname, publicKey)
ach.authResultChannel <- channels.AuthChannelResult{Hostname: hostname, Accepted: accepted, IsKnownContact: isKnownContact}
ach.authResultChannel <- channels.AuthChannelResult{Accepted: accepted, IsKnownContact: isKnownContact}
return accepted, isKnownContact
}
@ -87,6 +87,7 @@ func (ach *AutoConnectionHandler) OnOpenChannelRequest(ctype string) (channels.H
handler, ok := ach.handlerMap[ctype]
if ok {
h := handler()
log.Printf("Got Channel Handler")
return h, nil
}
return nil, utils.UnknownChannelTypeError

View File

@ -1,8 +1,8 @@
package connection
import (
"errors"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/utils"
)
// ChannelManager encapsulates the logic for server and client side assignment
@ -38,7 +38,7 @@ func NewServerChannelManager() *ChannelManager {
func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channels.Channel, error) {
// Some channels only allow us to open one of them per connection
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Outbound) != nil {
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
return nil, errors.New("Connection already has channel of type " + chandler.Type())
}
channel := new(channels.Channel)
@ -57,20 +57,20 @@ func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channe
func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler channels.Handler) (*channels.Channel, error) {
if cm.isClient && (channelID%2) != 0 {
// Server is trying to open odd numbered channels
return nil, utils.ServerAttemptedToOpenEvenNumberedChannelError
return nil, errors.New("server may only open even numbered channels")
} else if !cm.isClient && (channelID%2) == 0 {
// Server is trying to open odd numbered channels
return nil, utils.ClientAttemptedToOpenOddNumberedChannelError
return nil, errors.New("client may only open odd numbered channels")
}
_, exists := cm.channels[channelID]
if exists {
return nil, utils.ChannelIDIsAlreadyInUseError
return nil, errors.New("channel id is already in use")
}
// Some channels only allow us to open one of them per connection
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil {
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
return nil, errors.New("Connection already has channel of type " + chandler.Type())
}
channel := new(channels.Channel)

View File

@ -2,13 +2,13 @@ package connection
import (
"errors"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/control"
"io"
"log"
"time"
)
// Connection encapsulates the state required to maintain a connection to
@ -29,7 +29,6 @@ type Connection struct {
unlockResponseChannel chan bool
messageBuilder utils.MessageBuilder
trace bool
Conn io.ReadWriteCloser
IsInbound bool
@ -75,10 +74,6 @@ func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Conn
return rc
}
func (rc *Connection) TraceLog(enabled bool) {
rc.trace = enabled
}
// start
func (rc *Connection) start() {
for {
@ -96,11 +91,11 @@ func (rc *Connection) start() {
// use Do()
func (rc *Connection) Do(do func() error) error {
// Force process to soft-break so we can lock
rc.traceLog("request unlocking of process loop for do()")
log.Printf("UnLocking Processloop")
rc.unlockChannel <- true
rc.traceLog("process loop is unlocked for do()")
log.Printf("Unlocked Processloop")
ret := do()
rc.traceLog("giving up lock process loop after do() ")
log.Printf("Giving up lock Processloop")
rc.unlockResponseChannel <- true
return ret
}
@ -110,12 +105,10 @@ func (rc *Connection) Do(do func() error) error {
// are not met on the local side (a nill error return does not mean the
// channel was opened successfully)
func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error {
rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype))
return rc.Do(func() error {
chandler, err := handler.OnOpenChannelRequest(ctype)
if err != nil {
rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err))
return err
}
@ -124,14 +117,13 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error {
// Enforce Authentication Check.
_, authed := rc.Authentication[chandler.RequiresAuthentication()]
if !authed {
return utils.UnauthorizedActionError
return errors.New("connection is not auth'd")
}
}
channel, err := rc.channelManager.OpenChannelRequest(chandler)
if err != nil {
rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err))
return err
}
@ -147,10 +139,8 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error {
}
response, err := chandler.OpenOutbound(channel)
if err == nil {
rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype))
rc.SendRicochetPacket(rc.Conn, 0, response)
} else {
rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err))
rc.channelManager.RemoveChannel(channel.ID)
}
return nil
@ -167,18 +157,19 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error {
// Process blocks until the connection is closed or until Break() is called.
// If the connection is closed, a non-nil error is returned.
func (rc *Connection) Process(handler Handler) error {
rc.traceLog("entering process loop")
log.Printf("Entering Processloop")
handler.OnReady(rc)
breaked := false
for !breaked {
var packet utils.RicochetData
tick := time.Tick(30 * time.Second)
select {
case <-rc.unlockChannel:
<-rc.unlockResponseChannel
continue
case <-rc.breakChannel:
rc.traceLog("process has ended after break")
log.Printf("Process has Ended as Expected!!!")
breaked = true
continue
case packet = <-rc.packetChannel:
@ -187,10 +178,14 @@ func (rc *Connection) Process(handler Handler) error {
rc.Conn.Close()
handler.OnClosed(err)
return err
case <-tick:
log.Printf("timeout")
return errors.New("peer timed out")
}
log.Printf("Received Packet on Channel %d", packet.Channel)
if packet.Channel == 0 {
rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel))
res := new(Protocol_Data_Control.Packet)
err := proto.Unmarshal(packet.Data[:], res)
if err == nil {
@ -201,11 +196,9 @@ func (rc *Connection) Process(handler Handler) error {
channel, found := rc.channelManager.GetChannel(packet.Channel)
if found {
if len(packet.Data) == 0 {
rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel))
rc.channelManager.RemoveChannel(packet.Channel)
(*channel.Handler).Closed(utils.ChannelClosedByPeerError)
(*channel.Handler).Closed(errors.New("channel closed by peer"))
} else {
rc.traceLog(fmt.Sprintf("received packet on %v channel %d", (*channel.Handler).Type(), packet.Channel))
// Send The Ricochet Packet to the Handler
(*channel.Handler).Packet(packet.Data[:])
}
@ -213,7 +206,6 @@ func (rc *Connection) Process(handler Handler) error {
// When a non-zero packet is received for an unknown
// channel, the recipient responds by closing
// that channel.
rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel))
if len(packet.Data) != 0 {
rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{})
}
@ -242,15 +234,12 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
// Check that we have the authentication already
if chandler.RequiresAuthentication() != "none" {
rc.traceLog(fmt.Sprintf("channel %v requires authorization of type %v", chandler.Type(), chandler.RequiresAuthentication()))
// Enforce Authentication Check.
_, authed := rc.Authentication[chandler.RequiresAuthentication()]
if !authed {
rc.SendRicochetPacket(rc.Conn, 0, []byte{})
rc.traceLog(fmt.Sprintf("do not have required authorization to open channel type %v", chandler.Type()))
return
}
rc.traceLog("succeeded authorization check")
}
channel, err := rc.channelManager.OpenChannelRequestFromPeer(opm.GetChannelIdentifier(), chandler)
@ -270,17 +259,15 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
response, err := chandler.OpenInbound(channel, opm)
if err == nil && channel.Pending == false {
rc.traceLog(fmt.Sprintf("opening channel %v on %v", channel.Type, channel.ID))
log.Printf("Opening Channel %v on %v", channel.Type, channel.ID)
rc.SendRicochetPacket(rc.Conn, 0, response)
} else {
rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID))
rc.channelManager.RemoveChannel(channel.ID)
rc.SendRicochetPacket(rc.Conn, 0, []byte{})
}
} else {
// Send Error Packet
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), "GenericError")
rc.traceLog(fmt.Sprintf("sending reject open channel for %v", opm.GetChannelIdentifier()))
rc.SendRicochetPacket(rc.Conn, 0, response)
}
@ -291,15 +278,12 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
channel, found := rc.channelManager.GetChannel(id)
if !found {
rc.traceLog(fmt.Sprintf("channel result recived for unknown channel: %v", channel.Type, id))
return
}
if cr.GetOpened() {
rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id))
(*channel.Handler).OpenOutboundResult(nil, cr)
} else {
rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id))
(*channel.Handler).OpenOutboundResult(errors.New(""), cr)
}
@ -307,35 +291,25 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
// XXX Though not currently part of the protocol
// We should likely put these calls behind
// authentication.
rc.traceLog("received keep alive packet")
if res.GetKeepAlive().GetResponseRequested() {
messageBuilder := new(utils.MessageBuilder)
raw := messageBuilder.KeepAlive(true)
rc.traceLog("sending keep alive response")
rc.SendRicochetPacket(rc.Conn, 0, raw)
}
} else if res.GetEnableFeatures() != nil {
rc.traceLog("received features enabled packet")
// TODO Respond with an Empty List
messageBuilder := new(utils.MessageBuilder)
raw := messageBuilder.FeaturesEnabled([]string{})
rc.traceLog("sending featured enabled empty response")
rc.SendRicochetPacket(rc.Conn, 0, raw)
} else if res.GetFeaturesEnabled() != nil {
// TODO We should never send out an enabled features
// request.
rc.traceLog("sending unsolicited features enabled response")
}
}
func (rc *Connection) traceLog(message string) {
if rc.trace {
log.Printf(message)
}
}
// Break causes Process() to return, but does not close the underlying connection
func (rc *Connection) Break() {
rc.traceLog("breaking out of process loop")
log.Printf("breaking...")
rc.breakChannel <- true
<-rc.breakResultChannel // Wait for Process to End
}

View File

@ -21,7 +21,6 @@ func TestProcessAuthAsServer(t *testing.T) {
cconn, _ := net.Dial("tcp", ln.Addr().String())
orc := NewOutboundConnection(cconn, "kwke2hntvyfqm7dr")
orc.TraceLog(true)
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
known, err := HandleOutboundConnection(orc).ProcessAuthAsClient(privateKey)

View File

@ -35,10 +35,6 @@ func HandleInboundConnection(c *Connection) *InboundConnectionHandler {
// assume they are required to send a contact request before any other activity.
func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.PrivateKey, sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)) error {
if privateKey == nil {
return utils.PrivateKeyNotSetError
}
ach := new(AutoConnectionHandler)
ach.Init(privateKey, ich.connection.RemoteHostname)
ach.SetServerAuthHandler(sach)
@ -56,7 +52,6 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.Private
if err == nil {
if authResult.Accepted == true {
ich.connection.RemoteHostname = authResult.Hostname
return nil
}
return utils.ClientFailedToAuthenticateError

View File

@ -2,9 +2,10 @@ package connection
import (
"crypto/rsa"
"errors"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/policies"
"github.com/s-rah/go-ricochet/utils"
"log"
)
// OutboundConnectionHandler is a convieniance wrapper for handling outbound
@ -32,11 +33,6 @@ func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler {
// accepts us as a known contact. Unknown contacts will generally need to send a contact
// request before any other activity.
func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.PrivateKey) (bool, error) {
if privateKey == nil {
return false, utils.PrivateKeyNotSetError
}
ach := new(AutoConnectionHandler)
ach.Init(privateKey, och.connection.RemoteHostname)
@ -46,7 +42,9 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.Privat
if err != nil {
return
}
log.Printf("waiting for auth result")
result = ach.WaitForAuthenticationEvent()
log.Printf("received auth result")
och.connection.Break()
}()
@ -60,5 +58,5 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.Privat
return result.IsKnownContact, nil
}
}
return false, utils.ServerRejectedClientConnectionError
return false, errors.New("authentication was not accepted by the server")
}

View File

@ -58,7 +58,7 @@ func (echobot *RicochetEchoBot) Connect(privateKeyFile string, hostname string)
})
rc, _ := goricochet.Open(hostname)
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey)
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey)
if err == nil {
go rc.Process(echobot)

View File

@ -1,90 +1,55 @@
package goricochet
import (
"github.com/s-rah/go-ricochet/connection"
"github.com/s-rah/go-ricochet/utils"
"io"
"net"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/connection"
"io"
"net"
"log"
)
// Open establishes a protocol session on an established net.Conn, and returns a new
// OpenConnection instance representing this connection. On error, the connection
// will be closed. This function blocks until version negotiation has completed.
// The application should call Process() on the returned OpenConnection to continue
// handling protocol messages.
func Open(remoteHostname string) (*connection.Connection, error) {
networkResolver := utils.NetworkResolver{}
conn, remoteHostname, err := networkResolver.Resolve(remoteHostname)
networkResolver := utils.NetworkResolver{}
log.Printf("Connecting...")
conn, remoteHostname, err := networkResolver.Resolve(remoteHostname)
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
rc, err := negotiateVersion(conn, remoteHostname)
if err != nil {
conn.Close()
return nil, err
}
return rc, nil
log.Printf("Connected...negotiating version")
rc, err := negotiateVersion(conn, remoteHostname)
if err != nil {
conn.Close()
return nil, err
}
log.Printf("Connected...negotiated version")
return rc, nil
}
// negotiate version takes an open network connection and executes
// the ricochet version negotiation procedure.
func negotiateVersion(conn net.Conn, remoteHostname string) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x01}
if n, err := conn.Write(versions); err != nil || n < len(versions) {
return nil, utils.VersionNegotiationError
}
versions := []byte{0x49, 0x4D, 0x01, 0x01}
if n, err := conn.Write(versions); err != nil || n < len(versions) {
return nil, utils.VersionNegotiationError
}
res := make([]byte, 1)
if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil {
return nil, utils.VersionNegotiationError
}
res := make([]byte, 1)
if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil {
return nil, utils.VersionNegotiationError
}
if res[0] != 0x01 {
return nil, utils.VersionNegotiationFailed
}
rc := connection.NewOutboundConnection(conn, remoteHostname)
return rc, nil
if res[0] != 0x01 {
return nil, utils.VersionNegotiationFailed
}
rc := connection.NewOutboundConnection(conn,remoteHostname)
return rc, nil
}
// NegotiateVersionInbound takes in a connection and performs version negotiation
// as if that connection was a client. Returns a ricochet connection if successful
// error otherwise.
func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x01}
// Read version response header
header := make([]byte, 3)
if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil {
return nil, err
}
if header[0] != versions[0] || header[1] != versions[1] || header[2] < 1 {
return nil, utils.VersionNegotiationError
}
// Read list of supported versions (which is header[2] bytes long)
versionList := make([]byte, header[2])
if _, err := io.ReadAtLeast(conn, versionList, len(versionList)); err != nil {
return nil, utils.VersionNegotiationError
}
selectedVersion := byte(0xff)
for _, v := range versionList {
if v == 0x01 {
selectedVersion = v
break
}
}
if n, err := conn.Write([]byte{selectedVersion}); err != nil || n < 1 {
return nil, utils.VersionNegotiationFailed
}
if selectedVersion == 0xff {
return nil, utils.VersionNegotiationFailed
}
rc := connection.NewInboundConnection(conn)
return rc, nil
}

View File

@ -1,68 +1,70 @@
package goricochet
import (
"github.com/s-rah/go-ricochet/utils"
"net"
"testing"
"time"
"testing"
"github.com/s-rah/go-ricochet/utils"
"net"
"time"
)
func SimpleServer() {
ln, _ := net.Listen("tcp", "127.0.0.1:11000")
conn, _ := ln.Accept()
b := make([]byte, 4)
n, err := conn.Read(b)
if n == 4 && err == nil {
conn.Write([]byte{0x01})
}
conn.Close()
ln,_ := net.Listen("tcp", "127.0.0.1:11000")
conn,_ := ln.Accept()
b := make([]byte, 4)
n,err := conn.Read(b)
if n == 4 && err == nil {
conn.Write([]byte{0x01})
}
conn.Close()
}
func BadVersionNegotiation() {
ln, _ := net.Listen("tcp", "127.0.0.1:11001")
conn, _ := ln.Accept()
// We are already testing negotiation bytes, we don't care, just send a termination.
conn.Write([]byte{0x00})
conn.Close()
ln,_ := net.Listen("tcp", "127.0.0.1:11001")
conn,_ := ln.Accept()
// We are already testing negotiation bytes, we don't care, just send a termination.
conn.Write([]byte{0x00})
conn.Close()
}
func NotRicochetServer() {
ln, _ := net.Listen("tcp", "127.0.0.1:11002")
conn, _ := ln.Accept()
conn.Close()
ln,_ := net.Listen("tcp", "127.0.0.1:11002")
conn,_ := ln.Accept()
conn.Close()
}
func TestRicochet(t *testing.T) {
go SimpleServer()
// Wait for Server to Initialize
time.Sleep(time.Second)
go SimpleServer()
// Wait for Server to Initialize
time.Sleep(time.Second)
rc, err := Open("127.0.0.1:11000|abcdefghijklmno.onion")
if err == nil {
if rc.IsInbound {
t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen")
}
return
}
t.Errorf("RicochetProtocol: Open Failed: %v", err)
rc,err := Open("127.0.0.1:11000|abcdefghijklmno.onion")
if err == nil {
if rc.IsInbound {
t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen")
}
return
}
t.Errorf("RicochetProtocol: Open Failed: %v", err)
}
func TestBadVersionNegotiation(t *testing.T) {
go BadVersionNegotiation()
time.Sleep(time.Second)
func TestBadVersionNegotiation(t*testing.T) {
go BadVersionNegotiation()
time.Sleep(time.Second)
_, err := Open("127.0.0.1:11001|abcdefghijklmno.onion")
if err != utils.VersionNegotiationFailed {
t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err)
}
_,err := Open("127.0.0.1:11001|abcdefghijklmno.onion")
if err != utils.VersionNegotiationFailed {
t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err)
}
}
func TestNotARicochetServer(t *testing.T) {
go NotRicochetServer()
time.Sleep(time.Second)
_, err := Open("127.0.0.1:11002|abcdefghijklmno.onion")
if err != utils.VersionNegotiationError {
t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err)
}
func TestNotARicochetServer(t*testing.T) {
go NotRicochetServer()
time.Sleep(time.Second)
_,err := Open("127.0.0.1:11002|abcdefghijklmno.onion")
if err != utils.VersionNegotiationError {
t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err)
}
}

View File

@ -4,53 +4,22 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"errors"
"crypto/rand"
"io/ioutil"
)
const (
InvalidPrivateKeyFileError = Error("InvalidPrivateKeyFileError")
RICOCHET_KEY_SIZE = 1024
)
// Generate a private key for use
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, RICOCHET_KEY_SIZE)
if err != nil {
return nil, errors.New("Could not generate key: " + err.Error())
}
privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey)
return x509.ParsePKCS1PrivateKey(privateKeyDer)
}
// LoadPrivateKeyFromFile loads a private key from a file...
func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) {
pemData, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return ParsePrivateKey(pemData)
}
// Convert a private key string to a usable private key
func ParsePrivateKey(pemData []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(pemData)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return nil, InvalidPrivateKeyFileError
return nil, errors.New("not a private key")
}
return x509.ParsePKCS1PrivateKey(block.Bytes)
}
// turn a private key into storable string
func PrivateKeyToString(privateKey *rsa.PrivateKey) string {
privateKeyBlock := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
return string(pem.EncodeToMemory(&privateKeyBlock))
}

View File

@ -2,6 +2,7 @@ package utils
import (
"fmt"
"log"
)
// Error captures various common ricochet errors
@ -20,27 +21,21 @@ const (
UnknownChannelTypeError = Error("UnknownChannelTypeError")
UnauthorizedChannelTypeError = Error("UnauthorizedChannelTypeError")
// Timeout Errors
ActionTimedOutError = Error("ActionTimedOutError")
PeerTimedOutError = Error("PeerTimedOutError")
// Authentication Errors
ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError")
ServerRejectedClientConnectionError = Error("ServerRejectedClientConnectionError")
UnauthorizedActionError = Error("UnauthorizedActionError")
ChannelClosedByPeerError = Error("ChannelClosedByPeerError")
// Channel Management Errors
ServerAttemptedToOpenEvenNumberedChannelError = Error("ServerAttemptedToOpenEvenNumberedChannelError")
ClientAttemptedToOpenOddNumberedChannelError = Error("ClientAttemptedToOpenOddNumberedChannelError")
ChannelIDIsAlreadyInUseError = Error("ChannelIDIsAlreadyInUseError")
AttemptToOpenMoreThanOneSingletonChannelError = Error("AttemptToOpenMoreThanOneSingletonChannelError")
// Library Use Errors
PrivateKeyNotSetError = Error("ClientFailedToAuthenticateError")
ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError")
)
// RecoverFromError doesn't really recover from anything....see comment below
func RecoverFromError() {
if r := recover(); r != nil {
// This should only really happen if there is a failure de/serializing. If
// this does happen then we currently error. In the future we might be
// able to make this nicer.
log.Fatalf("Recovered from panic() - this really shouldn't happen. Reason: %v", r)
}
}
// CheckError is a helper function for panicing on errors which we need to handle
// but should be very rare e.g. failures deserializing a protobuf object that
// should only happen if there was a bug in the underlying library.

View File

@ -3,14 +3,10 @@ package utils
import (
"bytes"
"encoding/binary"
"errors"
"io"
)
const (
InvalidPacketLengthError = Error("InvalidPacketLengthError")
InvalidChannelIDError = Error("InvalidChannelIDError")
)
// RicochetData is a structure containing the raw data and the channel it the
// message originated on.
type RicochetData struct {
@ -40,11 +36,11 @@ type RicochetNetwork struct {
func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data []byte) error {
packet := make([]byte, 4+len(data))
if len(packet) > 65535 {
return InvalidPacketLengthError
return errors.New("packet too large")
}
binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet)))
if channel < 0 || channel > 65535 {
return InvalidChannelIDError
return errors.New("invalid channel ID")
}
binary.BigEndian.PutUint16(packet[2:4], uint16(channel))
copy(packet[4:], data[:])
@ -72,7 +68,7 @@ func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, e
size := int(binary.BigEndian.Uint16(header[0:2]))
if size < 4 {
return packet, InvalidPacketLengthError
return packet, errors.New("invalid packet length")
}
packet.Channel = int32(binary.BigEndian.Uint16(header[2:4]))

View File

@ -1,17 +1,12 @@
package utils
import (
"errors"
"golang.org/x/net/proxy"
"net"
"strings"
)
const (
CannotResolveLocalTCPAddressError = Error("CannotResolveLocalTCPAddressError")
CannotDialLocalTCPAddressError = Error("CannotDialLocalTCPAddressError")
CannotDialRicochetAddressError = Error("CannotDialRicochetAddressError")
)
// NetworkResolver allows a client to resolve various hostnames to connections
// The supported types are onions address are:
// * ricochet:jlq67qzo6s4yp3sp
@ -26,11 +21,11 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
addrParts := strings.Split(hostname, "|")
tcpAddr, err := net.ResolveTCPAddr("tcp", addrParts[0])
if err != nil {
return nil, "", CannotResolveLocalTCPAddressError
return nil, "", errors.New("Cannot Resolve Local TCP Address")
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
return nil, "", CannotDialLocalTCPAddressError
return nil, "", errors.New("Cannot Dial Local TCP Address")
}
// return just the onion address, not the local override for the hostname
@ -50,7 +45,7 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
conn, err := torDialer.Dial("tcp", resolvedHostname+".onion:9878")
if err != nil {
return nil, "", CannotDialRicochetAddressError
return nil, "", errors.New("Cannot Dial Remote Ricochet Address")
}
return conn, resolvedHostname, nil

View File

@ -1,5 +0,0 @@
*.swp
*~
examples/basic/basic
examples/listener/listener
examples/dialer/dialer

View File

@ -1,122 +0,0 @@
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.

View File

@ -1,18 +0,0 @@
## 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.

View File

@ -1,137 +0,0 @@
// 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")
}

View File

@ -1,186 +0,0 @@
// 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
}

View File

@ -1,95 +0,0 @@
// 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
}

View File

@ -1,233 +0,0 @@
// 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)

View File

@ -1,54 +0,0 @@
// 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)
}

View File

@ -1,116 +0,0 @@
// 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)
}

View File

@ -1,125 +0,0 @@
// 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])
}
}
}

View File

@ -1,71 +0,0 @@
// 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
}

View File

@ -1,101 +0,0 @@
//
// 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
}

View File

@ -1,81 +0,0 @@
// 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)
}