package tapir import ( "crypto/rand" "encoding/base64" "encoding/binary" "errors" "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/log" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/secretbox" "io" "net" "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) (string, error) Listen(application Application) error GetConnection(connectionID string) (*Connection, error) } // Connection defines a Tapir Connection type Connection struct { hostname string conn net.Conn capabilities sync.Map encrypted bool key [32]byte app Application ID identity.Identity 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 connection.conn = conn connection.app = app connection.ID = id connection.Outbound = outbound go connection.app.Init(connection) 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) 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 { var decryptNonce [24]byte copy(decryptNonce[:], buffer[:24]) decrypted, ok := secretbox.Open(nil, buffer[24:], &decryptNonce, &c.key) if ok { copy(buffer, decrypted) } else { log.Errorf("[%v -> %v] Error Decrypting Message On Wire", c.hostname, c.ID.Hostname()) return []byte{} } } len, _ := binary.Uvarint(buffer[0: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) { 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 buffer := make([]byte, maxLength) binary.PutUvarint(buffer[0:2], uint64(len(message))) copy(buffer[2:], message) if c.encrypted { var nonce [24]byte if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { panic(err) } // MaxLength - 40 = MaxLength - 24 nonce bytes and 16 auth tag. encrypted := secretbox.Seal(nonce[:], buffer[0:maxLength-40], &nonce, &c.key) copy(buffer, encrypted[0:1024]) } //log.Debugf("[%v -> %v] Wire Send %x", c.ID.Hostname(), c.hostname, buffer) 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 id identity.Identity 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 s.acn = acn s.id = id s.privateKey = sk } // 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 } // 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 { 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 } 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 ls, err := s.acn.Listen(s.privateKey, 9878) log.Debugf("Starting a service on %v ", ls.AddressFull()) if err == nil { for { conn, err := ls.Accept() if err == nil { tempHostname := s.getNewConnectionID() log.Debugf("Accepted connection from %v", tempHostname) s.connections.Store(tempHostname, NewConnection(s.id, tempHostname, false, conn, app.NewInstance())) } else { ls.Close() log.Debugf("Error accepting connection %v", err) return err } } } log.Debugf("Error listening to connection %v", err) return err }