github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/host/negotiaterecentrevision.go (about)

     1  package host
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/Synthesix/Sia/crypto"
     9  	"github.com/Synthesix/Sia/encoding"
    10  	"github.com/Synthesix/Sia/modules"
    11  	"github.com/Synthesix/Sia/types"
    12  	"github.com/NebulousLabs/fastrand"
    13  
    14  	"github.com/coreos/bbolt"
    15  )
    16  
    17  var (
    18  	// errRevisionWrongPublicKeyCount is returned when a stored file contract
    19  	// revision does not have enough public keys - such a situation should
    20  	// never happen, and is a critical / developer error.
    21  	errRevisionWrongPublicKeyCount = errors.New("wrong number of public keys in the unlock conditions of the file contract revision")
    22  
    23  	// errVerifyChallenge is returned to renter instead of any error
    24  	// returned by managedVerifyChallengeResponse. It is used instead
    25  	// of the original error not to leak if the host has the contract
    26  	// with the ID sent by renter.
    27  	errVerifyChallenge = errors.New("bad signature from renter or no such contract")
    28  )
    29  
    30  // managedVerifyChallengeResponse will verify that the renter's response to the
    31  // challenge provided by the host is correct. In the process, the storage
    32  // obligation and file contract revision will be loaded and returned.
    33  //
    34  // The storage obligation is returned under a storage obligation lock.
    35  func (h *Host) managedVerifyChallengeResponse(fcid types.FileContractID, challenge crypto.Hash, challengeResponse crypto.Signature) (so storageObligation, recentRevision types.FileContractRevision, revisionSigs []types.TransactionSignature, err error) {
    36  	// Grab a lock before it is possible to perform any operations on the
    37  	// storage obligation. Defer a call to unlock in the event of an error. If
    38  	// there is no error, the storage obligation will be returned with a lock.
    39  	err = h.managedTryLockStorageObligation(fcid)
    40  	if err != nil {
    41  		err = extendErr("could not get "+fcid.String()+" lock: ", ErrorInternal(err.Error()))
    42  		return storageObligation{}, types.FileContractRevision{}, nil, err
    43  	}
    44  	defer func() {
    45  		if err != nil {
    46  			h.managedUnlockStorageObligation(fcid)
    47  		}
    48  	}()
    49  
    50  	// Fetch the storage obligation, which has the revision, which has the
    51  	// renter's public key.
    52  	h.mu.RLock()
    53  	defer h.mu.RUnlock()
    54  	err = h.db.View(func(tx *bolt.Tx) error {
    55  		so, err = getStorageObligation(tx, fcid)
    56  		return err
    57  	})
    58  	if err != nil {
    59  		err = extendErr("could not fetch "+fcid.String()+": ", ErrorInternal(err.Error()))
    60  		return storageObligation{}, types.FileContractRevision{}, nil, err
    61  	}
    62  
    63  	// Pull out the file contract revision and the revision's signatures from
    64  	// the transaction.
    65  	revisionTxn := so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1]
    66  	recentRevision = revisionTxn.FileContractRevisions[0]
    67  	for _, sig := range revisionTxn.TransactionSignatures {
    68  		// Checking for just the parent id is sufficient, an over-signed file
    69  		// contract is invalid.
    70  		if sig.ParentID == crypto.Hash(fcid) {
    71  			revisionSigs = append(revisionSigs, sig)
    72  		}
    73  	}
    74  
    75  	// Verify that the challegne response matches the public key.
    76  	var renterPK crypto.PublicKey
    77  	// Sanity check - there should be two public keys.
    78  	if len(recentRevision.UnlockConditions.PublicKeys) != 2 {
    79  		// The error has to be set here so that the defered error check will
    80  		// unlock the storage obligation.
    81  		h.log.Critical("wrong public key count in file contract revision")
    82  		err = errRevisionWrongPublicKeyCount
    83  		err = extendErr("wrong public key count for "+fcid.String()+": ", ErrorInternal(err.Error()))
    84  		return storageObligation{}, types.FileContractRevision{}, nil, err
    85  	}
    86  	copy(renterPK[:], recentRevision.UnlockConditions.PublicKeys[0].Key)
    87  	err = crypto.VerifyHash(challenge, renterPK, challengeResponse)
    88  	if err != nil {
    89  		err = extendErr("bad signature from renter: ", ErrorCommunication(err.Error()))
    90  		return storageObligation{}, types.FileContractRevision{}, nil, err
    91  	}
    92  	return so, recentRevision, revisionSigs, nil
    93  }
    94  
    95  // managedRPCRecentRevision sends the most recent known file contract
    96  // revision, including signatures, to the renter, for the file contract with
    97  // the id given by the renter.
    98  //
    99  // The storage obligation is returned under a storage obligation lock.
   100  func (h *Host) managedRPCRecentRevision(conn net.Conn) (types.FileContractID, storageObligation, error) {
   101  	// Set the negotiation deadline.
   102  	conn.SetDeadline(time.Now().Add(modules.NegotiateRecentRevisionTime))
   103  
   104  	// Receive the file contract id from the renter.
   105  	var fcid types.FileContractID
   106  	err := encoding.ReadObject(conn, &fcid, uint64(len(fcid)))
   107  	if err != nil {
   108  		return types.FileContractID{}, storageObligation{}, extendErr("could not read file contract id: ", ErrorConnection(err.Error()))
   109  	}
   110  
   111  	// Send a challenge to the renter to verify that the renter has write
   112  	// access to the revision being opened.
   113  	var challenge crypto.Hash
   114  	fastrand.Read(challenge[16:])
   115  	err = encoding.WriteObject(conn, challenge)
   116  	if err != nil {
   117  		return types.FileContractID{}, storageObligation{}, extendErr("cound not write challenge: ", ErrorConnection(err.Error()))
   118  	}
   119  
   120  	// Read the signed response from the renter.
   121  	var challengeResponse crypto.Signature
   122  	err = encoding.ReadObject(conn, &challengeResponse, uint64(len(challengeResponse)))
   123  	if err != nil {
   124  		return types.FileContractID{}, storageObligation{}, extendErr("could not read challenge response: ", ErrorConnection(err.Error()))
   125  	}
   126  	// Verify the response. In the process, fetch the related storage
   127  	// obligation, file contract revision, and transaction signatures.
   128  	so, recentRevision, revisionSigs, err := h.managedVerifyChallengeResponse(fcid, challenge, challengeResponse)
   129  	if err != nil {
   130  		// Do not disclose the original error to renter not to leak
   131  		// if the host has the contract with the ID sent by renter.
   132  		modules.WriteNegotiationRejection(conn, errVerifyChallenge)
   133  		return types.FileContractID{}, storageObligation{}, extendErr("challenge failed: ", err)
   134  	}
   135  	// Defer a call to unlock the storage obligation in the event of an error.
   136  	defer func() {
   137  		if err != nil {
   138  			h.managedUnlockStorageObligation(fcid)
   139  		}
   140  	}()
   141  
   142  	// Send the file contract revision and the corresponding signatures to the
   143  	// renter.
   144  	err = modules.WriteNegotiationAcceptance(conn)
   145  	if err != nil {
   146  		err = extendErr("failed to write challenge acceptance: ", ErrorConnection(err.Error()))
   147  		return types.FileContractID{}, storageObligation{}, err
   148  	}
   149  	err = encoding.WriteObject(conn, recentRevision)
   150  	if err != nil {
   151  		err = extendErr("failed to write recent revision: ", ErrorConnection(err.Error()))
   152  		return types.FileContractID{}, storageObligation{}, err
   153  	}
   154  	err = encoding.WriteObject(conn, revisionSigs)
   155  	if err != nil {
   156  		err = extendErr("failed to write recent revision signatures: ", ErrorConnection(err.Error()))
   157  		return types.FileContractID{}, storageObligation{}, err
   158  	}
   159  	return fcid, so, nil
   160  }