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 (e.g. 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).