简体   繁体   中英

C# How to S/Mime in Asp.net 5 with Bouncy Castle instead of x509certificate2

I successfully used the below code to send an S/Mime e-mail on latest Windows Server, but it fails on 2012 Windows Server. I think 2012 does not support newer AES encryption.

How would I rewrite the following code to replace System.Security.Cryptography.X509Certificates.X509Certificate2() with Bouncy Castle, also using MimeKit and Mailkit? I tried using older DES3 for 2012, but it fails with CryptoThrowHelper+WindowsCryptographicException: The specified.network password is not correct. Exactly the same code runs just fine on Windows 10 on my developer machine. Ultimately I want this to run in a Linux container, plus I think Bouncy Castle is much more fun to type and say. :)

var message = new MimeMessage(); // Using MimeKit, MailKit 
message.To.Add( new MailboxAddress("John Doe","jdoe@somewhere.com"));
message.From.Add( new MailboxAddress("Jane Doe","jndoe@somewhere.com"));
message.Headers.Add( "AS3-From", "PILM");
message.Headers.Add( "AS3-To", "SARS");
message.Date = DateTimeOffset.Now;
message.MessageId = "42";
message.Subject = "This is a subject";
message.Body = new TextPart("html") { Text = "This is a body" };

using (var context = new TemporarySecureMimeContext())
{   // TODO: Replace Microsoft Cryptography with BouncyCastle
    var cert = new System.Security.Cryptography
        .X509Certificates.X509Certificate2(
        @"c:\security\smime.p12", "VeryCoolPassword",
        System.Security.Cryptography.X509Certificates
            .X509KeyStorageFlags.EphemeralKeySet);

    var recip = new CmsRecipient(cert) {
        EncryptionAlgorithms = new EncryptionAlgorithm[] { 
            EncryptionAlgorithm.TripleDes }
    };
    var recips = new CmsRecipientCollection();
    recips.Add(recip);

    message.Body = ApplicationPkcs7Mime.Encrypt(
        context, recips, message.Body );
};

var client = new SmtpClient();
client.Connect("MySuperEmailServer", 465, true);
client.Authenticate("MySuperUserName", "VeryCoolPassword");
client.Send(message);
client.Disconnect(true);
client.Dispose();

There seems to be several parts to this question.

First, you want to know how to load a S/MIME certificate using BouncyCastle. Since, as Crypt32 pointed out, you aren't likely to need to load *.pfx (aka *.p12) files since you wouldn't have their private key, you'll likely need to load a *.cer (or *.crt) file:

using (var stream = File.OpenRead ("smime-recipient-certificate.cer")) {
    var parser = new X509CertificateParser ();

    var certificate = parser.ReadCertificate (stream);
}

MimeKit's CmsRecipient class will do this for you if you call the CmsRecipient.ctor that takes a fileName argument.

That said, it never hurts to know how to load it manually in case you maybe don't have it stored on disk somewhere (eg maybe a MemoryStream or something).

The CmsRecipient.ctor that takes an X509Certificate2 (that you are currently using) is also fine to use. MimeKit just converts the X509Certificate2 certificate into a BouncyCastle certificate internally.

That said, based on your conversation with Crypt32, it sounds like the issue you were having is with loading an X509Certificate2 from a.pfx file on Windows Server 2012? In which case, loading the certificate using BouncyCastle is probably the way to go.

If you need or want to know how to load a.pfx file using BouncyCastle, you can do that using the following logic:

// Note: BouncyCastle's X509Certificate class is named X509Certificate, not to be
// confused with System.Security.Cryptography.X509Certificates.X509Certificate.
X509Certificate LoadPfx (Stream stream, string password, out AsymmetricKeyParameter privateKey)
{
    var pkcs12 = new Pkcs12Store (stream, password.ToCharArray ());

    foreach (string alias in pkcs12.Aliases) {
        if (!pkcs12.IsKeyEntry (alias))
            continue;

        var chain = pkcs12.GetCertificateChain (alias);

        if (chain.Length == 0)
            continue;

        var keyEntry = pkcs12.GetKey (alias);

        if (!keyEntry.Key.IsPrivate)
            continue;

        privateKey = keyEntry.Key;

        return chain[0].Certificate;
    }

    throw Exception ("Did not find a certificate with a private key.");
}

Note: I would probably suggest changing this:

message.MessageId = "42";

to this:

message.MessageId = MimeUtils.GenerateMessageId();

This will generate a random/unique message-id value using the correct syntax (The Message-Id header isn't a number, it's a xyz@abc.com type value).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM