github.com/braveheart12/insolar-09-08-19@v0.8.7/network/controller/bootstrap/challenge_response.go (about)

     1  /*
     2   * The Clear BSD License
     3   *
     4   * Copyright (c) 2019 Insolar Technologies
     5   *
     6   * All rights reserved.
     7   *
     8   * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met:
     9   *
    10   *  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    11   *  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    12   *  Neither the name of Insolar Technologies nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    13   *
    14   * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    15   *
    16   */
    17  
    18  package bootstrap
    19  
    20  import (
    21  	"context"
    22  	"encoding/gob"
    23  
    24  	"github.com/insolar/insolar/component"
    25  	"github.com/insolar/insolar/core"
    26  	"github.com/insolar/insolar/instrumentation/inslogger"
    27  	"github.com/insolar/insolar/instrumentation/instracer"
    28  	"github.com/insolar/insolar/log"
    29  	"github.com/insolar/insolar/network"
    30  	"github.com/insolar/insolar/network/controller/common"
    31  	"github.com/insolar/insolar/network/transport/host"
    32  	"github.com/insolar/insolar/network/transport/packet/types"
    33  	base58 "github.com/jbenet/go-base58"
    34  	"github.com/pkg/errors"
    35  )
    36  
    37  type ChallengeResponseController interface {
    38  	component.Initer
    39  
    40  	Execute(ctx context.Context, discoveryNode *DiscoveryNode, sessionID SessionID) (*ChallengePayload, error)
    41  }
    42  
    43  type challengeResponseController struct {
    44  	SessionManager SessionManager           `inject:""`
    45  	Cryptography   core.CryptographyService `inject:""`
    46  	NodeKeeper     network.NodeKeeper       `inject:""`
    47  
    48  	options   *common.Options
    49  	transport network.InternalTransport
    50  }
    51  
    52  type Nonce []byte
    53  type SignedNonce []byte
    54  
    55  // Node                           Discovery Node
    56  //  1| ------ ChallengeRequest -----> |
    57  //  2| <-- SignedChallengeResponse -- |
    58  //  3| --- SignedChallengeRequest --> |
    59  //  4| <----- ChallengeResponse ----- |
    60  // ------------------------------------
    61  
    62  type ChallengeResponseHeader struct {
    63  	Success bool
    64  	Error   string
    65  }
    66  
    67  type ChallengeRequest struct {
    68  	SessionID SessionID
    69  
    70  	Nonce Nonce
    71  }
    72  
    73  type SignedChallengeResponse struct {
    74  	Header  ChallengeResponseHeader
    75  	Payload *SignedChallengePayload
    76  }
    77  
    78  type SignedChallengePayload struct {
    79  	SignedNonce       SignedNonce
    80  	XorDiscoveryNonce Nonce
    81  	DiscoveryNonce    Nonce
    82  }
    83  
    84  type SignedChallengeRequest struct {
    85  	SessionID SessionID
    86  
    87  	SignedDiscoveryNonce SignedNonce
    88  	XorNonce             Nonce
    89  }
    90  
    91  type ChallengeResponse struct {
    92  	Header  ChallengeResponseHeader
    93  	Payload *ChallengePayload
    94  }
    95  
    96  type ChallengePayload struct {
    97  	// CurrentPulse  core.Pulse
    98  	// State         core.NetworkState
    99  	AssignShortID core.ShortNodeID
   100  }
   101  
   102  func init() {
   103  	gob.Register(&ChallengeRequest{})
   104  	gob.Register(&SignedChallengeResponse{})
   105  	gob.Register(&SignedChallengeRequest{})
   106  	gob.Register(&ChallengeResponse{})
   107  }
   108  
   109  func (cr *challengeResponseController) processChallenge1(ctx context.Context, request network.Request) (network.Response, error) {
   110  	ctx, span := instracer.StartSpan(ctx, "ChallengeResponseController.processChallenge1")
   111  	defer span.End()
   112  	data := request.GetData().(*ChallengeRequest)
   113  	// CheckSession is performed in SetDiscoveryNonce too, but we want to return early if the request is invalid
   114  	err := cr.SessionManager.CheckSession(data.SessionID, Authorized)
   115  	if err != nil {
   116  		return cr.buildChallenge1ErrorResponse(ctx, request, err.Error()), nil
   117  	}
   118  	xorNonce, err := GenerateNonce()
   119  	if err != nil {
   120  		return cr.buildChallenge1ErrorResponse(ctx, request, "error generating discovery xor nonce: "+err.Error()), nil
   121  	}
   122  	sign, err := cr.Cryptography.Sign(Xor(data.Nonce, xorNonce))
   123  	if err != nil {
   124  		return cr.buildChallenge1ErrorResponse(ctx, request, "error signing nonce: "+err.Error()), nil
   125  	}
   126  	discoveryNonce, err := GenerateNonce()
   127  	if err != nil {
   128  		return cr.buildChallenge1ErrorResponse(ctx, request, "error generating discovery nonce: "+err.Error()), nil
   129  	}
   130  	err = cr.SessionManager.SetDiscoveryNonce(data.SessionID, discoveryNonce)
   131  	if err != nil {
   132  		return cr.buildChallenge1ErrorResponse(ctx, request, err.Error()), nil
   133  	}
   134  	response := cr.transport.BuildResponse(ctx, request, &SignedChallengeResponse{
   135  		Header: ChallengeResponseHeader{
   136  			Success: true,
   137  		},
   138  		Payload: &SignedChallengePayload{
   139  			SignedNonce:       sign.Bytes(),
   140  			XorDiscoveryNonce: xorNonce,
   141  			DiscoveryNonce:    discoveryNonce,
   142  		},
   143  	})
   144  	return response, nil
   145  }
   146  
   147  func (cr *challengeResponseController) buildChallenge1ErrorResponse(ctx context.Context, request network.Request, err string) network.Response {
   148  	log.Warn(err)
   149  	return cr.transport.BuildResponse(ctx, request, &ChallengeResponse{
   150  		Header: ChallengeResponseHeader{
   151  			Success: false,
   152  			Error:   err,
   153  		},
   154  	})
   155  }
   156  
   157  func (cr *challengeResponseController) processChallenge2(ctx context.Context, request network.Request) (network.Response, error) {
   158  	ctx, span := instracer.StartSpan(ctx, "ChallengeResponseController.processChallenge2")
   159  	defer span.End()
   160  	data := request.GetData().(*SignedChallengeRequest)
   161  	cert, discoveryNonce, err := cr.SessionManager.GetChallengeData(data.SessionID)
   162  	if err != nil {
   163  		return cr.buildChallenge2ErrorResponse(ctx, request, err.Error()), nil
   164  	}
   165  	sign := core.SignatureFromBytes(data.SignedDiscoveryNonce)
   166  	success := cr.Cryptography.Verify(cert.GetPublicKey(), sign, Xor(data.XorNonce, discoveryNonce))
   167  	if !success {
   168  		return cr.buildChallenge2ErrorResponse(ctx, request, "node %s signature check failed"), nil
   169  	}
   170  	err = cr.SessionManager.ChallengePassed(data.SessionID)
   171  	if err != nil {
   172  		return cr.buildChallenge2ErrorResponse(ctx, request, err.Error()), nil
   173  	}
   174  	response := cr.transport.BuildResponse(ctx, request, &ChallengeResponse{
   175  		Header: ChallengeResponseHeader{
   176  			Success: true,
   177  		},
   178  		Payload: &ChallengePayload{
   179  			AssignShortID: GenerateShortID(cr.NodeKeeper, *cert.GetNodeRef()),
   180  		},
   181  	})
   182  	return response, nil
   183  }
   184  
   185  func (cr *challengeResponseController) buildChallenge2ErrorResponse(ctx context.Context, request network.Request, err string) network.Response {
   186  	log.Warn(err)
   187  	return cr.transport.BuildResponse(ctx, request, &SignedChallengeResponse{
   188  		Header: ChallengeResponseHeader{
   189  			Success: false,
   190  			Error:   err,
   191  		},
   192  	})
   193  }
   194  
   195  func (cr *challengeResponseController) Init(ctx context.Context) error {
   196  	cr.transport.RegisterPacketHandler(types.Challenge1, cr.processChallenge1)
   197  	cr.transport.RegisterPacketHandler(types.Challenge2, cr.processChallenge2)
   198  	return nil
   199  }
   200  
   201  func (cr *challengeResponseController) sendRequest1(ctx context.Context, discoveryHost *host.Host,
   202  	sessionID SessionID, nonce Nonce) (*SignedChallengePayload, error) {
   203  
   204  	ctx, span := instracer.StartSpan(ctx, "ChallengeResponseController.sendRequest1")
   205  	defer span.End()
   206  	request := cr.transport.NewRequestBuilder().Type(types.Challenge1).Data(&ChallengeRequest{
   207  		SessionID: sessionID, Nonce: nonce}).Build()
   208  	future, err := cr.transport.SendRequestPacket(ctx, request, discoveryHost)
   209  	if err != nil {
   210  		return nil, errors.Wrap(err, "Error sending challenge request")
   211  	}
   212  	response, err := future.GetResponse(cr.options.PacketTimeout)
   213  	if err != nil {
   214  		return nil, errors.Wrap(err, "Error getting response for challenge request")
   215  	}
   216  	data := response.GetData().(*SignedChallengeResponse)
   217  	if !data.Header.Success {
   218  		return nil, errors.Wrap(err, "Discovery node returned error for challenge request: "+data.Header.Error)
   219  	}
   220  	return data.Payload, nil
   221  }
   222  
   223  func (cr *challengeResponseController) sendRequest2(ctx context.Context, discoveryHost *host.Host,
   224  	sessionID SessionID, signedDiscoveryNonce SignedNonce, xorNonce Nonce) (*ChallengePayload, error) {
   225  
   226  	ctx, span := instracer.StartSpan(ctx, "ChallengeResponseController.sendRequest2")
   227  	defer span.End()
   228  	request := cr.transport.NewRequestBuilder().Type(types.Challenge2).Data(&SignedChallengeRequest{
   229  		SessionID: sessionID, XorNonce: xorNonce, SignedDiscoveryNonce: signedDiscoveryNonce}).Build()
   230  	future, err := cr.transport.SendRequestPacket(ctx, request, discoveryHost)
   231  	if err != nil {
   232  		return nil, errors.Wrap(err, "Error sending challenge request")
   233  	}
   234  	response, err := future.GetResponse(cr.options.PacketTimeout)
   235  	if err != nil {
   236  		return nil, errors.Wrap(err, "Error getting response for challenge request")
   237  	}
   238  	data := response.GetData().(*ChallengeResponse)
   239  	if !data.Header.Success {
   240  		return nil, errors.Wrap(err, "Discovery node returned error for challenge request: "+data.Header.Error)
   241  	}
   242  	return data.Payload, nil
   243  }
   244  
   245  // Execute double challenge response between the node and the discovery node (step 3 of the bootstrap process)
   246  func (cr *challengeResponseController) Execute(ctx context.Context, discoveryNode *DiscoveryNode, sessionID SessionID) (*ChallengePayload, error) {
   247  	ctx, span := instracer.StartSpan(ctx, "ChallengeResponseController.Execute")
   248  	defer span.End()
   249  	nonce, err := GenerateNonce()
   250  	if err != nil {
   251  		return nil, errors.Wrap(err, "error generating nonce")
   252  	}
   253  	inslogger.FromContext(ctx).Debugf("Generated nonce: %s", base58.Encode(nonce))
   254  
   255  	data, err := cr.sendRequest1(ctx, discoveryNode.Host, sessionID, nonce)
   256  	if err != nil {
   257  		return nil, errors.Wrap(err, "error executing challenge response (step 1)")
   258  	}
   259  
   260  	inslogger.FromContext(ctx).Debugf("Discovery SignedNonce: %s", base58.Encode(data.SignedNonce))
   261  	inslogger.FromContext(ctx).Debugf("Discovery DiscoveryNonce: %s", base58.Encode(data.DiscoveryNonce))
   262  	inslogger.FromContext(ctx).Debugf("Discovery XorDiscoveryNonce: %s", base58.Encode(data.XorDiscoveryNonce))
   263  
   264  	sign := core.SignatureFromBytes(data.SignedNonce)
   265  	success := cr.Cryptography.Verify(discoveryNode.Node.GetPublicKey(), sign, Xor(nonce, data.XorDiscoveryNonce))
   266  	if !success {
   267  		return nil, errors.New("Error checking signed nonce from discovery node")
   268  	}
   269  
   270  	xorNonce, err := GenerateNonce()
   271  	if err != nil {
   272  		return nil, errors.Wrap(err, "error generating xor nonce")
   273  	}
   274  	signedDiscoveryNonce, err := cr.Cryptography.Sign(Xor(xorNonce, data.DiscoveryNonce))
   275  	if err != nil {
   276  		return nil, errors.Wrap(err, "error signing discovery nonce")
   277  	}
   278  	payload, err := cr.sendRequest2(ctx, discoveryNode.Host, sessionID, signedDiscoveryNonce.Bytes(), xorNonce)
   279  	if err != nil {
   280  		return nil, errors.Wrap(err, "error executing challenge response (step 2)")
   281  	}
   282  	return payload, nil
   283  }
   284  
   285  func NewChallengeResponseController(options *common.Options, transport network.InternalTransport) ChallengeResponseController {
   286  	return &challengeResponseController{
   287  		options:   options,
   288  		transport: transport,
   289  	}
   290  }