diff --git a/catalog/messageTypes.go b/catalog/messageTypes.go index 969e6c703d1f56ab5db32bcbebfed0acf0d74c42..3f33fe648995ca275177d35f7fd32961bdb5b01e 100644 --- a/catalog/messageTypes.go +++ b/catalog/messageTypes.go @@ -42,4 +42,9 @@ const ( // EndFileTransfer is sent once all file parts have been transmitted to // inform the receiver that the file transfer has ended. EndFileTransfer = 51 + + // ConnectionAuthenticationRequest is sent by the recipient + // of an authenticated connection request + // (see the connect/ package) + ConnectionAuthenticationRequest = 60 ) diff --git a/connect/authenticated.go b/connect/authenticated.go new file mode 100644 index 0000000000000000000000000000000000000000..518bbc2eb482f12c23005071e4e81f42c47caf38 --- /dev/null +++ b/connect/authenticated.go @@ -0,0 +1,219 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/cmix" + clientE2e "gitlab.com/elixxir/client/e2e" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" + "sync" + "time" +) + +// Constant error messages +const ( + roundTrackingTimeoutErr = "timed out waiting for round results" + notAllRoundsSucceededErr = "not all rounds succeeded" + failedToCloseConnectionErr = "failed to close connection with %s " + + "after error %v: %+v" +) + +// AuthenticatedConnection is a connect.Connection interface that +// has the receiver authenticating their identity back to the +// initiator. +type AuthenticatedConnection interface { + // Connection is the base Connect API. This allows + // sending and listening to the partner + Connection + + // IsAuthenticated is a function which returns whether the + // authenticated connection has been completely established. + IsAuthenticated() bool +} + +// AuthenticatedCallback is the callback format required to retrieve +// new AuthenticatedConnection objects as they are established. +type AuthenticatedCallback func(connection AuthenticatedConnection) + +// ConnectWithAuthentication is called by the client, ie the one establishing +// connection with the server. Once a connect.Connection has been established +// with the server and then authenticate their identity to the server. +func ConnectWithAuthentication(recipient contact.Contact, myId *id.ID, + salt []byte, myRsaPrivKey *rsa.PrivateKey, myDhPrivKey *cyclic.Int, + rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client, + p Params) (AuthenticatedConnection, error) { + + // Track the time since we started to attempt to establish a connection + timeStart := netTime.Now() + + // Establish a connection with the server + conn, err := Connect(recipient, myId, myDhPrivKey, rng, grp, net, p) + if err != nil { + return nil, errors.Errorf("failed to establish connection "+ + "with recipient %s: %+v", recipient.ID, err) + } + + // Build the authenticated connection and return + return connectWithAuthentication(conn, timeStart, recipient, salt, myRsaPrivKey, + rng, net, p) +} + +// connectWithAuthentication builds and sends an IdentityAuthentication to +// the server. This will wait until the round it sends on completes or a +// timeout occurs. +func connectWithAuthentication(conn Connection, timeStart time.Time, + recipient contact.Contact, salt []byte, myRsaPrivKey *rsa.PrivateKey, + rng *fastRNG.StreamGenerator, + net cmix.Client, p Params) (AuthenticatedConnection, error) { + // Construct message to prove your identity to the server + payload, err := buildClientAuthRequest(conn.GetPartner(), rng, + myRsaPrivKey, salt) + if err != nil { + // Close connection on an error + errClose := conn.Close() + if errClose != nil { + return nil, errors.Errorf( + failedToCloseConnectionErr, + recipient.ID, err, errClose) + } + return nil, errors.WithMessagef(err, "failed to construct client "+ + "authentication message") + } + + // Send message to server + rids, _, _, err := conn.SendE2E(catalog.ConnectionAuthenticationRequest, + payload, clientE2e.GetDefaultParams()) + if err != nil { + // Close connection on an error + errClose := conn.Close() + if errClose != nil { + return nil, errors.Errorf( + failedToCloseConnectionErr, + recipient.ID, err, errClose) + } + return nil, errors.WithMessagef(err, "failed to send client "+ + "authentication message") + } + + // Determine that the message is properly sent by tracking the success + // of the round(s) + roundErr := make(chan error, 1) + roundCb := cmix.RoundEventCallback(func(allRoundsSucceeded, + timedOut bool, rounds map[id.Round]cmix.RoundResult) { + // Check for failures while tracking rounds + if timedOut || !allRoundsSucceeded { + if timedOut { + roundErr <- errors.New(roundTrackingTimeoutErr) + } else { + // If we did not time out, then not all rounds succeeded + roundErr <- errors.New(notAllRoundsSucceededErr) + } + return + } + + // If no errors occurred, signal so; an authenticated channel may + // be constructed now + roundErr <- nil + }) + + // Find the remaining time in the timeout since we first sent the message + remainingTime := p.Timeout - netTime.Since(timeStart) + + // Track the result of the round(s) we sent the + // identity authentication message on + err = net.GetRoundResults(remainingTime, + roundCb, rids...) + if err != nil { + return nil, errors.Errorf("could not track rounds for successful " + + "identity confirmation message delivery") + } + // Block waiting for confirmation of the round(s) success (or timeout + jww.DEBUG.Printf("AuthenticatedConnection waiting for authenticated "+ + "connection with %s to be established...", recipient.ID.String()) + // Wait for the round callback to send a round error + err = <-roundErr + if err != nil { + // Close connection on an error + errClose := conn.Close() + if errClose != nil { + return nil, errors.Errorf( + failedToCloseConnectionErr, + recipient.ID, err, errClose) + } + + return nil, errors.Errorf("failed to confirm if identity "+ + "authentication message was sent to %s: %v", recipient.ID, err) + } + + // If channel received no error, construct and return the + // authenticated connection + authConn := buildAuthenticatedConnection(conn) + authConn.setAuthenticated() + return authConn, nil +} + +// StartAuthenticatedServer is called by the receiver of an +// authenticated connection request. Calling this will indicate that they +// will handle authenticated requests and verify the client's attempt to +// authenticate themselves. An established AuthenticatedConnection will +// be passed via the callback. +func StartAuthenticatedServer(cb AuthenticatedCallback, + myId *id.ID, privKey *cyclic.Int, + rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client, + p Params) error { + + // Register the waiter for a connection establishment + connCb := Callback(func(connection Connection) { + // Upon establishing a connection, register a listener for the + // client's identity proof. If an identity authentication + // message is received and validated, an authenticated connection will + // be passed along via the AuthenticatedCallback + connection.RegisterListener(catalog.ConnectionAuthenticationRequest, + buildAuthConfirmationHandler(cb, connection)) + }) + return StartServer(connCb, myId, privKey, rng, grp, + net, p) +} + +// authenticatedHandler provides an implementation for the +// AuthenticatedConnection interface. +type authenticatedHandler struct { + Connection + isAuthenticated bool + authMux sync.Mutex +} + +// buildAuthenticatedConnection assembles an AuthenticatedConnection object. +func buildAuthenticatedConnection(conn Connection) *authenticatedHandler { + return &authenticatedHandler{ + Connection: conn, + isAuthenticated: false, + } +} + +// IsAuthenticated returns whether the AuthenticatedConnection has completed +// the authentication process. +func (h *authenticatedHandler) IsAuthenticated() bool { + return h.isAuthenticated +} + +// setAuthenticated is a helper function which sets the +// AuthenticatedConnection as authenticated. +func (h *authenticatedHandler) setAuthenticated() { + h.authMux.Lock() + defer h.authMux.Unlock() + h.isAuthenticated = true +} diff --git a/connect/authenticated.pb.go b/connect/authenticated.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..a12961650fe8a763a5b07be50163f4b4ef496acc --- /dev/null +++ b/connect/authenticated.pb.go @@ -0,0 +1,100 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: connect/authenticated/authenticated.proto + +package connect // import "gitlab.com/elixxir/client/connect/authenticated" + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Sent by the receiver of the authenticated connection request. +type IdentityAuthentication struct { + Signature []byte `protobuf:"bytes,1,opt,name=Signature,proto3" json:"Signature,omitempty"` + // established between the two partners + RsaPubKey []byte `protobuf:"bytes,2,opt,name=RsaPubKey,proto3" json:"RsaPubKey,omitempty"` + // PEM-encoded + Salt []byte `protobuf:"bytes,3,opt,name=Salt,proto3" json:"Salt,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IdentityAuthentication) Reset() { *m = IdentityAuthentication{} } +func (m *IdentityAuthentication) String() string { return proto.CompactTextString(m) } +func (*IdentityAuthentication) ProtoMessage() {} +func (*IdentityAuthentication) Descriptor() ([]byte, []int) { + return fileDescriptor_authenticated_9ed9358e4abe7a3a, []int{0} +} +func (m *IdentityAuthentication) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_IdentityAuthentication.Unmarshal(m, b) +} +func (m *IdentityAuthentication) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_IdentityAuthentication.Marshal(b, m, deterministic) +} +func (dst *IdentityAuthentication) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdentityAuthentication.Merge(dst, src) +} +func (m *IdentityAuthentication) XXX_Size() int { + return xxx_messageInfo_IdentityAuthentication.Size(m) +} +func (m *IdentityAuthentication) XXX_DiscardUnknown() { + xxx_messageInfo_IdentityAuthentication.DiscardUnknown(m) +} + +var xxx_messageInfo_IdentityAuthentication proto.InternalMessageInfo + +func (m *IdentityAuthentication) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +func (m *IdentityAuthentication) GetRsaPubKey() []byte { + if m != nil { + return m.RsaPubKey + } + return nil +} + +func (m *IdentityAuthentication) GetSalt() []byte { + if m != nil { + return m.Salt + } + return nil +} + +func init() { + proto.RegisterType((*IdentityAuthentication)(nil), "authenticatedConnectionMessages.IdentityAuthentication") +} + +func init() { + proto.RegisterFile("connect/authenticated/authenticated.proto", fileDescriptor_authenticated_9ed9358e4abe7a3a) +} + +var fileDescriptor_authenticated_9ed9358e4abe7a3a = []byte{ + // 180 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xce, 0xcf, 0xcb, + 0x4b, 0x4d, 0x2e, 0xc9, 0xcc, 0xcf, 0x2b, 0xd6, 0x4f, 0x2c, 0x2d, 0xc9, 0x48, 0xcd, 0x2b, 0xc9, + 0x4c, 0x4e, 0x2c, 0x49, 0x4d, 0x41, 0xe5, 0xe9, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0xc9, 0xa3, + 0x08, 0x3a, 0xc3, 0xf5, 0xfa, 0xa6, 0x16, 0x17, 0x27, 0xa6, 0xa7, 0x16, 0x2b, 0x65, 0x70, 0x89, + 0x79, 0xa6, 0x80, 0x14, 0x94, 0x54, 0x3a, 0x22, 0x94, 0x66, 0xe6, 0xe7, 0x09, 0xc9, 0x70, 0x71, + 0x06, 0x67, 0xa6, 0xe7, 0x25, 0x96, 0x94, 0x16, 0xa5, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x04, + 0x21, 0x04, 0x40, 0xb2, 0x41, 0xc5, 0x89, 0x01, 0xa5, 0x49, 0xde, 0xa9, 0x95, 0x12, 0x4c, 0x10, + 0x59, 0xb8, 0x80, 0x90, 0x10, 0x17, 0x4b, 0x70, 0x62, 0x4e, 0x89, 0x04, 0x33, 0x58, 0x02, 0xcc, + 0x76, 0x32, 0x8d, 0x32, 0x4e, 0xcf, 0x2c, 0xc9, 0x49, 0x4c, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, + 0xcd, 0xc9, 0xac, 0xa8, 0xc8, 0x2c, 0xd2, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0xd1, 0xc7, 0xe9, + 0xab, 0x24, 0x36, 0xb0, 0x47, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x71, 0x89, 0x27, 0xcf, + 0xf9, 0x00, 0x00, 0x00, +} diff --git a/connect/authenticated.proto b/connect/authenticated.proto new file mode 100644 index 0000000000000000000000000000000000000000..926ca845e35c6a25e91ff4b1d4f477c2eaea5c2d --- /dev/null +++ b/connect/authenticated.proto @@ -0,0 +1,22 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +syntax = "proto3"; +package authenticatedConnectionMessages; +option go_package = "gitlab.com/elixxir/client/connect/authenticated"; + +// Sent by the receiver of the authenticated connection request. +message IdentityAuthentication { + bytes Signature = 1; // Signature of the connection fingerprint + // established between the two partners + bytes RsaPubKey = 2; // The RSA public key of the sender of this message, + // PEM-encoded + bytes Salt = 3; // Salt used to generate the network ID of the client +} + + + diff --git a/connect/authenticated_test.go b/connect/authenticated_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3668a742229ed15a27af3697c8a1ae05ad7deca2 --- /dev/null +++ b/connect/authenticated_test.go @@ -0,0 +1,108 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/diffieHellman" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/crypto/xx" + "gitlab.com/xx_network/primitives/id" + "math/rand" + "testing" + "time" +) + +// TestConnectWithAuthentication will test the client/server relationship for +// an AuthenticatedConnection. This will construct a client which will send an +// IdentityAuthentication message to the server, who will hear it and verify +// the contents. This will use a mock connection interface and private +// production code helper functions for easier testing. +func TestConnectWithAuthentication(t *testing.T) { + grp := getGroup() + numPrimeByte := len(grp.GetPBytes()) + + // Set up cmix handler + mockNet := newMockCmix() + + // Set up connect arguments + prng := rand.New(rand.NewSource(42)) + dhPrivKey := diffieHellman.GeneratePrivateKey( + numPrimeByte, grp, prng) + dhPubKey := diffieHellman.GeneratePublicKey(dhPrivKey, grp) + salt := make([]byte, 32) + copy(salt, "salt") + + myRsaPrivKey, err := rsa.LoadPrivateKeyFromPem(getPrivKey()) + if err != nil { + t.Fatalf("Faled to load private key: %v", err) + } + + // Construct client ID the proper way as server will need to verify it + // using the xx.NewID function call + myId, err := xx.NewID(myRsaPrivKey.GetPublic(), salt, id.User) + if err != nil { + t.Fatalf("Failed to generate client's id: %+v", err) + } + + // Generate server ID using testing interface + serverID := id.NewIdFromString("server", id.User, t) + + // Construct recipient + recipient := contact.Contact{ + ID: serverID, + DhPubKey: dhPubKey, + } + + rng := fastRNG.NewStreamGenerator(1, 1, + csprng.NewSystemRNG) + + // Create the mock connection, which will be shared by the client and + // server. This will send the client's request to the server internally + mockConn := newMockConnection(myId, serverID, dhPrivKey, dhPubKey) + + // Set up the server's callback, which will pass the authenticated + // connection through via a channel + authConnChan := make(chan AuthenticatedConnection, 1) + serverCb := AuthenticatedCallback( + func(connection AuthenticatedConnection) { + authConnChan <- connection + }) + + // Initialize params with a shorter timeout to hasten test results + customParams := GetDefaultParams() + customParams.Timeout = 3 * time.Second + + // Initialize the server + serverHandler := buildAuthConfirmationHandler(serverCb, mockConn) + + // Pass the server's listener to the mock connection so the connection + // can pass the client's message directly to the server + mockConn.listener = serverHandler + + // Initialize the client + _, err = connectWithAuthentication(mockConn, time.Now(), recipient, + salt, myRsaPrivKey, rng, mockNet, + customParams) + if err != nil { + t.Fatalf("ConnectWithAuthentication error: %+v", err) + } + + // Wait for the server to establish it's connection via the callback + timeout := time.NewTimer(customParams.Timeout) + select { + case <-authConnChan: + return + case <-timeout.C: + t.Fatalf("Timed out waiting for server's authenticated connection " + + "to be established") + } + +} diff --git a/connect/client.go b/connect/client.go new file mode 100644 index 0000000000000000000000000000000000000000..a916bdca352da2e7aee21d0b2549702acfeebfdf --- /dev/null +++ b/connect/client.go @@ -0,0 +1,54 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/e2e/ratchet/partner" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/signature/rsa" +) + +// buildClientAuthRequest is a helper function which constructs a marshalled +// IdentityAuthentication message. +func buildClientAuthRequest(newPartner partner.Manager, + rng *fastRNG.StreamGenerator, rsaPrivKey *rsa.PrivateKey, + salt []byte) ([]byte, error) { + + // The connection fingerprint (hashed) will be used as a nonce + connectionFp := newPartner.ConnectionFingerprint().Bytes() + opts := rsa.NewDefaultOptions() + h := opts.Hash.New() + h.Write(connectionFp) + nonce := h.Sum(nil) + + // Sign the connection fingerprint + stream := rng.GetStream() + defer stream.Close() + signature, err := rsa.Sign(stream, rsaPrivKey, + opts.Hash, nonce, opts) + if err != nil { + return nil, errors.Errorf("failed to sign nonce: %+v", err) + } + + // Construct message + pemEncodedRsaPubKey := rsa.CreatePublicKeyPem(rsaPrivKey.GetPublic()) + iar := &IdentityAuthentication{ + Signature: signature, + RsaPubKey: pemEncodedRsaPubKey, + Salt: salt, + } + payload, err := proto.Marshal(iar) + if err != nil { + return nil, errors.Errorf("failed to marshal identity request "+ + "message: %+v", err) + } + + return payload, nil +} diff --git a/connect/connect.go b/connect/connect.go index 6c9fb120e91d25695ad3f3f5ac0dd3578f07c9f7..1f0a6db7449f9c3fac9db297114997bce803fdd1 100644 --- a/connect/connect.go +++ b/connect/connect.go @@ -30,10 +30,17 @@ import ( "time" ) +const ( + // connectionTimeout is the time.Duration for a connection + // to be established before the requester times out. + connectionTimeout = 15 * time.Second +) + // Connection is a wrapper for the E2E and auth packages. // It can be used to automatically establish an E2E partnership // with a partner.Manager, or be built from an existing E2E partnership. -// You can then use this interface to send to and receive from the newly-established partner.Manager. +// You can then use this interface to send to and receive from the +// newly-established partner.Manager. type Connection interface { // Closer deletes this Connection's partner.Manager and releases resources io.Closer @@ -41,7 +48,8 @@ type Connection interface { // GetPartner returns the partner.Manager for this Connection GetPartner() partner.Manager - // SendE2E is a wrapper for sending specifically to the Connection's partner.Manager + // SendE2E is a wrapper for sending specifically to the Connection's + // partner.Manager SendE2E(mt catalog.MessageType, payload []byte, params clientE2e.Params) ( []id.Round, e2e.MessageID, time.Time, error) @@ -53,37 +61,35 @@ type Connection interface { Unregister(listenerID receive.ListenerID) } -// Callback is the callback format required to retrieve new Connection objects as they are established +// Callback is the callback format required to retrieve +// new Connection objects as they are established. type Callback func(connection Connection) -// handler provides an implementation for the Connection interface -type handler struct { - partner partner.Manager - e2e clientE2e.Handler - params Params -} - -// Params for managing Connection objects +// Params for managing Connection objects. type Params struct { - Auth auth.Param - Rekey rekey.Params - Event event.Reporter + Auth auth.Param + Rekey rekey.Params + Event event.Reporter + Timeout time.Duration } -// GetDefaultParams returns a usable set of default Connection parameters +// GetDefaultParams returns a usable set of default Connection parameters. func GetDefaultParams() Params { return Params{ - Auth: auth.GetDefaultParams(), - Rekey: rekey.GetDefaultParams(), - Event: event.NewEventManager(), + Auth: auth.GetDefaultParams(), + Rekey: rekey.GetDefaultParams(), + Event: event.NewEventManager(), + Timeout: connectionTimeout, } } // Connect performs auth key negotiation with the given recipient, // and returns a Connection object for the newly-created partner.Manager -// This function is to be used sender-side and will block until the partner.Manager is confirmed -func Connect(recipient contact.Contact, myId *id.ID, privKey *cyclic.Int, rng *fastRNG.StreamGenerator, - grp *cyclic.Group, net cmix.Client, p Params) (Connection, error) { +// This function is to be used sender-side and will block until the +// partner.Manager is confirmed. +func Connect(recipient contact.Contact, myId *id.ID, privKey *cyclic.Int, + rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client, + p Params) (Connection, error) { // Build an ephemeral KV kv := versioned.NewKV(ekv.MakeMemstore()) @@ -119,23 +125,32 @@ func Connect(recipient contact.Contact, myId *id.ID, privKey *cyclic.Int, rng *f } // Block waiting for auth to confirm - jww.DEBUG.Printf("Connection waiting for auth request for %s to be confirmed...", recipient.ID.String()) - newConnection := <-signalChannel - - // Verify the Connection is complete - if newConnection == nil { - return nil, errors.Errorf("Unable to complete connection with partner %s", recipient.ID.String()) + jww.DEBUG.Printf("Connection waiting for auth request "+ + "for %s to be confirmed...", recipient.ID.String()) + timeout := time.NewTimer(p.Timeout) + defer timeout.Stop() + select { + case newConnection := <-signalChannel: + // Verify the Connection is complete + if newConnection == nil { + return nil, errors.Errorf("Unable to complete connection "+ + "with partner %s", recipient.ID.String()) + } + jww.DEBUG.Printf("Connection auth request for %s confirmed", + recipient.ID.String()) + return newConnection, nil + case <-timeout.C: + return nil, errors.Errorf("Connection request with "+ + "partner %s timed out", recipient.ID.String()) } - jww.DEBUG.Printf("Connection auth request for %s confirmed", recipient.ID.String()) - - return newConnection, nil } -// RegisterConnectionCallback assembles a Connection object on the reception-side +// StartServer assembles a Connection object on the reception-side // and feeds it into the given Callback whenever an incoming request // for an E2E partnership with a partner.Manager is confirmed. -func RegisterConnectionCallback(cb Callback, myId *id.ID, privKey *cyclic.Int, rng *fastRNG.StreamGenerator, - grp *cyclic.Group, net cmix.Client, p Params) error { +func StartServer(cb Callback, myId *id.ID, privKey *cyclic.Int, + rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client, + p Params) error { // Build an ephemeral KV kv := versioned.NewKV(ekv.MakeMemstore()) @@ -154,14 +169,24 @@ func RegisterConnectionCallback(cb Callback, myId *id.ID, privKey *cyclic.Int, r callback := getAuthCallback(cb, e2eHandler, p) // Build auth object for E2E negotiation - _, err = auth.NewState(kv, net, e2eHandler, + authState, err := auth.NewState(kv, net, e2eHandler, rng, p.Event, p.Auth, callback, nil) + callback.authState = authState return err } +// handler provides an implementation for the Connection interface. +type handler struct { + partner partner.Manager + e2e clientE2e.Handler + params Params +} + // BuildConnection assembles a Connection object -// after an E2E partnership has already been confirmed with the given partner.Manager -func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler, p Params) Connection { +// after an E2E partnership has already been confirmed with the given +// partner.Manager. +func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler, + p Params) Connection { return &handler{ partner: partner, params: p, @@ -169,36 +194,41 @@ func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler, p Pa } } -// Close deletes this Connection's partner.Manager and releases resources +// Close deletes this Connection's partner.Manager and releases resources. func (h *handler) Close() error { return h.e2e.DeletePartner(h.partner.PartnerId()) } -// GetPartner returns the partner.Manager for this Connection +// GetPartner returns the partner.Manager for this Connection. func (h *handler) GetPartner() partner.Manager { return h.partner } -// SendE2E is a wrapper for sending specifically to the Connection's partner.Manager -func (h *handler) SendE2E(mt catalog.MessageType, payload []byte, params clientE2e.Params) ( +// SendE2E is a wrapper for sending specifically to the Connection's +// partner.Manager. +func (h *handler) SendE2E(mt catalog.MessageType, payload []byte, + params clientE2e.Params) ( []id.Round, e2e.MessageID, time.Time, error) { return h.e2e.SendE2E(mt, h.partner.PartnerId(), payload, params) } // RegisterListener is used for E2E reception -// and allows for reading data sent from the partner.Manager -func (h *handler) RegisterListener(messageType catalog.MessageType, newListener receive.Listener) receive.ListenerID { - return h.e2e.RegisterListener(h.partner.PartnerId(), messageType, newListener) +// and allows for reading data sent from the partner.Manager. +func (h *handler) RegisterListener(messageType catalog.MessageType, + newListener receive.Listener) receive.ListenerID { + return h.e2e.RegisterListener(h.partner.PartnerId(), + messageType, newListener) } -// Unregister listener for E2E reception +// Unregister listener for E2E reception. func (h *handler) Unregister(listenerID receive.ListenerID) { h.e2e.Unregister(listenerID) } -// authCallback provides callback functionality for interfacing between auth.State and Connection -// This is used both for blocking creation of a Connection object until the auth Request is confirmed -// and for dynamically building new Connection objects when an auth Request is received. +// authCallback provides callback functionality for interfacing between +// auth.State and Connection. This is used both for blocking creation of a +// Connection object until the auth Request is confirmed and for dynamically +// building new Connection objects when an auth Request is received. type authCallback struct { // Used for signaling confirmation of E2E partnership connectionCallback Callback @@ -206,38 +236,54 @@ type authCallback struct { // Used for building new Connection objects connectionE2e clientE2e.Handler connectionParams Params + authState auth.State } -// getAuthCallback returns a callback interface to be passed into the creation of an auth.State object. -func getAuthCallback(cb Callback, e2e clientE2e.Handler, params Params) authCallback { - return authCallback{ +// getAuthCallback returns a callback interface to be passed into the creation +// of an auth.State object. +func getAuthCallback(cb Callback, e2e clientE2e.Handler, + params Params) *authCallback { + return &authCallback{ connectionCallback: cb, connectionE2e: e2e, connectionParams: params, } } -// Confirm will be called when an auth Confirm message is processed -func (a authCallback) Confirm(requestor contact.Contact, receptionID receptionID.EphemeralIdentity, round rounds.Round) { - jww.DEBUG.Printf("Connection auth request for %s confirmed", requestor.ID.String()) +// Confirm will be called when an auth Confirm message is processed. +func (a authCallback) Confirm(requestor contact.Contact, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + jww.DEBUG.Printf("Connection auth request for %s confirmed", + requestor.ID.String()) // After confirmation, get the new partner newPartner, err := a.connectionE2e.GetPartner(requestor.ID) if err != nil { - jww.ERROR.Printf("Unable to build connection with partner %s: %+v", requestor.ID, err) + jww.ERROR.Printf("Unable to build connection with "+ + "partner %s: %+v", requestor.ID, err) // Send a nil connection to avoid hold-ups down the line a.connectionCallback(nil) return } // Return the new Connection object - a.connectionCallback(BuildConnection(newPartner, a.connectionE2e, a.connectionParams)) + a.connectionCallback(BuildConnection(newPartner, a.connectionE2e, + a.connectionParams)) } -// Request will be called when an auth Request message is processed -func (a authCallback) Request(requestor contact.Contact, receptionID receptionID.EphemeralIdentity, round rounds.Round) { +// Request will be called when an auth Request message is processed. +func (a authCallback) Request(requestor contact.Contact, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + _, err := a.authState.Confirm(requestor) + if err != nil { + jww.ERROR.Printf("Unable to build connection with "+ + "partner %s: %+v", requestor.ID, err) + // Send a nil connection to avoid hold-ups down the line + a.connectionCallback(nil) + } } -// Reset will be called when an auth Reset operation occurs -func (a authCallback) Reset(requestor contact.Contact, receptionID receptionID.EphemeralIdentity, round rounds.Round) { +// Reset will be called when an auth Reset operation occurs. +func (a authCallback) Reset(requestor contact.Contact, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { } diff --git a/connect/generateProto.sh b/connect/generateProto.sh new file mode 100755 index 0000000000000000000000000000000000000000..b27e351d7d0c5784e9564cfc2b1e6b2ba7889aa6 --- /dev/null +++ b/connect/generateProto.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# This script will generate the protobuf Golang file (pb.go) out of the protobuf file (.proto). +# This is meant to be called from the top level of the repo. + +protoc --go_out=paths=source_relative:. connections/authenticated/authenticated.proto diff --git a/connect/server.go b/connect/server.go new file mode 100644 index 0000000000000000000000000000000000000000..78e22eed6fedfbe965ac89dcd8cbdf15579d4a26 --- /dev/null +++ b/connect/server.go @@ -0,0 +1,132 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/crypto/xx" + "gitlab.com/xx_network/primitives/id" +) + +// authenticatedServerListenerName is the name of the client's +//listener interface. +const authenticatedServerListenerName = "AuthenticatedServerListener" + +// server is an interface that wraps receive.Listener. This handles +// the server listening for the client's proof of identity message. +type server interface { + receive.Listener +} + +// serverListener provides an implementation of the server interface. +// This will handle the identity message sent by the client. +type serverListener struct { + // connectionCallback allows an AuthenticatedConnection + // to be passed back upon establishment. + connectionCallback AuthenticatedCallback + + // conn used to retrieve the connection context with the partner. + conn Connection +} + +// buildAuthConfirmationHandler returns a serverListener object. +// This will handle incoming identity authentication confirmations +// via the serverListener.Hear method. A successful AuthenticatedConnection +// will be passed along via the serverListener.connectionCallback +func buildAuthConfirmationHandler(cb AuthenticatedCallback, + connection Connection) server { + return &serverListener{ + connectionCallback: cb, + conn: connection, + } +} + +// Hear handles the reception of an IdentityAuthentication by the +// server. It will attempt to verify the identity confirmation of +// the given client. +func (a serverListener) Hear(item receive.Message) { + // Process the message data into a protobuf + iar := &IdentityAuthentication{} + err := proto.Unmarshal(item.Payload, iar) + if err != nil { + a.handleAuthConfirmationErr(err, item.Sender) + return + } + + // Process the PEM encoded public key to an rsa.PublicKey object + partnerPubKey, err := rsa.LoadPublicKeyFromPem(iar.RsaPubKey) + if err != nil { + a.handleAuthConfirmationErr(err, item.Sender) + return + } + + // Get the new partner + newPartner := a.conn.GetPartner() + + // Verify the partner's known ID against the information passed + // along the wire + partnerWireId, err := xx.NewID(partnerPubKey, iar.Salt, id.User) + if err != nil { + a.handleAuthConfirmationErr(err, item.Sender) + return + } + + if !newPartner.PartnerId().Cmp(partnerWireId) { + err = errors.New("Failed confirm partner's ID over the wire") + a.handleAuthConfirmationErr(err, item.Sender) + return + } + + // The connection fingerprint (hashed) will be used as a nonce + connectionFp := newPartner.ConnectionFingerprint().Bytes() + + // Hash the connection fingerprint + opts := rsa.NewDefaultOptions() + h := opts.Hash.New() + h.Write(connectionFp) + nonce := h.Sum(nil) + + // Verify the signature + err = rsa.Verify(partnerPubKey, opts.Hash, nonce, iar.Signature, opts) + if err != nil { + a.handleAuthConfirmationErr(err, item.Sender) + return + } + + // If successful, pass along the established authenticated connection + // via the callback + jww.DEBUG.Printf("AuthenticatedConnection auth request for %s confirmed", + item.Sender.String()) + authConn := buildAuthenticatedConnection(a.conn) + authConn.setAuthenticated() + go a.connectionCallback(authConn) +} + +// handleAuthConfirmationErr is a helper function which will close the connection +// between the server and the client. It will also print out the passed in error. +func (a serverListener) handleAuthConfirmationErr(err error, sender *id.ID) { + jww.ERROR.Printf("Unable to build connection with "+ + "partner %s: %+v", sender, err) + // Send a nil connection to avoid hold-ups down the line + a.connectionCallback(nil) + err = a.conn.Close() + if err != nil { + jww.ERROR.Printf("Failed to close connection with partner %s: %v", + sender, err) + } +} + +// Name returns the name of this listener. This is typically for +// printing/debugging purposes. +func (a serverListener) Name() string { + return authenticatedServerListenerName +} diff --git a/connect/utils_test.go b/connect/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..39f518b15a92bc9c588558eb4ee3fb4f52997f3f --- /dev/null +++ b/connect/utils_test.go @@ -0,0 +1,484 @@ +package connect + +import ( + "github.com/cloudflare/circl/dh/sidh" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/gateway" + "gitlab.com/elixxir/client/cmix/identity" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/e2e" + "gitlab.com/elixxir/client/e2e/ratchet/partner" + "gitlab.com/elixxir/client/e2e/ratchet/partner/session" + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" + cryptoE2e "gitlab.com/elixxir/crypto/e2e" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/large" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" + "gitlab.com/xx_network/primitives/netTime" + "time" +) + +//////////////////////////////////////////////////////////////////////////////// +// Mock Partner Interface // +//////////////////////////////////////////////////////////////////////////////// + +type mockPartner struct { + partnerId *id.ID + myID *id.ID + myDhPrivKey *cyclic.Int + partnerDhPubKey *cyclic.Int +} + +func newMockPartner(partnerId, myId *id.ID, + myDhPrivKey, partnerDhPubKey *cyclic.Int) *mockPartner { + return &mockPartner{ + partnerId: partnerId, + myID: myId, + myDhPrivKey: myDhPrivKey, + partnerDhPubKey: partnerDhPubKey, + } +} + +func (m mockPartner) PartnerId() *id.ID { + return m.partnerId +} + +func (m mockPartner) MyId() *id.ID { + return m.myID +} + +func (m mockPartner) MyRootPrivateKey() *cyclic.Int { + return m.myDhPrivKey +} + +func (m mockPartner) PartnerRootPublicKey() *cyclic.Int { + return m.partnerDhPubKey +} + +func (m mockPartner) SendRelationshipFingerprint() []byte { + return nil +} + +func (m mockPartner) ReceiveRelationshipFingerprint() []byte { + return nil +} + +func (m mockPartner) ConnectionFingerprint() partner.ConnectionFp { + return partner.ConnectionFp{} +} + +func (m mockPartner) Contact() contact.Contact { + return contact.Contact{ + ID: m.partnerId, + DhPubKey: m.partnerDhPubKey, + } +} + +func (m mockPartner) PopSendCypher() (*session.Cypher, error) { + return nil, nil +} + +func (m mockPartner) PopRekeyCypher() (*session.Cypher, error) { + return nil, nil +} + +func (m mockPartner) NewReceiveSession(partnerPubKey *cyclic.Int, partnerSIDHPubKey *sidh.PublicKey, e2eParams session.Params, source *session.Session) (*session.Session, bool) { + return nil, false +} + +func (m mockPartner) NewSendSession(myDHPrivKey *cyclic.Int, mySIDHPrivateKey *sidh.PrivateKey, e2eParams session.Params, source *session.Session) *session.Session { + return nil +} + +func (m mockPartner) GetSendSession(sid session.SessionID) *session.Session { + return nil +} + +func (m mockPartner) GetReceiveSession(sid session.SessionID) *session.Session { + return nil +} + +func (m mockPartner) Confirm(sid session.SessionID) error { + return nil +} + +func (m mockPartner) TriggerNegotiations() []*session.Session { + return nil +} + +func (m mockPartner) MakeService(tag string) message.Service { + return message.Service{} +} + +func (m mockPartner) Delete() error { + return nil +} + +//////////////////////////////////////////////////////////////////////////////// +// Mock Connection Interface // +//////////////////////////////////////////////////////////////////////////////// + +type mockConnection struct { + partner *mockPartner + payloadChan chan []byte + listener server +} + +func newMockConnection(partnerId, myId *id.ID, + myDhPrivKey, partnerDhPubKey *cyclic.Int) *mockConnection { + + return &mockConnection{ + partner: newMockPartner(partnerId, myId, + myDhPrivKey, partnerDhPubKey), + payloadChan: make(chan []byte, 1), + } +} + +func (m mockConnection) Close() error { + return nil +} + +func (m mockConnection) GetPartner() partner.Manager { + return m.partner +} + +func (m mockConnection) SendE2E(mt catalog.MessageType, payload []byte, params e2e.Params) ([]id.Round, cryptoE2e.MessageID, time.Time, error) { + m.payloadChan <- payload + m.listener.Hear(receive.Message{ + MessageType: mt, + Payload: payload, + Sender: m.partner.myID, + RecipientID: m.partner.partnerId, + }) + return nil, cryptoE2e.MessageID{}, time.Time{}, nil +} + +func (m mockConnection) RegisterListener(messageType catalog.MessageType, newListener receive.Listener) receive.ListenerID { + return receive.ListenerID{} +} + +func (m mockConnection) Unregister(listenerID receive.ListenerID) { + +} + +//////////////////////////////////////////////////////////////////////////////// +// Mock cMix Client // +//////////////////////////////////////////////////////////////////////////////// + +type mockCmix struct { + instance *network.Instance +} + +func newMockCmix() *mockCmix { + + return &mockCmix{} +} + +func (m mockCmix) Connect(ndf *ndf.NetworkDefinition) error { + return nil +} + +func (m *mockCmix) Follow(report cmix.ClientErrorReport) (stoppable.Stoppable, error) { + return nil, nil +} + +func (m *mockCmix) GetMaxMessageLength() int { + return 4096 +} + +func (m *mockCmix) Send(recipient *id.ID, fingerprint format.Fingerprint, + service message.Service, payload, mac []byte, + cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) { + + return 0, ephemeral.Id{}, nil +} + +func (m *mockCmix) SendMany(messages []cmix.TargetedCmixMessage, p cmix.CMIXParams) (id.Round, []ephemeral.Id, error) { + return 0, []ephemeral.Id{}, nil +} + +func (m *mockCmix) AddIdentity(id *id.ID, validUntil time.Time, persistent bool) { +} + +func (m *mockCmix) RemoveIdentity(id *id.ID) { +} + +func (m *mockCmix) GetIdentity(get *id.ID) (identity.TrackedID, error) { + return identity.TrackedID{ + Creation: netTime.Now().Add(-time.Minute), + }, nil +} + +func (m *mockCmix) AddFingerprint(identity *id.ID, fp format.Fingerprint, mp message.Processor) error { + return nil +} + +func (m *mockCmix) DeleteFingerprint(identity *id.ID, fingerprint format.Fingerprint) { + return +} + +func (m *mockCmix) DeleteClientFingerprints(identity *id.ID) { + return +} + +func (m *mockCmix) AddService(clientID *id.ID, newService message.Service, response message.Processor) { + return +} + +func (m *mockCmix) DeleteService(clientID *id.ID, toDelete message.Service, processor message.Processor) { + return +} + +func (m *mockCmix) DeleteClientService(clientID *id.ID) { +} + +func (m *mockCmix) TrackServices(tracker message.ServicesTracker) { + return +} + +func (m *mockCmix) CheckInProgressMessages() { + return +} + +func (m *mockCmix) IsHealthy() bool { + return true +} + +func (m *mockCmix) WasHealthy() bool { + return true +} + +func (m *mockCmix) AddHealthCallback(f func(bool)) uint64 { + return 0 +} + +func (m *mockCmix) RemoveHealthCallback(u uint64) { + return +} + +func (m *mockCmix) HasNode(nid *id.ID) bool { + return true +} + +func (m *mockCmix) NumRegisteredNodes() int { + return 24 +} + +func (m *mockCmix) TriggerNodeRegistration(nid *id.ID) { + return +} + +func (m *mockCmix) GetRoundResults(timeout time.Duration, roundCallback cmix.RoundEventCallback, roundList ...id.Round) error { + roundCallback(true, false, nil) + return nil +} + +func (m *mockCmix) LookupHistoricalRound(rid id.Round, callback rounds.RoundResultCallback) error { + return nil +} + +func (m *mockCmix) SendToAny(sendFunc func(host *connect.Host) (interface{}, error), stop *stoppable.Single) (interface{}, error) { + return nil, nil +} + +func (m *mockCmix) SendToPreferred(targets []*id.ID, sendFunc gateway.SendToPreferredFunc, stop *stoppable.Single, timeout time.Duration) (interface{}, error) { + return nil, nil +} + +func (m *mockCmix) SetGatewayFilter(f gateway.Filter) { + return +} + +func (m *mockCmix) GetHostParams() connect.HostParams { + return connect.GetDefaultHostParams() +} + +func (m *mockCmix) GetAddressSpace() uint8 { + return 32 +} + +func (m *mockCmix) RegisterAddressSpaceNotification(tag string) (chan uint8, error) { + return nil, nil +} + +func (m *mockCmix) UnregisterAddressSpaceNotification(tag string) { + return +} + +func (m *mockCmix) GetInstance() *network.Instance { + return m.instance +} + +func (m *mockCmix) GetVerboseRounds() string { + return "" +} + +//////////////////////////////////////////////////////////////////////////////// +// Misc set-up utils // +//////////////////////////////////////////////////////////////////////////////// + +var testCert = `-----BEGIN CERTIFICATE----- +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 +EnretBzQkeKeBwoB2u6NTiOmUjk= +-----END CERTIFICATE----- +` + +func getNDF() *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ + UDB: ndf.UDB{ + ID: id.DummyUser.Bytes(), + Cert: testCert, + Address: "address", + DhPubKey: []byte{123, 34, 86, 97, 108, 117, 101, 34, 58, 53, 48, 49, 53, 53, 53, 52, 54, 53, 49, 48, 54, 49, 56, 57, 53, 54, 51, 48, 54, 52, 49, 51, 53, 49, 57, 56, 55, 57, 52, 57, 50, 48, 56, 49, 52, 57, 52, 50, 57, 51, 57, 53, 49, 50, 51, 54, 52, 56, 49, 57, 55, 48, 50, 50, 49, 48, 55, 55, 50, 52, 52, 48, 49, 54, 57, 52, 55, 52, 57, 53, 53, 56, 55, 54, 50, 57, 53, 57, 53, 48, 54, 55, 57, 55, 48, 53, 48, 48, 54, 54, 56, 49, 57, 50, 56, 48, 52, 48, 53, 51, 50, 48, 57, 55, 54, 56, 56, 53, 57, 54, 57, 56, 57, 49, 48, 54, 56, 54, 50, 52, 50, 52, 50, 56, 49, 48, 51, 51, 51, 54, 55, 53, 55, 54, 52, 51, 54, 55, 54, 56, 53, 56, 48, 55, 56, 49, 52, 55, 49, 52, 53, 49, 52, 52, 52, 52, 53, 51, 57, 57, 51, 57, 57, 53, 50, 52, 52, 53, 51, 56, 48, 49, 48, 54, 54, 55, 48, 52, 50, 49, 55, 54, 57, 53, 57, 57, 57, 51, 52, 48, 54, 54, 54, 49, 50, 48, 54, 56, 57, 51, 54, 57, 48, 52, 55, 55, 54, 50, 49, 49, 56, 56, 53, 51, 50, 57, 57, 50, 54, 53, 48, 52, 57, 51, 54, 55, 54, 48, 57, 56, 56, 49, 55, 52, 52, 57, 53, 57, 54, 53, 50, 55, 53, 52, 52, 52, 49, 57, 55, 49, 54, 50, 52, 52, 56, 50, 55, 55, 50, 49, 48, 53, 56, 56, 57, 54, 51, 53, 54, 54, 53, 53, 53, 53, 49, 56, 50, 53, 49, 49, 50, 57, 50, 48, 49, 56, 48, 48, 54, 49, 56, 57, 48, 55, 48, 51, 53, 51, 51, 56, 57, 52, 49, 50, 57, 49, 55, 50, 56, 55, 57, 57, 52, 55, 53, 51, 49, 55, 55, 48, 53, 55, 55, 49, 50, 51, 57, 49, 51, 55, 54, 48, 50, 49, 55, 50, 54, 54, 52, 56, 52, 48, 48, 54, 48, 52, 48, 53, 56, 56, 53, 54, 52, 56, 56, 49, 52, 52, 51, 57, 56, 51, 51, 57, 54, 55, 48, 49, 53, 55, 52, 53, 50, 56, 51, 49, 51, 48, 53, 52, 49, 49, 49, 49, 49, 56, 51, 53, 52, 52, 52, 52, 48, 53, 54, 57, 48, 54, 52, 56, 57, 52, 54, 53, 50, 56, 51, 53, 50, 48, 48, 50, 48, 48, 49, 50, 51, 51, 48, 48, 53, 48, 49, 50, 52, 56, 57, 48, 49, 51, 54, 55, 52, 57, 55, 50, 49, 48, 55, 53, 54, 49, 50, 52, 52, 57, 55, 48, 50, 56, 55, 55, 51, 51, 50, 53, 50, 48, 57, 52, 56, 57, 49, 49, 56, 49, 54, 57, 50, 55, 50, 51, 57, 51, 57, 54, 50, 56, 48, 54, 54, 49, 57, 55, 48, 50, 48, 57, 49, 51, 54, 50, 49, 50, 53, 50, 54, 50, 53, 53, 55, 57, 54, 51, 56, 49, 57, 48, 51, 49, 54, 54, 53, 51, 56, 56, 49, 48, 56, 48, 51, 57, 53, 49, 53, 53, 55, 49, 53, 57, 48, 57, 57, 55, 49, 56, 53, 55, 54, 48, 50, 54, 48, 49, 55, 57, 52, 55, 53, 51, 57, 49, 51, 53, 52, 49, 48, 50, 49, 55, 52, 51, 57, 48, 50, 56, 48, 50, 51, 53, 51, 54, 56, 49, 56, 50, 49, 55, 50, 57, 52, 51, 49, 56, 48, 56, 56, 50, 51, 53, 52, 56, 55, 49, 52, 55, 53, 50, 56, 48, 57, 55, 49, 53, 48, 48, 51, 50, 48, 57, 50, 50, 53, 50, 56, 51, 57, 55, 57, 49, 57, 50, 53, 56, 51, 55, 48, 51, 57, 54, 48, 50, 55, 54, 48, 54, 57, 55, 52, 53, 54, 52, 51, 56, 52, 53, 54, 48, 51, 57, 55, 55, 55, 49, 53, 57, 57, 49, 57, 52, 57, 56, 56, 54, 56, 50, 49, 49, 54, 56, 55, 56, 55, 51, 51, 57, 52, 53, 49, 52, 52, 55, 57, 53, 57, 49, 57, 52, 48, 51, 53, 49, 49, 49, 51, 48, 53, 54, 54, 50, 49, 56, 57, 52, 55, 50, 49, 54, 53, 57, 53, 50, 57, 50, 48, 51, 51, 52, 48, 56, 55, 54, 50, 49, 49, 49, 56, 53, 54, 57, 51, 57, 50, 53, 48, 53, 56, 56, 55, 56, 53, 54, 55, 51, 56, 55, 50, 53, 57, 56, 52, 54, 53, 49, 51, 50, 54, 51, 50, 48, 56, 56, 57, 52, 53, 57, 53, 56, 57, 57, 54, 52, 55, 55, 50, 57, 51, 51, 52, 55, 51, 48, 52, 56, 56, 50, 51, 50, 52, 53, 48, 51, 50, 56, 56, 50, 49, 55, 51, 51, 53, 54, 55, 51, 50, 51, 52, 56, 53, 52, 55, 48, 51, 56, 50, 51, 49, 53, 55, 52, 53, 53, 48, 55, 55, 56, 55, 48, 50, 51, 52, 50, 53, 52, 51, 48, 57, 56, 56, 54, 56, 54, 49, 57, 54, 48, 55, 55, 52, 57, 55, 56, 51, 48, 51, 57, 49, 55, 52, 49, 51, 49, 54, 57, 54, 50, 49, 52, 50, 55, 57, 55, 56, 56, 51, 49, 55, 51, 50, 54, 56, 49, 56, 53, 57, 48, 49, 49, 53, 48, 52, 53, 51, 51, 56, 52, 57, 57, 55, 54, 51, 55, 55, 48, 55, 49, 52, 50, 49, 54, 48, 49, 54, 52, 49, 57, 53, 56, 49, 54, 50, 55, 49, 52, 49, 52, 56, 49, 51, 52, 50, 53, 56, 55, 53, 57, 55, 52, 49, 57, 49, 55, 51, 55, 49, 51, 57, 54, 51, 49, 51, 49, 56, 53, 50, 49, 53, 52, 49, 51, 44, 34, 70, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116, 34, 58, 49, 54, 56, 48, 49, 53, 52, 49, 53, 49, 49, 50, 51, 51, 48, 57, 56, 51, 54, 51, 125}, + }, + E2E: ndf.Group{ + Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" + + "8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D" + + "D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615" + + "75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC" + + "6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C" + + "4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2" + + "6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE" + + "448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E" + + "198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF" + + "DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323" + + "631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C" + + "3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63" + + "19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3" + + "5873847AEF49F66E43873", + Generator: "2", + }, + CMIX: ndf.Group{ + Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642" + + "F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757" + + "264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F" + + "9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091E" + + "B51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D" + + "0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D3" + + "92145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A" + + "2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7" + + "995FAD5AABBCFBE3EDA2741E375404AE25B", + Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E2480" + + "9670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D" + + "1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A33" + + "8661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361" + + "C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B28" + + "5DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD929" + + "59859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D83" + + "2186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8" + + "B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", + }, + } +} + +func getGroup() *cyclic.Group { + return cyclic.NewGroup( + large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D4941"+ + "3394C049B7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688"+ + "B55B3DD2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861"+ + "575E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC"+ + "718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FF"+ + "B1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBC"+ + "A23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD"+ + "161C7738F32BF29A841698978825B4111B4BC3E1E198455095958333D776D8B2B"+ + "EEED3A1A1A221A6E37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C"+ + "4F50D7D7803D2D4F278DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F"+ + "1390B5D3FEACAF1696015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F"+ + "96789C38E89D796138E6319BE62E35D87B1048CA28BE389B575E994DCA7554715"+ + "84A09EC723742DC35873847AEF49F66E43873", 16), + large.NewIntFromString("2", 16)) +} + +func getPrivKey() []byte { + return []byte(`-----BEGIN PRIVATE KEY----- +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 +OaRA5ZbdE7g7vxKRV36jT3wvD7W+ +-----END PRIVATE KEY-----`) +} diff --git a/restlike/connect/receiver.go b/restlike/connect/receiver.go new file mode 100644 index 0000000000000000000000000000000000000000..d6e87c57489a4235cd0e225fb38c4c87dad4afc3 --- /dev/null +++ b/restlike/connect/receiver.go @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/connect" + "gitlab.com/elixxir/client/e2e" + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/restlike" + "google.golang.org/protobuf/proto" +) + +// receiver is the reception handler for a RestServer +type receiver struct { + conn connect.Connection + endpoints *restlike.Endpoints +} + +// Hear handles connect.Connection message reception for a RestServer +// Automatically responds to invalid endpoint requests +func (c receiver) Hear(item receive.Message) { + // Unmarshal the request payload + newMessage := &restlike.Message{} + err := proto.Unmarshal(item.Payload, newMessage) + if err != nil { + jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err) + return + } + + var respondErr error + if cb, err := c.endpoints.Get(restlike.URI(newMessage.GetUri()), restlike.Method(newMessage.GetMethod())); err == nil { + // Send the payload to the proper Callback if it exists and singleRespond with the result + respondErr = respond(cb(newMessage), c.conn) + } else { + // If no callback, automatically send an error response + respondErr = respond(&restlike.Message{Error: err.Error()}, c.conn) + } + if respondErr != nil { + jww.ERROR.Printf("Unable to singleRespond to request: %+v", err) + } +} + +// respond to connect.Connection with the given Message +func respond(response *restlike.Message, conn connect.Connection) error { + payload, err := proto.Marshal(response) + if err != nil { + return errors.Errorf("unable to marshal restlike response message: %+v", err) + } + + // TODO: Parameterize params + _, _, _, err = conn.SendE2E(catalog.XxMessage, payload, e2e.GetDefaultParams()) + if err != nil { + return errors.Errorf("unable to send restlike response message: %+v", err) + } + return nil +} + +// Name is used for debugging +func (c receiver) Name() string { + return "Restlike" +} diff --git a/restlike/connect/receiver_test.go b/restlike/connect/receiver_test.go new file mode 100644 index 0000000000000000000000000000000000000000..723ec019fbedf4532b4f2a3d2bf3f66be51d36b1 --- /dev/null +++ b/restlike/connect/receiver_test.go @@ -0,0 +1,21 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/restlike" + "testing" +) + +// Test failure of proto unmarshal +func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) { + ep := restlike.NewEndpoints() + r := receiver{endpoints: ep} + + r.Hear(receive.Message{Payload: []byte("test")}) +} diff --git a/restlike/connect/request.go b/restlike/connect/request.go new file mode 100644 index 0000000000000000000000000000000000000000..3d836f2ad7377365bd83b4fc6abb214b7bb88b37 --- /dev/null +++ b/restlike/connect/request.go @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/connect" + "gitlab.com/elixxir/client/e2e" + "gitlab.com/elixxir/client/restlike" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/crypto/csprng" + "google.golang.org/protobuf/proto" +) + +// Request allows for making REST-like requests to a RestServer using connect.Connection +// Can be used as stateful or declared inline without state +type Request struct { + Net connect.Connection + Rng csprng.Source + E2eGrp *cyclic.Group +} + +// Request provides several Method of sending Data to the given URI +// and blocks until the Message is returned +func (s *Request) Request(method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, e2eParams e2e.Params) (*restlike.Message, error) { + // Build the Message + newMessage := &restlike.Message{ + Content: content, + Headers: headers, + Method: uint32(method), + Uri: string(path), + } + msg, err := proto.Marshal(newMessage) + if err != nil { + return nil, err + } + + // Build callback for the response + signalChannel := make(chan *restlike.Message, 1) + cb := func(msg *restlike.Message) { + signalChannel <- msg + } + s.Net.RegisterListener(catalog.XxMessage, response{responseCallback: cb}) + + // Transmit the Message + _, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams) + if err != nil { + return nil, err + } + + // Block waiting for single-use response + jww.DEBUG.Printf("Restlike waiting for connect response from %s...", + s.Net.GetPartner().PartnerId().String()) + newResponse := <-signalChannel + jww.DEBUG.Printf("Restlike connect response received from %s", + s.Net.GetPartner().PartnerId().String()) + + return newResponse, nil +} + +// AsyncRequest provides several Method of sending Data to the given URI +// and will return the Message to the given Callback when received +func (s *Request) AsyncRequest(method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, cb restlike.RequestCallback, e2eParams e2e.Params) error { + // Build the Message + newMessage := &restlike.Message{ + Content: content, + Headers: headers, + Method: uint32(method), + Uri: string(path), + } + msg, err := proto.Marshal(newMessage) + if err != nil { + return err + } + + // Build callback for the response + s.Net.RegisterListener(catalog.XxMessage, response{responseCallback: cb}) + + // Transmit the Message + _, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams) + return err +} diff --git a/restlike/connect/response.go b/restlike/connect/response.go new file mode 100644 index 0000000000000000000000000000000000000000..99042d7c4bd4947a2f2eed560f4fef8114998d9d --- /dev/null +++ b/restlike/connect/response.go @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/restlike" + "google.golang.org/protobuf/proto" +) + +// response is the response handler for a Request +type response struct { + responseCallback restlike.RequestCallback +} + +// Hear handles for connect.Connection message responses for a Request +func (r response) Hear(item receive.Message) { + newMessage := &restlike.Message{} + + // Unmarshal the payload + err := proto.Unmarshal(item.Payload, newMessage) + if err != nil { + newMessage.Error = err.Error() + } + + // Send the response payload to the responseCallback + r.responseCallback(newMessage) +} + +// Name is used for debugging +func (r response) Name() string { + return "Restlike" +} diff --git a/restlike/connect/response_test.go b/restlike/connect/response_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f10ea9d8292d7905105edb94b6533347715a4211 --- /dev/null +++ b/restlike/connect/response_test.go @@ -0,0 +1,76 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "bytes" + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/restlike" + "google.golang.org/protobuf/proto" + "testing" + "time" +) + +// Test happy path +func TestSingleResponse_Callback(t *testing.T) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { + resultChan <- input + } + testPath := "test/path" + testMethod := restlike.Get + testMessage := &restlike.Message{ + Content: []byte("test"), + Headers: nil, + Method: uint32(testMethod), + Uri: testPath, + Error: "", + } + + r := response{cb} + + testPayload, err := proto.Marshal(testMessage) + if err != nil { + t.Errorf(err.Error()) + } + r.Hear(receive.Message{Payload: testPayload}) + + select { + case result := <-resultChan: + if result.Uri != testPath { + t.Errorf("Mismatched uri") + } + if result.Method != uint32(testMethod) { + t.Errorf("Mismatched method") + } + if !bytes.Equal(testMessage.Content, result.Content) { + t.Errorf("Mismatched content") + } + case <-time.After(3 * time.Second): + t.Errorf("Test SingleResponse timed out!") + } +} + +// Test proto error path +func TestSingleResponse_Callback_ProtoErr(t *testing.T) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { + resultChan <- input + } + r := response{cb} + + r.Hear(receive.Message{Payload: []byte("test")}) + + select { + case result := <-resultChan: + if len(result.Error) == 0 { + t.Errorf("Expected cb proto error!") + } + case <-time.After(3 * time.Second): + t.Errorf("Test SingleResponse proto error timed out!") + } +} diff --git a/restlike/connect/server.go b/restlike/connect/server.go new file mode 100644 index 0000000000000000000000000000000000000000..977edb7f212781b43f683f2c0f850d00d829344b --- /dev/null +++ b/restlike/connect/server.go @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/connect" + "gitlab.com/elixxir/client/restlike" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/primitives/id" +) + +// Server implements the RestServer interface using connect.Connection +type Server struct { + receptionId *id.ID + endpoints *restlike.Endpoints +} + +// NewServer builds a RestServer with connect.Connection and +// the provided arguments, then registers necessary external services +func NewServer(receptionId *id.ID, privKey *cyclic.Int, + rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client, p connect.Params) (*Server, error) { + newServer := &Server{ + receptionId: receptionId, + endpoints: restlike.NewEndpoints(), + } + + // Callback for connection requests + cb := func(conn connect.Connection) { + handler := receiver{endpoints: newServer.endpoints, conn: conn} + conn.RegisterListener(catalog.XxMessage, handler) + } + + // Build the connection listener + err := connect.StartServer(cb, receptionId, privKey, rng, grp, net, p) + if err != nil { + return nil, err + } + return newServer, nil +} + +// GetEndpoints returns the association of a Callback with +// a specific URI and a variety of different REST Method +func (c *Server) GetEndpoints() *restlike.Endpoints { + return c.endpoints +} + +// Close the internal RestServer endpoints and external services +func (c *Server) Close() { + // Clear all internal endpoints + c.endpoints = nil + // TODO: Destroy external services +} diff --git a/restlike/restServer.go b/restlike/restServer.go deleted file mode 100644 index dccb13ad3da83850bdd98be9b568dfab5c933102..0000000000000000000000000000000000000000 --- a/restlike/restServer.go +++ /dev/null @@ -1,67 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// - -package restlike - -import ( - "gitlab.com/elixxir/client/catalog" - "gitlab.com/elixxir/client/single" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/xx_network/primitives/id" -) - -// RestServer allows for clients to make REST-like requests this client -type RestServer interface { - // RegisterEndpoint allows the association of a Callback with - // a specific URI and a variety of different REST Method - RegisterEndpoint(path URI, method Method, cb Callback) error - - // UnregisterEndpoint removes the Callback associated with - // a specific URI and REST Method - UnregisterEndpoint(path URI, method Method) error - - // Close the internal RestServer endpoints and external services - Close() -} - -// singleServer implements the RestServer interface using single-use -type singleServer struct { - receptionId *id.ID - listener single.Listener - endpoints *Endpoints -} - -// NewSingleServer builds a RestServer with single-use and -// the provided arguments, then registers necessary external services -func NewSingleServer(receptionId *id.ID, privKey *cyclic.Int, net single.ListenCmix, e2eGrp *cyclic.Group) RestServer { - newServer := &singleServer{ - receptionId: receptionId, - endpoints: &Endpoints{endpoints: make(map[URI]map[Method]Callback)}, - } - newServer.listener = single.Listen(catalog.RestLike, receptionId, privKey, - net, e2eGrp, &singleReceiver{newServer.endpoints}) - return newServer -} - -// RegisterEndpoint allows the association of a Callback with -// a specific URI and a variety of different REST Method -func (r *singleServer) RegisterEndpoint(path URI, method Method, cb Callback) error { - return r.endpoints.Add(path, method, cb) -} - -// UnregisterEndpoint removes the Callback associated with -// a specific URI and REST Method -func (r *singleServer) UnregisterEndpoint(path URI, method Method) error { - return r.endpoints.Remove(path, method) -} - -// Close the internal RestServer endpoints and external services -func (r *singleServer) Close() { - // Clear all internal endpoints - r.endpoints = nil - // Destroy external services - r.listener.Stop() -} diff --git a/restlike/singleReceiver.go b/restlike/single/receiver.go similarity index 63% rename from restlike/singleReceiver.go rename to restlike/single/receiver.go index 7a137631bea709c8b9d7dabbb0888fec60a60cdc..f82a1f327ee9b7215aa20748da044bdc63f464c9 100644 --- a/restlike/singleReceiver.go +++ b/restlike/single/receiver.go @@ -4,7 +4,7 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "github.com/pkg/errors" @@ -12,21 +12,22 @@ import ( "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/restlike" "gitlab.com/elixxir/client/single" "google.golang.org/protobuf/proto" "time" ) -// processor is the reception handler for a RestServer -type singleReceiver struct { - endpoints *Endpoints +// receiver is the reception handler for a RestServer +type receiver struct { + endpoints *restlike.Endpoints } // Callback is the handler for single-use message reception for a RestServer // Automatically responds to invalid endpoint requests -func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.EphemeralIdentity, rounds []rounds.Round) { +func (s *receiver) Callback(req *single.Request, receptionId receptionID.EphemeralIdentity, rounds []rounds.Round) { // Unmarshal the request payload - newMessage := &Message{} + newMessage := &restlike.Message{} err := proto.Unmarshal(req.GetPayload(), newMessage) if err != nil { jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err) @@ -34,20 +35,20 @@ func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.E } var respondErr error - if cb, err := s.endpoints.Get(URI(newMessage.GetUri()), Method(newMessage.GetMethod())); err == nil { - // Send the payload to the proper Callback if it exists and respond with the result - respondErr = respond(cb(newMessage), req) + if cb, err := s.endpoints.Get(restlike.URI(newMessage.GetUri()), restlike.Method(newMessage.GetMethod())); err == nil { + // Send the payload to the proper Callback if it exists and singleRespond with the result + respondErr = singleRespond(cb(newMessage), req) } else { // If no callback, automatically send an error response - respondErr = respond(&Message{Error: err.Error()}, req) + respondErr = singleRespond(&restlike.Message{Error: err.Error()}, req) } if respondErr != nil { - jww.ERROR.Printf("Unable to respond to request: %+v", err) + jww.ERROR.Printf("Unable to singleRespond to request: %+v", err) } } -// respond to a single.Request with the given Message -func respond(response *Message, req *single.Request) error { +// singleRespond to a single.Request with the given Message +func singleRespond(response *restlike.Message, req *single.Request) error { payload, err := proto.Marshal(response) if err != nil { return errors.Errorf("unable to marshal restlike response message: %+v", err) diff --git a/restlike/singleReceiver_test.go b/restlike/single/receiver_test.go similarity index 87% rename from restlike/singleReceiver_test.go rename to restlike/single/receiver_test.go index 56a46586daaf6411d589b79c0e179ade2ce4e017..6922991fdb686a7350095fe52462699361953e8c 100644 --- a/restlike/singleReceiver_test.go +++ b/restlike/single/receiver_test.go @@ -4,21 +4,22 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/restlike" "gitlab.com/elixxir/client/single" "testing" ) // Test failure of proto unmarshal func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) { - ep := &Endpoints{endpoints: make(map[URI]map[Method]Callback)} - receiver := singleReceiver{endpoints: ep} + ep := restlike.NewEndpoints() + r := receiver{endpoints: ep} testReq := single.BuildTestRequest(make([]byte, 0), t) - receiver.Callback(testReq, receptionID.EphemeralIdentity{}, nil) + r.Callback(testReq, receptionID.EphemeralIdentity{}, nil) } // Test happy path @@ -43,7 +44,7 @@ func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) { // if err != nil { // t.Errorf(err.Error()) // } -// receiver := singleReceiver{endpoints: ep} +// receiver := receiver{endpoints: ep} // // testPayload, err := proto.Marshal(testMessage) // if err != nil { diff --git a/restlike/singleRequest.go b/restlike/single/request.go similarity index 69% rename from restlike/singleRequest.go rename to restlike/single/request.go index 774f112c43745c0a36b9692ceb209bd4e8b783f1..8a0b550867b5d2ebdaa43333fcf529940c701982 100644 --- a/restlike/singleRequest.go +++ b/restlike/single/request.go @@ -4,11 +4,12 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/restlike" "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" @@ -16,9 +17,9 @@ import ( "google.golang.org/protobuf/proto" ) -// SingleRequest allows for making REST-like requests to a RestServer using single-use messages +// Request allows for making REST-like requests to a RestServer using single-use messages // Can be used as stateful or declared inline without state -type SingleRequest struct { +type Request struct { Net single.Cmix Rng csprng.Source E2eGrp *cyclic.Group @@ -26,10 +27,10 @@ type SingleRequest struct { // Request provides several Method of sending Data to the given URI // and blocks until the Message is returned -func (s *SingleRequest) Request(recipient contact.Contact, method Method, path URI, - content Data, headers *Headers, singleParams single.RequestParams) (*Message, error) { +func (s *Request) Request(recipient contact.Contact, method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, singleParams single.RequestParams) (*restlike.Message, error) { // Build the Message - newMessage := &Message{ + newMessage := &restlike.Message{ Content: content, Headers: headers, Method: uint32(method), @@ -41,14 +42,14 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U } // Build callback for the single-use response - signalChannel := make(chan *Message, 1) - cb := func(msg *Message) { + signalChannel := make(chan *restlike.Message, 1) + cb := func(msg *restlike.Message) { signalChannel <- msg } // Transmit the Message _, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, - &singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) + &response{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) if err != nil { return nil, err } @@ -63,10 +64,10 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U // AsyncRequest provides several Method of sending Data to the given URI // and will return the Message to the given Callback when received -func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, path URI, - content Data, headers *Headers, cb RequestCallback, singleParams single.RequestParams) error { +func (s *Request) AsyncRequest(recipient contact.Contact, method restlike.Method, path restlike.URI, + content restlike.Data, headers *restlike.Headers, cb restlike.RequestCallback, singleParams single.RequestParams) error { // Build the Message - newMessage := &Message{ + newMessage := &restlike.Message{ Content: content, Headers: headers, Method: uint32(method), @@ -79,6 +80,6 @@ func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, p // Transmit the Message _, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg, - &singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) + &response{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp) return err } diff --git a/restlike/singleResponse.go b/restlike/single/response.go similarity index 74% rename from restlike/singleResponse.go rename to restlike/single/response.go index 41e21b6061c110eaaa06fc25a472bf0326ca1ff3..edc4833226d0372ae6816f4a190772127aaf6e7e 100644 --- a/restlike/singleResponse.go +++ b/restlike/single/response.go @@ -4,22 +4,23 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/restlike" "google.golang.org/protobuf/proto" ) -// processor is the response handler for a Request -type singleResponse struct { - responseCallback RequestCallback +// response is the response handler for a Request +type response struct { + responseCallback restlike.RequestCallback } // Callback is the handler for single-use message responses for a Request -func (s *singleResponse) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) { - newMessage := &Message{} +func (s *response) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) { + newMessage := &restlike.Message{} // Handle response errors if err != nil { diff --git a/restlike/singleResponse_test.go b/restlike/single/response_test.go similarity index 74% rename from restlike/singleResponse_test.go rename to restlike/single/response_test.go index 0381b452cca26c9d892abfe2b8329569a1eef689..b5e11bf81acca0a91cd9342390b7e712125a4518 100644 --- a/restlike/singleResponse_test.go +++ b/restlike/single/response_test.go @@ -4,12 +4,13 @@ // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// -package restlike +package single import ( "bytes" "github.com/pkg/errors" "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/restlike" "google.golang.org/protobuf/proto" "testing" "time" @@ -17,13 +18,13 @@ import ( // Test happy path func TestSingleResponse_Callback(t *testing.T) { - resultChan := make(chan *Message, 1) - cb := func(input *Message) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { resultChan <- input } testPath := "test/path" - testMethod := Get - testMessage := &Message{ + testMethod := restlike.Get + testMessage := &restlike.Message{ Content: []byte("test"), Headers: nil, Method: uint32(testMethod), @@ -31,13 +32,13 @@ func TestSingleResponse_Callback(t *testing.T) { Error: "", } - response := singleResponse{cb} + r := response{cb} testPayload, err := proto.Marshal(testMessage) if err != nil { t.Errorf(err.Error()) } - response.Callback(testPayload, receptionID.EphemeralIdentity{}, nil, nil) + r.Callback(testPayload, receptionID.EphemeralIdentity{}, nil, nil) select { case result := <-resultChan: @@ -57,13 +58,13 @@ func TestSingleResponse_Callback(t *testing.T) { // Test error input path func TestSingleResponse_Callback_Err(t *testing.T) { - resultChan := make(chan *Message, 1) - cb := func(input *Message) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { resultChan <- input } - response := singleResponse{cb} + r := response{cb} - response.Callback(nil, receptionID.EphemeralIdentity{}, nil, errors.New("test")) + r.Callback(nil, receptionID.EphemeralIdentity{}, nil, errors.New("test")) select { case result := <-resultChan: @@ -77,13 +78,13 @@ func TestSingleResponse_Callback_Err(t *testing.T) { // Test proto error path func TestSingleResponse_Callback_ProtoErr(t *testing.T) { - resultChan := make(chan *Message, 1) - cb := func(input *Message) { + resultChan := make(chan *restlike.Message, 1) + cb := func(input *restlike.Message) { resultChan <- input } - response := singleResponse{cb} + r := response{cb} - response.Callback([]byte("test"), receptionID.EphemeralIdentity{}, nil, nil) + r.Callback([]byte("test"), receptionID.EphemeralIdentity{}, nil, nil) select { case result := <-resultChan: diff --git a/restlike/single/server.go b/restlike/single/server.go new file mode 100644 index 0000000000000000000000000000000000000000..b5bbbd4ee8dc14f58e4dddbfdc356c6b33582d5c --- /dev/null +++ b/restlike/single/server.go @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package single + +import ( + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/restlike" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/primitives/id" +) + +// Server implements the RestServer interface using single-use +type Server struct { + receptionId *id.ID + listener single.Listener + endpoints *restlike.Endpoints +} + +// NewServer builds a RestServer with single-use and +// the provided arguments, then registers necessary external services +func NewServer(receptionId *id.ID, privKey *cyclic.Int, net single.ListenCmix, e2eGrp *cyclic.Group) *Server { + newServer := &Server{ + receptionId: receptionId, + endpoints: restlike.NewEndpoints(), + } + newServer.listener = single.Listen(catalog.RestLike, receptionId, privKey, + net, e2eGrp, &receiver{newServer.endpoints}) + return newServer +} + +// GetEndpoints returns the association of a Callback with +// a specific URI and a variety of different REST Method +func (r *Server) GetEndpoints() *restlike.Endpoints { + return r.endpoints +} + +// Close the internal RestServer endpoints and external services +func (r *Server) Close() { + // Clear all internal endpoints + r.endpoints = nil + // Destroy external services + r.listener.Stop() +} diff --git a/restlike/types.go b/restlike/types.go index 247943844613a43c99b5d666f412a5c53f078471..8937575ed720bb14634bf34e6d02908988a28b13 100644 --- a/restlike/types.go +++ b/restlike/types.go @@ -68,6 +68,11 @@ type Endpoints struct { sync.RWMutex } +// NewEndpoints returns a new Endpoints object +func NewEndpoints() *Endpoints { + return &Endpoints{endpoints: make(map[URI]map[Method]Callback)} +} + // Add a new Endpoint // Returns an error if Endpoint already exists func (e *Endpoints) Add(path URI, method Method, cb Callback) error {