diff --git a/auth_app.go b/applications/auth.go similarity index 94% rename from auth_app.go rename to applications/auth.go index c17194a..4306394 100644 --- a/auth_app.go +++ b/applications/auth.go @@ -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() } } diff --git a/notifications/main.go b/notifications/main.go index de7fd2a..a887869 100644 --- a/notifications/main.go +++ b/notifications/main.go @@ -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) } - diff --git a/primitives/bloom.go b/primitives/bloom.go index e698086..6e0b204 100644 --- a/primitives/bloom.go +++ b/primitives/bloom.go @@ -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 } - diff --git a/service.go b/service.go index 4ae4ed2..372fe0e 100644 --- a/service.go +++ b/service.go @@ -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 -}