Skip to content
Snippets Groups Projects
Commit b9d73b83 authored by Josh Brooks's avatar Josh Brooks
Browse files

Add logic for authenticated connections

parent 6ec3fcdd
No related branches found
No related tags found
3 merge requests!510Release,!216Xx 3895/authenticated connection,!207WIP: Client Restructure
......@@ -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
// IdentityAuthenticationRequest is sent by the recipient
// of an authenticated connection request
// (see the connections/ package)
ConnectionAuthenticationRequest = 60
)
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package authenticated
import (
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/auth"
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/cmix"
"gitlab.com/elixxir/client/connections/connect"
clientE2e "gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/client/e2e/ratchet/partner"
"gitlab.com/elixxir/client/storage/versioned"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/elixxir/ekv"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/primitives/id"
"sync"
"time"
)
// Connection is a connect.Connection interface that
// has the receiver authenticating their identity back to the
// initiator.
type Connection interface {
// Connection is the base connections API. This allows
// sending and listening to the partner
connect.Connection
// IsAuthenticated is a function which returns whether the
// authenticated connection has been completely established.
IsAuthenticated() bool
}
// ConnectionCallback is the callback format required to retrieve
// new authenticated.Connection objects as they are established.
type ConnectionCallback func(connection Connection)
// ConnectWithAuthentication is called by the client, ie the initiator
// of an authenticated.Connection. This will send a request for an
// authenticated connection to the server.
func ConnectWithAuthentication(recipient contact.Contact,
myId *id.ID, dhPrivKey *cyclic.Int,
rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client,
p connect.Params) (Connection, error) {
// Build an ephemeral KV
kv := versioned.NewKV(ekv.MakeMemstore())
// Build E2e handler
err := clientE2e.Init(kv, myId, dhPrivKey, grp, p.Rekey)
if err != nil {
return nil, err
}
e2eHandler, err := clientE2e.Load(kv, net, myId, grp, rng, p.Event)
if err != nil {
return nil, err
}
// Build the callback for the connection being established
authConnChan := make(chan Connection, 1)
cb := func(authConn Connection) {
authConnChan <- authConn
}
// Build callback for E2E negotiation in the auth package
clientHandler := getClient(cb, e2eHandler, p)
// Register a listener for the server's response
e2eHandler.RegisterListener(recipient.ID,
catalog.ConnectionAuthenticationRequest,
clientHandler)
// Build auth object for E2E negotiation
authState, err := auth.NewState(kv, net, e2eHandler,
rng, p.Event, p.Auth, clientHandler, nil)
if err != nil {
return nil, err
}
// Perform the auth request
_, err = authState.Request(recipient, nil)
if err != nil {
return nil, err
}
// Block waiting for auth to confirm
jww.DEBUG.Printf("Connection waiting for authenticated "+
"connection with %s to be established...", recipient.ID.String())
timeout := time.NewTimer(p.Timeout)
select {
case newConnection := <-authConnChan:
if newConnection == nil {
return nil, errors.Errorf(
"Unable to complete authenticated connection with partner %s",
recipient.ID.String())
}
return newConnection, nil
case <-timeout.C:
return nil, errors.Errorf("Authenticated connection with "+
"partner %s timed out", recipient.ID.String())
}
}
// StartAuthenticatedConnectionServer is Called by the receiver of an
// authenticated connection request. Calling this indicated that they will
// recognize and respond to identity authentication requests by
// a client.
func StartAuthenticatedConnectionServer(cb ConnectionCallback,
myId *id.ID, salt []byte, rsaPrivkey *rsa.PrivateKey, privKey *cyclic.Int,
rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client,
p connect.Params) error {
// Build an ephemeral KV
kv := versioned.NewKV(ekv.MakeMemstore())
// Build E2e handler
err := clientE2e.Init(kv, myId, privKey, grp, p.Rekey)
if err != nil {
return err
}
e2eHandler, err := clientE2e.Load(kv, net, myId, grp, rng, p.Event)
if err != nil {
return err
}
// Build callback for E2E negotiation
callback := getServer(cb, e2eHandler, net, rsaPrivkey, rng, p)
// Build auth object for E2E negotiation
_, err = auth.NewState(kv, net, e2eHandler,
rng, p.Event, p.Auth, callback, nil)
return err
}
// handler provides an implementation for the authenticated.Connection
// interface.
type handler struct {
connect.Connection
isAuthenticated bool
authMux sync.Mutex
}
// buildAuthenticatedConnection assembles an authenticated.Connection object.
// This is called by the connection server once it has sent an
// IdentityAuthentication to the client.
// This is called by the client when they have received and confirmed the data within
// a IdentityAuthentication message.
func buildAuthenticatedConnection(partner partner.Manager,
e2eHandler clientE2e.Handler,
p connect.Params) *handler {
return &handler{
Connection: connect.BuildConnection(partner, e2eHandler, p),
}
}
// IsAuthenticated returns whether the Connection has completed the authentication
// process.
func (h *handler) IsAuthenticated() bool {
return h.isAuthenticated
}
// setAuthenticated is a helper function which sets the Connection as authenticated.
func (h *handler) setAuthenticated() {
h.authMux.Lock()
defer h.authMux.Unlock()
h.isAuthenticated = true
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: connections/authenticated/authenticated.proto
package authenticated // import "gitlab.com/elixxir/client/connections/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"`
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_3bb97abbbfff0896, []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 init() {
proto.RegisterType((*IdentityAuthentication)(nil), "authenticatedConnectionMessages.IdentityAuthentication")
}
func init() {
proto.RegisterFile("connections/authenticated/authenticated.proto", fileDescriptor_authenticated_3bb97abbbfff0896)
}
var fileDescriptor_authenticated_3bb97abbbfff0896 = []byte{
// 166 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, 0x85, 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, 0x93, 0x69, 0x94, 0x71, 0x7a, 0x66, 0x49, 0x4e, 0x62, 0x92, 0x5e, 0x72, 0x7e,
0xae, 0x7e, 0x6a, 0x4e, 0x66, 0x45, 0x45, 0x66, 0x91, 0x7e, 0x72, 0x4e, 0x66, 0x6a, 0x5e, 0x89,
0x3e, 0x4e, 0x1f, 0x24, 0xb1, 0x81, 0x1d, 0x6d, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x7c, 0xda,
0xe4, 0xd4, 0xe5, 0x00, 0x00, 0x00,
}
///////////////////////////////////////////////////////////////////////////////
// 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/connections/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
}
package authenticated
import (
"github.com/golang/protobuf/proto"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/auth"
"gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/cmix/rounds"
"gitlab.com/elixxir/client/connections/connect"
clientE2e "gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/xx_network/crypto/signature/rsa"
)
// clientListenerName is the name of the client's listener interface.
const clientListenerName = "AuthenticatedClientListener"
// client is an interface that collates the receive.Listener and
// auth.Callbacks interfaces. This allows a singular object
// to handle auth package endpoints as well as handling the
// custom catalog.MessageType's.
type client interface {
receive.Listener
auth.Callbacks
}
// clientHandler provides an implementation of the client interface.
type clientHandler struct {
// connectionCallback allows an authenticated.Connection
// to be passed back upon establishment.
connectionCallback ConnectionCallback
// Used for building new Connection objects
connectionE2e clientE2e.Handler
connectionParams connect.Params
}
// getClient returns a client interface to be used to handle
// auth.Callbacks and receive.Listener operations.
func getClient(cb ConnectionCallback,
e2e clientE2e.Handler, p connect.Params) client {
return clientHandler{
connectionCallback: cb,
connectionE2e: e2e,
connectionParams: p,
}
}
// Request will be called when an auth Request message is processed.
func (a clientHandler) Request(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
}
// Confirm will be called when an auth Confirm message is processed.
func (a clientHandler) Confirm(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
}
// Reset will be called when an auth Reset operation occurs.
func (a clientHandler) Reset(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
}
// Hear handles the reception of an IdentityAuthentication by the
// server.
func (a clientHandler) Hear(item receive.Message) {
// Process the message data into a protobuf
iar := &IdentityAuthentication{}
err := proto.Unmarshal(item.Payload, iar)
if err != nil {
jww.ERROR.Printf("Unable to build connection with "+
"partner %s: %+v", item.Sender, err)
// Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil)
return
}
// Process the PEM encoded public key to an rsa.PublicKey object
partnerPubKey, err := rsa.LoadPublicKeyFromPem(iar.RsaPubKey)
if err != nil {
jww.ERROR.Printf("Unable to build connection with "+
"partner %s: %+v", item.Sender, err)
// Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil)
return
}
// Get the new partner
newPartner, err := a.connectionE2e.GetPartner(item.Sender)
if err != nil {
jww.ERROR.Printf("Unable to build connection with "+
"partner %s: %+v", item.Sender, err)
// Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil)
return
}
// The connection fingerprint (hashed) represents a shared nonce
// between these two partners
conneptionFp := newPartner.ConnectionFingerprint().Bytes()
// Hash the connection fingerprint
opts := rsa.NewDefaultOptions()
h := opts.Hash.New()
h.Write(conneptionFp)
nonce := h.Sum(nil)
// Verify the signature
err = rsa.Verify(partnerPubKey, opts.Hash, nonce, iar.Signature, opts)
if err != nil {
jww.ERROR.Printf("Unable to build connection with "+
"partner %s: %+v", item.Sender, err)
// Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil)
}
// If successful, pass along the established connection
jww.DEBUG.Printf("Connection auth request for %s confirmed",
item.Sender.String())
a.connectionCallback(buildAuthenticatedConnection(newPartner, a.connectionE2e,
a.connectionParams))
}
func (a clientHandler) Name() string {
return clientListenerName
}
#!/bin/bash
protoc --go_out=paths=source_relative:. connections/authenticated/authenticated.proto
package authenticated
import (
"github.com/golang/protobuf/proto"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/cmix"
"gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/cmix/rounds"
"gitlab.com/elixxir/client/connections/connect"
clientE2e "gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/primitives/id"
)
// serverHandler handles the io operations of an
// authenticated.Connection server.
type serverHandler struct {
// connectionCallback allows an authenticated.Connection
// to be passed back upon establishment.
connectionCallback ConnectionCallback
// Used for building new Connection objects
connectionE2e clientE2e.Handler
connectionParams connect.Params
// Used for tracking the round the identity confirmation message was sent.
// A successful round assumes the client received the confirmation and
// an authenticated.Connection has been established
services cmix.Client
// Used for signing the connection fingerprint used as a nonce
// to confirm the user's identity
privateKey *rsa.PrivateKey
rng *fastRNG.StreamGenerator
}
// getServer returns a serverHandler object. This is used to pass
// into the auth.State object to handle the auth.Callbacks.
func getServer(cb ConnectionCallback,
e2e clientE2e.Handler, services cmix.Client,
privateKey *rsa.PrivateKey,
rng *fastRNG.StreamGenerator, p connect.Params) serverHandler {
return serverHandler{
connectionCallback: cb,
connectionE2e: e2e,
connectionParams: p,
privateKey: privateKey,
rng: rng,
services: services,
}
}
// Request will be called when an auth Request message is processed.
func (a serverHandler) Request(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
// 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)
// Send a nil connection to avoid hold-ups down the line
a.connectionCallback(nil)
return
}
authConn := buildAuthenticatedConnection(newPartner, a.connectionE2e,
a.connectionParams)
// The connection fingerprint (hashed) represents a shared nonce
// between these two partners
connectionFp := newPartner.ConnectionFingerprint().Bytes()
opts := rsa.NewDefaultOptions()
h := opts.Hash.New()
h.Write(connectionFp)
nonce := h.Sum(nil)
// Sign the connection fingerprint
stream := a.rng.GetStream()
defer stream.Close()
signature, err := rsa.Sign(stream, a.privateKey,
opts.Hash, nonce, opts)
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)
}
// Construct message
pemEncodedRsaPubKey := rsa.CreatePublicKeyPem(a.privateKey.GetPublic())
iar := &IdentityAuthentication{
Signature: signature,
RsaPubKey: pemEncodedRsaPubKey,
}
payload, err := proto.Marshal(iar)
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)
}
// Send message to user
rids, _, _, err := authConn.SendE2E(catalog.ConnectionAuthenticationRequest,
payload, clientE2e.GetDefaultParams())
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)
}
// Determine that the message is properly sent by tracking the success
// of the round(s)
roundCb := cmix.RoundEventCallback(func(allRoundsSucceeded,
timedOut bool, rounds map[id.Round]cmix.RoundResult) {
if allRoundsSucceeded {
// If rounds succeeded, assume recipient has successfully
// confirmed the authentication
authConn.setAuthenticated()
a.connectionCallback(authConn)
} else {
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)
}
})
err = a.services.GetRoundResults(a.connectionParams.Timeout,
roundCb, rids...)
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)
}
}
// Confirm will be called when an auth Confirm message is processed.
func (a serverHandler) Confirm(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
}
// Reset will be called when an auth Reset operation occurs.
func (a serverHandler) Reset(requestor contact.Contact,
receptionID receptionID.EphemeralIdentity, round rounds.Round) {
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment