@@ -1240,6 +1240,7 @@ impl EncryptedClient {
12401240 ///
12411241 /// # Note
12421242 /// Handles both single-block and chunked files automatically.
1243+ /// Uses nonce from share token if available, otherwise falls back to metadata headers.
12431244 pub async fn get_object_with_share (
12441245 & self ,
12451246 bucket : & str ,
@@ -1269,7 +1270,35 @@ impl EncryptedClient {
12691270 ) ) ;
12701271 }
12711272
1272- // Fetch the object
1273+ // Check if share token includes encryption metadata (nonce or chunked info)
1274+ // If so, we can decrypt using just the raw data without needing S3 metadata headers
1275+ if accepted_share. chunked_metadata . is_some ( ) {
1276+ // CHUNKED FILE: Use chunked metadata from share token
1277+ return self . get_object_chunked_with_share_token ( bucket, storage_key, accepted_share) . await ;
1278+ }
1279+
1280+ if let Some ( ref nonce_b64) = accepted_share. nonce {
1281+ // SINGLE FILE: Use nonce from share token - fetch raw data only
1282+ let data = self . inner . get_object ( bucket, storage_key) . await ?;
1283+
1284+ let nonce_bytes = base64:: Engine :: decode (
1285+ & base64:: engine:: general_purpose:: STANDARD ,
1286+ nonce_b64,
1287+ ) . map_err ( |e| ClientError :: Encryption (
1288+ fula_crypto:: CryptoError :: Decryption ( format ! ( "Invalid nonce in share token: {}" , e) )
1289+ ) ) ?;
1290+ let nonce = Nonce :: from_bytes ( & nonce_bytes)
1291+ . map_err ( ClientError :: Encryption ) ?;
1292+
1293+ let aead = Aead :: new_default ( & accepted_share. dek ) ;
1294+ let plaintext = aead. decrypt ( & nonce, & data)
1295+ . map_err ( ClientError :: Encryption ) ?;
1296+
1297+ return Ok ( Bytes :: from ( plaintext) ) ;
1298+ }
1299+
1300+ // FALLBACK: Share token doesn't have encryption metadata, try to get it from S3 headers
1301+ // This is for backwards compatibility with old share tokens
12731302 let result = self . inner . get_object_with_metadata ( bucket, storage_key) . await ?;
12741303
12751304 // Check if encrypted
@@ -1288,11 +1317,11 @@ impl EncryptedClient {
12881317 . map ( |v| v == "true" )
12891318 . unwrap_or ( false ) ;
12901319
1291- // Parse encryption metadata
1320+ // Parse encryption metadata from S3 headers
12921321 let enc_metadata_str = result. metadata
12931322 . get ( "x-fula-encryption" )
12941323 . ok_or_else ( || ClientError :: Encryption (
1295- fula_crypto:: CryptoError :: Decryption ( "Missing encryption metadata" . to_string ( ) )
1324+ fula_crypto:: CryptoError :: Decryption ( "Missing encryption metadata (not in share token or S3 headers) " . to_string ( ) )
12961325 ) ) ?;
12971326
12981327 let enc_metadata: serde_json:: Value = serde_json:: from_str ( enc_metadata_str)
@@ -1302,12 +1331,12 @@ impl EncryptedClient {
13021331
13031332 if is_chunked {
13041333 // 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
1334+ self . get_object_chunked_with_share_metadata ( bucket, storage_key, & enc_metadata, & accepted_share. dek ) . await
13061335 } else {
13071336 // SINGLE OBJECT: Decrypt directly
13081337 let nonce_b64 = enc_metadata[ "nonce" ] . as_str ( )
13091338 . ok_or_else ( || ClientError :: Encryption (
1310- fula_crypto:: CryptoError :: Decryption ( "Missing nonce" . to_string ( ) )
1339+ fula_crypto:: CryptoError :: Decryption ( "Missing nonce in encryption metadata " . to_string ( ) )
13111340 ) ) ?;
13121341 let nonce_bytes = base64:: Engine :: decode (
13131342 & base64:: engine:: general_purpose:: STANDARD ,
@@ -1327,8 +1356,46 @@ impl EncryptedClient {
13271356 }
13281357 }
13291358
1330- /// Internal: Download and decrypt a chunked file using a share's DEK
1331- async fn get_object_chunked_with_share (
1359+ /// Internal: Download and decrypt a chunked file using metadata from share token
1360+ async fn get_object_chunked_with_share_token (
1361+ & self ,
1362+ bucket : & str ,
1363+ storage_key : & str ,
1364+ accepted_share : & AcceptedShare ,
1365+ ) -> Result < Bytes > {
1366+ let chunked_json = accepted_share. chunked_metadata . as_ref ( )
1367+ . ok_or_else ( || ClientError :: Encryption (
1368+ fula_crypto:: CryptoError :: Decryption ( "Missing chunked metadata in share token" . to_string ( ) )
1369+ ) ) ?;
1370+
1371+ let chunked_meta: ChunkedFileMetadata = serde_json:: from_str ( chunked_json)
1372+ . map_err ( |e| ClientError :: Encryption (
1373+ fula_crypto:: CryptoError :: Decryption ( format ! ( "Invalid chunked metadata in share token: {}" , e) )
1374+ ) ) ?;
1375+
1376+ // Create decoder
1377+ let mut decoder = fula_crypto:: ChunkedDecoder :: new ( accepted_share. dek . clone ( ) , chunked_meta. clone ( ) ) ;
1378+
1379+ // Pre-allocate result buffer
1380+ let mut plaintext = Vec :: with_capacity ( chunked_meta. total_size as usize ) ;
1381+
1382+ // Download and decrypt each chunk in order
1383+ for chunk_index in 0 ..chunked_meta. num_chunks {
1384+ let chunk_key = ChunkedFileMetadata :: chunk_key ( storage_key, chunk_index) ;
1385+
1386+ let chunk_result = self . inner . get_object ( bucket, & chunk_key) . await ?;
1387+
1388+ let chunk_plaintext = decoder. decrypt_chunk ( chunk_index, & chunk_result)
1389+ . map_err ( ClientError :: Encryption ) ?;
1390+
1391+ plaintext. extend_from_slice ( & chunk_plaintext) ;
1392+ }
1393+
1394+ Ok ( Bytes :: from ( plaintext) )
1395+ }
1396+
1397+ /// Internal: Download and decrypt a chunked file using metadata from S3 headers
1398+ async fn get_object_chunked_with_share_metadata (
13321399 & self ,
13331400 bucket : & str ,
13341401 storage_key : & str ,
0 commit comments