加入憑證簽章的郵件必須使用 S/MIME 標準,之前為了讓系統發出的郵件可以加入憑證簽章費了好一番功夫,有鑑於此需求非常罕見,在國內我是沒找到任何相關資料,即便在國外的網站相關資訊也很少,所以特此紀錄一下當時研究的過程。
要發送 S/MIME 郵件至少必須有以下條件先成立:
- 取得一個 PKCS#12 憑證檔 ( *.pfx ),這個憑證可以到相關單位申請「電子郵件專用」憑證
- 要在 ASP.NET 應用程式中使用憑證必須再參考我之前寫的幾篇文章:
然後就可以開始寫程式了,共有 7 大步驟:
1. 先在專案中加入組件參考:System.Security
2. 設定 using 以下兩個命名空間
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
3. 準備郵件內容
string EmailBody = "XXXXX";
StringBuilder sb = new StringBuilder();
sb.AppendLine("Content-Type: text/html; charset=\"big5\"");
sb.AppendLine("Content-Transfer-Encoding: 8bit");
sb.AppendLine();
sb.AppendLine(EmailContent.ToString());
byte[] data = Encoding.GetEncoding("Big5").GetBytes(sb.ToString());
4. 替郵件內容做簽章 ( 重點程式 )
4.1 一般 .Net 的寫法
string pfxPath = "test.pfx";
string pfxPassword = "test";
X509Certificate2 certificate = new X509Certificate2(pfxPath, pfxPassword);
ContentInfo content = new ContentInfo(data);
SignedCms signedCms = new SignedCms(content, false);
CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, certificate);
signedCms.ComputeSignature(signer);
byte[] signedbytes = signedCms.Encode();
4.2 在 ASP.NET 中的寫法 ( 憑證必須先匯入本機憑證儲存區 )
X509Store store = new X509Store("My", StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2 signCert = store
.Certificates.Find(X509FindType.FindBySubjectName, "憑證名稱", false)[0];
SignedCms signedCms = new SignedCms(new ContentInfo(data), false);
CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, signCert);
signedCms.ComputeSignature(signer);
byte[] signedbytes = signedCms.Encode();
5. 準備郵件內容(MailMessage)
MailMessage msg = new MailMessage();
msg.From = new MailAddress("test@example.com");
msg.To.Add(new MailAddress("test@example.com"));
msg.Subject = "test s/mime";
// 千萬不要加上 msg.Body 內容,否則驗證 S/MIME 郵件時會失敗
// msg.Body = EmailBody;
6. 將簽章的內容加入到訊息的另一個 View 中 ( AlternateView ),這樣可以讓「可以讀取 S/MIME 郵件」的人開啟 S/MIME 郵件檢視,也可以讓「無法讀取 S/MIME 郵件」的人也可以看到郵件。
MemoryStream ms = new MemoryStream(signedbytes);
AlternateView av = new AlternateView(ms,
"application/pkcs7-mime; smime-type=signed-data;name=smime.p7m");
msg.AlternateViews.Add(av);
7. 發出郵件
SmtpClient client = new SmtpClient("localhost", 25);
client.UseDefaultCredentials = true;
client.Send(msg);
都撰寫完成並測試成功後,所收到的郵件會呈現如下圖 ( 以 Outlook 2007 為例 ):
最後我要說明一下,使用 .NET 2.0 內建的郵件相關類別寄送有 AlternativeView 的郵件時,在 msg.Body 屬性一定只能用「純文字」當郵件內容,就算你將 msg.IsBodyHtml 設定為 true,在加入 AlternativeView 之後,當讀取郵件的工具對 AlternativeView 無法解析或顯示時,還是只會顯示成純文字,這點問題算是 .NET 2.0 內建的一個小瑕疵吧,真的要發進階 MIME 格式的郵件,我想只能買元件,否則就要自己寫了。
若要利用 .NET 發送含有 S/MIME 簽章的郵件,絕對不能設定 msg.Body 的內容或加上任何附件檔案,否則簽章永遠會失敗,進而導致在支援 S/MIME 協定的讀信軟體(如:Outlook)無法正確驗證郵件,如果在 Outlook 中就會發現根本看不到任何「簽章圖示」,因為 S/MIME 簽章的目的就是確保郵件內容不被竄改,但基於 .NET 郵件類別的限制,要發送具有附件檔案的 S/MIME 郵件應該是不太可能的。
我好幾次都有這樣的衝動想自己寫一組 Mail Library,只是這真的是件大工程,最終還是妥協。目前在 .NET 1.1 與 .NET 2.0 內建的 Mail 類別都還是有問題,我也反應給微軟好久了 ( 一年多 ),也反應不止一次,依然是音訊全無,真不知道 .NET BCL 對於郵件的控制何時可以更強些。
相關連結