@@ -1225,18 +1225,21 @@ impl EncryptedClient {
12251225 // ═══════════════════════════════════════════════════════════════════════════
12261226
12271227 /// Get and decrypt an object using a ShareToken
1228- ///
1228+ ///
12291229 /// This allows recipients of a share to read encrypted objects without
12301230 /// having the owner's keys. The ShareToken contains a wrapped DEK that
12311231 /// was encrypted for the recipient's public key.
1232- ///
1232+ ///
12331233 /// # Arguments
12341234 /// * `bucket` - The bucket containing the object
12351235 /// * `storage_key` - The storage key of the object
12361236 /// * `accepted_share` - An accepted share containing the DEK
1237- ///
1237+ ///
12381238 /// # Returns
12391239 /// The decrypted object data
1240+ ///
1241+ /// # Note
1242+ /// Handles both single-block and chunked files automatically.
12401243 pub async fn get_object_with_share (
12411244 & self ,
12421245 bucket : & str ,
@@ -1268,7 +1271,7 @@ impl EncryptedClient {
12681271
12691272 // Fetch the object
12701273 let result = self . inner . get_object_with_metadata ( bucket, storage_key) . await ?;
1271-
1274+
12721275 // Check if encrypted
12731276 let is_encrypted = result. metadata
12741277 . get ( "x-fula-encrypted" )
@@ -1279,6 +1282,12 @@ impl EncryptedClient {
12791282 return Ok ( result. data ) ;
12801283 }
12811284
1285+ // Check if this is a chunked object
1286+ let is_chunked = result. metadata
1287+ . get ( "x-fula-chunked" )
1288+ . map ( |v| v == "true" )
1289+ . unwrap_or ( false ) ;
1290+
12821291 // Parse encryption metadata
12831292 let enc_metadata_str = result. metadata
12841293 . get ( "x-fula-encryption" )
@@ -1291,24 +1300,65 @@ impl EncryptedClient {
12911300 fula_crypto:: CryptoError :: Decryption ( e. to_string ( ) )
12921301 ) ) ?;
12931302
1294- // Extract nonce
1295- let nonce_b64 = enc_metadata[ "nonce" ] . as_str ( )
1296- . ok_or_else ( || ClientError :: Encryption (
1297- fula_crypto:: CryptoError :: Decryption ( "Missing nonce" . to_string ( ) )
1303+ if is_chunked {
1304+ // CHUNKED DOWNLOAD: Download and decrypt each chunk using the share's DEK
1305+ self . get_object_chunked_with_share ( bucket, storage_key, & enc_metadata, & accepted_share. dek ) . await
1306+ } else {
1307+ // SINGLE OBJECT: Decrypt directly
1308+ let nonce_b64 = enc_metadata[ "nonce" ] . as_str ( )
1309+ . ok_or_else ( || ClientError :: Encryption (
1310+ fula_crypto:: CryptoError :: Decryption ( "Missing nonce" . to_string ( ) )
1311+ ) ) ?;
1312+ let nonce_bytes = base64:: Engine :: decode (
1313+ & base64:: engine:: general_purpose:: STANDARD ,
1314+ nonce_b64,
1315+ ) . map_err ( |e| ClientError :: Encryption (
1316+ fula_crypto:: CryptoError :: Decryption ( e. to_string ( ) )
12981317 ) ) ?;
1299- let nonce_bytes = base64:: Engine :: decode (
1300- & base64:: engine:: general_purpose:: STANDARD ,
1301- nonce_b64,
1318+ let nonce = Nonce :: from_bytes ( & nonce_bytes)
1319+ . map_err ( ClientError :: Encryption ) ?;
1320+
1321+ // Use the DEK from the accepted share (already decrypted for recipient)
1322+ let aead = Aead :: new_default ( & accepted_share. dek ) ;
1323+ let plaintext = aead. decrypt ( & nonce, & result. data )
1324+ . map_err ( ClientError :: Encryption ) ?;
1325+
1326+ Ok ( Bytes :: from ( plaintext) )
1327+ }
1328+ }
1329+
1330+ /// Internal: Download and decrypt a chunked file using a share's DEK
1331+ async fn get_object_chunked_with_share (
1332+ & self ,
1333+ bucket : & str ,
1334+ storage_key : & str ,
1335+ enc_metadata : & serde_json:: Value ,
1336+ dek : & fula_crypto:: keys:: DekKey ,
1337+ ) -> Result < Bytes > {
1338+ // Parse chunked metadata
1339+ let chunked_meta: ChunkedFileMetadata = serde_json:: from_value (
1340+ enc_metadata[ "chunked" ] . clone ( )
13021341 ) . map_err ( |e| ClientError :: Encryption (
1303- fula_crypto:: CryptoError :: Decryption ( e . to_string ( ) )
1342+ fula_crypto:: CryptoError :: Decryption ( format ! ( "Invalid chunked metadata: {}" , e ) )
13041343 ) ) ?;
1305- let nonce = Nonce :: from_bytes ( & nonce_bytes)
1306- . map_err ( ClientError :: Encryption ) ?;
13071344
1308- // Use the DEK from the accepted share (already decrypted for recipient)
1309- let aead = Aead :: new_default ( & accepted_share. dek ) ;
1310- let plaintext = aead. decrypt ( & nonce, & result. data )
1311- . map_err ( ClientError :: Encryption ) ?;
1345+ // Create decoder
1346+ let mut decoder = fula_crypto:: ChunkedDecoder :: new ( dek. clone ( ) , chunked_meta. clone ( ) ) ;
1347+
1348+ // Pre-allocate result buffer
1349+ let mut plaintext = Vec :: with_capacity ( chunked_meta. total_size as usize ) ;
1350+
1351+ // Download and decrypt each chunk in order
1352+ for chunk_index in 0 ..chunked_meta. num_chunks {
1353+ let chunk_key = ChunkedFileMetadata :: chunk_key ( storage_key, chunk_index) ;
1354+
1355+ let chunk_result = self . inner . get_object ( bucket, & chunk_key) . await ?;
1356+
1357+ let chunk_plaintext = decoder. decrypt_chunk ( chunk_index, & chunk_result)
1358+ . map_err ( ClientError :: Encryption ) ?;
1359+
1360+ plaintext. extend_from_slice ( & chunk_plaintext) ;
1361+ }
13121362
13131363 Ok ( Bytes :: from ( plaintext) )
13141364 }
0 commit comments