storj.io/uplink@v1.13.0/private/multipart/multipart.go (about) 1 // Copyright (C) 2022 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package multipart 5 6 import ( 7 "context" 8 "crypto/rand" 9 "time" 10 _ "unsafe" // for go:linkname 11 12 "github.com/spacemonkeygo/monkit/v3" 13 "github.com/zeebo/errs" 14 15 "storj.io/common/base58" 16 "storj.io/common/encryption" 17 "storj.io/common/paths" 18 "storj.io/common/pb" 19 "storj.io/common/storj" 20 "storj.io/uplink" 21 "storj.io/uplink/private/metaclient" 22 ) 23 24 var mon = monkit.Package() 25 26 // UploadOptions contains additional options for uploading. 27 type UploadOptions struct { 28 // When Expires is zero, there is no expiration. 29 Expires time.Time 30 31 CustomMetadata uplink.CustomMetadata 32 } 33 34 // BeginUpload begins a new multipart upload to bucket and key. 35 // 36 // Use project.UploadPart to upload individual parts. 37 // 38 // Use project.CommitUpload to finish the upload. 39 // 40 // Use project.AbortUpload to cancel the upload at any time. 41 // 42 // UploadObject is a convenient way to upload single part objects. 43 func BeginUpload(ctx context.Context, project *uplink.Project, bucket, key string, options *UploadOptions) (info uplink.UploadInfo, err error) { 44 defer mon.Task()(&ctx)(&err) 45 46 switch { 47 case bucket == "": 48 return uplink.UploadInfo{}, convertKnownErrors(metaclient.ErrNoBucket.New(""), bucket, key) 49 case key == "": 50 return uplink.UploadInfo{}, convertKnownErrors(metaclient.ErrNoPath.New(""), bucket, key) 51 } 52 53 if options == nil { 54 options = &UploadOptions{} 55 } 56 57 encPath, err := encryptPath(project, bucket, key) 58 if err != nil { 59 return uplink.UploadInfo{}, convertKnownErrors(err, bucket, key) 60 } 61 62 metainfoClient, err := dialMetainfoClient(ctx, project) 63 if err != nil { 64 return uplink.UploadInfo{}, convertKnownErrors(err, bucket, key) 65 } 66 defer func() { err = errs.Combine(err, metainfoClient.Close()) }() 67 68 metadata, err := encryptMetadata(project, bucket, key, options.CustomMetadata) 69 if err != nil { 70 return uplink.UploadInfo{}, convertKnownErrors(err, bucket, key) 71 } 72 73 response, err := metainfoClient.BeginObject(ctx, metaclient.BeginObjectParams{ 74 Bucket: []byte(bucket), 75 EncryptedObjectKey: []byte(encPath.Raw()), 76 ExpiresAt: options.Expires, 77 EncryptionParameters: encryptionParameters(project), 78 79 EncryptedMetadata: metadata.EncryptedContent, 80 EncryptedMetadataEncryptedKey: metadata.EncryptedKey, 81 EncryptedMetadataNonce: metadata.EncryptedKeyNonce, 82 }) 83 if err != nil { 84 return uplink.UploadInfo{}, convertKnownErrors(err, bucket, key) 85 } 86 87 encodedStreamID := base58.CheckEncode(response.StreamID[:], 1) 88 return uplink.UploadInfo{ 89 Key: key, 90 UploadID: encodedStreamID, 91 System: uplink.SystemMetadata{ 92 Expires: options.Expires, 93 }, 94 Custom: options.CustomMetadata, 95 }, nil 96 } 97 98 type encryptedMetadata struct { 99 EncryptedContent []byte 100 EncryptedKey []byte 101 EncryptedKeyNonce storj.Nonce 102 } 103 104 func encryptMetadata(project *uplink.Project, bucket, key string, metadata uplink.CustomMetadata) (encryptedMetadata, error) { 105 if len(metadata) == 0 { 106 return encryptedMetadata{}, nil 107 } 108 109 metadataBytes, err := pb.Marshal(&pb.SerializableMeta{ 110 UserDefined: metadata.Clone(), 111 }) 112 if err != nil { 113 return encryptedMetadata{}, errs.Wrap(err) 114 } 115 116 streamInfo, err := pb.Marshal(&pb.StreamInfo{ 117 Metadata: metadataBytes, 118 }) 119 if err != nil { 120 return encryptedMetadata{}, errs.Wrap(err) 121 } 122 123 derivedKey, err := deriveContentKey(project, bucket, key) 124 if err != nil { 125 return encryptedMetadata{}, errs.Wrap(err) 126 } 127 128 var metadataKey storj.Key 129 // generate random key for encrypting the segment's content 130 _, err = rand.Read(metadataKey[:]) 131 if err != nil { 132 return encryptedMetadata{}, errs.Wrap(err) 133 } 134 135 var encryptedKeyNonce storj.Nonce 136 // generate random nonce for encrypting the metadata key 137 _, err = rand.Read(encryptedKeyNonce[:]) 138 if err != nil { 139 return encryptedMetadata{}, errs.Wrap(err) 140 } 141 142 encryptionParameters := encryptionParameters(project) 143 encryptedKey, err := encryption.EncryptKey(&metadataKey, encryptionParameters.CipherSuite, derivedKey, &encryptedKeyNonce) 144 if err != nil { 145 return encryptedMetadata{}, errs.Wrap(err) 146 } 147 148 // encrypt metadata with the content encryption key and zero nonce. 149 encryptedStreamInfo, err := encryption.Encrypt(streamInfo, encryptionParameters.CipherSuite, &metadataKey, &storj.Nonce{}) 150 if err != nil { 151 return encryptedMetadata{}, errs.Wrap(err) 152 } 153 154 // TODO should we commit StreamMeta or commit only encrypted StreamInfo 155 streamMetaBytes, err := pb.Marshal(&pb.StreamMeta{ 156 EncryptedStreamInfo: encryptedStreamInfo, 157 }) 158 if err != nil { 159 return encryptedMetadata{}, errs.Wrap(err) 160 } 161 162 return encryptedMetadata{ 163 EncryptedContent: streamMetaBytes, 164 EncryptedKey: encryptedKey, 165 EncryptedKeyNonce: encryptedKeyNonce, 166 }, nil 167 } 168 169 //go:linkname convertKnownErrors storj.io/uplink.convertKnownErrors 170 func convertKnownErrors(err error, bucket, key string) error 171 172 //go:linkname dialMetainfoClient storj.io/uplink.dialMetainfoClient 173 func dialMetainfoClient(ctx context.Context, project *uplink.Project) (_ *metaclient.Client, err error) 174 175 //go:linkname encryptionParameters storj.io/uplink.encryptionParameters 176 func encryptionParameters(project *uplink.Project) storj.EncryptionParameters 177 178 //go:linkname encryptPath storj.io/uplink.encryptPath 179 func encryptPath(project *uplink.Project, bucket, key string) (paths.Encrypted, error) 180 181 //go:linkname deriveContentKey storj.io/uplink.deriveContentKey 182 func deriveContentKey(project *uplink.Project, bucket, key string) (*storj.Key, error)