Select Git revision
utilsInterfaces_test.go
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()
}