storj.io/uplink@v1.13.0/move.go (about)

     1  // Copyright (C) 2021 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package uplink
     5  
     6  import (
     7  	"context"
     8  	"crypto/rand"
     9  	"strings"
    10  
    11  	"github.com/zeebo/errs"
    12  
    13  	"storj.io/common/encryption"
    14  	"storj.io/common/storj"
    15  	"storj.io/uplink/private/metaclient"
    16  )
    17  
    18  // MoveObjectOptions options for MoveObject method.
    19  type MoveObjectOptions struct {
    20  	// may contain StreamID and Version in the future
    21  }
    22  
    23  // MoveObject moves object to a different bucket or/and key.
    24  func (project *Project) MoveObject(ctx context.Context, oldbucket, oldkey, newbucket, newkey string, options *MoveObjectOptions) (err error) {
    25  	defer mon.Task()(&ctx)(&err)
    26  
    27  	err = validateMoveCopyInput(oldbucket, oldkey, newbucket, newkey)
    28  	if err != nil {
    29  		return packageError.Wrap(err)
    30  	}
    31  
    32  	oldEncKey, err := encryptPath(project, oldbucket, oldkey)
    33  	if err != nil {
    34  		return packageError.Wrap(err)
    35  	}
    36  
    37  	newEncKey, err := encryptPath(project, newbucket, newkey)
    38  	if err != nil {
    39  		return packageError.Wrap(err)
    40  	}
    41  
    42  	metainfoClient, err := project.dialMetainfoClient(ctx)
    43  	if err != nil {
    44  		return packageError.Wrap(err)
    45  	}
    46  	defer func() { err = errs.Combine(err, metainfoClient.Close()) }()
    47  
    48  	response, err := metainfoClient.BeginMoveObject(ctx, metaclient.BeginMoveObjectParams{
    49  		Bucket:                []byte(oldbucket),
    50  		EncryptedObjectKey:    []byte(oldEncKey.Raw()),
    51  		NewBucket:             []byte(newbucket),
    52  		NewEncryptedObjectKey: []byte(newEncKey.Raw()),
    53  	})
    54  	if err != nil {
    55  		return convertKnownErrors(err, oldbucket, oldkey)
    56  	}
    57  
    58  	oldDerivedKey, err := deriveContentKey(project, oldbucket, oldkey)
    59  	if err != nil {
    60  		return packageError.Wrap(err)
    61  	}
    62  
    63  	newDerivedKey, err := deriveContentKey(project, newbucket, newkey)
    64  	if err != nil {
    65  		return packageError.Wrap(err)
    66  	}
    67  
    68  	newMetadataEncryptedKey, newMetadataKeyNonce, err := project.reencryptMetadataKey(response.EncryptedMetadataKey, response.EncryptedMetadataKeyNonce, oldDerivedKey, newDerivedKey)
    69  	if err != nil {
    70  		return packageError.Wrap(err)
    71  	}
    72  
    73  	newKeys, err := project.reencryptKeys(response.SegmentKeys, oldDerivedKey, newDerivedKey)
    74  	if err != nil {
    75  		return packageError.Wrap(err)
    76  	}
    77  
    78  	err = metainfoClient.FinishMoveObject(ctx, metaclient.FinishMoveObjectParams{
    79  		StreamID:                     response.StreamID,
    80  		NewBucket:                    []byte(newbucket),
    81  		NewEncryptedObjectKey:        []byte(newEncKey.Raw()),
    82  		NewEncryptedMetadataKeyNonce: newMetadataKeyNonce,
    83  		NewEncryptedMetadataKey:      newMetadataEncryptedKey,
    84  		NewSegmentKeys:               newKeys,
    85  	})
    86  	return convertKnownErrors(err, oldbucket, oldkey)
    87  }
    88  
    89  func validateMoveCopyInput(oldbucket, oldkey, newbucket, newkey string) error {
    90  	switch {
    91  	case oldbucket == "":
    92  		return errwrapf("%w (%q)", ErrBucketNameInvalid, oldbucket)
    93  	case oldkey == "":
    94  		return errwrapf("%w (%q)", ErrObjectKeyInvalid, oldkey)
    95  	case strings.HasSuffix(oldkey, "/"):
    96  		return packageError.New("oldkey cannot be a prefix")
    97  	case newbucket == "": // TODO should we make this error different
    98  		return errwrapf("%w (%q)", ErrBucketNameInvalid, newbucket)
    99  	case newkey == "": // TODO should we make this error different
   100  		return errwrapf("%w (%q)", ErrObjectKeyInvalid, newkey)
   101  	case strings.HasSuffix(newkey, "/"):
   102  		return packageError.New("newkey cannot be a prefix")
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // TODO for now this method is duplicted in private/metainfo package, duplication will be removed later.
   109  func (project *Project) reencryptKeys(keys []metaclient.EncryptedKeyAndNonce, oldDerivedKey, newDerivedKey *storj.Key) ([]metaclient.EncryptedKeyAndNonce, error) {
   110  	cipherSuite := project.encryptionParameters.CipherSuite
   111  
   112  	newKeys := make([]metaclient.EncryptedKeyAndNonce, len(keys))
   113  	for i, oldKey := range keys {
   114  		// decrypt old key
   115  		contentKey, err := encryption.DecryptKey(oldKey.EncryptedKey, cipherSuite, oldDerivedKey, &oldKey.EncryptedKeyNonce)
   116  		if err != nil {
   117  			return nil, packageError.Wrap(err)
   118  		}
   119  
   120  		// create new random nonce and encrypt
   121  		var newEncryptedKeyNonce storj.Nonce
   122  		// generate random nonce for encrypting the content key
   123  		_, err = rand.Read(newEncryptedKeyNonce[:])
   124  		if err != nil {
   125  			return nil, packageError.Wrap(err)
   126  		}
   127  
   128  		newEncryptedKey, err := encryption.EncryptKey(contentKey, cipherSuite, newDerivedKey, &newEncryptedKeyNonce)
   129  		if err != nil {
   130  			return nil, packageError.Wrap(err)
   131  		}
   132  
   133  		newKeys[i] = metaclient.EncryptedKeyAndNonce{
   134  			Position:          oldKey.Position,
   135  			EncryptedKeyNonce: newEncryptedKeyNonce,
   136  			EncryptedKey:      newEncryptedKey,
   137  		}
   138  	}
   139  
   140  	return newKeys, nil
   141  }
   142  
   143  // TODO for now this method is duplicted in private/metainfo package, duplication will be removed later.
   144  func (project *Project) reencryptMetadataKey(encryptedMetadataKey []byte, encryptedMetadataKeyNonce storj.Nonce, oldDerivedKey, newDerivedKey *storj.Key) ([]byte, storj.Nonce, error) {
   145  	if len(encryptedMetadataKey) == 0 {
   146  		return nil, storj.Nonce{}, nil
   147  	}
   148  
   149  	cipherSuite := project.encryptionParameters.CipherSuite
   150  
   151  	// decrypt old metadata key
   152  	metadataContentKey, err := encryption.DecryptKey(encryptedMetadataKey, cipherSuite, oldDerivedKey, &encryptedMetadataKeyNonce)
   153  	if err != nil {
   154  		return nil, storj.Nonce{}, packageError.Wrap(err)
   155  	}
   156  
   157  	// encrypt metadata content key with new derived key and old nonce
   158  	newMetadataKeyNonce := encryptedMetadataKeyNonce
   159  	newMetadataEncryptedKey, err := encryption.EncryptKey(metadataContentKey, cipherSuite, newDerivedKey, &newMetadataKeyNonce)
   160  	if err != nil {
   161  		return nil, storj.Nonce{}, packageError.Wrap(err)
   162  	}
   163  
   164  	return newMetadataEncryptedKey, newMetadataKeyNonce, nil
   165  }