在Python中验证Windows 8内购收据签名
我正在尝试使用pyxmlsec来验证Windows 8的收据XML签名。
我的收据文件(receipt.xml
)长这样:
<?xml version="1.0"?><Receipt Version="1.0" ReceiptDate="2012-08-30T23:10:05Z" CertificateId="b809e47cd0110a4db043b3f73e83acd917fe1336" ReceiptDeviceId="4e362949-acc3-fe3a-e71b-89893eb4f528"><AppReceipt Id="8ffa256d-eca8-712a-7cf8-cbf5522df24b" AppId="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" PurchaseDate="2012-06-04T23:07:24Z" LicenseType="Full" /><ProductReceipt Id="6bbf4366-6fb2-8be8-7947-92fd5f683530" ProductId="Product1" PurchaseDate="2012-08-30T23:08:52Z" ExpirationDate="2012-09-02T23:08:49Z" ProductType="Durable" AppId="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" /><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /><DigestValue>cdiU06eD8X/w1aGCHeaGCG9w/kWZ8I099rw4mmPpvdU=</DigestValue></Reference></SignedInfo><SignatureValue>SjRIxS/2r2P6ZdgaR9bwUSa6ZItYYFpKLJZrnAa3zkMylbiWjh9oZGGng2p6/gtBHC2dSTZlLbqnysJjl7mQp/A3wKaIkzjyRXv3kxoVaSV0pkqiPt04cIfFTP0JZkE5QD/vYxiWjeyGp1dThEM2RV811sRWvmEs/hHhVxb32e8xCLtpALYx3a9lW51zRJJN0eNdPAvNoiCJlnogAoTToUQLHs72I1dECnSbeNPXiG7klpy5boKKMCZfnVXXkneWvVFtAA1h2sB7ll40LEHO4oYN6VzD+uKd76QOgGmsu9iGVyRvvmMtahvtL1/pxoxsTRedhKq6zrzCfT8qfh3C1w==</SignatureValue></Signature></Receipt>
这是我的证书(cert
):
-----BEGIN CERTIFICATE-----
MIIDyTCCArGgAwIBAgIQNP+YKvSo8IVArhlhpgc/xjANBgkqhkiG9w0BAQsFADCB
jjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1Jl
ZG1vbmQxHjAcBgNVBAoMFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UECwwN
V2luZG93cyBTdG9yZTEgMB4GA1UEAwwXV2luZG93cyBTdG9yZSBMaWNlbnNpbmcw
HhcNMTExMTE3MjMwNTAyWhcNMzYxMTEwMjMxMzQ0WjCBjjELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1JlZG1vbmQxHjAcBgNVBAoM
FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UECwwNV2luZG93cyBTdG9yZTEg
MB4GA1UEAwwXV2luZG93cyBTdG9yZSBMaWNlbnNpbmcwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCcr4/vgqZFtzMqy3jO0XHjBUNx6j7ZTXEnNpLl2VSe
zVQA9KK2RlvroXKhYMUUdJpw+txm1mqi/W7D9QOYTq1e83GLhWC9IRh/OSmSYt0e
kgVLB+icyRH3dtpYcJ5sspU2huPf4I/Nc06OuXlMsD9MU4Ug9IBD2HSDBEquhGRo
xV64YuEH4645oB14LlEay0+JZlkKZ/mVhx/sdzSBfrda1X/Ckc7SOgnTSM3d/DnO
5DKwV2WYn+7i/rBqe4/op6IqQMrPpHyem9Sny+i0xiUMA+1IwkX0hs0gvHM6zDww
TMDiTapbCy9LnmMx65oMq56hhsQydLEmquq8lVYUDEzLAgMBAAGjITAfMB0GA1Ud
DgQWBBREzrOBz7zw+HWskxonOXAPMa6+NzANBgkqhkiG9w0BAQsFAAOCAQEAeVtN
4c6muxO6yfht9SaxEfleUBIjGfe0ewLBp00Ix7b7ldJ/lUQcA6y+Drrl7vjmkHQK
OU3uZiFbCxTvgTcoz9o+1rzR/WPXmqH5bqu6ua/UrobGKavAScqqI/G6o56Xmx/y
oErWN0VapN370crKJvNWxh3yw8DCl+W0EcVRiWX5lFsMBNBbVpK4Whp+VhkSJilu
iRpe1B35Q8EqOz/4RQkOpVI0dREnuSYkBy/h2ggCtiQ5yfvH5zCdcfhFednYDevS
axmt3W5WuHz8zglkg+OQ3qpXaXySRlrmLdxEmWu2MOiZbQkU2ZjBSQmvFAOy0dd6
P1YLS4+Eyh5drQJc0Q==
-----END CERTIFICATE-----
当我使用xmlsec1
这个控制台程序时,它是可以验证通过的(感谢我之前的一个问题):
$ xmlsec1 --verify --pubkey-cert-pem cert receipt.xml
OK
SignedInfo References (ok/all): 1/1
Manifests References (ok/all): 0/0
现在我想用pyxmlsec这个包来做同样的事情(以及它的文档):
In [1]: import xmlsec; xmlsec.init(); xmlsec.cryptoInit(); xmlsec.cryptoAppInit(None)
Out[1]: 0
In [2]: mngr = xmlsec.KeysMngr(); xmlsec.cryptoAppDefaultKeysMngrInit(mngr)
Out[2]: 0
In [3]: mngr.certLoad('cert', xmlsec.KeyDataFormatCertPem , xmlsec.KeyDataTypePublic)
Out[3]: 0
In [4]: dsig_ctx = xmlsec.DSigCtx(mngr)
In [5]: import libxml2; f = libxml2.parseFile('receipt.xml'); node = xmlsec.findNode(f.getRootElement(), xmlsec.NodeSignature, xmlsec.DSigNs)
In [6]: dsig_ctx.verify(node)
func=xmlSecKeysMngrGetKey:file=keys.c:line=1370:obj=unknown:subj=xmlSecKeysMngrFindKey:error=1:xmlsec library function failed:
func=xmlSecDSigCtxProcessKeyInfoNode:file=xmldsig.c:line=871:obj=unknown:subj=unknown:error=45:key is not found:
func=xmlSecDSigCtxProcessSignatureNode:file=xmldsig.c:line=565:obj=unknown:subj=xmlSecDSigCtxProcessKeyInfoNode:error=1:xmlsec library function failed:
func=xmlSecDSigCtxVerify:file=xmldsig.c:line=366:obj=unknown:subj=xmlSecDSigCtxSigantureProcessNode:error=1:xmlsec library function failed:
Out[6]: -1
我在这里做错了什么?怎么才能解决这个问题?或者有没有更好的Python包可以完成这个任务?
2 个回答
import xmlsec
from lxml import etree
from M2Crypto import X509
import StringIO
这是如何在不使用外部文件的情况下验证收据的方法:
首先,我们定义一个叫做`validate_win_signature`的函数,它需要两个东西:收据和证书。接下来,我们把收据的内容转成一种叫做XML的格式,这样我们就能更方便地处理它。
然后,我们给这个XML添加一个ID,以便后续使用。接着,我们找到XML中的签名部分,确保它存在并且是正确的格式。
接下来,我们创建一个签名上下文,这个上下文会帮助我们进行验证。然后,我们从证书中加载公钥,这个公钥是用来验证签名的。
我们把公钥转换成一种特定的格式,这样程序就能理解。然后,我们把这个公钥放到上下文中,最后用这个上下文来验证之前找到的签名部分。
问题似乎是,当使用 mngr.certLoad
加载证书文件时,它返回0,也就是成功,但实际上密钥管理器并没有持有任何有效的密钥(可能是个bug?)。我从这两行中得到了这个想法,它们暗示没有密钥:
func=xmlSecKeysMngrGetKey:file=keys.c:line=1370:obj=unknown:subj=xmlSecKeysMngrFindKey:error=1:xmlsec library function failed:
func=xmlSecDSigCtxProcessKeyInfoNode:file=xmldsig.c:line=871:obj=unknown:subj=unknown:error=45:key is not found:
解决办法是使用openssl将证书文件转换为公钥文件:
openssl x509 -inform pem -in cert -pubkey -noout > pubkey
然后,你可以用这个公钥来验证签名,使用Python库:
>>> key = xmlsec.cryptoAppKeyLoad('pubkey', xmlsec.KeyDataFormatPem, None, None, None)
>>> dsig_ctx = xmlsec.DSigCtx()
>>> dsig_ctx.signKey = key
>>> dsig_ctx.verify(node)
0
>>> dsig_ctx.status == xmlsec.DSigStatusSucceeded
True
或者,用密钥管理器做同样的事情:
>>> key = xmlsec.cryptoAppKeyLoad('pubkey', xmlsec.KeyDataFormatPem, None, None, None)
>>> mngr = xmlsec.KeysMngr(); xmlsec.cryptoAppDefaultKeysMngrInit(mngr)
0
>>> xmlsec.cryptoAppDefaultKeysMngrAdoptKey(mngr, key)
0
>>> dsig_ctx = xmlsec.DSigCtx(mngr)
>>> dsig_ctx.verify(node)
0
>>> dsig_ctx.status == xmlsec.DSigStatusSucceeded
True
我偶然发现了一个旧的邮件讨论串,里面有一个xmlsec c库的用户描述了他直接使用证书文件时遇到的问题,并给出了上述命令来将其转换为公钥。
他还能够通过调用 xmlSecOpenSSLAppKeyFromCertLoadBIO
让库执行转换,几封邮件后他提到的这个方法。可以推测,当给定 --pubkey-cert-pem
标志时,xmlsec命令行工具会执行这个操作。不过,我在Python库中找不到对应的方法,搜索了一下也没找到。所以目前看来,这个方法是不可行的。
编辑
可以使用M2Crypto(文档),这是一个OpenSSL的Python封装,来将证书转换为公钥。我参考了从证书提取公钥...的答案和Sheogora的博客文章,调整了代码以适应这个特定的情况。
>>> from M2Crypto import X509
>>> cert = X509.load_cert('cert', X509.FORMAT_PEM)
>>> pubkey = cert.get_pubkey().get_rsa()
>>> pubkey.save_key('pubkey', cipher=None)
1 # Success
公钥将以PEM格式保存,且没有加密,文件名为pubkey,现在可以使用 xmlsec.cryptoAppKeyLoad('pubkey', xmlsec.KeyDataFormatPem, None, None, None)
来加载它。