@@ -238,8 +238,13 @@ public async Task<IActionResult> SendPqcEncryptedEmail([FromBody] SendPqcEncrypt
238238
239239 // PHASE 2: Apply AES encryption to PQC-encrypted data
240240 _logger . LogInformation ( "Applying AES encryption to PQC data" ) ;
241- var aesSubject = await EncryptWithAESGCMAsync ( request . PqcEncryptedSubject ) ;
242- var aesBody = await EncryptWithAESGCMAsync ( request . PqcEncryptedBody ) ;
241+
242+ // Prepare PQC JSON envelopes for AES encryption (base64 encode to preserve structure)
243+ var pqcSubjectForAES = PreparePqcEnvelopeForAES ( request . PqcEncryptedSubject ) ;
244+ var pqcBodyForAES = PreparePqcEnvelopeForAES ( request . PqcEncryptedBody ) ;
245+
246+ var aesSubject = await EncryptWithAESGCMAsync ( pqcSubjectForAES ) ;
247+ var aesBody = await EncryptWithAESGCMAsync ( pqcBodyForAES ) ;
243248
244249 // PHASE 3: Apply OTP encryption to AES-encrypted data
245250 _logger . LogInformation ( "Applying OTP encryption to AES data" ) ;
@@ -257,10 +262,13 @@ public async Task<IActionResult> SendPqcEncryptedEmail([FromBody] SendPqcEncrypt
257262 // Step 1: Encrypt with PQC (ContentBase64 is already base64, use directly!)
258263 var pqcEnvelope = await EncryptSingleWithPQC3LayerAsync ( a . ContentBase64 , recipient . PqcPublicKey ) ;
259264
260- // Step 2: Wrap PQC envelope with AES (NEW architecture )
261- var aesEnvelope = await EncryptWithAESGCMAsync ( pqcEnvelope ) ;
265+ // Step 2: Prepare PQC JSON envelope for AES encryption (base64 encode to preserve structure )
266+ var pqcEnvelopeForAES = PreparePqcEnvelopeForAES ( pqcEnvelope ) ;
262267
263- // Step 3: Wrap AES envelope with OTP (NEW architecture)
268+ // Step 3: Wrap PQC envelope with AES (NEW architecture)
269+ var aesEnvelope = await EncryptWithAESGCMAsync ( pqcEnvelopeForAES ) ;
270+
271+ // Step 4: Wrap AES envelope with OTP (NEW architecture)
264272 var finalEnvelope = await EncryptBodyAsync ( aesEnvelope ) ;
265273 encrypted . Add ( new { fileName = a . FileName , contentType = a . ContentType , envelope = finalEnvelope } ) ;
266274 }
@@ -816,14 +824,19 @@ public async Task<IActionResult> DecryptToPqc(Guid emailId)
816824
817825 if ( ! subjectDecryptionFailed && TryParseAESEnvelope ( aesSubject , out var aesSubjectEnvelope ) )
818826 {
819- pqcSubject = await DecryptAESAsync ( aesSubjectEnvelope ) ;
827+ var aesSubjectResult = await DecryptAESAsync ( aesSubjectEnvelope ) ;
820828 // Check if AES decryption failed
821- if ( pqcSubject . StartsWith ( "AES decryption failed" ) )
829+ if ( aesSubjectResult . StartsWith ( "AES decryption failed" ) )
822830 {
823831 _logger . LogWarning ( "AES decryption failed for subject, using fallback message" ) ;
824832 pqcSubject = "[Decryption Failed - AES decryption error]" ;
825833 subjectDecryptionFailed = true ;
826834 }
835+ else
836+ {
837+ // Restore the PQC JSON envelope from AES decryption result
838+ pqcSubject = RestorePqcEnvelopeFromAES ( aesSubjectResult ) ;
839+ }
827840 }
828841 else
829842 {
@@ -836,14 +849,19 @@ public async Task<IActionResult> DecryptToPqc(Guid emailId)
836849
837850 if ( ! bodyDecryptionFailed && TryParseAESEnvelope ( aesBody , out var aesBodyEnvelope ) )
838851 {
839- pqcBody = await DecryptAESAsync ( aesBodyEnvelope ) ;
852+ var aesBodyResult = await DecryptAESAsync ( aesBodyEnvelope ) ;
840853 // Check if AES decryption failed
841- if ( pqcBody . StartsWith ( "AES decryption failed" ) )
854+ if ( aesBodyResult . StartsWith ( "AES decryption failed" ) )
842855 {
843856 _logger . LogWarning ( "AES decryption failed for body, using fallback message" ) ;
844857 pqcBody = "[Decryption Failed - AES decryption error. The encryption key for this message is no longer available.]" ;
845858 bodyDecryptionFailed = true ;
846859 }
860+ else
861+ {
862+ // Restore the PQC JSON envelope from AES decryption result
863+ pqcBody = RestorePqcEnvelopeFromAES ( aesBodyResult ) ;
864+ }
847865 }
848866 else
849867 {
@@ -893,15 +911,18 @@ public async Task<IActionResult> DecryptToPqc(Guid emailId)
893911 // Then decrypt AES layer to get PQC envelope
894912 if ( TryParseAESEnvelope ( aesEnvelope , out var aesEnvelopeObj ) )
895913 {
896- var pqcEnvelope = await DecryptAESAsync ( aesEnvelopeObj ) ;
914+ var aesDecryptedResult = await DecryptAESAsync ( aesEnvelopeObj ) ;
897915
898916 // Check if AES decryption failed
899- if ( pqcEnvelope . StartsWith ( "AES decryption failed" ) )
917+ if ( aesDecryptedResult . StartsWith ( "AES decryption failed" ) )
900918 {
901919 _logger . LogWarning ( "AES decryption failed for attachment {Index}: {FileName}, skipping" , idx , fileName ) ;
902920 continue ;
903921 }
904922
923+ // Restore the PQC JSON envelope from AES decryption result
924+ var pqcEnvelope = RestorePqcEnvelopeFromAES ( aesDecryptedResult ) ;
925+
905926 decryptedAttachments . Add ( new { fileName , contentType , pqcEnvelope } ) ;
906927 _logger . LogInformation ( "Successfully decrypted attachment {Index}: {FileName}" , idx , fileName ) ;
907928 }
@@ -1792,6 +1813,102 @@ private async Task<EncryptionResult> EncryptWithPQC3LayerAsync(string subject, s
17921813 return new EncryptionResult { SubjectEnvelope = subjectEnvelope , BodyEnvelope = bodyEnvelope , AttachmentsJson = attachmentsJson } ;
17931814 }
17941815
1816+ /// <summary>
1817+ /// Prepares PQC JSON envelope for AES encryption by base64 encoding it
1818+ /// This ensures the full PQC envelope is preserved through AES encryption/decryption
1819+ /// </summary>
1820+ /// <param name="pqcJsonEnvelope">PQC JSON envelope containing encrypted data</param>
1821+ /// <returns>Base64 encoded PQC JSON envelope for AES encryption</returns>
1822+ private string PreparePqcEnvelopeForAES ( string pqcJsonEnvelope )
1823+ {
1824+ try
1825+ {
1826+ _logger . LogInformation ( "Preparing PQC JSON envelope for AES encryption" ) ;
1827+
1828+ // Validate that the input is a valid JSON envelope
1829+ if ( string . IsNullOrWhiteSpace ( pqcJsonEnvelope ) )
1830+ {
1831+ _logger . LogWarning ( "Empty PQC envelope provided, returning as-is" ) ;
1832+ return pqcJsonEnvelope ;
1833+ }
1834+
1835+ // Quick validation - check if it's valid JSON
1836+ using var testDoc = JsonDocument . Parse ( pqcJsonEnvelope ) ;
1837+ if ( ! testDoc . RootElement . TryGetProperty ( "encryptedBody" , out _ ) )
1838+ {
1839+ _logger . LogWarning ( "PQC envelope missing 'encryptedBody' property, returning as-is" ) ;
1840+ return pqcJsonEnvelope ;
1841+ }
1842+
1843+ // Convert the JSON envelope to base64 so it can be safely encrypted with AES
1844+ // This preserves the entire PQC envelope structure through the AES layer
1845+ var base64Encoded = Convert . ToBase64String ( System . Text . Encoding . UTF8 . GetBytes ( pqcJsonEnvelope ) ) ;
1846+ _logger . LogInformation ( "Successfully prepared PQC envelope for AES encryption (size: {Size} chars)" , base64Encoded . Length ) ;
1847+ return base64Encoded ;
1848+ }
1849+ catch ( JsonException ex )
1850+ {
1851+ _logger . LogError ( ex , "Invalid JSON in PQC envelope, using original" ) ;
1852+ return pqcJsonEnvelope ;
1853+ }
1854+ catch ( Exception ex )
1855+ {
1856+ _logger . LogError ( ex , "Failed to prepare PQC envelope for AES, using original" ) ;
1857+ return pqcJsonEnvelope ; // Fallback to original if conversion fails
1858+ }
1859+ }
1860+
1861+ /// <summary>
1862+ /// Restores PQC JSON envelope from AES decryption result
1863+ /// This reverses the base64 encoding applied before AES encryption
1864+ /// </summary>
1865+ /// <param name="aesDecryptedResult">Base64 encoded PQC JSON envelope from AES decryption</param>
1866+ /// <returns>Original PQC JSON envelope</returns>
1867+ private string RestorePqcEnvelopeFromAES ( string aesDecryptedResult )
1868+ {
1869+ try
1870+ {
1871+ _logger . LogInformation ( "Restoring PQC JSON envelope from AES decryption result" ) ;
1872+
1873+ // Validate input
1874+ if ( string . IsNullOrWhiteSpace ( aesDecryptedResult ) )
1875+ {
1876+ _logger . LogWarning ( "Empty AES decryption result provided, returning as-is" ) ;
1877+ return aesDecryptedResult ;
1878+ }
1879+
1880+ // Convert base64 back to JSON envelope
1881+ var jsonBytes = Convert . FromBase64String ( aesDecryptedResult ) ;
1882+ var originalJsonEnvelope = System . Text . Encoding . UTF8 . GetString ( jsonBytes ) ;
1883+
1884+ // Validate that the result is valid JSON with expected structure
1885+ using var testDoc = JsonDocument . Parse ( originalJsonEnvelope ) ;
1886+ if ( ! testDoc . RootElement . TryGetProperty ( "encryptedBody" , out _ ) )
1887+ {
1888+ _logger . LogWarning ( "Restored envelope missing 'encryptedBody' property, using original" ) ;
1889+ return aesDecryptedResult ;
1890+ }
1891+
1892+ _logger . LogInformation ( "Successfully restored PQC envelope from AES decryption (size: {Size} chars)" , originalJsonEnvelope . Length ) ;
1893+ return originalJsonEnvelope ;
1894+ }
1895+ catch ( FormatException ex )
1896+ {
1897+ _logger . LogError ( ex , "Invalid base64 format in AES decryption result, using original" ) ;
1898+ return aesDecryptedResult ;
1899+ }
1900+ catch ( JsonException ex )
1901+ {
1902+ _logger . LogError ( ex , "Invalid JSON after base64 decode, using original" ) ;
1903+ return aesDecryptedResult ;
1904+ }
1905+ catch ( Exception ex )
1906+ {
1907+ _logger . LogError ( ex , "Failed to restore PQC envelope from AES, using original: {Error}" , ex . Message ) ;
1908+ return aesDecryptedResult ; // Fallback to original if conversion fails
1909+ }
1910+ }
1911+
17951912 private async Task < string > EncryptWithAESGCMAsync ( string plaintext )
17961913 {
17971914 return await RetryAsync ( async ( ) =>
0 commit comments