.NET Framework 2.0 中安全性的增强
本页内容
System.Security
System.Security.Principal
System.Net.Security
System.Runtime.Remoting
System.Security.Cryptography
System.Security.Cryptography.X509Certificates 和 PKCS
System.Security.Cryptography.Xml
System.Diagnostics
System.DirectoryServices
System.DirectoryServices.ActiveDirectory
System.Web
代码访问安全中的变化
包装起来
在我撰写本专栏时,Microsoft? .NET Framework 2.0 是 Beta 1 版的。在进行内部测试时,我编写了一个小程序,来转储整个框架中所有公共类型的所有公共成员,并在版本 1.1 和版本 2.0 上运行。然后,我用 WINDIFF.EXE 来比较这两个文本文件,并花费了几个小时翻阅这些记录的变化,而且特别关注与安全性有关的问题。
.NET Framework 中的安全性支持在版本 2.0 中备受关爱,本月我将带您快速浏览在其中发现的引人之处。我不会讨论所有内容,但您将会了解从哪里开始查看,并在哪里停留以查看新的变化。我每次讨论一个命名空间。对了,要提出这样显而易见的警告:这是测试版软件,所以我这里讲述的所有内容在最终版本发行之前都有可能更改。
System.Security
托管字符串对机密内容来说并不是好的存储媒体。的确没有办法删除一个。垃圾回收对改写所收集的内存没有任何担保,在很多情况下,在由堆压缩进行改写之前,成为垃圾的字符串仍会在托管堆中保留一段时间。并且,垃圾回收器可能会在托管堆中到处移动它们。将其与不能将旧内存清零的事实结合起来,您可能会终止地址空间中您试图保存机密的许多字符串实例。引入了一个新类 SecureString 来帮助解决这些问题。
该类使用数据保护 API (DPAPI) 保护的内存模型存储其数据。换句话说,数据总是以其加密的形式存储在 SecureString 内。密钥是由本地安全性授权子系统 (LSASS.EXE) 通过 DPAPI托管的,这些数据可以通过进程间通信解密。
为了使该类更加有效,任何读取机密数据(可能是来自控制台的密码,也可能是来自 Web.config 的连接字符串)的 .NET Framework 方法都需要直接封装 SecureString 中的数据并将它返回给您。您不能仅通过用 SecureString来包装普通字符串,就期望会更加安全 ― 为了更加有效,不能在一个普通托管字符串中放置机密内容。 字串6
同样的规则也适合于将机密返回给 .NET Framework。例如,ProcessStartInfo 中的 Password 新属性(随后将讲到)是 SecureString 类型的。在内部,当需要机密数据时,.NET Framework 将通过 Marshal.SecureStringToBSTR 等新函数使用 Marshal 类来检索机密数据的实际内容。因为 Marshal 类是在非托管内存中处理的,所以当框架使用结束后,可以将这些内存正确地清零。毫无疑问,您不能使用 ToString 来检索来自 SecureString 的机密。记住,永远不能将机密数据最终保存在普通字符串对象中。
但是我弄不明白的一件事就是在交换文件之外保守机密的方法。当机密数据未进行封送处理就进入本机内存时,我想没有什么办法可以保证未进行封送处理的页面可以通过 VirtualLock 进行锁定。
在 Beta 1 中我弄不明白的另外一件事是将 SecureString 广泛地集成到框架中。迄今为止,我发现唯一使用它的地方是 ProcessStartInfo。该类只有更充分地集成到框架中,它的真正功能才能实现。
例如,我希望看到下面的构造函数:
public SqlConnection(SecureString connStr);
我还希望看到包装 Win32? 函数(如 CredUIPromptForCredentials)的托管类,这样我可以询问用户密码,从而返回 SecureString。集成是该功能成功的关键。
除了 SecureString,该命名空间中还有另一个新类吸引了我的眼球:SecurityContext。在 Beta 1 版本中甚至还有该类的一些文档,这是个功能极其强大的类!它允许您捕捉某个线程的安全性上下文,并将其还原到另一个线程上。这包括代码访问安全 (CAS) 线程标记,如 Assert 和 PermitOnly,还包括非托管线程的模拟标记。
该类还有一些看起来不一致的函数,如 SuppressFlow,该函数控制 CAS 标记是否从一个线程流动到另一个线程(通常是这样)。那么就有一个令人吃惊的结果:SuppressFlowWindowsIdentity。任何在 Windows? 中已经较长时间从事安全性编程的人员都知道,当您开始一个新线程或通过委托执行异步调用时,模拟标记并不流动到新线程。这种不起眼的安全性局限在过去的几年中已经刺痛了不知多少个开发人员。如果 SecurityContext 提供一种可以抑制 WindowsIdentity 在线程间流动的函数,那一定意味着 .NET Framework 现在可以自动地流动模拟标记。 字串9
为了测试这一点,我编写了一些称为 LogonUser 的代码并模拟了结果标记。然后,我使用 WindowsIdentity 上 GetCurrent 方法的一种全新重载来确定我是否是在模拟(这一点随后有更多讲述)。我从四个不同的地方调用该函数:主线程,通过 Thread.Start 开始的新线程,通过异步委托开始的辅助线程,以及从我们来自 Win32 的老朋友,CreateThread 开始的线程。对了,我甚至尝试着测试 QueueUserWorkItem,在过去它对流动安全性上下文的处理已经有些不同(特别是对 CAS 标记)。下面是输出结果:
Main thread
Thread 2488 is impersonating V-CAMP-XPalice
Testing Thread.Start
Thread 2508 is impersonating V-CAMP-XPalice
Testing QueueUserWorkItem
Thread 2504 is impersonating V-CAMP-XPalice
Testing Delegate.BeginInvoke
Thread 2516 is impersonating V-CAMP-XPalice
Testing Native CreateThread
Thread 2520 is not impersonating! 字串1
哇!所以运行时现在的确是在线程间自动传播模拟标记的。如果想更改该行为,您可使用 SecurityContext 类(虽然我并不推荐这样做)。
注意,并未涉及由非托管 API CreateThread 创建的线程,但期望是这样,因为是运行时而不是操作系统来进行这项工作。这表明由于线程模型不匹配而对生成线程切换的进程内 COM 组件的调用将和以前一样仍会撤销模拟标记,但是对大多应用程序来说,跨线程的标识流动问题已经解决。这是很重要的消息,因为它将给大多数人带来不足为奇的结果。这个变化既重要又微妙,所以如果您正在应用程序中使用模拟和异步,就要为之做好准备。
返回页首
System.Security.Principal
虽然它看起来微不足道,但这个模拟安全标识符 (SID) 的新类为广泛支持 Windows 安全编程接口铺平了道路。我将花费少得多的时间来编写托管 C++,从而架起桥梁,这使我成为一名非常快乐的宿营者。有三个类可以使之成为可能。前两个类是表示两种方式的具体类,通过这两种方式您可以引用一个安全帐户(可以是用户、组或计算机):SecurityIdentifier 表示机器可读的 SID,而 NTAccount 存储人工可读的字符串。第三个类是称为 IdentityReference 的抽象类,它将这些类绑定在一起。 字串9
正常返回一个 SID、SID 集合,或者包含 SID 的数据结构集合的方法,将在其签名中使用该抽象类。该方法将接受另外的 Type 参数,允许您选择是否希望该方法返回 SID 或名称。然后您就可以安全地向下转换到正确的类型。下面的示例显示了拥有一个文件的用户的帐户名:
void printTheOwnerOfThisFile(string path) {
FileSecurity s = File.GetAccessControl(path);
NTAccount user = (NTAccount)s.GetOwner(typeof(NTAccount));
Console.WriteLine(user);
}
...
printTheOwnerOfThisFile(@"c:autoexec.bat");
在我的系统上运行这些代码输出了下面的字符串:
BUILTINAdministrators
在称为 WellKnownSidType 的新枚举中还有一个人所共知的 SID 的完整列表。这使得不用对诸如“Administrators”(在德国它的拼写与在美国的拼写不同)之类的字符串进行硬编码就可以构造人所共知帐户的 SID。
WindowsIdentity 类也得到一些关注。如前所述,我最喜欢的一个新增功能就是重载 GetCurrent,这可使您在模拟过程中区分线程和进程安全性上下文:
bool ifImpersonating = true;
WindowsIdentity threadIdentity =
WindowsIdentity.GetCurrent(ifImpersonating);
if (null == threadIdentity) Console.WriteLine("Not impersonating");
else Console.WriteLine("Impersonating {0}",threadIdentity.Name);
System.Security.AccessControl
这很重要。框架现在模拟 Windows 安全描述符,允许您以编程方式读取并修改访问控制列表 (ACL),取得对象的所有权,并在安全描述符描述语言 (SDDL) 字符串和二进制安全描述符之间进行转换,等等。从 Alpha 预发布版本以来我就关注着该命名空间设计的发展,我必须要说明的是,在 Alpha 后深入其中的工作已经使其成为一种非常强大并且易于使用的附加功能。
在我打印出文件所有者时,您就已经看到这项新功能的简单示例。注意我是如何通过 System.IO.File 类来访问该文件的安全描述符的。您会发现,在框架中,安全对象已产生两种新方法:GetAccessControl 和 SetAccessControl。诸如 AutoResetEvent、Mutex 和新 Semaphore 的类表示三个这样的对象,与更明显的 File 和 Directory 类表示的对象相同。
更改服务的自由访问控制列表 (DACL) 没有直接的方法。当前只是没有服务对象的内置托管表示方法。但是在了解了访问控制基础结构的工作方法之后,我花了大约 15 分钟就创建了我对服务安全性的支持。System.Security.AccessControl 命名空间是从头开始设计的,并且始终考虑可扩展性。Kudos 这帮家伙!
对于以前曾经对 ACL 进行过编程的开发人员来说,您一定会欣赏有关代码的简单性(请参见图 1)。有关 System.Security.AccessControl 命名空间的更多信息,请参阅 2004 年 11 月号的 MSDN?Magazine 中 Mark Pustilnik 的文章,可从 Manage Access to Windows Objects with ACLs and the .NET Framework 获得。
返回页首
System.Net.Security
我最喜欢的一个讨论主题是身份验证协议,但针对它们的编程一直都是很需要技巧的。将 Kerberos 添加到应用程序通常涉及调用安全支持提供程序接口 (SSPI) 中的一些低级函数,而添加安全套接字层 (SSL) 支持则更为困难。
令人欣慰的是,现在 SSPI 的托管包装允许您使用 Kerberos 或 SSL 来实现客户端和服务器端的安全通道。Kerberos 支持由 NegotiateStream 提供给您,它在 Kerberos 和称为 NTLM 的质询-响应的旧协议间进行技术协商。客户端握手可以是同步的,也可以是异步的。下面是客户端同步安装程序的示例: 字串7
NegotiateStream secureChannel = new NegotiateStream(networkStream);
secureChannel.ClientAuthenticate(
CredentialCache.DefaultNetworkCredentials,
"SSPISample/TargetMachine:4242", ProtectionLevel.EncryptAndSign,
TokenImpersonationLevel.Impersonation);
完成这些后,您可以像使用一般的 NetworkStream 一样来使用 secureChannel,但通过 secureChannel 的任何数据都将通过消息身份验证代码 (MAC) 得到完整保护,并进行加密。您在服务器端还需要 NegotiateStream 的实例以便解密传入的消息,当然,该服务器可以通过 RemoteIdentity 属性获得表示其客户端的 WindowsIdentity。这样服务器就可以模拟或执行基于角色的访问检查。
SslStream 看起来很类似,但是毫无疑问,在身份验证过程中要使用与 Kerberos 票证相对的 X.509 证书。它允许客户端检查服务器证书吊销并为相互进行身份验证提供客户端证书。同样,一旦完成身份验证握手,您就可以在通道中传输数据,并可以放心,数据将受到完整性保护并进行加密。 字串9
返回页首
System.Runtime.Remoting
另一个令人兴奋的发展是 TCP 信道现在支持安全性。这是使用我前面讲过的 NegotiateStream 类内置到信道中的,它可以通过指定客户端和服务器远程配置文件中的一些新属性而激活的。我在本专栏中不再深入探讨这个主题,但是我的书中有一章讲述了这个问题,您可以在 The .NET Developer’s Guide to Windows Security 处在线阅读。
返回页首
System.Security.Cryptography
DPAPI 现在是 .NET Framework 中的一流成员,两个 .NETclasses、ProtectedData 和 ProtectedMemory 使其使用非常简单:
private static byte[] Decrypt(byte[] ciphertext) {
byte[] applicationEntropy = Encoding.ASCII.GetBytes("AcmeWidgets");
return ProtectedData.Unprotect(ciphertext, applicationEntropy,
DataProtectionScope.LocalMachine);
} 字串6
对于你们中那些使用 .NET Framework 构建密码系统的人,你们将很高兴地了解到有一个全新的类族,它们派生于新的抽象类、密钥哈希消息身份验证代码 (HMAC)。包含了所有的 SHA 变量。下面是一个示例:
byte[] CalcMAC(byte[] key, byte[] msg) {
return new HMACSHA256(key).ComputeHash(msg);
}
如果您在从事基于密码的加密技术,用密码或通行码生成密钥材料,您现在就可以用业界标准算法生成密码 PKCS#5(文档位于 RFC 2898 中)的字节流。新的 Rfc2898DeriveBytes 使得保存和取回密码很容易,正如我在上一专栏中所讲述的。在内部,它使用带有 HMAC-SHA-1 的 PBKDF2 函数:
void CreateNewUserAccount(string name, string pwd) {
Rfc2898DeriveBytes db = new Rfc2898DeriveBytes(password, 32, 1000);
byte[] hash = db.GetBytes(32);
addUserAccountToDatabase(name, db.Salt, hash);
}
返回页首
System.Security.Cryptography.X509Certificates 和 PKCS
不再有 CAPICOM!利用这些新的命名空间,您现在就可以获得 CryptoAPI (CAPI) 的托管包装,它允许您管理证书并直接从任一种托管语言创建 CMS/PKCS#7 封装或签名的消息。
需要要求用户从个人存储中选择证书吗?现在这是很自然的:
X509Store store = new X509Store("My", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509CertificateExCollection certs =
store.Certificates.Select( "MyTitle", "MyMessage",
X509SelectionFlag.SingleSelection);
if (1 == certs.Count) {
Console.WriteLine("You selected "{0}"",
certs[0].SubjectName.Name);
}
else Console.WriteLine("You canceled my dialog!"); 字串8
图 2 显示了结果对话框。如果您注意了该代码,那么您可能已经注意到 .NET Framework 小组已经走到用“Ex”后缀命名类的棘手境地。难道不是 Bunny 先生的 Guide to ActiveX? Controls 一书告诫我们不要这样命名 COM 中的东西吗?等一等,我敢打赌几年后您就将看到 Ex2 版本。
图 2 选择证书
返回页首
System.Security.Cryptography.Xml
该命名空间已经开始支持 XML 加密。有关使用 XML 签名和 XML 加密来安全交换数据的信息,请参阅 2004 年 11 月号的 MSDN Magazine 中 Mike Downen 和 Shawn Farkas 的文章 Exchange Data More Securely with XML Signatures and Encryption。
返回页首
System.Diagnostics
除了许多新的跟踪和调试功能、更完美的事件日志支持乃至 Stopwatch,在 Process 类内还潜伏着一个隐藏的“珍宝”:不必显式使用 P/Invoke 就能调用 CreateProcessWithLogonW。这是 Windows 中的“run as”功能,开发人员在测试时总是使用替换凭据来运行程序。现在很容易从托管代码中以编程方式使用它:
void RunNotepadAsThisUser(string authority,
string principal, SecureString password)
{
Process.Start(@"c:windowsnotepad.exe",
principal, password, authority).Dispose();
}
注意 SecureString 的使用 ― 这是这里唯一的小窍门,因为您从那里可以获得其中的一种?一种方法是使用一个 Win32 凭据 API(如 CredUIPromptForCredentials)来询问用户密码,然后将非托管字符串填充到您可以传递给 Process.Start 方法的 SecureString 实例中。然后小心地将保存密码的缓冲区清零。我希望在 .NET Framework 中包装凭据 API。也许我们很幸运,在下一个 Beta 版本中就能看到它。
返回页首
System.DirectoryServices
这里最大的变化好像就是添加了对虚拟列表视图 (VLV) 的支持。这项 Windows Server? 2003 功能允许您执行非常大型的搜索,并可以在它们中高效地滚动。您不必立即检索整个结果集。利用 VLV,您就可以指定想要的结果窗口,并滚动该窗口。下面是搜索目录林中所有用户的示例,但只返回前 25 条记录: 字串8
DirectorySearcher search = new DirectorySearcher(
"(&(objectClass=user)" + "(objectCategory=person))");
search.VirtualListView = new DirectoryVirtualListView(25);
foreach (SearchResult result in search.FindAll()) {
Console.WriteLine(result.Path);
}
一旦创建了 VLV 并将它连接到一个搜索器,您就可以通过设置如 Offset(以记录编号表示的结果集中的索引)、TargetPercentage(以记录数百分比表示的结果集中的索引)或 Target(以字符串表示的结果集中的索引,期望的结果应该以该字符串开头)等属性来控制显示结果的窗口。
返回页首
System.DirectoryServices.ActiveDirectory
这是一个新的命名空间,它使得查看和操作 Active Directory? 的物理和逻辑配置更加容易。下面这些类名称可以为您提供所模拟内容的概念:Forest、Site、Domain、DomainController、GlobalCatalog,还有更多。这里还给出可以读取和操作 Active Directory 架构的一组类。我可以占用整个专栏来仅仅讲述该命名空间 ― 它有很多内容,所以一定要核对一下。
返回页首
System.Web
回顾 2004 年 6 月,我撰写了一篇有关 ASP.NET 2.0 中新的安全功能的文章,所以这里我不再重复(请参阅 Security Headaches? Take ASP.NET 2.0!)。但有一件没有涉及而现在炒得很热的事情,那就是配置文件加密。让 .NET Framework 只使用 DPAPI 来加密整个配置文件,或许 AppSettings 部分,难道不好吗?好的,查看这些代码:
// use this app’s config file
string configFilePath = null;
Configuration config = Configuration.GetExeConfiguration(path,
ConfigurationUserLevel.None);
config.AppSettings.ProtectSection(ProtectedConfiguration.
DataProtectionProviderName);
config.Update();
这会将以如下代码开头的配置文件
字串3
转变成如图 3 所示的文件(为了可读性,我缩简了加密文本)。
现在猜猜如何访问“MySecret”数据。这是绝妙的部分 ― 您可以像前面那样访问它,而解密在后台进行:
Console.WriteLine(ConfigurationSettings.AppSettings["MySecret"]);
您还可以使用 RSA 提供程序,它依靠 CAPI 密钥容器来存储密钥。例如,如果需要将单个配置文件部署到 Web 场中很多不同的机器上,那么可以使用该类。您可用 CAPI 跨越整个场同步密钥,而 DPAPI 是机器特定的加密技巧。
返回页首
代码访问安全中的变化
.NET Framework 的 2.0 版本中,CAS 基础结构中添加了很多内容。堆栈漫游机制中新的扩展点允许重写对要求的处理(如果您有兴趣了解更多的内容,请查看 CodeAccessSecurity.CheckDemand 和友元)。
新证据类会告诉您是否从全局程序集缓存 (GAC) 中加载了程序集。 字串5
有几种权限可以调节对 .NET Framework 2.0 中新功能的访问。比如,调整对客户端 SMTP 功能访问的 SmtpPermission 由新的 System.Net.Mail 命名空间公开。
我认为一种很灵活的新权限就是 DataProtectionPermission,它控制允许哪些代码使用 DPAPI 类、ProtectedData 和 ProtectedMemory。这确实很重要,因为您并不想让从某些不可信源下载来的随机代码允许对我们因加密而为之兴奋的配置文件进行解密。
除了所有这些,还有一个全新的类集可以处理 ClickOnce 部署及信任确定,我将这些留到下个专栏讲述。
返回页首
包装起来
.NET Framework 的 2.0 版对于那些关心安全性的开发人员来说是个恩赐。我只是提到了该小组所做工作的皮毛 ― 据我所知,那些成就至少和我们在 ASP.NET 2.0 中看到的成就一样大。这无疑是我所盼望使用的一个版本。开心体验 Beta 版本吧!
将给 Keith 的问题和建议发送到 briefs@microsoft.com。 字串9
Keith Brown 是 Pluralsight 的合作创始人,专门开展和传播针对软件开发人员的高质量培训。Keith 的新书,The .NET Developer’s Guide to Windows Security,可从这里获得。Keith 还在 www.pluralsight.com/keith 保留有一个网络日志和联系信息。
System.Security
System.Security.Principal
System.Net.Security
System.Runtime.Remoting
System.Security.Cryptography
System.Security.Cryptography.X509Certificates 和 PKCS
System.Security.Cryptography.Xml
System.Diagnostics
System.DirectoryServices
System.DirectoryServices.ActiveDirectory
System.Web
代码访问安全中的变化
包装起来
在我撰写本专栏时,Microsoft? .NET Framework 2.0 是 Beta 1 版的。在进行内部测试时,我编写了一个小程序,来转储整个框架中所有公共类型的所有公共成员,并在版本 1.1 和版本 2.0 上运行。然后,我用 WINDIFF.EXE 来比较这两个文本文件,并花费了几个小时翻阅这些记录的变化,而且特别关注与安全性有关的问题。
.NET Framework 中的安全性支持在版本 2.0 中备受关爱,本月我将带您快速浏览在其中发现的引人之处。我不会讨论所有内容,但您将会了解从哪里开始查看,并在哪里停留以查看新的变化。我每次讨论一个命名空间。对了,要提出这样显而易见的警告:这是测试版软件,所以我这里讲述的所有内容在最终版本发行之前都有可能更改。
字串8
System.Security
托管字符串对机密内容来说并不是好的存储媒体。的确没有办法删除一个。垃圾回收对改写所收集的内存没有任何担保,在很多情况下,在由堆压缩进行改写之前,成为垃圾的字符串仍会在托管堆中保留一段时间。并且,垃圾回收器可能会在托管堆中到处移动它们。将其与不能将旧内存清零的事实结合起来,您可能会终止地址空间中您试图保存机密的许多字符串实例。引入了一个新类 SecureString 来帮助解决这些问题。
该类使用数据保护 API (DPAPI) 保护的内存模型存储其数据。换句话说,数据总是以其加密的形式存储在 SecureString 内。密钥是由本地安全性授权子系统 (LSASS.EXE) 通过 DPAPI托管的,这些数据可以通过进程间通信解密。
为了使该类更加有效,任何读取机密数据(可能是来自控制台的密码,也可能是来自 Web.config 的连接字符串)的 .NET Framework 方法都需要直接封装 SecureString 中的数据并将它返回给您。您不能仅通过用 SecureString来包装普通字符串,就期望会更加安全 ― 为了更加有效,不能在一个普通托管字符串中放置机密内容。 字串6
同样的规则也适合于将机密返回给 .NET Framework。例如,ProcessStartInfo 中的 Password 新属性(随后将讲到)是 SecureString 类型的。在内部,当需要机密数据时,.NET Framework 将通过 Marshal.SecureStringToBSTR 等新函数使用 Marshal 类来检索机密数据的实际内容。因为 Marshal 类是在非托管内存中处理的,所以当框架使用结束后,可以将这些内存正确地清零。毫无疑问,您不能使用 ToString 来检索来自 SecureString 的机密。记住,永远不能将机密数据最终保存在普通字符串对象中。
但是我弄不明白的一件事就是在交换文件之外保守机密的方法。当机密数据未进行封送处理就进入本机内存时,我想没有什么办法可以保证未进行封送处理的页面可以通过 VirtualLock 进行锁定。
在 Beta 1 中我弄不明白的另外一件事是将 SecureString 广泛地集成到框架中。迄今为止,我发现唯一使用它的地方是 ProcessStartInfo。该类只有更充分地集成到框架中,它的真正功能才能实现。
例如,我希望看到下面的构造函数:
public SqlConnection(SecureString connStr);
字串1
我还希望看到包装 Win32? 函数(如 CredUIPromptForCredentials)的托管类,这样我可以询问用户密码,从而返回 SecureString。集成是该功能成功的关键。
除了 SecureString,该命名空间中还有另一个新类吸引了我的眼球:SecurityContext。在 Beta 1 版本中甚至还有该类的一些文档,这是个功能极其强大的类!它允许您捕捉某个线程的安全性上下文,并将其还原到另一个线程上。这包括代码访问安全 (CAS) 线程标记,如 Assert 和 PermitOnly,还包括非托管线程的模拟标记。
该类还有一些看起来不一致的函数,如 SuppressFlow,该函数控制 CAS 标记是否从一个线程流动到另一个线程(通常是这样)。那么就有一个令人吃惊的结果:SuppressFlowWindowsIdentity。任何在 Windows? 中已经较长时间从事安全性编程的人员都知道,当您开始一个新线程或通过委托执行异步调用时,模拟标记并不流动到新线程。这种不起眼的安全性局限在过去的几年中已经刺痛了不知多少个开发人员。如果 SecurityContext 提供一种可以抑制 WindowsIdentity 在线程间流动的函数,那一定意味着 .NET Framework 现在可以自动地流动模拟标记。 字串9
为了测试这一点,我编写了一些称为 LogonUser 的代码并模拟了结果标记。然后,我使用 WindowsIdentity 上 GetCurrent 方法的一种全新重载来确定我是否是在模拟(这一点随后有更多讲述)。我从四个不同的地方调用该函数:主线程,通过 Thread.Start 开始的新线程,通过异步委托开始的辅助线程,以及从我们来自 Win32 的老朋友,CreateThread 开始的线程。对了,我甚至尝试着测试 QueueUserWorkItem,在过去它对流动安全性上下文的处理已经有些不同(特别是对 CAS 标记)。下面是输出结果:
Main thread
Thread 2488 is impersonating V-CAMP-XPalice
Testing Thread.Start
Thread 2508 is impersonating V-CAMP-XPalice
Testing QueueUserWorkItem
Thread 2504 is impersonating V-CAMP-XPalice
Testing Delegate.BeginInvoke
Thread 2516 is impersonating V-CAMP-XPalice
Testing Native CreateThread
Thread 2520 is not impersonating! 字串1
哇!所以运行时现在的确是在线程间自动传播模拟标记的。如果想更改该行为,您可使用 SecurityContext 类(虽然我并不推荐这样做)。
注意,并未涉及由非托管 API CreateThread 创建的线程,但期望是这样,因为是运行时而不是操作系统来进行这项工作。这表明由于线程模型不匹配而对生成线程切换的进程内 COM 组件的调用将和以前一样仍会撤销模拟标记,但是对大多应用程序来说,跨线程的标识流动问题已经解决。这是很重要的消息,因为它将给大多数人带来不足为奇的结果。这个变化既重要又微妙,所以如果您正在应用程序中使用模拟和异步,就要为之做好准备。
返回页首
System.Security.Principal
虽然它看起来微不足道,但这个模拟安全标识符 (SID) 的新类为广泛支持 Windows 安全编程接口铺平了道路。我将花费少得多的时间来编写托管 C++,从而架起桥梁,这使我成为一名非常快乐的宿营者。有三个类可以使之成为可能。前两个类是表示两种方式的具体类,通过这两种方式您可以引用一个安全帐户(可以是用户、组或计算机):SecurityIdentifier 表示机器可读的 SID,而 NTAccount 存储人工可读的字符串。第三个类是称为 IdentityReference 的抽象类,它将这些类绑定在一起。 字串9
正常返回一个 SID、SID 集合,或者包含 SID 的数据结构集合的方法,将在其签名中使用该抽象类。该方法将接受另外的 Type 参数,允许您选择是否希望该方法返回 SID 或名称。然后您就可以安全地向下转换到正确的类型。下面的示例显示了拥有一个文件的用户的帐户名:
void printTheOwnerOfThisFile(string path) {
FileSecurity s = File.GetAccessControl(path);
NTAccount user = (NTAccount)s.GetOwner(typeof(NTAccount));
Console.WriteLine(user);
}
...
printTheOwnerOfThisFile(@"c:autoexec.bat");
在我的系统上运行这些代码输出了下面的字符串:
BUILTINAdministrators
在称为 WellKnownSidType 的新枚举中还有一个人所共知的 SID 的完整列表。这使得不用对诸如“Administrators”(在德国它的拼写与在美国的拼写不同)之类的字符串进行硬编码就可以构造人所共知帐户的 SID。
字串9
WindowsIdentity 类也得到一些关注。如前所述,我最喜欢的一个新增功能就是重载 GetCurrent,这可使您在模拟过程中区分线程和进程安全性上下文:
bool ifImpersonating = true;
WindowsIdentity threadIdentity =
WindowsIdentity.GetCurrent(ifImpersonating);
if (null == threadIdentity) Console.WriteLine("Not impersonating");
else Console.WriteLine("Impersonating {0}",threadIdentity.Name);
System.Security.AccessControl
这很重要。框架现在模拟 Windows 安全描述符,允许您以编程方式读取并修改访问控制列表 (ACL),取得对象的所有权,并在安全描述符描述语言 (SDDL) 字符串和二进制安全描述符之间进行转换,等等。从 Alpha 预发布版本以来我就关注着该命名空间设计的发展,我必须要说明的是,在 Alpha 后深入其中的工作已经使其成为一种非常强大并且易于使用的附加功能。
在我打印出文件所有者时,您就已经看到这项新功能的简单示例。注意我是如何通过 System.IO.File 类来访问该文件的安全描述符的。您会发现,在框架中,安全对象已产生两种新方法:GetAccessControl 和 SetAccessControl。诸如 AutoResetEvent、Mutex 和新 Semaphore 的类表示三个这样的对象,与更明显的 File 和 Directory 类表示的对象相同。
字串2
更改服务的自由访问控制列表 (DACL) 没有直接的方法。当前只是没有服务对象的内置托管表示方法。但是在了解了访问控制基础结构的工作方法之后,我花了大约 15 分钟就创建了我对服务安全性的支持。System.Security.AccessControl 命名空间是从头开始设计的,并且始终考虑可扩展性。Kudos 这帮家伙!
对于以前曾经对 ACL 进行过编程的开发人员来说,您一定会欣赏有关代码的简单性(请参见图 1)。有关 System.Security.AccessControl 命名空间的更多信息,请参阅 2004 年 11 月号的 MSDN?Magazine 中 Mark Pustilnik 的文章,可从 Manage Access to Windows Objects with ACLs and the .NET Framework 获得。
返回页首
System.Net.Security
我最喜欢的一个讨论主题是身份验证协议,但针对它们的编程一直都是很需要技巧的。将 Kerberos 添加到应用程序通常涉及调用安全支持提供程序接口 (SSPI) 中的一些低级函数,而添加安全套接字层 (SSL) 支持则更为困难。
令人欣慰的是,现在 SSPI 的托管包装允许您使用 Kerberos 或 SSL 来实现客户端和服务器端的安全通道。Kerberos 支持由 NegotiateStream 提供给您,它在 Kerberos 和称为 NTLM 的质询-响应的旧协议间进行技术协商。客户端握手可以是同步的,也可以是异步的。下面是客户端同步安装程序的示例: 字串7
NegotiateStream secureChannel = new NegotiateStream(networkStream);
secureChannel.ClientAuthenticate(
CredentialCache.DefaultNetworkCredentials,
"SSPISample/TargetMachine:4242", ProtectionLevel.EncryptAndSign,
TokenImpersonationLevel.Impersonation);
完成这些后,您可以像使用一般的 NetworkStream 一样来使用 secureChannel,但通过 secureChannel 的任何数据都将通过消息身份验证代码 (MAC) 得到完整保护,并进行加密。您在服务器端还需要 NegotiateStream 的实例以便解密传入的消息,当然,该服务器可以通过 RemoteIdentity 属性获得表示其客户端的 WindowsIdentity。这样服务器就可以模拟或执行基于角色的访问检查。
SslStream 看起来很类似,但是毫无疑问,在身份验证过程中要使用与 Kerberos 票证相对的 X.509 证书。它允许客户端检查服务器证书吊销并为相互进行身份验证提供客户端证书。同样,一旦完成身份验证握手,您就可以在通道中传输数据,并可以放心,数据将受到完整性保护并进行加密。 字串9
返回页首
System.Runtime.Remoting
另一个令人兴奋的发展是 TCP 信道现在支持安全性。这是使用我前面讲过的 NegotiateStream 类内置到信道中的,它可以通过指定客户端和服务器远程配置文件中的一些新属性而激活的。我在本专栏中不再深入探讨这个主题,但是我的书中有一章讲述了这个问题,您可以在 The .NET Developer’s Guide to Windows Security 处在线阅读。
返回页首
System.Security.Cryptography
DPAPI 现在是 .NET Framework 中的一流成员,两个 .NETclasses、ProtectedData 和 ProtectedMemory 使其使用非常简单:
private static byte[] Decrypt(byte[] ciphertext) {
byte[] applicationEntropy = Encoding.ASCII.GetBytes("AcmeWidgets");
return ProtectedData.Unprotect(ciphertext, applicationEntropy,
DataProtectionScope.LocalMachine);
} 字串6
对于你们中那些使用 .NET Framework 构建密码系统的人,你们将很高兴地了解到有一个全新的类族,它们派生于新的抽象类、密钥哈希消息身份验证代码 (HMAC)。包含了所有的 SHA 变量。下面是一个示例:
byte[] CalcMAC(byte[] key, byte[] msg) {
return new HMACSHA256(key).ComputeHash(msg);
}
如果您在从事基于密码的加密技术,用密码或通行码生成密钥材料,您现在就可以用业界标准算法生成密码 PKCS#5(文档位于 RFC 2898 中)的字节流。新的 Rfc2898DeriveBytes 使得保存和取回密码很容易,正如我在上一专栏中所讲述的。在内部,它使用带有 HMAC-SHA-1 的 PBKDF2 函数:
void CreateNewUserAccount(string name, string pwd) {
Rfc2898DeriveBytes db = new Rfc2898DeriveBytes(password, 32, 1000);
byte[] hash = db.GetBytes(32);
addUserAccountToDatabase(name, db.Salt, hash);
}
字串5
返回页首
System.Security.Cryptography.X509Certificates 和 PKCS
不再有 CAPICOM!利用这些新的命名空间,您现在就可以获得 CryptoAPI (CAPI) 的托管包装,它允许您管理证书并直接从任一种托管语言创建 CMS/PKCS#7 封装或签名的消息。
需要要求用户从个人存储中选择证书吗?现在这是很自然的:
X509Store store = new X509Store("My", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509CertificateExCollection certs =
store.Certificates.Select( "MyTitle", "MyMessage",
X509SelectionFlag.SingleSelection);
if (1 == certs.Count) {
Console.WriteLine("You selected "{0}"",
certs[0].SubjectName.Name);
}
else Console.WriteLine("You canceled my dialog!"); 字串8
图 2 显示了结果对话框。如果您注意了该代码,那么您可能已经注意到 .NET Framework 小组已经走到用“Ex”后缀命名类的棘手境地。难道不是 Bunny 先生的 Guide to ActiveX? Controls 一书告诫我们不要这样命名 COM 中的东西吗?等一等,我敢打赌几年后您就将看到 Ex2 版本。
图 2 选择证书
返回页首
System.Security.Cryptography.Xml
该命名空间已经开始支持 XML 加密。有关使用 XML 签名和 XML 加密来安全交换数据的信息,请参阅 2004 年 11 月号的 MSDN Magazine 中 Mike Downen 和 Shawn Farkas 的文章 Exchange Data More Securely with XML Signatures and Encryption。
返回页首
System.Diagnostics
除了许多新的跟踪和调试功能、更完美的事件日志支持乃至 Stopwatch,在 Process 类内还潜伏着一个隐藏的“珍宝”:不必显式使用 P/Invoke 就能调用 CreateProcessWithLogonW。这是 Windows 中的“run as”功能,开发人员在测试时总是使用替换凭据来运行程序。现在很容易从托管代码中以编程方式使用它:
字串7
void RunNotepadAsThisUser(string authority,
string principal, SecureString password)
{
Process.Start(@"c:windowsnotepad.exe",
principal, password, authority).Dispose();
}
注意 SecureString 的使用 ― 这是这里唯一的小窍门,因为您从那里可以获得其中的一种?一种方法是使用一个 Win32 凭据 API(如 CredUIPromptForCredentials)来询问用户密码,然后将非托管字符串填充到您可以传递给 Process.Start 方法的 SecureString 实例中。然后小心地将保存密码的缓冲区清零。我希望在 .NET Framework 中包装凭据 API。也许我们很幸运,在下一个 Beta 版本中就能看到它。
返回页首
System.DirectoryServices
这里最大的变化好像就是添加了对虚拟列表视图 (VLV) 的支持。这项 Windows Server? 2003 功能允许您执行非常大型的搜索,并可以在它们中高效地滚动。您不必立即检索整个结果集。利用 VLV,您就可以指定想要的结果窗口,并滚动该窗口。下面是搜索目录林中所有用户的示例,但只返回前 25 条记录: 字串8
DirectorySearcher search = new DirectorySearcher(
"(&(objectClass=user)" + "(objectCategory=person))");
search.VirtualListView = new DirectoryVirtualListView(25);
foreach (SearchResult result in search.FindAll()) {
Console.WriteLine(result.Path);
}
一旦创建了 VLV 并将它连接到一个搜索器,您就可以通过设置如 Offset(以记录编号表示的结果集中的索引)、TargetPercentage(以记录数百分比表示的结果集中的索引)或 Target(以字符串表示的结果集中的索引,期望的结果应该以该字符串开头)等属性来控制显示结果的窗口。
返回页首
System.DirectoryServices.ActiveDirectory
这是一个新的命名空间,它使得查看和操作 Active Directory? 的物理和逻辑配置更加容易。下面这些类名称可以为您提供所模拟内容的概念:Forest、Site、Domain、DomainController、GlobalCatalog,还有更多。这里还给出可以读取和操作 Active Directory 架构的一组类。我可以占用整个专栏来仅仅讲述该命名空间 ― 它有很多内容,所以一定要核对一下。
字串3
返回页首
System.Web
回顾 2004 年 6 月,我撰写了一篇有关 ASP.NET 2.0 中新的安全功能的文章,所以这里我不再重复(请参阅 Security Headaches? Take ASP.NET 2.0!)。但有一件没有涉及而现在炒得很热的事情,那就是配置文件加密。让 .NET Framework 只使用 DPAPI 来加密整个配置文件,或许 AppSettings 部分,难道不好吗?好的,查看这些代码:
// use this app’s config file
string configFilePath = null;
Configuration config = Configuration.GetExeConfiguration(path,
ConfigurationUserLevel.None);
config.AppSettings.ProtectSection(ProtectedConfiguration.
DataProtectionProviderName);
config.Update();
这会将以如下代码开头的配置文件
转变成如图 3 所示的文件(为了可读性,我缩简了加密文本)。
现在猜猜如何访问“MySecret”数据。这是绝妙的部分 ― 您可以像前面那样访问它,而解密在后台进行:
Console.WriteLine(ConfigurationSettings.AppSettings["MySecret"]);
您还可以使用 RSA 提供程序,它依靠 CAPI 密钥容器来存储密钥。例如,如果需要将单个配置文件部署到 Web 场中很多不同的机器上,那么可以使用该类。您可用 CAPI 跨越整个场同步密钥,而 DPAPI 是机器特定的加密技巧。
返回页首
代码访问安全中的变化
.NET Framework 的 2.0 版本中,CAS 基础结构中添加了很多内容。堆栈漫游机制中新的扩展点允许重写对要求的处理(如果您有兴趣了解更多的内容,请查看 CodeAccessSecurity.CheckDemand 和友元)。
新证据类会告诉您是否从全局程序集缓存 (GAC) 中加载了程序集。 字串5
有几种权限可以调节对 .NET Framework 2.0 中新功能的访问。比如,调整对客户端 SMTP 功能访问的 SmtpPermission 由新的 System.Net.Mail 命名空间公开。
我认为一种很灵活的新权限就是 DataProtectionPermission,它控制允许哪些代码使用 DPAPI 类、ProtectedData 和 ProtectedMemory。这确实很重要,因为您并不想让从某些不可信源下载来的随机代码允许对我们因加密而为之兴奋的配置文件进行解密。
除了所有这些,还有一个全新的类集可以处理 ClickOnce 部署及信任确定,我将这些留到下个专栏讲述。
返回页首
包装起来
.NET Framework 的 2.0 版对于那些关心安全性的开发人员来说是个恩赐。我只是提到了该小组所做工作的皮毛 ― 据我所知,那些成就至少和我们在 ASP.NET 2.0 中看到的成就一样大。这无疑是我所盼望使用的一个版本。开心体验 Beta 版本吧!
将给 Keith 的问题和建议发送到 briefs@microsoft.com。 字串9
Keith Brown 是 Pluralsight 的合作创始人,专门开展和传播针对软件开发人员的高质量培训。Keith 的新书,The .NET Developer’s Guide to Windows Security,可从这里获得。Keith 还在 www.pluralsight.com/keith 保留有一个网络日志和联系信息。
Tags:
责任编辑:
上一篇:通过 J# 求解约束条件 下一篇:通过七个关键编程技巧得益于静态内容
您的评论
·用户发表意见仅代表其个人意见,并且承担一切因发表内容引起的纠纷和责任
·本站管理人员有权在不通知用户的情况下删除不符合规定的评论信息或留做证据
·请客观的评价您所看到的资讯,提倡就事论事,杜绝漫骂和人身攻击等不文明行为
·本站管理人员有权在不通知用户的情况下删除不符合规定的评论信息或留做证据
·请客观的评价您所看到的资讯,提倡就事论事,杜绝漫骂和人身攻击等不文明行为
精彩推荐
最新资讯


您的位置: