0

We have a scenario to decrypt emails which has attachments. We are using mimekit library for the same. We also use mimekit for the email encryption and it works properly.

In our case the encrypted email is only having an attachment, no such email body. There is an Azure Logic App which gets the encrypted email from an Oiifce365 mailbox (using the built-in connector) and then it send the details to an Azure Function App which runs the decryption logic. The decryption certificate is stored in Azure Key Vault.

Below is the code we tried, and it shows exception saying

Unable to cast object of type 'Org.BouncyCastle.Asn1.DerApplicationSpecific' to type 'Org.BouncyCastle.Asn1.Asn1SequenceParser'.

[FunctionName("DecryptSMIME")]
public static async Task<IActionResult> Decrypt([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            try
            {
                var temporarySecurityMimeContext = new TemporarySecureMimeContext();

                // get decryption Cert pfx
                var keyVaultClient = ServiceProvider.GetRequiredService<IKeyVaultClient>();
                var decryptionCertBundle = keyVaultClient.GetSecretAsync("https://my-key-vault.vault.azure.net/secrets/Decryption-Certificate-Base64/d7a84b415a494c1ebaseae88cff50028").Result;
                var decryptionCertBytes = Convert.FromBase64String(decryptionCertBundle.Value);
                log.LogInformation($"Decoded length of decryption certificate: '{decryptionCertBytes.Length}'");

                // get decryption Cert password
                var decryptionCertPasswordBundle = keyVaultClient.GetSecretAsync("https://my-key-vault.vault.azure.net/secrets/Decryption-Certificate-Pass/34judc9f575f467a96d9483dfc8kf467").Result;
                var decryptionCertPassword = decryptionCertPasswordBundle.Value;

                using var stream = new MemoryStream(decryptionCertBytes);
                temporarySecurityMimeContext.Import(stream, decryptionCertPassword);
                log.LogInformation("Imported The Decryption certificate as MemoryStream");

                using var encryptedContentStream = await GetMailAttachmentStreamAsync(req.Body, log) ;
                log.LogInformation("Loading pkcs7-mime entity.");
                ApplicationPkcs7Mime encryptedContent = (ApplicationPkcs7Mime)await MimeEntity.LoadAsync(ParserOptions.Default, ContentType.Parse(ParserOptions.Default, "application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m"), encryptedContentStream);

                log.LogInformation("Decrypting pkcs7-mime entity.");
                MimeEntity decryptedContent = encryptedContent.Decrypt();
                
                return new OkObjectResult("OK");
            }
            catch (Exception ex)
            {
                log.LogError(ex, "Failed to decrypt the secure mime part in the request body.");

                throw;
            }
        }

     private static async Task<MemoryStream> GetMailAttachmentStreamAsync(Stream attachmentObjectStream, ILogger log)
        {
            var memoryStream = new MemoryStream();
            await attachmentObjectStream.CopyToAsync(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);
            log.LogInformation($"Attachment Stream Processed. {memoryStream.Length} Bytes");
            return memoryStream;
        }

The certifcate is loading successfully. Also the email stream shows some data. However while running the decryption, it alsways shows the error. Any help will be helpful.

phuzi
  • 12,078
  • 3
  • 26
  • 50
skjcyber
  • 5,759
  • 12
  • 40
  • 60
  • A mime attachment starts with two dashes and then the mime data. There is a header on the first line with the type. It is possible that the data is a Base64 string. Best thing to do is try to capture the type of mime to see if it may be Base64 or something else. Also test your decrypt code on the send side to make sure it work. – jdweng Feb 23 '21 at 07:31
  • Where is the exception thrown? That makes a HUGE difference in tracking this down. – jstedfast Mar 02 '21 at 18:31

1 Answers1

1

Below is your code with some fixes (namely getting rid of code like MethodAsync(...).Result which is bad practice).

I've also taken the liberty to add a big comment asking for more information.

[FunctionName("DecryptSMIME")]
public static async Task<IActionResult> Decrypt([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
{
    try
    {
        using var temporarySecurityMimeContext = new TemporarySecureMimeContext();

        // get decryption Cert pfx
        var keyVaultClient = ServiceProvider.GetRequiredService<IKeyVaultClient>();
        var decryptionCertBundle = await keyVaultClient.GetSecretAsync("https://my-key-vault.vault.azure.net/secrets/Decryption-Certificate-Base64/d7a84b415a494c1ebaseae88cff50028");
        var decryptionCertBytes = Convert.FromBase64String(decryptionCertBundle.Value);
        log.LogInformation($"Decoded length of decryption certificate: '{decryptionCertBytes.Length}'");

        // get decryption Cert password
        var decryptionCertPasswordBundle = await keyVaultClient.GetSecretAsync("https://my-key-vault.vault.azure.net/secrets/Decryption-Certificate-Pass/34judc9f575f467a96d9483dfc8kf467");
        var decryptionCertPassword = decryptionCertPasswordBundle.Value;

        using var stream = new MemoryStream(decryptionCertBytes);
        temporarySecurityMimeContext.Import(stream, decryptionCertPassword);
        log.LogInformation("Imported The Decryption certificate as MemoryStream");

        using var encryptedContentStream = await GetMailAttachmentStreamAsync(req.Body, log) ;
        log.LogInformation("Loading pkcs7-mime entity.");

        // Ideally, you would not use the MimeEntity.LoadAsync() method that takes a
        // forged ContentType parameter. This is a huge hack and *may* be the cause
        // of your problem. In other words, the content that MimeKit is trying to
        // decrypt may be in the wrong format. To know for certain, I would need to
        // know what the HttpRequest headers and Body look like.
        //
        // I would probably recommend that your code that sends this request be
        // modified to send the entire raw MIME (i.e. including headers) of the
        // application/pkcs7-mime part as the HTTP request body instead so that you
        // would not need to forge the Content-Type header.
        ApplicationPkcs7Mime encryptedContent = (ApplicationPkcs7Mime)await MimeEntity.LoadAsync(ParserOptions.Default, ContentType.Parse(ParserOptions.Default, "application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m"), encryptedContentStream);

        log.LogInformation("Decrypting pkcs7-mime entity.");
        MimeEntity decryptedContent = encryptedContent.Decrypt();
            
        return new OkObjectResult("OK");
    }
    catch (Exception ex)
    {
        log.LogError(ex, "Failed to decrypt the secure mime part in the request body.");

        throw;
    }
}

private static async Task<MemoryStream> GetMailAttachmentStreamAsync(Stream attachmentObjectStream, ILogger log)
{
    var memoryStream = new MemoryStream();
    await attachmentObjectStream.CopyToAsync(memoryStream);
    memoryStream.Seek(0, SeekOrigin.Begin);
    log.LogInformation($"Attachment Stream Processed. {memoryStream.Length} Bytes");
    return memoryStream;
}
jstedfast
  • 35,744
  • 5
  • 97
  • 110
  • the logic app might not be sending the raw mime. I will try the suggestions – skjcyber Mar 04 '21 at 22:14
  • Now, I removed the content type parameter from MimeEntity.LoadAsync() method. But while converting to ApplicationPkcs7Mime, I am getting the exception: Unable to cast object of type 'MimeKit.TextPart' to type 'MimeKit.Cryptography.ApplicationPkcs7Mime'. below is data that the client code is sending in a json format (the client code reads these from an email) "Subject": "Decryption email test [sign] [encrypt]", "attachmentContent": "0��\u000f-LotsOfJunkCharacters", "attachmentMediaType": "application/pkcs7-mime", "attachmentName": "smime.p7m", "body": "" – skjcyber Mar 26 '21 at 15:25
  • Looks like the attachmentContent json property is coming across as a string. That's not going to work well because a text encoding conversion needs to be done in order to make a byte stream into a string. Can you fix the json converter to use base64 strings for the attachmentContent? As far as the TextPart conversion goes, how are you creating the stream that gets loaded by MimeEntity.Load()? I'll update my answer showing how to do it correctly. – jstedfast Mar 26 '21 at 16:07
  • 1
    Nevermind, just use the LoadAsync() method that takes a ContentType parameter like you were doing before. When I recommended avoiding that method, I had meant that you should parse the HTTP response headers combined with the body of the HTTP response, but if you are having to construct this based on JSON, then that isn't an option and what you were doing before is the only thing you can realistically do. That said, I would still recommend sending the json attachmentContent value as base64 and then using `Convert.FromBase64String()` to get the raw bytes of the MIME part content. – jstedfast Mar 26 '21 at 16:25
  • 1
    The reason you are getting the TextPart conversion error is because you are telling MimeKit to parse a stream that doesn't include any MIME headers (e.g. the Content-Type header) and so it assumes that the content is text and not application/pkcs7-mime. – jstedfast Mar 26 '21 at 16:26
  • It is working fine now.. after sending the content-type header and using the LoadAsync() with a ContentType parameter... thanks – skjcyber Apr 20 '21 at 19:27