如何透過 .NET 送出一個包含 S/MIME 簽章的郵件

分享到噗浪!

加入憑證簽章的郵件必須使用 S/MIME 標準,之前為了讓系統發出的郵件可以加入憑證簽章費了好一番功夫,有鑑於此需求非常罕見,在國內我是沒找到任何相關資料,即便在國外的網站相關資訊也很少,所以特此紀錄一下當時研究的過程。

要發送 S/MIME 郵件至少必須有以下條件先成立:

然後就可以開始寫程式了,共有 7 大步驟:

1. 先在專案中加入組件參考:System.Security

在專案中加入組件參考: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 為例 ):

 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 對於郵件的控制何時可以更強些。

相關連結

  

此文章由 will 發表於 2009/6/6 下午 09:20:14

永久連結 | 評論 (9) | 此文章的RSSRSS comment feed |

分類: .Net | ASP.NET | C# | Security

標籤: , , , ,

評論

六月 6. 2009 23:03

劉取哲

您好,
由於我們公司的開發環境也有包含 .NET Framework,
並且開發工具亦有採用 Visual Studio 軟體,
再加上我們公司主要就是在處理憑證與智慧卡應用的領域.

很感謝您持續的編寫工作上遇到的狀況,
並且提出相對應的處置與建議.

若是您工作上有需要協助處理憑證與智慧卡之議題時,
很歡迎您來信討論.

劉取哲 台灣

六月 8. 2009 16:47

dan

先前有遇過一個寄送憑證問題.就是憑證在WEB MAIL 不WORK..@@

dan 台灣

六月 8. 2009 20:44

will

這的確是一件很難搞的事,還好現在都弄通了。

will 台灣

六月 10. 2009 00:44

sermoco

送出的簽章mail在outlook看會有一個鎖的符號(一般應該是加密才會有),是我弄錯了嗎?
另外附加檔案之後,簽章似乎就失效了,不知道能否解決?
在coplex上有看到C# mail library http://csharpmail.codeplex.com/
應該會比.NET內建的可用性高

sermoco 台灣

六月 10. 2009 13:06

will

圖示有好幾種喔,如果你看到的是「鎖」,那就是「加密」才會有。

附加檔案之後,簽章似乎就失效了
=> 沒錯,因為附加檔案也要簽章。我提供的範例可能沒辦法解決你的問題,要解決就要改程式了,你可能要自行研究。

我有大致讀了一下 C#Mail 的原始碼,他用很底層的方式實做,目前功能不太夠(例如不能發Inline附件),但程式碼寫得很簡潔,看起來蠻好改的,熟悉 SMTP 與 MIME 的人改起來應該會很順,要拿來自己實做 S/MIME 應該也不難,我覺得是個好的開始,下次要是有遇到這個需求應該可以來這套來改。

will 台灣

六月 18. 2009 19:23

will

經由高人(Stone)指點,發送簽章的郵件有以下特性:

1. 數位簽章(Signed)內容通常用 *.p7s,其 Content-Type 為
  application/x-pkcs7-signature

2. 數位信封(Enveloped)內容通常用 *.p7m,其 Content-Type 為
  application/pkcs7-mime

所以透過我這篇文章的說明所產生的簽章郵件是不完整的,只會有數位信封(Enveloped)的內容而已,並不包含完整獨立的簽章資料,所以當 Mail Client 無法解讀簽章內容時(例如: Gmail, Yahoo! Webmail, Hotmail,...等),就會導致信件無法開啟,或內容呈現空白的情況。

依照我目前研究的心得來看,使用 .NET Framework 內建的類別應該沒辦法產生自訂的 multipart 的 MIME 郵件,也就無法自行附加 S/MIME 的數位簽章(Signed)當成其中一個 Part 的資料。

如果有人知道如何透過 .NET 內建的類別發出自訂的 multipart 郵件,或可以發送數位簽章(Signed)的郵件,希望可以分享給我知道,謝謝!

will 台灣

八月 2. 2009 02:27

YF

請問透過Pop3client進行收信
遇到有透過 p12 進行 encrypt message,
是否可以可直接 Decryption 內文 ?

YF 台灣

八月 2. 2009 13:11

will

YF: 當然可以啊!你可能要自己想辦法解摟,我還沒這個需求,還沒研究過。

will 台灣

四月 29. 2010 15:05

過客

您可以看一下這個連結 (在 http://istern.dk/blog/ 網站上)

istern.dk/.../...ontent-and-sending-the-mail).aspx


過客 台灣

新增評論


( 您輸入的Email不會顯示於網站上 )

  Country flag

biuquote
  • 評論
  • 線上預覽
Loading