164 lines
4.4 KiB
Go
164 lines
4.4 KiB
Go
|
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"
|
||
|
)
|
||
|
|
||
|
type Service interface {
|
||
|
Start(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity identity.Identity)
|
||
|
Connect(hostname string, application Application) error
|
||
|
Listen(application Application) error
|
||
|
}
|
||
|
|
||
|
type Connection struct {
|
||
|
hostname string
|
||
|
conn net.Conn
|
||
|
capabilities sync.Map
|
||
|
encrypted bool
|
||
|
key [32]byte
|
||
|
app Application
|
||
|
ID identity.Identity
|
||
|
Outbound bool
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
func (c *Connection) SetHostname(hostname string) {
|
||
|
log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.ID.Hostname(), c.hostname, hostname)
|
||
|
c.hostname = hostname
|
||
|
}
|
||
|
|
||
|
func (c *Connection) SetCapability(name string) {
|
||
|
log.Debugf("[%v -- %v] Setting Capability %v", c.ID.Hostname(), c.hostname, name)
|
||
|
c.capabilities.Store(name, true)
|
||
|
}
|
||
|
|
||
|
func (c *Connection) HasCapability(name string) bool {
|
||
|
_, ok := c.capabilities.Load(name)
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
func (c *Connection) Expect() []byte {
|
||
|
buffer := make([]byte, 1024)
|
||
|
// TODO handle ERROR
|
||
|
io.ReadFull(c.conn, buffer)
|
||
|
//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]
|
||
|
}
|
||
|
|
||
|
func (c *Connection) SetEncryptionKey(key [32]byte) {
|
||
|
c.key = key
|
||
|
c.encrypted = true
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
type BaseOnionService struct {
|
||
|
connections sync.Map
|
||
|
acn connectivity.ACN
|
||
|
id identity.Identity
|
||
|
privateKey ed25519.PrivateKey
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
func (s *BaseOnionService) GetConnection(hostname string) error {
|
||
|
return errors.New("no connection found")
|
||
|
}
|
||
|
|
||
|
func (s *BaseOnionService) Connect(hostname string, app Application) 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
|
||
|
}
|
||
|
log.Debugf("Error connecting to %v %v", hostname, err)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
id := make([]byte, 10)
|
||
|
rand.Read(id)
|
||
|
tempHostname := "unknown-" + base64.StdEncoding.EncodeToString(id)
|
||
|
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
|
||
|
}
|