diff --git a/cmix/nodes/interfaces.go b/cmix/nodes/interfaces.go new file mode 100644 index 0000000000000000000000000000000000000000..cced1ecd17772741bf647cdde9c41fe901c8f522 --- /dev/null +++ b/cmix/nodes/interfaces.go @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package nodes + +import ( + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "time" +) + +// Session is a sub-interface of the storage.Session interface relevant to +// the methods used in this package. +type Session interface { + GetTransmissionID() *id.ID + IsPrecanned() bool + GetTransmissionRSA() *rsa.PrivateKey + GetRegistrationTimestamp() time.Time + GetTransmissionSalt() []byte + GetTransmissionRegistrationValidationSignature() []byte +} diff --git a/cmix/nodes/register.go b/cmix/nodes/register.go index af18976e4402845ea513aae0e8ae400f6683bbb5..9a3c3854481e9b48ec200657db88a9672a267fa6 100644 --- a/cmix/nodes/register.go +++ b/cmix/nodes/register.go @@ -10,31 +10,19 @@ package nodes import ( "crypto/sha256" "encoding/hex" + "gitlab.com/xx_network/crypto/csprng" "strconv" "sync" "time" - "github.com/golang/protobuf/proto" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix/gateway" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" - pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/comms/network" "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/diffieHellman" - "gitlab.com/elixxir/crypto/hash" - "gitlab.com/elixxir/crypto/registration" - "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/comms/messages" - "gitlab.com/xx_network/crypto/chacha" - "gitlab.com/xx_network/crypto/csprng" - "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/crypto/tls" - "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/ndf" - "gitlab.com/xx_network/primitives/netTime" ) func registerNodes(r *registrar, s storage.Session, stop *stoppable.Single, @@ -64,7 +52,8 @@ func registerNodes(r *registrar, s storage.Session, stop *stoppable.Single, "Not registering node %s, already registered", nid) } - if _, operating := inProgress.LoadOrStore(nidStr, struct{}{}); operating { + if _, operating := inProgress.LoadOrStore(nidStr, + struct{}{}); operating { continue } @@ -105,8 +94,8 @@ func registerNodes(r *registrar, s storage.Session, stop *stoppable.Single, // registerWithNode serves as a helper for registerNodes. It registers a user // with a specific in the client's NDF. func registerWithNode(sender gateway.Sender, comms RegisterNodeCommsInterface, - ngw network.NodeGateway, s storage.Session, r *registrar, rng csprng.Source, - stop *stoppable.Single) error { + ngw network.NodeGateway, s Session, r *registrar, + rng csprng.Source, stop *stoppable.Single) error { nodeID, err := ngw.Node.GetNodeId() if err != nil { @@ -118,7 +107,8 @@ func registerWithNode(sender gateway.Sender, comms RegisterNodeCommsInterface, return nil } - jww.INFO.Printf("registerWithNode begin registration with node: %s", nodeID) + jww.INFO.Printf("registerWithNode begin registration with node: %s", + nodeID) var transmissionKey *cyclic.Int var validUntil uint64 @@ -149,165 +139,3 @@ func registerWithNode(sender gateway.Sender, comms RegisterNodeCommsInterface, return nil } - -func requestKey(sender gateway.Sender, comms RegisterNodeCommsInterface, - ngw network.NodeGateway, s storage.Session, r *registrar, rng csprng.Source, - stop *stoppable.Single) (*cyclic.Int, []byte, uint64, error) { - - grp := r.session.GetCmixGroup() - - prime := grp.GetPBytes() - keyLen := len(prime) - dhPrivBytes, err := csprng.GenerateInGroup(prime, keyLen, rng) - if err != nil { - return nil, nil, 0, err - } - - dhPriv := grp.NewIntFromBytes(dhPrivBytes) - - dhPub := diffieHellman.GeneratePublicKey(dhPriv, grp) - - // Reconstruct client confirmation message - userPubKeyRSA := rsa.CreatePublicKeyPem(s.GetTransmissionRSA().GetPublic()) - confirmation := &pb.ClientRegistrationConfirmation{ - RSAPubKey: string(userPubKeyRSA), - Timestamp: s.GetRegistrationTimestamp().UnixNano(), - } - confirmationSerialized, err := proto.Marshal(confirmation) - if err != nil { - return nil, nil, 0, err - } - - keyRequest := &pb.ClientKeyRequest{ - Salt: s.GetTransmissionSalt(), - ClientTransmissionConfirmation: &pb.SignedRegistrationConfirmation{ - RegistrarSignature: &messages.RSASignature{ - Signature: s.GetTransmissionRegistrationValidationSignature()}, - ClientRegistrationConfirmation: confirmationSerialized, - }, - ClientDHPubKey: dhPub.Bytes(), - RegistrationTimestamp: s.GetRegistrationTimestamp().UnixNano(), - RequestTimestamp: netTime.Now().UnixNano(), - } - - serializedMessage, err := proto.Marshal(keyRequest) - if err != nil { - return nil, nil, 0, err - } - - opts := rsa.NewDefaultOptions() - opts.Hash = hash.CMixHash - h := opts.Hash.New() - h.Write(serializedMessage) - data := h.Sum(nil) - - // Sign DH pubkey - clientSig, err := rsa.Sign(rng, s.GetTransmissionRSA(), opts.Hash, data, opts) - if err != nil { - return nil, nil, 0, err - } - - gwId := ngw.Gateway.ID - gatewayID, err := id.Unmarshal(gwId) - if err != nil { - jww.ERROR.Printf("registerWithNode failed to decode gateway ID: %v", err) - return nil, nil, 0, err - } - - // Request nonce message from gateway - jww.INFO.Printf("Register: Requesting client key from gateway %s", gatewayID) - - result, err := sender.SendToAny(func(host *connect.Host) (interface{}, error) { - keyResponse, err2 := comms.SendRequestClientKeyMessage(host, - &pb.SignedClientKeyRequest{ - ClientKeyRequest: serializedMessage, - ClientKeyRequestSignature: &messages.RSASignature{Signature: clientSig}, - Target: gatewayID.Bytes(), - }) - if err2 != nil { - return nil, errors.WithMessage(err2, - "Register: Failed requesting client key from gateway") - } - if keyResponse.Error != "" { - return nil, errors.WithMessage(err2, - "requestKey: clientKeyResponse error") - } - - return keyResponse, nil - }, stop) - - if err != nil { - return nil, nil, 0, err - } - - signedKeyResponse := result.(*pb.SignedKeyResponse) - if signedKeyResponse.Error != "" { - return nil, nil, 0, errors.New(signedKeyResponse.Error) - } - - // Hash the response - h.Reset() - h.Write(signedKeyResponse.KeyResponse) - hashedResponse := h.Sum(nil) - - // Load nodes certificate - gatewayCert, err := tls.LoadCertificate(ngw.Gateway.TlsCertificate) - if err != nil { - return nil, nil, 0, - errors.WithMessagef(err, "Unable to load nodes's certificate") - } - - // Extract public key - nodePubKey, err := tls.ExtractPublicKey(gatewayCert) - if err != nil { - return nil, nil, 0, - errors.WithMessagef(err, "Unable to load node's public key") - } - - // Verify the response signature - err = rsa.Verify(nodePubKey, opts.Hash, hashedResponse, - signedKeyResponse.KeyResponseSignedByGateway.Signature, opts) - if err != nil { - return nil, nil, 0, - errors.WithMessagef(err, "Could not verify nodes's signature") - } - - // Unmarshal the response - keyResponse := &pb.ClientKeyResponse{} - err = proto.Unmarshal(signedKeyResponse.KeyResponse, keyResponse) - if err != nil { - return nil, nil, 0, - errors.WithMessagef(err, "Failed to unmarshal client key response") - } - - h.Reset() - - // Convert Node DH Public key to a cyclic.Int - nodeDHPub := grp.NewIntFromBytes(keyResponse.NodeDHPubKey) - - // Construct the session key - sessionKey := registration.GenerateBaseKey(grp, - nodeDHPub, dhPriv, h) - - // Verify the HMAC - h.Reset() - if !registration.VerifyClientHMAC(sessionKey.Bytes(), - keyResponse.EncryptedClientKey, opts.Hash.New, - keyResponse.EncryptedClientKeyHMAC) { - return nil, nil, 0, errors.New("Failed to verify client HMAC") - } - - // Decrypt the client key - clientKey, err := chacha.Decrypt( - sessionKey.Bytes(), keyResponse.EncryptedClientKey) - if err != nil { - return nil, nil, 0, - errors.WithMessagef(err, "Failed to decrypt client key") - } - - // Construct the transmission key from the client key - transmissionKey := grp.NewIntFromBytes(clientKey) - - // Use Client keypair to sign Server nonce - return transmissionKey, keyResponse.KeyID, keyResponse.ValidUntil, nil -} diff --git a/cmix/nodes/request.go b/cmix/nodes/request.go new file mode 100644 index 0000000000000000000000000000000000000000..fe251e46a97bae064b840e7bcc6ac84278a481fa --- /dev/null +++ b/cmix/nodes/request.go @@ -0,0 +1,223 @@ +package nodes + +import ( + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/gateway" + "gitlab.com/elixxir/client/stoppable" + pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/diffieHellman" + "gitlab.com/elixxir/crypto/hash" + "gitlab.com/elixxir/crypto/registration" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/comms/messages" + "gitlab.com/xx_network/crypto/chacha" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/crypto/tls" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" + "io" +) + +// requestKey is a helper function which constructs a ClientKeyRequest message. +// This message is sent to the passed gateway. It will further handle the +// request from the gateway. +func requestKey(sender gateway.Sender, comms RegisterNodeCommsInterface, + ngw network.NodeGateway, s Session, r *registrar, + rng io.Reader, + stop *stoppable.Single) (*cyclic.Int, []byte, uint64, error) { + + // Generate a Diffie-Hellman keypair + grp := r.session.GetCmixGroup() + + prime := grp.GetPBytes() + keyLen := len(prime) + dhPrivBytes, err := csprng.GenerateInGroup(prime, keyLen, rng) + if err != nil { + return nil, nil, 0, err + } + dhPriv := grp.NewIntFromBytes(dhPrivBytes) + dhPub := diffieHellman.GeneratePublicKey(dhPriv, grp) + + // Parse the ID into an id.ID object. + gwId := ngw.Gateway.ID + gatewayID, err := id.Unmarshal(gwId) + if err != nil { + jww.ERROR.Printf("registerWithNode failed to decode "+ + "gateway ID: %v", err) + return nil, nil, 0, err + } + + signedKeyReq, err := makeSignedKeyRequest(s, rng, gatewayID, dhPub) + if err != nil { + return nil, nil, 0, err + } + + // Request nonce message from gateway + jww.INFO.Printf("Register: Requesting client key from "+ + "gateway %s", gatewayID) + + result, err := sender.SendToAny(func(host *connect.Host) (interface{}, error) { + keyResponse, err2 := comms.SendRequestClientKeyMessage(host, signedKeyReq) + if err2 != nil { + return nil, errors.WithMessage(err2, + "Register: Failed requesting client key from gateway") + } + if keyResponse.Error != "" { + return nil, errors.WithMessage(err2, + "requestKey: clientKeyResponse error") + } + + return keyResponse, nil + }, stop) + + if err != nil { + return nil, nil, 0, err + } + + // Cast the response + signedKeyResponse := result.(*pb.SignedKeyResponse) + if signedKeyResponse.Error != "" { + return nil, nil, 0, errors.New(signedKeyResponse.Error) + } + + // Process the server's response + return processRequestResponse(signedKeyResponse, ngw, grp, dhPriv) +} + +// makeSignedKeyRequest is a helper function which constructs a +// pb.SignedClientKeyRequest to send to the node/gateway pair the +// user is trying to register with. +func makeSignedKeyRequest(s Session, rng io.Reader, + gwId *id.ID, dhPub *cyclic.Int) (*pb.SignedClientKeyRequest, error) { + + // Reconstruct client confirmation message + userPubKeyRSA := rsa.CreatePublicKeyPem(s.GetTransmissionRSA().GetPublic()) + confirmation := &pb.ClientRegistrationConfirmation{ + RSAPubKey: string(userPubKeyRSA), + Timestamp: s.GetRegistrationTimestamp().UnixNano(), + } + confirmationSerialized, err := proto.Marshal(confirmation) + if err != nil { + return nil, err + } + + // Construct a key request message + keyRequest := &pb.ClientKeyRequest{ + Salt: s.GetTransmissionSalt(), + ClientTransmissionConfirmation: &pb.SignedRegistrationConfirmation{ + RegistrarSignature: &messages.RSASignature{ + Signature: s.GetTransmissionRegistrationValidationSignature()}, + ClientRegistrationConfirmation: confirmationSerialized, + }, + ClientDHPubKey: dhPub.Bytes(), + RegistrationTimestamp: s.GetRegistrationTimestamp().UnixNano(), + RequestTimestamp: netTime.Now().UnixNano(), + } + + // Serialize the reconstructed message + serializedMessage, err := proto.Marshal(keyRequest) + if err != nil { + return nil, err + } + + // Sign DH public key + opts := rsa.NewDefaultOptions() + opts.Hash = hash.CMixHash + h := opts.Hash.New() + h.Write(serializedMessage) + data := h.Sum(nil) + clientSig, err := rsa.Sign(rng, s.GetTransmissionRSA(), opts.Hash, + data, opts) + if err != nil { + return nil, err + } + + // Construct signed key request message + signedRequest := &pb.SignedClientKeyRequest{ + ClientKeyRequest: serializedMessage, + ClientKeyRequestSignature: &messages.RSASignature{Signature: clientSig}, + Target: gwId.Bytes(), + } + + return signedRequest, nil +} + +// processRequestResponse is a helper function which handles the server's +// key request response. +func processRequestResponse(signedKeyResponse *pb.SignedKeyResponse, + ngw network.NodeGateway, grp *cyclic.Group, + dhPrivKey *cyclic.Int) (*cyclic.Int, []byte, uint64, error) { + // Define hashing algorithm + opts := rsa.NewDefaultOptions() + opts.Hash = hash.CMixHash + h := opts.Hash.New() + + // Hash the response + h.Reset() + h.Write(signedKeyResponse.KeyResponse) + hashedResponse := h.Sum(nil) + + // Load nodes certificate + gatewayCert, err := tls.LoadCertificate(ngw.Gateway.TlsCertificate) + if err != nil { + return nil, nil, 0, + errors.Errorf("Unable to load nodes's certificate: %+v", err) + } + + // Extract public key + nodePubKey, err := tls.ExtractPublicKey(gatewayCert) + if err != nil { + return nil, nil, 0, + errors.Errorf("Unable to load node's public key: %v", err) + } + + // Verify the response signature + err = rsa.Verify(nodePubKey, opts.Hash, hashedResponse, + signedKeyResponse.KeyResponseSignedByGateway.Signature, opts) + if err != nil { + return nil, nil, 0, + errors.Errorf("Could not verify nodes's signature: %v", err) + } + + // Unmarshal the response + keyResponse := &pb.ClientKeyResponse{} + err = proto.Unmarshal(signedKeyResponse.KeyResponse, keyResponse) + if err != nil { + return nil, nil, 0, + errors.WithMessagef(err, "Failed to unmarshal client key response") + } + + // Convert Node DH Public key to a cyclic.Int + nodeDHPub := grp.NewIntFromBytes(keyResponse.NodeDHPubKey) + + // Construct the session key + h.Reset() + sessionKey := registration.GenerateBaseKey(grp, + nodeDHPub, dhPrivKey, h) + + // Verify the HMAC + if !registration.VerifyClientHMAC(sessionKey.Bytes(), + keyResponse.EncryptedClientKey, opts.Hash.New, + keyResponse.EncryptedClientKeyHMAC) { + return nil, nil, 0, errors.New("Failed to verify client HMAC") + } + + // Decrypt the client key + clientKey, err := chacha.Decrypt( + sessionKey.Bytes(), keyResponse.EncryptedClientKey) + if err != nil { + return nil, nil, 0, + errors.WithMessagef(err, "Failed to decrypt client key") + } + + // Construct the transmission key from the client key + transmissionKey := grp.NewIntFromBytes(clientKey) + + // Use Client keypair to sign Server nonce + return transmissionKey, keyResponse.KeyID, keyResponse.ValidUntil, nil +}