Refactor to better define boundaries, formatting and linting

This commit is contained in:
Sarah Jamie Lewis 2019-06-05 12:02:03 -07:00
parent 3cada87147
commit 6cf2862681
4 changed files with 56 additions and 154 deletions

View File

@ -1,8 +1,9 @@
package tapir
package applications
import (
"crypto/rand"
"crypto/subtle"
"cwtch.im/tapir"
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
@ -26,13 +27,13 @@ type AuthApp struct {
}
// NewInstance creates a new instance of the AuthApp
func (ea AuthApp) NewInstance() Application {
func (ea AuthApp) NewInstance() tapir.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) {
func (ea AuthApp) Init(connection *tapir.Connection) {
longTermPubKey := ed25519.PublicKey(connection.ID.PublicKeyBytes())
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
ephemeralPublicKey := ed25519.PublicKey(epk)
@ -46,7 +47,7 @@ func (ea AuthApp) Init(connection *Connection) {
var remoteAuthMessage AuthMessage
err := json.Unmarshal(message, &remoteAuthMessage)
if err != nil {
connection.conn.Close()
connection.Close()
return
}
@ -96,6 +97,6 @@ func (ea AuthApp) Init(connection *Connection) {
connection.SetCapability(AuthCapability)
} else {
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
connection.conn.Close()
connection.Close()
}
}

View File

@ -4,6 +4,8 @@ import (
"crypto/rand"
"crypto/sha512"
"cwtch.im/tapir"
"cwtch.im/tapir/applications"
"cwtch.im/tapir/networks/tor"
"cwtch.im/tapir/primitives"
"encoding/hex"
"encoding/json"
@ -21,26 +23,27 @@ import (
// Notification contains a Topic string and a Message.
type Notification struct {
Topic string // A hex encoded string of the hash of the topic string
Topic string // A hex encoded string of the hash of the topic string
Message string
}
// NotificationClient allows publishing and reading from the notifications server
type NotificationClient struct {
tapir.AuthApp
applications.AuthApp
connection *tapir.Connection
}
// NewInstance should always return a new instantiation of the application.
func (nc NotificationClient) NewInstance() tapir.Application {
app := new(NotificationClient)
app := new(NotificationClient)
return app
}
// Init is run when the connection is first started.
func (nc * NotificationClient) Init(connection *tapir.Connection) {
func (nc *NotificationClient) Init(connection *tapir.Connection) {
// First run the Authentication App
nc.AuthApp.Init(connection)
if connection.HasCapability(tapir.AuthCapability) {
if connection.HasCapability(applications.AuthCapability) {
nc.connection = connection
}
}
@ -50,7 +53,7 @@ func (nc * NotificationClient) Init(connection *tapir.Connection) {
func (nc NotificationClient) Publish(topic string, message string) {
log.Debugf("Sending Publish Request")
hashedTopic := sha512.Sum512([]byte(topic))
data,_ := json.Marshal(NotificationRequest{RequestType: "Publish", RequestData: map[string]string{"Topic": hex.EncodeToString(hashedTopic[:])}})
data, _ := json.Marshal(NotificationRequest{RequestType: "Publish", RequestData: map[string]string{"Topic": hex.EncodeToString(hashedTopic[:])}})
nc.connection.Send([]byte(data))
}
@ -59,7 +62,7 @@ func (nc NotificationClient) Publish(topic string, message string) {
func (nc NotificationClient) Check(topic string) bool {
log.Debugf("Sending Filter Request")
// Get an updated bloom filter
data,_ := json.Marshal(NotificationRequest{RequestType: "BloomFilter", RequestData: map[string]string{}})
data, _ := json.Marshal(NotificationRequest{RequestType: "BloomFilter", RequestData: map[string]string{}})
nc.connection.Send(data)
response := nc.connection.Expect()
var bf primitives.BloomFilter
@ -70,32 +73,32 @@ func (nc NotificationClient) Check(topic string) bool {
return bf.Check(hashedTopic[:])
}
type NotificationRequest struct {
type notificationRequest struct {
RequestType string
RequestData map[string]string
}
// PIRApp is a trivial implementation of a basic p2p application
// NotificationsServer implements the metadata resistant notifications server
type NotificationsServer struct {
tapir.AuthApp
Filter * primitives.BloomFilter
applications.AuthApp
Filter *primitives.BloomFilter
}
// NewInstance should always return a new instantiation of the application.
func (ns NotificationsServer) NewInstance() tapir.Application {
app := new(NotificationsServer)
app := new(NotificationsServer)
app.Filter = ns.Filter
return app
}
// Init initializes the application.
func (ns NotificationsServer) Init(connection *tapir.Connection) {
// First run the Authentication App
ns.AuthApp.Init(connection)
if connection.HasCapability(tapir.AuthCapability) {
if connection.HasCapability(applications.AuthCapability) {
for {
request := connection.Expect()
var nr NotificationRequest
var nr notificationRequest
json.Unmarshal(request, &nr)
log.Debugf("Received Request %v", nr)
switch nr.RequestType {
@ -140,11 +143,11 @@ func main() {
// Init the Server running the Simple App.
var service tapir.Service
service = new(tapir.BaseOnionService)
service = new(tor.BaseOnionService)
service.Init(acn, sk, id)
bf := new(primitives.BloomFilter)
bf.Init(1024)
service.Listen(NotificationsServer{Filter:bf})
service.Listen(NotificationsServer{Filter: bf})
}
// Client will Connect and launch it's own Echo App goroutine.
@ -154,25 +157,21 @@ func client(acn connectivity.ACN, key ed25519.PublicKey) {
pk := ed25519.PublicKey(pubkey)
id := identity.InitializeV3("client", &sk, &pk)
var client tapir.Service
client = new(tapir.BaseOnionService)
client = new(tor.BaseOnionService)
client.Init(acn, sk, id)
cid, _ := client.Connect(utils.GetTorV3Hostname(key), new(NotificationClient))
conn, err := client.WaitForCapabilityOrClose(cid, tapir.AuthCapability)
conn, err := client.WaitForCapabilityOrClose(cid, applications.AuthCapability)
if err == nil {
log.Debugf("Client has Auth: %v", conn.HasCapability(tapir.AuthCapability))
log.Debugf("Client has Auth: %v", conn.HasCapability(applications.AuthCapability))
nc := conn.App.(*NotificationClient)
// Basic Demonstration of Notification
log.Infof("Checking #astronomy: %v", nc.Check("#astronomy"))
log.Infof("Publishing to #astronomy: %v", nc.Check("#astronomy"))
nc.Publish("#astronomy", "New #Astronomy Post!")
log.Infof("Checking #astronomy: %v", nc.Check("#astronomy"))
}
os.Exit(0)
}

View File

@ -9,9 +9,8 @@ type BloomFilter struct {
B []bool
}
// Init constructs a bloom filter of size m
func (bf * BloomFilter) Init(m int) {
func (bf *BloomFilter) Init(m int) {
bf.B = make([]bool, m)
}
@ -20,27 +19,27 @@ func (bf * BloomFilter) Init(m int) {
func (bf BloomFilter) Hash(msg []byte) []int {
hash := sha256.Sum256(msg)
pos1a := (int(hash[0])+ int(hash[1]) + int(hash[2]) + int(hash[3])) % 0xFF
pos1b := (int(hash[4])+ int(hash[5]) + int(hash[6]) + int(hash[7])) % 0xFF
pos1 := ((pos1a <<8) + pos1b) & (0xFFFF % len(bf.B))
pos1a := (int(hash[0]) + int(hash[1]) + int(hash[2]) + int(hash[3])) % 0xFF
pos1b := (int(hash[4]) + int(hash[5]) + int(hash[6]) + int(hash[7])) % 0xFF
pos1 := ((pos1a << 8) + pos1b) & (0xFFFF % len(bf.B))
pos2a := (int(hash[8])+ int(hash[9]) + int(hash[10]) + int(hash[11])) % 0xFF
pos2b := (int(hash[12])+ int(hash[13]) + int(hash[14]) + int(hash[15])) % 0xFF
pos2 := ((pos2a <<8) + pos2b) & (0xFFFF % len(bf.B))
pos2a := (int(hash[8]) + int(hash[9]) + int(hash[10]) + int(hash[11])) % 0xFF
pos2b := (int(hash[12]) + int(hash[13]) + int(hash[14]) + int(hash[15])) % 0xFF
pos2 := ((pos2a << 8) + pos2b) & (0xFFFF % len(bf.B))
pos3a := (int(hash[16])+ int(hash[17]) + int(hash[18]) + int(hash[19])) % 0xFF
pos3b := (int(hash[20])+ int(hash[21]) + int(hash[22]) + int(hash[23])) % 0xFF
pos3:= ((pos3a <<8) + pos3b) & (0xFFFF % len(bf.B))
pos3a := (int(hash[16]) + int(hash[17]) + int(hash[18]) + int(hash[19])) % 0xFF
pos3b := (int(hash[20]) + int(hash[21]) + int(hash[22]) + int(hash[23])) % 0xFF
pos3 := ((pos3a << 8) + pos3b) & (0xFFFF % len(bf.B))
pos4a := (int(hash[24])+ int(hash[25]) + int(hash[26]) + int(hash[27])) % 0xFF
pos4b := (int(hash[28])+ int(hash[29]) + int(hash[30]) + int(hash[31])) % 0xFF
pos4:= ((pos4a <<8) + pos4b) & (0xFFFF % len(bf.B))
pos4a := (int(hash[24]) + int(hash[25]) + int(hash[26]) + int(hash[27])) % 0xFF
pos4b := (int(hash[28]) + int(hash[29]) + int(hash[30]) + int(hash[31])) % 0xFF
pos4 := ((pos4a << 8) + pos4b) & (0xFFFF % len(bf.B))
return []int{pos1, pos2, pos3, pos4}
}
// Insert updates the BloomFilter
func (bf * BloomFilter) Insert(msg []byte) {
func (bf *BloomFilter) Insert(msg []byte) {
pos := bf.Hash(msg)
bf.B[pos[0]] = true
bf.B[pos[1]] = true
@ -52,9 +51,8 @@ func (bf * BloomFilter) Insert(msg []byte) {
// (No false positives, possible false negatives due to the probabilistic nature of the filter)
func (bf BloomFilter) Check(msg []byte) bool {
pos := bf.Hash(msg)
if bf.B[pos[0]] && bf.B[pos[1]] && bf.B[pos[2]] && bf.B[pos[3]]{
if bf.B[pos[0]] && bf.B[pos[1]] && bf.B[pos[2]] && bf.B[pos[3]] {
return true
}
return false
}

View File

@ -2,9 +2,7 @@ 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"
@ -13,7 +11,6 @@ import (
"io"
"net"
"sync"
"time"
)
// Service defines the interface for a Tapir Service
@ -25,7 +22,7 @@ type Service interface {
WaitForCapabilityOrClose(connectionID string, capability string) (*Connection, error)
}
// This is a fairly arbitrary number that very much depends on the application and the available bandwidth/server
// MaxLength is a fairly arbitrary number that very much depends on the application and the available bandwidth/server
// storage.
const MaxLength = 8192
@ -39,7 +36,7 @@ type Connection struct {
App Application
ID identity.Identity
Outbound bool
closed bool
Closed bool
}
// NewConnection creates a new Connection
@ -72,6 +69,10 @@ func (c *Connection) HasCapability(name string) bool {
return ok
}
// Close forcibly closes the connection
func (c *Connection) Close() {
c.conn.Close()
}
// Expect blocks and reads a single Tapir packet (1024 bytes), from the connection.
func (c *Connection) Expect() []byte {
@ -81,7 +82,7 @@ func (c *Connection) Expect() []byte {
if n != MaxLength || err != nil {
log.Errorf("[%v -> %v] Wire Error Reading, Read %d bytes, Error: %v", c.hostname, c.ID.Hostname(), n, err)
c.conn.Close()
c.closed = true
c.Closed = true
return []byte{}
}
@ -94,7 +95,7 @@ func (c *Connection) Expect() []byte {
} else {
log.Errorf("[%v -> %v] Error Decrypting Message On Wire", c.hostname, c.ID.Hostname())
c.conn.Close()
c.closed = true
c.Closed = true
return []byte{}
}
}
@ -119,115 +120,18 @@ func (c *Connection) Send(message []byte) {
if c.encrypted {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
// TODO: Surface is Erro
// TODO: Surface is Error
c.conn.Close()
c.closed = true
c.Closed = true
}
// 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:MaxLength])
}
log.Debugf("[%v -> %v] Wire Send %x", c.ID.Hostname(), c.hostname, buffer)
log.Debugf("[%v -> %v] Wire Send %x", c.ID.Hostname(), c.hostname, buffer)
_, err := c.conn.Write(buffer)
if err != nil {
c.conn.Close()
c.closed = true
c.Closed = true
}
}
// 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
}
// Init 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) Init(acn connectivity.ACN, sk ed25519.PrivateKey, id identity.Identity) {
// run add onion
// get listen context
s.acn = acn
s.id = id
s.privateKey = sk
}
// WaitForCapabilityOrClose blocks until the connection has the given capability or the underlying connection is closed
// (through error or user action)
func (s * BaseOnionService) WaitForCapabilityOrClose(cid string, name string) (*Connection, error) {
conn, err := s.GetConnection(cid)
if err == nil {
for {
if conn.HasCapability(name) {
return conn, nil
}
if conn.closed {
return nil, errors.New("connection is closed")
}
time.Sleep(time.Millisecond * 200)
}
}
return nil, err
}
// 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
}