forked from cwtch.im/tapir
Error Handling
This commit is contained in:
parent
c769bdf208
commit
b204353516
|
@ -1,5 +1,7 @@
|
||||||
package tapir
|
package tapir
|
||||||
|
|
||||||
|
|
||||||
|
// Application defines the interface for all Tapir Applications
|
||||||
type Application interface {
|
type Application interface {
|
||||||
NewInstance() Application
|
NewInstance() Application
|
||||||
Init(connection *Connection)
|
Init(connection *Connection)
|
||||||
|
|
17
auth_app.go
17
auth_app.go
|
@ -12,18 +12,26 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthApp struct {
|
// AuthMessage is exchanged between peers to obtain the Auth Capability
|
||||||
}
|
|
||||||
|
|
||||||
type AuthMessage struct {
|
type AuthMessage struct {
|
||||||
LongTermPublicKey ed25519.PublicKey
|
LongTermPublicKey ed25519.PublicKey
|
||||||
EphemeralPublicKey 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 {
|
func (ea AuthApp) NewInstance() Application {
|
||||||
return new(AuthApp)
|
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) {
|
func (ea AuthApp) Init(connection *Connection) {
|
||||||
longTermPubKey := ed25519.PublicKey(connection.ID.PublicKeyBytes())
|
longTermPubKey := ed25519.PublicKey(connection.ID.PublicKeyBytes())
|
||||||
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
|
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
@ -85,8 +93,9 @@ func (ea AuthApp) Init(connection *Connection) {
|
||||||
remoteChallenge := connection.Expect()
|
remoteChallenge := connection.Expect()
|
||||||
if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {
|
if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {
|
||||||
connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
|
connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
|
||||||
connection.SetCapability("AUTH")
|
connection.SetCapability(AuthCapability)
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
|
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
|
||||||
|
connection.conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
cmd/main.go
10
cmd/main.go
|
@ -27,7 +27,7 @@ func (ea SimpleApp) Init(connection *tapir.Connection) {
|
||||||
// First run the Authentication App
|
// First run the Authentication App
|
||||||
ea.AuthApp.Init(connection)
|
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"
|
// The code for out simple application (We just send and receive "Hello"
|
||||||
connection.Send([]byte("Hello"))
|
connection.Send([]byte("Hello"))
|
||||||
message := connection.Expect()
|
message := connection.Expect()
|
||||||
|
@ -53,7 +53,7 @@ func main() {
|
||||||
// Start a Client to Connect to the Server
|
// Start a Client to Connect to the Server
|
||||||
go client(acn, pubkey)
|
go client(acn, pubkey)
|
||||||
|
|
||||||
// Start the Server running the Echo App.
|
// Start the Server running the Simple App.
|
||||||
var service tapir.Service
|
var service tapir.Service
|
||||||
service = new(tapir.BaseOnionService)
|
service = new(tapir.BaseOnionService)
|
||||||
service.Start(acn, sk, id)
|
service.Start(acn, sk, id)
|
||||||
|
@ -69,10 +69,14 @@ func client(acn connectivity.ACN, key ed25519.PublicKey) {
|
||||||
var client tapir.Service
|
var client tapir.Service
|
||||||
client = new(tapir.BaseOnionService)
|
client = new(tapir.BaseOnionService)
|
||||||
client.Start(acn, sk, id)
|
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
|
// 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.
|
// we will wait a little while then exit.
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(time.Second * 5)
|
||||||
|
|
||||||
|
conn,_ := client.GetConnection(cid)
|
||||||
|
log.Debugf("Client has Auth: %v", conn.HasCapability(tapir.AuthCapability))
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
63
service.go
63
service.go
|
@ -15,12 +15,17 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// Service defines the interface for a Tapir Service
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Start(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity identity.Identity)
|
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
|
Listen(application Application) error
|
||||||
|
GetConnection(connectionID string) (*Connection, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Connection defines a Tapir Connection
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
hostname string
|
hostname string
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
|
@ -32,6 +37,7 @@ type Connection struct {
|
||||||
Outbound bool
|
Outbound bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConnection creates a new Connection
|
||||||
func NewConnection(id identity.Identity, hostname string, outbound bool, conn net.Conn, app Application) *Connection {
|
func NewConnection(id identity.Identity, hostname string, outbound bool, conn net.Conn, app Application) *Connection {
|
||||||
connection := new(Connection)
|
connection := new(Connection)
|
||||||
connection.hostname = hostname
|
connection.hostname = hostname
|
||||||
|
@ -43,25 +49,33 @@ func NewConnection(id identity.Identity, hostname string, outbound bool, conn ne
|
||||||
return connection
|
return connection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHostname sets the hostname on the connection
|
||||||
func (c *Connection) SetHostname(hostname string) {
|
func (c *Connection) SetHostname(hostname string) {
|
||||||
log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.ID.Hostname(), c.hostname, hostname)
|
log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.ID.Hostname(), c.hostname, hostname)
|
||||||
c.hostname = hostname
|
c.hostname = hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCapability sets a capability on the connection
|
||||||
func (c *Connection) SetCapability(name string) {
|
func (c *Connection) SetCapability(name string) {
|
||||||
log.Debugf("[%v -- %v] Setting Capability %v", c.ID.Hostname(), c.hostname, name)
|
log.Debugf("[%v -- %v] Setting Capability %v", c.ID.Hostname(), c.hostname, name)
|
||||||
c.capabilities.Store(name, true)
|
c.capabilities.Store(name, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCapability checks if the connection has a given capability
|
||||||
func (c *Connection) HasCapability(name string) bool {
|
func (c *Connection) HasCapability(name string) bool {
|
||||||
_, ok := c.capabilities.Load(name)
|
_, ok := c.capabilities.Load(name)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expect blocks and reads a single Tapir packet (1024 bytes), from the connection.
|
||||||
func (c *Connection) Expect() []byte {
|
func (c *Connection) Expect() []byte {
|
||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
// TODO handle ERROR
|
n, err := io.ReadFull(c.conn, buffer)
|
||||||
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)
|
//log.Debugf("[%v -> %v] Wire Receive: %x", c.hostname, c.ID.Hostname(), buffer)
|
||||||
|
|
||||||
if c.encrypted {
|
if c.encrypted {
|
||||||
|
@ -79,11 +93,14 @@ func (c *Connection) Expect() []byte {
|
||||||
return buffer[2 : len+2]
|
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) {
|
func (c *Connection) SetEncryptionKey(key [32]byte) {
|
||||||
c.key = key
|
c.key = key
|
||||||
c.encrypted = true
|
c.encrypted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send writes a given message to a Tapir packet (of 1024 bytes in length).
|
||||||
func (c *Connection) Send(message []byte) {
|
func (c *Connection) Send(message []byte) {
|
||||||
|
|
||||||
maxLength := 1024
|
maxLength := 1024
|
||||||
|
@ -104,6 +121,7 @@ func (c *Connection) Send(message []byte) {
|
||||||
c.conn.Write(buffer)
|
c.conn.Write(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BaseOnionService is a concrete implementation of the service interface over Tor onion services.
|
||||||
type BaseOnionService struct {
|
type BaseOnionService struct {
|
||||||
connections sync.Map
|
connections sync.Map
|
||||||
acn connectivity.ACN
|
acn connectivity.ACN
|
||||||
|
@ -111,6 +129,9 @@ type BaseOnionService struct {
|
||||||
privateKey ed25519.PrivateKey
|
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) {
|
func (s *BaseOnionService) Start(acn connectivity.ACN, sk ed25519.PrivateKey, id identity.Identity) {
|
||||||
// run add onion
|
// run add onion
|
||||||
// get listen context
|
// get listen context
|
||||||
|
@ -119,24 +140,42 @@ func (s *BaseOnionService) Start(acn connectivity.ACN, sk ed25519.PrivateKey, id
|
||||||
s.privateKey = sk
|
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
|
// connects to a remote server
|
||||||
// spins off to a connection struct
|
// spins off to a connection struct
|
||||||
log.Debugf("Connecting to %v", hostname)
|
log.Debugf("Connecting to %v", hostname)
|
||||||
conn, _, err := s.acn.Open(hostname)
|
conn, _, err := s.acn.Open(hostname)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Debugf("Connected to %v", hostname)
|
connectionID := s.getNewConnectionID()
|
||||||
s.connections.Store(hostname, NewConnection(s.id, hostname, true, conn, app.NewInstance()))
|
log.Debugf("Connected to %v [%v]", hostname, connectionID)
|
||||||
return nil
|
s.connections.Store(connectionID, NewConnection(s.id, hostname, true, conn, app.NewInstance()))
|
||||||
|
return connectionID, nil
|
||||||
}
|
}
|
||||||
log.Debugf("Error connecting to %v %v", hostname, err)
|
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 {
|
func (s *BaseOnionService) Listen(app Application) error {
|
||||||
// accepts a new connection
|
// accepts a new connection
|
||||||
// spins off to a connection struct
|
// spins off to a connection struct
|
||||||
|
@ -146,9 +185,7 @@ func (s *BaseOnionService) Listen(app Application) error {
|
||||||
for {
|
for {
|
||||||
conn, err := ls.Accept()
|
conn, err := ls.Accept()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
id := make([]byte, 10)
|
tempHostname := s.getNewConnectionID()
|
||||||
rand.Read(id)
|
|
||||||
tempHostname := "unknown-" + base64.StdEncoding.EncodeToString(id)
|
|
||||||
log.Debugf("Accepted connection from %v", tempHostname)
|
log.Debugf("Accepted connection from %v", tempHostname)
|
||||||
s.connections.Store(tempHostname, NewConnection(s.id, tempHostname, false, conn, app.NewInstance()))
|
s.connections.Store(tempHostname, NewConnection(s.id, tempHostname, false, conn, app.NewInstance()))
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue