Skip to content
Snippets Groups Projects
Select Git revision
  • bfca8d844e5c36fe09712b3bea609e2a12b13644
  • main default protected
  • dev protected
  • hotfixes-oct-2022
  • refactor/avatar-cell
  • 1.1.5
  • 1.1.4
  • 1.1.3
  • 1.1
  • 1.0.8
  • 1.0.7
  • 1.0.6
12 results

ChatInputReducer.swift

Blame
  • request.go 8.59 KiB
    ////////////////////////////////////////////////////////////////////////////////
    // Copyright © 2022 xx foundation                                             //
    //                                                                            //
    // Use of this source code is governed by a license that can be found in the  //
    // LICENSE file.                                                              //
    ////////////////////////////////////////////////////////////////////////////////
    
    package auth
    
    import (
    	"fmt"
    	"io"
    	"strings"
    
    	"github.com/cloudflare/circl/dh/sidh"
    	"github.com/pkg/errors"
    	jww "github.com/spf13/jwalterweatherman"
    	"gitlab.com/elixxir/client/cmix"
    	"gitlab.com/elixxir/client/cmix/message"
    	"gitlab.com/elixxir/client/e2e"
    	"gitlab.com/elixxir/client/e2e/ratchet"
    	util "gitlab.com/elixxir/client/storage/utility"
    	"gitlab.com/elixxir/crypto/contact"
    	"gitlab.com/elixxir/crypto/cyclic"
    	"gitlab.com/elixxir/crypto/diffieHellman"
    	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
    	"gitlab.com/elixxir/primitives/fact"
    	"gitlab.com/elixxir/primitives/format"
    	"gitlab.com/xx_network/primitives/id"
    )
    
    const terminator = ";"
    
    // Error constant strings. Any changes to these should go over usages of the
    // affected messages in other applications (if applicable)
    const (
    	// ErrChannelExists is a message returned in state.Request when an
    	// authenticated channel exists between the partner and me.
    	ErrChannelExists = "Authenticated channel already established with partner"
    )
    
    // Request sends a contact request from the user identity in the imported e2e
    // structure to the passed contact, as well as the passed facts (will error if
    // they are too long).
    // The other party must accept the request by calling Confirm in order to be
    // able to send messages using e2e.Handler.SendE2e. When the other party does so,
    // the "confirm" callback will get called.
    // The round the request is initially sent on will be returned, but the request
    // will be listed as a critical message, so the underlying cmix client will
    // auto resend it in the event of failure.
    // A request cannot be sent for a contact who has already received a request or
    // who is already a partner.
    func (s *state) Request(partner contact.Contact, myfacts fact.FactList) (id.Round, error) {
    	// check that an authenticated channel does not already exist
    	if _, err := s.e2e.GetPartner(partner.ID); err == nil ||
    		!strings.Contains(err.Error(), ratchet.NoPartnerErrorStr) {
    		return 0, errors.Errorf(ErrChannelExists)
    	}
    
    	return s.request(partner, myfacts, false)
    }
    
    // request internal helper
    func (s *state) request(partner contact.Contact, myfacts fact.FactList,
    	reset bool) (id.Round, error) {
    
    	jww.INFO.Printf("request(...) called")
    
    	//do key generation
    	rng := s.rng.GetStream()
    	defer rng.Close()
    
    	me := s.e2e.GetReceptionID()
    
    	dhGrp := s.e2e.GetGroup()
    
    	dhPriv, dhPub := genDHKeys(dhGrp, rng)
    	sidhPriv, sidhPub := util.GenerateSIDHKeyPair(
    		sidh.KeyVariantSidhA, rng)
    
    	historicalDHPriv := s.e2e.GetHistoricalDHPrivkey()
    	historicalDHPub := diffieHellman.GeneratePublicKey(historicalDHPriv,
    		dhGrp)
    
    	if !dhGrp.Inside(partner.DhPubKey.GetLargeInt()) {
    		return 0, errors.Errorf("partner's DH public key is not in the E2E "+
    			"group; E2E group fingerprint is %d and DH key has %d",
    			dhGrp.GetFingerprint(), partner.DhPubKey.GetGroupFingerprint())
    	}
    	ownership := cAuth.MakeOwnershipProof(historicalDHPriv,
    		partner.DhPubKey, dhGrp)
    	confirmFp := cAuth.MakeOwnershipProofFP(ownership)
    
    	// Add the sent request and use the return to build the
    	// send. This will replace the send with an old one if one was
    	// in process, wasting the key generation above. This is
    	// considered a reasonable loss due to the increase in code
    	// simplicity of this approach
    	sr, err := s.store.AddSent(partner.ID, partner.DhPubKey, dhPriv, dhPub,
    		sidhPriv, sidhPub, confirmFp, reset)
    	if err != nil {
    		if sr == nil {
    			return 0, err
    		} else {
    			jww.INFO.Printf("Resending request to %s from %s as "+
    				"one was already sent", partner.ID, me)
    			dhPriv = sr.GetMyPrivKey()
    			dhPub = sr.GetMyPubKey()
    			//sidhPriv = sr.GetMySIDHPrivKey()
    			sidhPub = sr.GetMySIDHPubKey()
    		}
    	}
    
    	// cMix fingerprint. Used in old versions by the recipient can recognize
    	// this is a request message. Unchanged for backwards compatability
    	// (the SIH is used now)
    	requestfp := cAuth.MakeRequestFingerprint(partner.DhPubKey)
    
    	// My fact data so we can display in the interface.
    	msgPayload := []byte(myfacts.Stringify() + terminator)
    
    	// Create the request packet.
    	request, mac, err := createRequestAuth(me, msgPayload, ownership,
    		dhPriv, dhPub, partner.DhPubKey, sidhPub,
    		s.e2e.GetGroup(), s.net.GetMaxMessageLength())
    	if err != nil {
    		return 0, err
    	}
    	contents := request.Marshal()
    
    	jww.TRACE.Printf("AuthRequest MYPUBKEY: %v", dhPub.TextVerbose(16, 0))
    	jww.TRACE.Printf("AuthRequest PARTNERPUBKEY: %v",
    		partner.DhPubKey.TextVerbose(16, 0))
    	jww.TRACE.Printf("AuthRequest MYSIDHPUBKEY: %s",
    		util.StringSIDHPubKey(sidhPub))
    
    	jww.TRACE.Printf("AuthRequest HistoricalPUBKEY: %v",
    		historicalDHPub.TextVerbose(16, 0))
    
    	jww.TRACE.Printf("AuthRequest ECRPAYLOAD: %v", request.GetEcrPayload())
    	jww.TRACE.Printf("AuthRequest MAC: %v", mac)
    
    	jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s, confirmFp: %s",
    		partner.ID, format.DigestContents(contents), confirmFp)
    
    	p := cmix.GetDefaultCMIXParams()
    	p.DebugTag = "auth.Request"
    	tag := s.params.RequestTag
    	if reset {
    		tag = s.params.ResetRequestTag
    	}
    	svc := message.Service{
    		Identifier: partner.ID.Marshal(),
    		Tag:        tag,
    		Metadata:   nil,
    	}
    	round, _, err := s.net.Send(partner.ID, requestfp, svc, contents, mac, p)
    	if err != nil {
    		// if the send fails just set it to failed, it will
    		// but automatically retried
    		return 0, errors.WithMessagef(err, "Auth Request with %s "+
    			"(msgDigest: %s) failed to transmit: %+v", partner.ID,
    			format.DigestContents(contents), err)
    	}
    
    	em := fmt.Sprintf("Auth Request with %s (msgDigest: %s) sent"+
    		" on round %d", partner.ID, format.DigestContents(contents), round.ID)
    	jww.INFO.Print(em)
    	s.event.Report(1, "Auth", "RequestSent", em)
    	return round.ID, nil
    
    }
    
    // genDHKeys is a short helper to generate a Diffie-Helman Keypair
    func genDHKeys(dhGrp *cyclic.Group, csprng io.Reader) (priv, pub *cyclic.Int) {
    	numBytes := len(dhGrp.GetPBytes())
    	newPrivKey := diffieHellman.GeneratePrivateKey(numBytes, dhGrp, csprng)
    	newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, dhGrp)
    	return newPrivKey, newPubKey
    }
    
    // createRequestAuth Creates the request packet, including encrypting the
    // required parts of it.
    func createRequestAuth(sender *id.ID, payload, ownership []byte, myDHPriv,
    	myDHPub, theirDHPub *cyclic.Int, mySIDHPub *sidh.PublicKey,
    	dhGrp *cyclic.Group, cMixSize int) (*baseFormat, []byte, error) {
    	/*generate embedded message structures and check payload*/
    	dhPrimeSize := dhGrp.GetP().ByteLen()
    
    	// FIXME: This base -> ecr -> request structure is a little wonky.
    	// We should refactor so that is is more direct.
    	// I recommend we move to a request object that takes:
    	//   sender, dhPub, sidhPub, ownershipProof, payload
    	// with a Marshal/Unmarshal that takes the Dh/grp needed to gen
    	// the session key and encrypt or decrypt.
    
    	// baseFmt wraps ecrFmt. ecrFmt is encrypted
    	baseFmt := newBaseFormat(cMixSize, dhPrimeSize)
    	// ecrFmt wraps requestFmt
    	ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
    	requestFmt, err := newRequestFormat(ecrFmt)
    	if err != nil {
    		return nil, nil, errors.Errorf("failed to make request format: %+v", err)
    	}
    
    	if len(payload) > requestFmt.MsgPayloadLen() {
    		return nil, nil, errors.Errorf(
    			"Combined message longer than space "+
    				"available in payload; available: %v, length: %v",
    			requestFmt.MsgPayloadLen(), len(payload))
    	}
    
    	/*encrypt payload*/
    	requestFmt.SetID(sender)
    	requestFmt.SetMsgPayload(payload)
    	ecrFmt.SetOwnership(ownership)
    	ecrFmt.SetSidHPubKey(mySIDHPub)
    	ecrPayload, mac := cAuth.Encrypt(myDHPriv, theirDHPub, ecrFmt.data,
    		dhGrp)
    	/*construct message*/
    	baseFmt.SetEcrPayload(ecrPayload)
    	baseFmt.SetPubKey(myDHPub)
    
    	return &baseFmt, mac, nil
    }
    
    func (s *state) GetReceivedRequest(partner *id.ID) (contact.Contact, error) {
    	return s.store.GetReceivedRequest(partner)
    }
    
    func (s *state) VerifyOwnership(received, verified contact.Contact,
    	e2e e2e.Handler) bool {
    	return VerifyOwnership(received, verified, e2e)
    }
    
    func (s *state) DeleteRequest(partnerID *id.ID) error {
    	return s.store.DeleteRequest(partnerID)
    }
    
    func (s *state) DeleteAllRequests() error {
    	return s.store.DeleteAllRequests()
    }
    
    func (s *state) DeleteSentRequests() error {
    	return s.store.DeleteSentRequests()
    }
    
    func (s *state) DeleteReceiveRequests() error {
    	return s.store.DeleteReceiveRequests()
    }