代码改变世界

Windows PowerShell 2.0语言开发之脚本签名

2010-12-02 00:28  @天行健中国元素  阅读(2159)  评论(2编辑  收藏  举报

脚本签名用来保护代码在发布之后,用户使用之前不被篡改。数据源发送方使用自己的私钥加密数据校验和其他与数据内容有关的变量,完成对数据的合法“签名”;数据接收方则利用对方的公钥来解密收到的“数字签名”。并将结果用于数据完整性检验,以确认签名的合法性。

1 代码签名如何工作

签名代码确保程序对目标用户无害,其背后存在复杂而严密的操作来保证签名的有效性。当处理可执行代码签名时存在代码的完整性和来源的有效性问题,为此需要确保代码没有被篡改。如需要确认没有人中途截取并替换发送端的代码,并确认文件的来源是其标识的来源人。

1.1 保证脚本的完整性

为了验证将要执行的代码确系出自原作者手中并没有任何增删,在发送代码给其他人之前可以用算法获取代码的消息摘要,计算机科学中已经提供了相应的函数用于计算数据流的校验和。一些流行的能获取消息摘要的算法包括MD5和SHA-1,这些函数有时称为“哈希函数”,它们会遍历所有数据并计算出完全不同于另外一端数据流的摘要。改变原始数据中的任何一位都将会完全改变校验和的值。获取代码后可以使用相同的算法获取消息摘要,如果两段消息摘要相同,那么脚本未被修改。

哈希函数在设计上存在缺陷,恶意用户可以截取消息、修改脚本、重新获取并替换消息摘要。同样需要保证消息摘要未被篡改,可以加密消息摘要,但是存在传输密钥的问题。如果在传输的过程中包含密钥,则未起到加密消息摘要的作用。攻击者可以修改脚本并用密钥重新加密消息摘要后传输,此时不能使用带密钥或对称算法解决。需要使用非对称算法加密密钥,该算法采用一对密钥,其中一个用来加密数据,另外一个用来解密数据。加密密钥被称为“私钥”,从不传输;解密密钥或称为“公钥”,可以被自由地分发给任何人。脚本作者计算脚本的消息摘要并用自己的私钥加密。接收方可以使用公钥解密消息摘要并计算另外一个消息摘要,对比两个值是否相等即可知道代码是否被篡改过。

1.2 保证原始脚本

验证脚本完整性的方案看起来很完美,但是如果恶意用户在中途截取消息、修改该脚本、更新消息摘要,用其私钥加密消息摘要并用其公钥替换原有包含的公钥,则接收方将不会发现问题。解决方法是由中立的第三方来收藏所有人的公钥,这样即可保证某个邮件中加密消息摘要的私钥对应公钥的作者不是脚本作者。这里受信任的第三方的角色由中立的CA(Certification Authorities)来承担,现在流行最广的CA公司有VerSign和Thawte。

CA发行的所有公钥/私钥对能够确认某个公钥确实属于某个个人或组织,当用户从其购买数字证书时本质上获取的是一个公钥/私钥对。获取私钥和包含公钥的证书文件,以及认证持有者的附加信息和过期时间等。数字证书能够分发给任何人,任何人可以验证自己是否属于正确的用户。

脚本开发人员从CA处购买数字证书,计算出脚本的校验和,然后发送脚本、加密的校验和及其证书(其中包含其公钥)。收到信息后,接收方可以使用证书发放机构的公钥来验证脚本开发人员的数字签名是否真实。如果真实且计算出的消息摘要与用开发人员公钥解密的结果一致,则消息没有被篡改。在这个过程中,CA机构的私钥必须准确无误才能保证数字签名有效,攻击者没有它则无法分发假证书。通常情况下CA的私钥作为高级机密存在,攻击者根本无法获取,从而保证了数字签名的有效性。

上述交换机制中密钥和证书的产生称为“公共密钥基础设施”(PKI),它广泛应用于在Internet中建立信任体系,以验证其他用户和实体。完整的PKI标准包括多种超约束力的公钥用户身份验证,如撤销机制、证书过期和证书泄露等。

2 管理证书

在现实生活中,数字证书的使用不仅限于信任的安全网站或者运行的签名程序,而且需要用户交换密钥跟踪证书。一旦信任一个软件发行商,则每次运行其软件时不再需要确认。所有信任的证书均保存在Windows证书存储区中,通过微软管理控制台工具(mmc)和证书管理单元很容易管理证书,在Windows Vista下运行mmc即可。如果使用其他版本的Windows,则启动管理控制台后可通过文件菜单中的添加或删除管理单元来实现。图1所示为“添加或删除管理单元”对话框。

单击“添加”按钮,显示“证书管理单元”对话框,如图2所示。

选择用户账户管理的证书,单击“完成”按钮。

如果需要在服务账户或所有用户下运行签名脚本,则需要添加另一个证书管理单元实例。图3所示为在管理控制台中添加了当前用户和计算机两个管理单元。

也可以使用PowerShell来访问证书,默认情况下证书提供程序注册了证书驱动器。可以如同访问文件系统那样来访问证书系统,如下代码列举当前用户中的所有证书:

PS C:\> dir cert:\CurrentUser
Name : SmartCardRoot
Name : UserDS
Name : AuthRoot
Name : CA
Name : Trust
Name : Disallowed
Name : My
Name : Root
Name : TrustedPeople
Name : TrustedPublisher

从管理控制台获取的结果更为友好,如个人和信任根证书授权等。而PowerShell提供程序则用了简短名,如My和AuthRoot。

3 创建自签名证书

从CA获取一张证书需要花费较长的一段时间,而且并不便宜。使用代码签名工具和基础设施提供的手段可以很好地平衡安全性和实用性。这种方法即用户创建自签名证书并用其签名脚本,如同使用第三方CA签发的数字证书。唯一的区别是自签名证书建立在自己的信任体系上,而第三方CA证书建立在整个社会的信任体系上。自签名证书可以让每个人创建一张相同的证书,而不是人人保护自己的私钥。在大多数情况下这类有限的保护没有严重的问题,在单一网络中分发脚本并不会存在大的安全漏洞。正如所有任何安全并不是绝对的,而总是相对的。

3.1 创建认证证书

发行脚本签名证书之前需要创建CA认证证书,即根证书。通常情况下,第三方CA的根证书会被导人本地证书存储区,因此可以用来验证脚本签名。这里使用作为微软.NET 2.0框架SDK一部分且自由免费下载的Makecert.exe工具来创建根证书,如果计算机已经安装SDK,则makecert.exe可能已经包含在其路径中,可以直接使用程序名来调用;否则可以自己添加。使用以下参数来调用makecert.exe:

PS C:\> .\makecert -n "CN=Windows PowerShell Certification Authority"`
>> -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -ss Root -sr CurrentUser `
>> -sv PowerShell_CA.pvk PowerShell_CA.cer
>>
Succeeded

执行后显示如图4所示的“Create Private Key Password”对话框。

在其中输入私钥密码,涉及处理私钥保存的文件均受密码保护,从而增加了一道安全屏障。单击“OK”按钮,显示“Enter Private Key Password”对话框,如图5所示。

再次输入私钥密码,单击“OK”按钮,CA根证书会被自动添加到当前用户的证书存储区内。Windows会弹出警告信息提示用户,一旦信任这张证书,则自动信任所有由此签发的所有证书,如图6所示。

单击“是”按钮,然后可在已导入证书的Trusted Root Certification Authorities(信任的根CA)目录中找到已导入的根证书,如图7所示。

如果用户不再信任此证书,将其从该目录中删除即可。

双击其中的证书显示“证书”对话框,如图8所示。

其中显示证书在2040年1月1日之前一直有效。

makecert.exe工具包含的参数如下。

(1)-n:证书存储区内的证书名,可以是任何满足X.500标准的字符串,语法为CN=。

(2)-a:指定签名算法,在上例中通过指定sha1选项使用SHA-1算法。也可以使用MD5算法,此算法主要用计算文件完整性校验和,语法为-a MD5。

(3)-eku:指定增强型密钥使用对象标识符,上例中的1.3.6.1.5.5.7.3.3为标准化常数。为了让用户免于阅读X.500冗长的标准文档,不同的常数代表不同的用途。makecert.exe可以生成所有类型的证书,1.3.6.1.5.5.7.3.1将会生成用于服务器SSL通信的认证证书。

(4)-r:标识自签名证书,并告诉makecert.exe不使用CA私钥来生成该证书。

(5)-ss:指定特定的证书,将新生成的证书导入到目标区域内。在上例中使用Root是因为将证书作为根CA证书。

(6)-sr:指定证书存储位置,可选项为CurrentUser和LocalMachine,分别将证书添加到当前用户和整个计算机。

(7)-sv:指定私钥文件的存储的位置,makecert.exe自动创建这个文件夹并且在其中保存私钥文件。

(8)ProPowerShell_CA.cer:指定包含证书的文件名,其中包含公钥和关于证书持有人的附加数据。

3.2 发行代码签名证书

创建证书后需要使用makecert.exe工具关联证书所有者的公钥和私钥,执行命令:

PS C:\> .\makecert -n "CN=Windows PowerShell Script Publisher"`
>> -ss MY  -a sha1 -eku 1.3.6.1.5.5.7.3.3 –pe `
>> -iv PowerShell_CA.pvk -ic PowerShell_CA.cer
>>
Succeeded

其中的选项说明如下。

(1)-n:指定证书持有者名,因为这是作为脚本发布人的证书,所以在前面设置的是CN=Windows PowerShell Script Publisher。通过这个值可以区分证书所有者和脚本发布人的证书,并在如图9所示的“证书”对话框中通过查看发布人证书的证书路径查看二者的关系。

(2)-ss:指定包含证书的证书存储区名,这里使用的类型是MY,即将证书存放在个人证书存储区中。因为不是个CA授权证书,所以将其放置在不同的位置。

(3)-a:指定校验和算法,同样使用SHA-1。

(4)-eku:指定增强密钥使用参数,与CA证书类似,但这里传递的参数1.3.6.1.5.5.7.3.3用来指定证书被用做可执行代码签名。

(5)-pe:指定证书为可导出的并告诉makecert.exe在证书存储区中存储私钥,这样即可将证书与其相关的私钥一起导出。

(6)-iv PowerShell_CA.pvk:指定用来签发证书的私钥文件,这样后面才能验证证书。

(7)-ic PowerShell_CA.cer:指定证书授权文件,用于获取证书所有者的附加信息。

执行上述命令,显示“输入私钥密码”对话框,如图10所示。

输入私钥密码,单击“确定”按钮,显示“控制台1”窗口。可以在证书存储区中找到证书,如图11所示。

双击证书显示“证书”对话框,如图12所示。

其中提示以下内容。

(1)证书的发布目的是为了确保软件来自软件发行者,保护软件在发行后不被更改。

(2)证书由前面创建的私钥发布给Windows PowerShell Script Publisher使用。

(3)密钥图标指出证书存在与之关联的私钥存储在证书存储区内,这是由于在生成证书时使用了-pe的选项。

为复制证书,右击“控制台1”窗口证书列表中的所需证书。单击快捷菜单中的“导出”选项,启动如图13所示的证书导出向导。

选择“是,导出私钥”单选按钮,单击“下一步”按钮,显示如图14所示的“导出文件格式”对话框。

选择“个人信息交换-PKCS #12(PFX)”单选按钮,PFX(Personal Information Exchange)是唯一可用的文件格式,它是标准化且支持的包含证书和私钥关联的证书格式。

单击“下一步”按钮,显示如图15所示的“密码”对话框。

输入密码,单击“下一步”按钮。最后需要选择存放证书文件的位置,导出后即可使用这个证书来签发脚本。

4 签发脚本

在对脚本进行签名前需要确认任何运行的脚本与现有数字签名,为了强制做到这一点,可以将脚本执行策略切换到AllSigned。只有管理员用户的权限能够在本机切换执行策略,执行方式如下:

PS C:\> Set-ExecutionPolicy AllSigned
PS C:\> Get-ExecutionPolicy
AllSigned

下面来创建一个简单的脚本用于返回所有当前目录下的证书文件,并尝试执行:

PS C:\> .\Get-CertificateFiles.ps1
File C:\Get-CertificateFiles.ps1 cannot be loaded. The file
 C:\Get-CertificateFiles.ps1 is not digitally signed. The sc
ript will not execute on the system. 
Please see "get-help about_signing" for more details..
At line:1 char:27
+ .\Get-CertificateFiles.ps1 <<<<
    + CategoryInfo          : NotSpecified: (:) [], PSSecurityException
    + FullyQualifiedErrorId : RuntimeException

在设置AllSigned脚本执行策略后,不允许执行没有签名脚本。下面为脚本进行数字签名,需要获取签名证书与脚本路径并传递给Set-AuthenticodeSignature cmdlet:

PS C:\> $certStore = dir cert:\CurrentUser\My
PS C:\> $certStore

    Directory: Microsoft.PowerShell.Security
\Certificate::CurrentUser\My

Thumbprint                                Subject
----------                                -------
917A05EA8A3C2A9A0CE88B8EADFEA30D8C97869E  
CN=Windows PowerShell Script Publisher


PS C:\> Set-AuthenticodeSignature
 Get-CertificateFiles.ps1 -cert $certStore

    Directory: C:\

SignerCertificate                                     
Status                  Path
917A05EA8A3C2A9A0CE88B8EADFEA30D8C97869E  Valid     Get-CertificateFiles.ps1

需要强调的是在当前的个人证书存储区中仅包含一张证书,如果有多张,则需要增加索引值来指定特定的证书,如(dir cert:\CurrentUser\My)[0]返回特定的证书。上例中的Set-AuthenticodeSignature返回脚本签名,也可以通过使用Get-AuthenticodeSignature这个cmdlet来获取特定脚本的签名是签发的:

PS C:\> Get-AuthenticodeSignature .\Get-CertificateFiles.ps1

    Directory: C:\

SignerCertificate                                     Status          Path
917A05EA8A3C2A9A0CE88B8EADFEA30D8C97869E  Valid   Get-CertificateFiles.ps1

如果状态为Valid,则可安全执行脚本,下面调用它:

PS C:\> .\Get-CertificateFiles.ps1

Do you want to run software from this untrusted publisher?
File C:\Get-CertificateFiles.ps1 is published by 
CN=Windows PowerShell Script Publisher and is not 
trusted on your
system. Only run scripts from trusted publishers.
[V] Never run  [D] Do not run  [R] Run once  
[A] Always run  [?] Help (default is "D"): A

    Directory: C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         2009/3/31     21:07       
 618 PowerShell_CA.cer

这是首次运行由此签发人发布的脚本,PowerShell提示用户是否信任这个脚本的签名发布人。因为能够确认这个脚本是安全的,而且脚本签发人是合法的,所以允许其执行。选择[A],这样以后由Windows PowerShell Script Publisher这个签发人发布的脚本总是可以执行的。一旦信任某个脚本发行人,PowerShell会提取出证书并将其保存在受信任的发行人证书存储区中,如图16所示。

从受信任的发行人存储区删除这张证书,在下次执行这个已经签名的脚本时,PowerShell会提示是否要执行不信任的发行人发布的脚本。

前面曾经导出发行人证书,也可以再次从导出的PFX文件中获取该证书,为此执行Get-PfxCerttficate这个cmdlet:

PS C:\> $certFile = Get-PfxCertificate .\ScriptSign.pfx
Enter password: ******

需要强调的是必须得输入私钥密码,证书存储区是一个相对安全的存储区域,而且管理方便。在获取证书后有关脚本签名的操作相同:

PS C:\> Set-AuthenticodeSignature .\Get-CertificateFiles.ps1 -cert $certFile

    Directory: C:\

SignerCertificate                        Status                         Path
-----------------                         ------                           ----
917A05EA8A3C2A9A0CE88B8EADFEA30D8C97869E  Valid     Get-CertificateFiles.ps1

PS C:\> Get-AuthenticodeSignature .\Get-CertificateFiles.ps1

    Directory: C:\

SignerCertificate                                   Status              Path
-----------------                                      ------            ----
917A05EA8A3C2A9A0CE88B8EADFEA30D8C97869E  Valid         CertificateFiles.ps1

查看脚本的源文件能够看到在原有的代码外增加了如下内容:

PS C:\> type Get-CertificateFiles.ps1
dir  *.cer

# SIG # Begin signature block
# MIIEYgYJKoZIhvcNAQcCoIIEUzCCBE8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# ……
# JbSQuddaE5+ZzOyYakEOQrY1taMiFQ==
# SIG # End signature block

签名在原有代码后以注释形式添加了签名的内容,这样可以通过更改脚本来校验数字签名可否发现脚本已被改变。假设一个恶意用户在已签名文档前面增加如下恶意语句:

del /Q C:\*.* #这是模拟恶意用户增加的安静模式下删除所有系统盘文件的命令
dir  *.cer

# SIG # Begin signature block
# MIIEYgYJKoZIhvcNAQcCoIIEUzCCBE8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# ......
# JbSQuddaE5+ZzOyYakEOQrY1taMiFQ==
# SIG # End signature block

再次执行脚本:

PS C:\> .\Get-CertificateFiles.ps1
File C:\Get-CertificateFiles.ps1 cannot be loaded. 
The contents of file C:\Get-CertificateFiles.ps1 
may have been tampe
red because the hash of the file does not match the 
hash stored in the digital signature. The script will not execute o
n the system. Please see "get-help about_signing" for more details..
At line:1 char:27
+ .\Get-CertificateFiles.ps1 <<<<
    + CategoryInfo          : NotSpecified: (:) [], PSSecurityException
    + FullyQualifiedErrorId : RuntimeException

可以看到PowerShell提示脚本内容在签名之后已被修改,并拒绝执行内部的代码。下面手动验证签名:

PS C:\> Get-AuthenticodeSignature .\Get-CertificateFiles.ps1

    Directory: C:\

SignerCertificate                         
Status                                 Path
917A05EA8A3C2A9A0CE88B8EADFEA30D8C97869E  HashMismatch                           
Get-CertificateFiles.ps1

可以看到其中的Status属性是HashMismach(哈希不匹配)值,这意味着计算现有脚本校验和与原始数字签名时的代码不一致。这样在数字签名不一致的情况下,不能在本机执行其他脚本。

如果删除刚才添加的语句,则再次验证脚本签名为合法;如果更改其中的内容,如增加空格、回车,或改变命令大小写,则提示HashMismatch;如果更改签名中的内容,如替换原有签名中的任何字符,则提示noSigned。如果收到一个陌生人发送的脚本,在执行之前除了检查脚本内容外,可以按照检查签名证书路径(已签名脚本文件的属性中会增加一个“数字签名”选项卡,单击其中的“详细信息”按钮打开“证书路径”选项卡,其中显示签名证书与其根证书之间的关系)。如果根证书是安全的(会有多种自签名和第三方CA根证书,前者依赖对签名根证书的所有者的信任,以个人信任为基础;后者为公认的可信任第三方),则在这个基础上验证脚本签名即可确认脚本的安全性。当然检查代码才是保证脚本执行绝对安全的方法。

5 在其他电脑中运行脚本

自签名CA发布的证书签名脚本的局限是无法在其他机器验证签名,如果之前签名的证书由VeriSign证书机构颁发,则不存在这个问题,因为每台Windows主机均安装了VeriSign根证书。为了达到相同的目的,可以在要执行签名脚本的主机中安装自己创建的根CA证书。下例是在没有安装CA根证书的计算机上执行签名脚本的结果:

PS C:\> .\Get-CertificateFiles.ps1
File C:\Get-CertificateFiles.ps1 cannot be 
loaded because the execution of scripts is disabled 
on this system. Please s
ee "get-help about_signing" for more details.
At line:1 char:27
+ .\Get-CertificateFiles.ps1 <<<<
    + CategoryInfo          : NotSpecified: (:) [], PSSecurityException
    + FullyQualifiedErrorId : RuntimeException

Powershell尝试在收信人的根证书授权存储区中查找当前脚本的签名根证书时失败,下面导出根证书并导入到每台需要该CA根证书支持的目标主机中。

右击需要导出的证书,单击快捷菜单中的“导出”选项。然后在导出向导的第2步选择默认的DER编码二进制文件类型,如图17所示。

最后保存为.cer文件,并传输到目标主机。需要强调的是这次导入不要求密码,这是因为该证书仅包含公钥。没有私钥的情况下不需要密码,密码伴随私钥而生。makecert.exe创建认证授权的私钥并将其保存在PowerShell_CA.pvk文件中,应保证这个文件的安全。

在要导入证书的目标主机上打开管理控制台,添加证书管理单元。右击受信任的证书存储区中的证书子目录,单击快捷菜单中的“导入”选项。导入向导第1步会要求定位到相应的证书,并要求确认是否要把证书导入到受信任的根证书目录下,如图18所示。

确认导入根CA证书,即可执行由此根证书发布的证书签名的脚本。

6 总 结

签发代码并使用校验和与加密算法是个很宽泛的话题,本文仅介绍了签名文件如何工作、如何在不安全的媒介上传输时存在的缺陷、如何交换密钥和证书,以及如何管理和配置信任的签名授权。

在本文展示了如何建立自己的PowerShell运行脚本的实验环境,签署脚本和签署其他类型的代码类似,由此可以学到对程序进行签名的基本方法。任何手段的安全都是相对的,没有绝对的安全,只有用得好才能够保证相对的安全。

赛迪网专稿地址:http://tech.ccidnet.com/art/3539/20100709/2111047_1.html

作者: 付海军
出处:http://fuhj02.cnblogs.com
版权:本文版权归作者和博客园共有
转载:欢迎转载,为了保存作者的创作热情,请按要求【转载】,谢谢
要求:未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
个人网站: http://txj.shell.tor.hu/