d80cfeb4ab
When error is formatted using %v it's not possible to check whether the error was caused by a context cancellation. Change-Id: Ia77dfb0817e49d9a7b168c12a6300d131007d0ee
155 lines
6.3 KiB
Go
155 lines
6.3 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package piecestore
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/common/errs2"
|
|
"storj.io/common/pb"
|
|
"storj.io/common/rpc/rpcstatus"
|
|
"storj.io/common/signing"
|
|
)
|
|
|
|
var (
|
|
// ErrVerifyNotAuthorized is returned when the one submitting the action is not authorized to perform that action.
|
|
ErrVerifyNotAuthorized = errs.Class("not authorized")
|
|
// ErrVerifyUntrusted is returned when action is not trusted.
|
|
ErrVerifyUntrusted = errs.Class("untrusted")
|
|
// ErrVerifyDuplicateRequest is returned when serial number has been already used to submit an action.
|
|
ErrVerifyDuplicateRequest = errs.Class("duplicate request")
|
|
)
|
|
|
|
// VerifyOrderLimit verifies that the order limit is properly signed and has sane values.
|
|
// It also verifies that the serial number has not been used.
|
|
func (endpoint *Endpoint) verifyOrderLimit(ctx context.Context, limit *pb.OrderLimit) (err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
// sanity checks
|
|
now := time.Now()
|
|
switch {
|
|
case limit.Limit < 0:
|
|
return rpcstatus.Error(rpcstatus.InvalidArgument, "order limit is negative")
|
|
case endpoint.signer.ID() != limit.StorageNodeId:
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "order intended for other storagenode: %v", limit.StorageNodeId)
|
|
case endpoint.IsExpired(limit.PieceExpiration):
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "piece expired: %v", limit.PieceExpiration)
|
|
case endpoint.IsExpired(limit.OrderExpiration):
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "order expired: %v", limit.OrderExpiration)
|
|
case now.Sub(limit.OrderCreation) > endpoint.config.OrderLimitGracePeriod:
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "order created too long ago: OrderCreation %v < SystemClock %v", limit.OrderCreation, now)
|
|
case limit.OrderCreation.Sub(now) > endpoint.config.OrderLimitGracePeriod:
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "order created too far in the future: OrderCreation %v > SystemClock %v", limit.OrderCreation, now)
|
|
case limit.SatelliteId.IsZero():
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "missing satellite id")
|
|
case limit.UplinkPublicKey.IsZero():
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "missing uplink public key")
|
|
case len(limit.SatelliteSignature) == 0:
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "missing satellite signature")
|
|
case limit.PieceId.IsZero():
|
|
return rpcstatus.Errorf(rpcstatus.InvalidArgument, "missing piece id")
|
|
}
|
|
|
|
if err := endpoint.trust.VerifySatelliteID(ctx, limit.SatelliteId); err != nil {
|
|
return rpcstatus.Errorf(rpcstatus.PermissionDenied, "untrusted: %+v", err)
|
|
}
|
|
|
|
if err := endpoint.VerifyOrderLimitSignature(ctx, limit); err != nil {
|
|
if errs2.IsCanceled(err) {
|
|
return rpcstatus.Error(rpcstatus.Canceled, "context has been canceled")
|
|
}
|
|
|
|
return rpcstatus.Errorf(rpcstatus.Unauthenticated, "untrusted: %+v", err)
|
|
}
|
|
|
|
serialExpiration := limit.OrderExpiration
|
|
|
|
// Expire the serial earlier if the grace period is smaller than the serial expiration.
|
|
if graceExpiration := now.Add(endpoint.config.OrderLimitGracePeriod); graceExpiration.Before(serialExpiration) {
|
|
serialExpiration = graceExpiration
|
|
}
|
|
|
|
if err := endpoint.usedSerials.Add(ctx, limit.SatelliteId, limit.SerialNumber, serialExpiration); err != nil {
|
|
return rpcstatus.Errorf(rpcstatus.Unauthenticated, "serial number is already used: %+v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyOrder verifies that the order corresponds to the order limit and has all the necessary fields.
|
|
func (endpoint *Endpoint) VerifyOrder(ctx context.Context, limit *pb.OrderLimit, order *pb.Order, largestOrderAmount int64) (err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
if order.SerialNumber != limit.SerialNumber {
|
|
return ErrProtocol.New("order serial number changed during upload") // TODO: report rpc status bad message
|
|
}
|
|
// TODO: add check for minimum allocation step
|
|
if order.Amount < largestOrderAmount {
|
|
return ErrProtocol.New("order contained smaller amount=%v, previous=%v", order.Amount, largestOrderAmount) // TODO: report rpc status bad message
|
|
}
|
|
if order.Amount > limit.Limit {
|
|
return ErrProtocol.New("order exceeded allowed amount=%v, limit=%v", order.Amount, limit.Limit) // TODO: report rpc status bad message
|
|
}
|
|
|
|
if err := signing.VerifyUplinkOrderSignature(ctx, limit.UplinkPublicKey, order); err != nil {
|
|
return ErrVerifyUntrusted.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyPieceHash verifies whether the piece hash is properly signed and matches the locally computed hash.
|
|
func (endpoint *Endpoint) VerifyPieceHash(ctx context.Context, limit *pb.OrderLimit, hash *pb.PieceHash, expectedHash []byte) (err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
if limit == nil || hash == nil || len(expectedHash) == 0 {
|
|
return ErrProtocol.New("invalid arguments")
|
|
}
|
|
if limit.PieceId != hash.PieceId {
|
|
return ErrProtocol.New("piece id changed") // TODO: report rpc status bad message
|
|
}
|
|
if !bytes.Equal(hash.Hash, expectedHash) {
|
|
return ErrProtocol.New("hashes don't match") // TODO: report rpc status bad message
|
|
}
|
|
|
|
if err := signing.VerifyUplinkPieceHashSignature(ctx, limit.UplinkPublicKey, hash); err != nil {
|
|
return ErrVerifyUntrusted.New("invalid piece hash signature") // TODO: report rpc status bad message
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyOrderLimitSignature verifies that the order limit signature is valid.
|
|
func (endpoint *Endpoint) VerifyOrderLimitSignature(ctx context.Context, limit *pb.OrderLimit) (err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
signee, err := endpoint.trust.GetSignee(ctx, limit.SatelliteId)
|
|
if err != nil {
|
|
if errs2.IsCanceled(err) {
|
|
return err
|
|
}
|
|
return ErrVerifyUntrusted.New("unable to get signee: %w", err) // TODO: report rpc status bad message
|
|
}
|
|
|
|
if err := signing.VerifyOrderLimitSignature(ctx, signee, limit); err != nil {
|
|
return ErrVerifyUntrusted.New("invalid order limit signature: %w", err) // TODO: report rpc status bad message
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsExpired checks whether the date has already expired (with a threshold) at the time of calling this function.
|
|
func (endpoint *Endpoint) IsExpired(expiration time.Time) bool {
|
|
if expiration.IsZero() {
|
|
return false
|
|
}
|
|
|
|
// TODO: return specific error about either exceeding the expiration completely or just the grace period
|
|
return expiration.Before(time.Now().Add(-endpoint.config.ExpirationGracePeriod))
|
|
}
|