本文讨论:
Windows 证书存储区
.NET 中的证书类
验证、SSL、Web 服务和代码签名
对数据进行签名和加密
本文使用了以下技术:
.NET Framework 2.0
目录
如何获得一个证书
Windows 证书存储区
使用证书
访问证书
显示证书详细信息和证书选择器
验证证书
SSL 支持
Web 服务安全性
安全策略和代码签名
ClickOnce 清单
对数据进行签名和加密
解密数据和验证签名
综述
证书在 Microsoft® .NET Framework 中应用十分广泛,从安全通信到 代码签名再到安全策略。.NET Framework 2.0 改进了对证书的支持,为使用证书进行符合标准的加密操作添加了一个全新的命名空间。在本文中,我将讨论证书和 Windows® 证书存储区的背景知识。同时我还会为您介绍证书 API 的使用方法和 Framework 如何使用这些 API 实现安全功能。
“证书”实际上是一种 ASN.1 (Abstract Syntax Notation One) 编码的文件,它包含一个公钥和其他有关该密钥及其所有者的信息。另外,证书具有有效期,并通过另一密钥(所谓的颁发者)进行签名,该密钥能保证这些属性的 真实性,最重要的是,保证公钥本身的真实性。您可以将 ASN.1 看成是一种二进制 XML。与 XML 一样,它也具有编码规则、强类型和标记;但是,这些都是二进制值,通常没有可打印字符与之对应。
要使这种文件能够在系统之间互换,需要一种标准的格式。这种标准格式即 X.509(当前为第 3 版),RFC 3280 (tools.ietf.org/html/rfc3280 )) 中对其进行了描述。虽然 X.509 并未规定证书中嵌入的密钥类型,但 RSA 算法是目前使用最为普遍的非对称加密算法。
首先让我们回顾一下这种算法的历史由来。“RSA”这一名称是发明该算法的三个人的姓氏首字母缩写:Ron Rivest、Adi Shamir 和 Len Adleman。他们成立了一家名为 RSA Security 的公司,该公司发布了几个名为公钥加密标准 (PKCS) 的标准文档。这些文档对加密技术的几个方面进行了介绍。
其中最流行的文档之一,即 PKCS #7,为已签名和加密的数据定义了一种名为加密消息语法 (CMS) 的二进制格式。目前 CMS 广泛应用于众多流行的安全协议,其中包括安全套接字层 (SSL) 和安全多用途 Internet 邮件扩展 (S/MIME)。由于它是一种标准,因此当应用程序需要在几方之间交换已签名和加密的数据时,它也是一种可供选择的格式。您可以从 RSA Laboratories 网站 (www.rsasecurity.com/rsalabs/node.asp?id=2124 ) 获得这些 PKCS 文档。
如何获得一个证书
目前有几种方法可以获取证书。在交换文件时,证书通常以两种格式之一出现。扩展名为 .cer 的文件是采用 X.509v3 格式的已签名 ASN.1 文件。这些文件中包含着我之前提到的一个公钥和额外的信息。您要将这些文件中包含的内容提供给业务合作伙伴或朋友,以便他们能够使用公钥为您加密数据。
此外,您可能会遇到扩展名为 .pfx(个人信息交换,Personal Information Exchange)的文件。.pfx 文件包含一个证书和与之对应的私钥(PKCS #12 标准对该格式有所说明)。这类文件是高度敏感的,通常用于导入服务器上的密钥对或用于备份目的。在导出密钥对时,Windows 提供用密码加密 .pfx 文件;而在导入密钥对时,您必须再次提供此密码方可导入。
您还可以生成自己的证书。证书的生成方式通常取决于其使用方式。在 对等方身份不明的常规 Internet 环境下,您通常要向某个商业证书颁发机构 (CA) 申请证书。该方法的优点在于这些已知的 CA 已经得到 Windows 和其他任何支持证书及 SSL 的 OS(包括浏览器)的信任。因此不必进行 CA 密钥的交换。
对于 B2B 和 Intranet 环境,您可以使用内部 CA。Windows 2000 和 Windows Server® 2003 中包含了证书服务。配合 Active Directory® 一起使用,此功能允许您在组织内轻松地分发证书。(稍后我将介绍如何从私有 CA 申请证书。)
在开发过程中,有时您可能会发现,刚才提到的方法不起作用了。例如,如果您出于测试的需要希望很快获得一个证书, 可以使用 makecert.exe。该工具包随附于 .NET Framework SDK 中,能够生成证书和密钥对。在 IIS 资源工具包中也有一个与之类似的名为 selfssl.exe 的工具。它专门用于创建 SSL 密钥对,而且使用这种密钥,只需一个步骤即可对 IIS 进行配置。
Windows 证书存储区
证书和与之对应的私钥可存储在各种设备上,例如硬盘、智能卡和 USB 令牌。Windows 提供了一个名为证书存储区的抽象层,用于统一证书的访问方式,不管这些证书存储在何处。只要硬件设备具有 Windows 支持的加密服务提供程序 (CSP),就可以使用证书存储区 API 访问其上存储的数据。
证 书存储区位于用户配置文件的深处。这样就可以对特定帐户的密钥使用 ACL。每个存储区被划分为若干个容器。例如,其中有一个名为 Personal 的容器,您可以将自己的证书(具有关联私钥的证书)存储在其中。Trusted Root Certification Authorities 容器包含了所有您信任的 CA 的证书。Other People 容器则保存着与您进行安全通信的人员的证书。此外还有其他一些证书。访问证书存储区最简单方法是运行 certmgr.msc。
另外还有一个供 Windows 计算机帐户(NETWORK、LOCAL SERVICE 和 LOCAL SYSTEM) 使用的计算机范围的存储区,如果您希望跨帐户共享证书或密钥,可以使用该存储区。 ASP.NET 应用程序总是使用计算机存储区;而对于桌面应用程序,证书通常安装在用户存储区。
只有管理员才能对计算机存储区和服务帐户存储区进行管理。要实现这一目的,您必须启动 Microsoft 管理控制台 (mmc.exe) 并添加“证书”管理单元,从中可以选择要管理的存储区。图 1 显示了 MMC 管理单元的一个屏幕快照。
图 1“证书”MMC 管理单元
除了可以导入、导出和搜索证书外,您还可以通过管理单元从内部企业 CA 申请证书。只需右键单击 Personal 容器并选择 All Tasks | Request Certificate。本地计算机会随后生成一个 RSA 密钥对,并将公钥部分发送给 CA 以进行签名。Windows 将已签名的证书添加到证书存储区,并将对应的私钥添加到密钥容器。证书通过存储属性被链接到密钥容器。
对于对应帐户或 LOCAL SYSTEM,私钥容器受到 ACL 的严密保护。当您要从 ASP.NET 或其他用户帐户访问计算机配置文件中存储的密钥时,这就成为一个问题。为此我编写了一个工具,您可以用它修改容器文件的 ACL (可从 www.leastprivilege.com/HowToGetToThePrivateKeyFileFromACertificate.aspx 下载该工具)。
商业 CA 和 Windows CA 还为申请证书提供了 Web 界面。通常在这些时候,由 Internet Explorer® 中的 ActiveX® 控件生成密钥并将其导入当前用户的存储区中。按照惯例,当您想指定某个证书可以由某个用户或服务访问时,有以下两种选择:一是将该证书导入该用户的存储 区,二是在以该用户身份登录时申请证书。
使用证书
证书可用于 .NET Framework 中的各个位置,而且在某种程度上此功能依 赖于 System.Security.X509Certificates 命名空间中的 X509Certificate 类。如果您更仔细地观察,还会发现证书类是以 2 结尾。这是因为 .NET Framework 1.x 具有一个名为 X509Certificate 的 X.509 证书表示形式。该类的功能有限,而且不支持加密操作。2.0 版中新添加了一个名为 X509Certificate2 的类。该类派生自 X509Certificate,同时添加了许多功能。您可以根据需要在二者之间来回转换,但无论何时都应尽可能使用最新版本。
访问证书
您可以直接从文件系统检索证书。但更好的办法是从证书存储区进行检索。要从一个 .cer 文件创建一个 X509Certificate2 实例,只需将文件名传递给构造函数即可:
X509Certificate2 cert1 = new X509Certificate2("alice.cer");
您也可以从 .pfx 文件加载证书。但是,如前所述,.pfx 文件可以通过设置密码加以保护,因此您应当将该密码以 SecureString 的形式提供给他人。SecureString 在内部对密码进行加密,并尽可能降低密码在内存、页面文件和崩溃转储期间的暴露几率。因此,您每次只能向字符串添加一个(值类型)字符。如果要从控制台要 求用户提供密码,那么图 2 中的代码是非常有用的,这些代码可禁用控制台回显并返回 SecureString。
Figure2从控制台申请密码
private SecureString GetSecureStringFromConsole() { SecureString password = new SecureString(); Console.Write("Enter Password: "); while (true) { ConsoleKeyInfo cki = Console.ReadKey(true); if (cki.Key == ConsoleKey.Enter) break; else if (cki.Key == ConsoleKey.Escape) { password.Dispose(); return null; } else if (cki.Key == ConsoleKey.Backspace) { if (password.Length != 0) password.RemoveAt(password.Length - 1); } else password.AppendChar(cki.KeyChar); } return password; }
在“使用 .NET Framework 2.0 进行加密管理”(可从 msdn.microsoft.com/library/en-us/dnnetsec/html/credmgmt.asp 获得)一文中,Kenny Kerr 谈到了将常见 Windows 凭据对话框的结果转换为 SecureString 的代码。不管您以何种方式获得 SecureString,它都可以随后被传递到 X509Certificate2 构造函数,以加载 .pfx 文件,如下所示:
X509Certificate2 cert2 = new X509Certificate2("alice.pfx", password);
要访问 Windows 证书存储区,请使用 X509Store 类。在其构造函数中,您要提供存储区位置(当前用户或计算机)和存储区名称。可以使用字符串或 StoreName 枚举来指定要打开的容器。注意,内部名称并不总是与 MMC 管理单元中找到的名称匹配。Personal 容器映射到名称 My,而 Other People 则变为 AddressBook。
获得有效的 X509Store 实例后,就可以搜索、检索、删除和添加证书。 除非在部署情况下,否则使用最为频繁的恐怕要数搜索功能。您可以按各种条件搜索证书,其中包括使用者名称、序列号、指纹、颁发者和有效期。如果以编程方式 从存储区检索应用程序中的证书,则应当使用唯一的属性,例如接收者密钥标识符。指纹虽然也是唯一的,但要记住,它是证书的一个 SHA-1 哈希值,在诸如续订证书时会发生改变。图 3 中的代码显示了一种搜索证书的常规方法。
Figure3搜索证书
static void Main(string[] args) { // search for the subject key id X509Certificate2 cert = FindCertificate( StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectKeyIdentifier, "21f2bf447298e83056a69eb02ebe9085ed97f10a"); } static X509Certificate2 FindCertificate( StoreLocation location, StoreName name, X509FindType findType, string findValue) { X509Store store = new X509Store(name, location); try { // create and open store for read-only access store.Open(OpenFlags.ReadOnly); // search store X509Certificate2Collection col = store.Certificates.Find( findType, findValue, true); // return first certificate found return col[0]; } // always close the store finally { store.Close(); } }
在获得 X509 Certificate2 的一个实例后,可以检查证书的各个属性(如使用者名称、到期日期、颁发者和友好名称)。HasPrivateKey 属性会告知您是否存在关联私钥。PrivateKey 和 PublicKey 属性将对应密钥作为一个 RSACryptoServiceProvider 实例返回。
要导入证书,请对 X509Store 实例调用 Add 方法。当存储区的构造函数中不存在您所指定的存储区名称时,就会创建一个新容器。以下说明了如何将名为 alice.cer 的文件中的一个证书导入到名为 Test 的新容器中:
static void ImportCert() { X509Certificate2 cert = new X509Certificate2("alice.cer"); X509Store store = new X509Store("Test", StoreLocation.CurrentUser); try { store.Open(OpenFlags.ReadWrite); store.Add(cert); } finally { store.Close(); } }
显示证书详细信息和证书选择器
Windows 提供了两个标准对话框对证书进行操作:其中一个对话框用于显示证书的详细信息(各个属性和证书路径),另一个则供用户从列表中选择证书。您可以使用 X509Certificate2UI 类的两个静态方法访问这两个对话框:SelectFromCollection 和 DisplayCertificate。
要 显示证书列表,必须填充 X509Certificate2Collection 并将其传递给 SelectFromCollection。让用户从存储区内的个人证书之一进行选择是很常见的。为此,只需传入一个已打开的 X509Store 的 Certificates 属性即可。您还可以控制对话框标题、消息以及是否允许多个选择。DisplayCertificate 方法显示的对话框与在 Windows 资源管理器中双击 .cer 文件时看到的对话框相同。图 4 显示了选择证书所用的对话框,图 5 提供相应的代码。
Figure5用于选择证书的代码
private static X509Certificate2 PickCertificate( StoreLocation location, StoreName name) { X509Store store = new X509Store(name, location); try { store.Open(OpenFlags.ReadOnly); // pick a certificate from the store X509Certificate2 cert = X509Certificate2UI.SelectFromCollection( store.Certificates, "Caption", "Message", X509SelectionFlag.SingleSelection)[0]; // show certificate details dialog X509Certificate2UI.DisplayCertificate(cert); return cert; } finally { store.Close(); } }
}
图 4用于选择证书的对话框
验证证书
验证证书时需要考虑几个条件,尤其是颁发方(通常仅信任可信 CA 列表中的 CA 所颁发的证书)及其当前有效性(证书可能会失效,例如当过期或者被颁发证书的 CA 吊销时)。X509Chain 类可以用来检查这些不同属性。使用该类,您可以为有效性检查指定策略,例如,可以要求一个受信任根 CA 或指定是否进行联机检查或检查本地吊销列表。如果需要对用于数据签名的证书进行检查,则在计算签名时检查证书是否有效就变得很重要;为此,X509Chain 允许更改验证时间。
构造策略后,可以调用 Build 方法来获取有关 ChainStatus 属性验证结果的信息。如果出现多个验证错误,您可以循环访问 ChainElement 集合以获取更多详细信息。图 6 显示了如何根据脱机和联机吊销列表对证书及其颁发者执行严格的验证。
Figure6严格的证书验证
static void ValidateCert(X509Certificate2 cert) { X509Chain chain = new X509Chain(); // check entire chain for revocation chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain; // check online and offline revocation lists chain.ChainPolicy.RevocationMode = X509RevocationMode.Online | X509RevocationMode.Offline; // timeout for online revocation list chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30); // no exceptions, check all properties chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; // modify time of verification //chain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1); chain.Build(cert); if (chain.ChainStatus.Length != 0) Console.WriteLine(chain.ChainStatus[0].Status); }
SSL 支持
SSL 身份验证协议依赖于证书。.NET Framework 中对 SSL 的支持包含两个部分。HTTP 上的 SSL 这种特殊情况(但使用最为广泛)由 HttpWebRequest 类(它最终还可用于 Web 服务客户端代理)实现。要启用 SSL,除了要指定一个使用 Https: 协议的 URL 外,不必执行任何特殊操作。
当连接到一个受 SSL 保护的终结点时,会在客户端上对服务器证书进行验证。如果验证失败,连接会根据默认设置立即关闭。您可以回调一个名为 ServicePointManager 的类来重写该行为。每当 HTTP 客户端的堆栈进行证书验证时,都会首先检查是否可以回调;如果可以,则执行您的代码。要挂接该回调,您必须提供类型 RemoteCertificateValidationCallback 的一个委托:
/ override default certificate policy // (for example, for testing purposes) ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(VerifyServerCertificate);
在回调中,您会获得服务器 证书、一个错误代码和一个传入的链对象,然后可以执行自己的检查并返回 true 或 false。如果出现诸如证书在开发或测试期间就已过期的情况,那么关闭其中某项检查是有好处的。另一方面,这样做还可以执行比默认更为严格的验证策略。 图 7 提供了一个验证回调的示例。
Figure7验证回调
private bool VerifyServerCertificate( object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors == SslPolicyErrors.None) return true; foreach (X509ChainStatus s in chain.ChainStatus) { // allows expired certificates if (string.Equals(s.Status.ToString(), "NotTimeValid", StringComparison.OrdinalIgnoreCase)) return true; } return false; }
SSL 还支持使用证书对客户端进行身份验证。如果您要访问的网站或服务要求提供客户端证书,那么 Web 服务客户端代理和 HttpWebRequest 都可提供 X509Certicate 类型的 ClientCertificates 属性:
http://www.3648.com/article/sort02/sort024/info-20597_10.html