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 }