I'm trying to validate Windows 8 receipt XML signature using pyxmlsec.

My receipt (receipt.xml) looks like this:

<?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>

And here's my certificate (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-----

And it does validates when I'm using xmlsec1 console program (thanks to my previous question):

$ xmlsec1 --verify --pubkey-cert-pem cert receipt.xml
OK
SignedInfo References (ok/all): 1/1
Manifests References (ok/all): 0/0

Now I'm trying to do the same using pyxmlsec package (and its docs):

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

What am I doing wrong here? How can it be fixed? Or is there any better python package for this task?

有帮助吗?

解决方案 2

The problem seems to be that using mngr.certLoad on a certificate file returns 0, ie. success, when in fact the key manager does not hold any valid keys (bug?). I got the idea from these two lines, which imply that there is no key:

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:

The solution is to convert the certificate file into a public key file using openssl:

openssl x509 -inform pem -in cert -pubkey -noout > pubkey

Then, you can use this key to verify the signature with the python library:

>>> 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

Or, same thing with a key manager:

>>> 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

I stumbled upon an old email thread in which a user of the xmlsec c library describes his trouble with using the certificate file directly, and gives the aforementioned command to convert it to a public key.

He is also able to get the library to perform the conversion a few emails later by making a call to xmlSecOpenSSLAppKeyFromCertLoadBIO. Presumably the xmlsec command line utility does this when given the --pubkey-cert-pem flag. However, I was not able to find a corresponding method in the python library after greping around for a bit. So it looks like at the moment it's not possible.

Edit

It is possible to convert the certificate to a public key using M2Crypto (documentation), a Python wrapper for OpenSSL. I adapted answers from Extracting public key from certificate... and a blog post from Sheogora to work with this particular case.

>>> 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

The public key will be saved in PEM format with no encryption to file pubkey and can now be loaded using xmlsec.cryptoAppKeyLoad('pubkey', xmlsec.KeyDataFormatPem, None, None, None).

其他提示

This is how the receipt can be validated without external files:


import xmlsec
from lxml import etree
from M2Crypto import X509
import StringIO

def validate_win_signature(receipt, cert): xml = etree.fromstring(receipt) xmlsec.tree.add_ids(xml, ["ID"]) signature_node = xmlsec.tree.find_node(xml, xmlsec.Node.SIGNATURE) assert signature_node is not None assert signature_node.tag.endswith(xmlsec.Node.SIGNATURE) ctx = xmlsec.SignatureContext() certx509 = X509.load_cert_string(cert) pubkey = certx509.get_pubkey().get_rsa().as_pem(cipher=None) keystream = StringIO.StringIO(pubkey) key = xmlsec.Key.from_memory(keystream, xmlsec.KeyFormat.PEM) ctx.key = key ctx.verify(signature_node)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top