diff --git a/application.go b/application.go index 2c39454..e2aabb9 100644 --- a/application.go +++ b/application.go @@ -1,5 +1,7 @@ package tapir + +// Application defines the interface for all Tapir Applications type Application interface { NewInstance() Application Init(connection *Connection) diff --git a/auth_app.go b/auth_app.go index e879441..c17194a 100644 --- a/auth_app.go +++ b/auth_app.go @@ -12,18 +12,26 @@ import ( "time" ) -type AuthApp struct { -} - +// AuthMessage is exchanged between peers to obtain the Auth Capability type AuthMessage struct { LongTermPublicKey ed25519.PublicKey EphemeralPublicKey ed25519.PublicKey } +// AuthCapability defines the Authentication Capability granted by AuthApp +const AuthCapability = "AUTH" + +// AuthApp is the concrete Application type that handles Authentication +type AuthApp struct { +} + +// NewInstance creates a new instance of the AuthApp func (ea AuthApp) NewInstance() Application { return new(AuthApp) } +// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability +// or the connection is closed. func (ea AuthApp) Init(connection *Connection) { longTermPubKey := ed25519.PublicKey(connection.ID.PublicKeyBytes()) epk, esk, _ := ed25519.GenerateKey(rand.Reader) @@ -85,8 +93,9 @@ func (ea AuthApp) Init(connection *Connection) { remoteChallenge := connection.Expect() if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 { connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)) - connection.SetCapability("AUTH") + connection.SetCapability(AuthCapability) } else { log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes) + connection.conn.Close() } } diff --git a/cmd/main.go b/cmd/main.go index b292bc5..3c67bd7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -12,7 +12,7 @@ import ( "time" ) -// Simple App is a trivial implementation of a basic p2p application +// SimpleApp is a trivial implementation of a basic p2p application type SimpleApp struct { tapir.AuthApp } @@ -27,7 +27,7 @@ func (ea SimpleApp) Init(connection *tapir.Connection) { // First run the Authentication App ea.AuthApp.Init(connection) - if connection.HasCapability("AUTH") { + if connection.HasCapability(tapir.AuthCapability) { // The code for out simple application (We just send and receive "Hello" connection.Send([]byte("Hello")) message := connection.Expect() @@ -53,7 +53,7 @@ func main() { // Start a Client to Connect to the Server go client(acn, pubkey) - // Start the Server running the Echo App. + // Start the Server running the Simple App. var service tapir.Service service = new(tapir.BaseOnionService) service.Start(acn, sk, id) @@ -69,10 +69,14 @@ func client(acn connectivity.ACN, key ed25519.PublicKey) { var client tapir.Service client = new(tapir.BaseOnionService) client.Start(acn, sk, id) - client.Connect(utils.GetTorV3Hostname(key), SimpleApp{}) + cid,_ := client.Connect(utils.GetTorV3Hostname(key), SimpleApp{}) // Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo // we will wait a little while then exit. time.Sleep(time.Second * 5) + + conn,_ := client.GetConnection(cid) + log.Debugf("Client has Auth: %v", conn.HasCapability(tapir.AuthCapability)) + os.Exit(0) } diff --git a/service.go b/service.go index be6ccde..fe06f37 100644 --- a/service.go +++ b/service.go @@ -15,12 +15,17 @@ import ( "sync" ) + +// Service defines the interface for a Tapir Service type Service interface { Start(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity identity.Identity) - Connect(hostname string, application Application) error + Connect(hostname string, application Application) (string, error) Listen(application Application) error + GetConnection(connectionID string) (*Connection, error) } + +// Connection defines a Tapir Connection type Connection struct { hostname string conn net.Conn @@ -32,6 +37,7 @@ type Connection struct { Outbound bool } +// NewConnection creates a new Connection func NewConnection(id identity.Identity, hostname string, outbound bool, conn net.Conn, app Application) *Connection { connection := new(Connection) connection.hostname = hostname @@ -43,25 +49,33 @@ func NewConnection(id identity.Identity, hostname string, outbound bool, conn ne return connection } +// SetHostname sets the hostname on the connection func (c *Connection) SetHostname(hostname string) { log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.ID.Hostname(), c.hostname, hostname) c.hostname = hostname } +// SetCapability sets a capability on the connection func (c *Connection) SetCapability(name string) { log.Debugf("[%v -- %v] Setting Capability %v", c.ID.Hostname(), c.hostname, name) c.capabilities.Store(name, true) } +// HasCapability checks if the connection has a given capability func (c *Connection) HasCapability(name string) bool { _, ok := c.capabilities.Load(name) return ok } +// Expect blocks and reads a single Tapir packet (1024 bytes), from the connection. func (c *Connection) Expect() []byte { buffer := make([]byte, 1024) - // TODO handle ERROR - io.ReadFull(c.conn, buffer) + n, err := io.ReadFull(c.conn, buffer) + + if n != 1024 || err != nil { + log.Errorf("[%v -> %v] Wire Error Reading, Read %d bytes, Error: %v", c.hostname, c.ID.Hostname(), n, err) + return []byte{} + } //log.Debugf("[%v -> %v] Wire Receive: %x", c.hostname, c.ID.Hostname(), buffer) if c.encrypted { @@ -79,11 +93,14 @@ func (c *Connection) Expect() []byte { return buffer[2 : len+2] } +// SetEncryptionKey turns on application-level encryption on the connection using the given key. func (c *Connection) SetEncryptionKey(key [32]byte) { c.key = key c.encrypted = true } + +// Send writes a given message to a Tapir packet (of 1024 bytes in length). func (c *Connection) Send(message []byte) { maxLength := 1024 @@ -104,6 +121,7 @@ func (c *Connection) Send(message []byte) { c.conn.Write(buffer) } +// BaseOnionService is a concrete implementation of the service interface over Tor onion services. type BaseOnionService struct { connections sync.Map acn connectivity.ACN @@ -111,6 +129,9 @@ type BaseOnionService struct { privateKey ed25519.PrivateKey } + +// Start initializes a BaseOnionService with a given private key and identity +// The private key is needed to initialize the Onion listen socket, ideally we could just pass an Identity in here. func (s *BaseOnionService) Start(acn connectivity.ACN, sk ed25519.PrivateKey, id identity.Identity) { // run add onion // get listen context @@ -119,24 +140,42 @@ func (s *BaseOnionService) Start(acn connectivity.ACN, sk ed25519.PrivateKey, id s.privateKey = sk } -func (s *BaseOnionService) GetConnection(hostname string) error { - return errors.New("no connection found") + +// GetConnection returns a connection for a given hostname. +func (s *BaseOnionService) GetConnection(connectionID string) (*Connection, error) { + conn,ok := s.connections.Load(connectionID) + if !ok { + return nil, errors.New("no connection found") + } + connection := conn.(*Connection) + return connection, nil } -func (s *BaseOnionService) Connect(hostname string, app Application) error { +// Connect initializes a new outbound connection to the given peer, using the defined Application +func (s *BaseOnionService) Connect(hostname string, app Application) (string, error) { // connects to a remote server // spins off to a connection struct log.Debugf("Connecting to %v", hostname) conn, _, err := s.acn.Open(hostname) if err == nil { - log.Debugf("Connected to %v", hostname) - s.connections.Store(hostname, NewConnection(s.id, hostname, true, conn, app.NewInstance())) - return nil + connectionID := s.getNewConnectionID() + log.Debugf("Connected to %v [%v]", hostname, connectionID) + s.connections.Store(connectionID, NewConnection(s.id, hostname, true, conn, app.NewInstance())) + return connectionID, nil } log.Debugf("Error connecting to %v %v", hostname, err) - return err + return "", err } +func (s *BaseOnionService) getNewConnectionID() string { + id := make([]byte, 10) + rand.Read(id) + connectionID := "connection-" + base64.StdEncoding.EncodeToString(id) + return connectionID +} + +// Listen starts a blocking routine that waits for incoming connections and initializes connections with them based +// on the given Application. func (s *BaseOnionService) Listen(app Application) error { // accepts a new connection // spins off to a connection struct @@ -146,9 +185,7 @@ func (s *BaseOnionService) Listen(app Application) error { for { conn, err := ls.Accept() if err == nil { - id := make([]byte, 10) - rand.Read(id) - tempHostname := "unknown-" + base64.StdEncoding.EncodeToString(id) + tempHostname := s.getNewConnectionID() log.Debugf("Accepted connection from %v", tempHostname) s.connections.Store(tempHostname, NewConnection(s.id, tempHostname, false, conn, app.NewInstance())) } else {