Sunday 13 October 2013

Cloud service: Windows Phone application - receipt verification

Introduction

It's time to share also something from our "technical" journey. As you may have already noticed from the earlier blog posts, our current focus has been to get Permia - Duels to work in Windows Phone, which will naturally mean that we need integrate a few modules into our cloud service to have an interface towards the Windows Phone "ecosystem".

Mobile purchase verification is an important feature that is needed to make sure that the subscriber has really purchased what it clams. In Windows Phone environment it means receipt verification. Sounds like a pretty simple feature, which it is in fact is: get receipt and verify it.

In general, the Microsoft MSDN Dev Center has instructions, guides and examples how the receipt verification works in Windows Phone, but I did find that the web pages are more or less not so well structured. I had to combine several sources to get a good idea how the verification should work and even after that "the final" solution it did not work. Well, after all we got it to work.

One notable thing is, that we had to implement almost everything by ourselves. There were lots of different kinds of implementation, but mostly those were not directly suitable for our use. As a background information, our cloud service is build up with  C/C++ and LUA scripting language, but we have also ways to extend it to use other technologies. I am going to write another post about that, so let's have a more closer look those topics then.

Receipt verification

Very first thing I did, was using search engines to find pages with keywords "Microsoft receipt verification".  I was lead to Microsoft's Dev Center web page Using receipts to verify purchases. There was a nice example in pseudo level (for me), as we are not using C# in our cloud service. Now I had an idea that in what format the receipt is received and in pseudo level what should be done.

XMLSig handling


Next step was to get further information about the receipt. The receipt is in fact an XML document having a signature element in it. There is a W3C Recommendation about XML signature syntax and I had to spend some time to get an idea how it was supposed to be used. Fortunately, I did find one nice web page, that did demonstrated how the XML signature works: Signing an XML document using XMLDSIG
The guide is split into two parts. Part 1 explains "enveloping" a signature while part 2 explains an "enveloped" signature. Microsoft receipt uses an "enveloped" signature, but it is also worth checking part 1 to get a general idea what it means.

It took a while to I implement what was needed for this, but in the end I had the needed pieces in place. I got the example from the article to work and the verification worked fine.

Fetching Certificate For Receipt Verification


Next, I started to study Microsoft's example from  Using receipts to verify purchases, and build up the certification fetch logic and used certificates to test my implementation. Below we have a receipt example and an example of HTTP messaging from certification fetch.

<receipt certificateid="b809e47cd0110a4db043b3f73e83acd917fe1336" receiptdate="2012-08-30T23:10:05Z" receiptdeviceid="4e362949-acc3-fe3a-e71b-89893eb4f528" version="1.0">
 <appreceipt appid="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" id="8ffa256d-eca8-712a-7cf8-cbf5522df24b" licensetype="Full" purchasedate="2012-06-04T23:07:24Z">
 <productreceipt appid="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" expirationdate="2012-09-02T23:08:49Z" id="6bbf4366-6fb2-8be8-7947-92fd5f683530" productid="Product1" producttype="Durable" purchasedate="2012-08-30T23:08:52Z">
 <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">
    </transform></transforms>
    <digestmethod algorithm="http://www.w3.org/2001/04/xmlenc#sha256">
    <digestvalue>cdiU06eD8X/w1aGCHeaGCG9w/kWZ8I099rw4mmPpvdU=</digestvalue>
   </digestmethod></reference>
  </signaturemethod></canonicalizationmethod></signedinfo>  <signaturevalue>SjRIxS/2r2P6ZdgaR9bwUSa6ZItYYFpKLJZrnAa3zkMylbiWjh9oZGGng2p6/gtBHC2dSTZlLbqnysJjl7mQp/A3wKaIkzjyRXv3kxoVaSV0pkqiPt04cIfFTP0JZkE5QD/vYxiWjeyGp1dThEM2RV811sRWvmEs/hHhVxb32e8xCLtpALYx3a9lW51zRJJN0eNdPAvNoiCJlnogAoTToUQLHs72I1dECnSbeNPXiG7klpy5boKKMCZfnVXXkneWvVFtAA1h2sB7ll40LEHO4oYN6VzD+uKd76QOgGmsu9iGVyRvvmMtahvtL1/pxoxsTRedhKq6zrzCfT8qfh3C1w==</signaturevalue>
 </signature>
</productreceipt></appreceipt></receipt>


First the real location of the certificate was queried from the "redirection" service (https://go.microsoft.com/fwlink/?LinkId=246509&cid=b809e47cd0110a4db043b3f73e83acd917fe1336) :

GET /fwlink/?LinkId=246509&cid=b809e47cd0110a4db043b3f73e83acd917fe1336&lt HTTP/1.1\r\n
Host: go.microsoft.com\r\n
User-Agent: seepia-rozelayde-ms-be/0.0.1\r\n
Content-Length: 0\r\n
\r\n

HTTP/1.1 302 Found\r\n
Cache-Control: private\r\n
Content-Type: text/html; charset=utf-8\r\n
Expires: Fri, 11 Oct 2013 07:26:01 GMT\r\n
Location: https://lic.apps.microsoft.com/licensing/certificateserver/?cid=b809e47cd0110a4db043b3f73e83acd917fe1336\r\n
Server: Microsoft-IIS/7.5\r\n
X-AspNet-Version: 4.0.30319\r\n
X-Powered-By: ASP.NET\r\n
Date: Fri, 11 Oct 2013 07:27:00 GMT\r\n
Content-Length: 221\r\n
\r\n
<html><head><title>Object moved</title></head><body>\r\n
<h2>
Object moved to <a href="https://lic.apps.microsoft.com/licensing/certificateserver/?cid=b809e47cd0110a4db043b3f73e83acd917fe1336">here</a>.</h2>
\r\n


GET /licensing/certificateserver/?cid=b809e47cd0110a4db043b3f73e83acd917fe1336 HTTP/1.1\r\n
Host: lic.apps.microsoft.com\r\n
User-Agent: seepia-rozelayde-ms-be/0.0.1\r\n
Content-Length: 0\r\n
\r\n

HTTP/1.1 200 OK\r\n
Cache-Control: private\r\n
Content-Type: text/plain\r\n
Server: Microsoft-IIS/7.5\r\n
X-AspNetMvc-Version: 2.0\r\n
X-AspNet-Version: 4.0.30319\r\n
X-Powered-By: ASP.NET\r\nDate: Fri, 11 Oct 2013 07:27:01 GMT\r\n
Content-Length: 1398\r\n
\r\n
-----BEGIN CERTIFICATE-----\r\n
MIIDyTCCArGgAwIBAgIQNP+YKvSo8IVArhlhpgc/xjANBgkqhkiG9w0BAQsFADCB\r\n
jjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1Jl\r\n
ZG1vbmQxHjAcBgNVBAoMFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UECwwN\r\n
V2luZG93cyBTdG9yZTEgMB4GA1UEAwwXV2luZG93cyBTdG9yZSBMaWNlbnNpbmcw\r\n
HhcNMTExMTE3MjMwNTAyWhcNMzYxMTEwMjMxMzQ0WjCBjjELMAkGA1UEBhMCVVMx\r\n
EzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1JlZG1vbmQxHjAcBgNVBAoM\r\n
FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UECwwNV2luZG93cyBTdG9yZTEg\r\n
MB4GA1UEAwwXV2luZG93cyBTdG9yZSBMaWNlbnNpbmcwggEiMA0GCSqGSIb3DQEB\r\n
AQUAA4IBDwAwggEKAoIBAQCcr4/vgqZFtzMqy3jO0XHjBUNx6j7ZTXEnNpLl2VSe\r\n
zVQA9KK2RlvroXKhYMUUdJpw+txm1mqi/W7D9QOYTq1e83GLhWC9IRh/OSmSYt0e\r\n
kgVLB+icyRH3dtpYcJ5sspU2huPf4I/Nc06OuXlMsD9MU4Ug9IBD2HSDBEquhGRo\r\n
xV64YuEH4645oB14LlEay0+JZlkKZ/mVhx/sdzSBfrda1X/Ckc7SOgnTSM3d/DnO\r\n
5DKwV2WYn+7i/rBqe4/op6IqQMrPpHyem9Sny+i0xiUMA+1IwkX0hs0gvHM6zDww\r\n
TMDiTapbCy9LnmMx65oMq56hhsQydLEmquq8lVYUDEzLAgMBAAGjITAfMB0GA1Ud\r\n
DgQWBBREzrOBz7zw+HWskxonOXAPMa6+NzANBgkqhkiG9w0BAQsFAAOCAQEAeVtN\r\n
4c6muxO6yfht9SaxEfleUBIjGfe0ewLBp00Ix7b7ldJ/lUQcA6y+Drrl7vjmkHQK\r\n
OU3uZiFbCxTvgTcoz9o+1rzR/WPXmqH5bqu6ua/UrobGKavAScqqI/G6o56Xmx/y\r\n
oErWN0VapN370crKJvNWxh3yw8DCl+W0EcVRiWX5lFsMBNBbVpK4Whp+VhkSJilu\r\n
iRpe1B35Q8EqOz/4RQkOpVI0dREnuSYkBy/h2ggCtiQ5yfvH5zCdcfhFednYDevS\r\n
axmt3W5WuHz8zglkg+OQ3qpXaXySRlrmLdxEmWu2MOiZbQkU2ZjBSQmvFAOy0dd6\r\n
P1YLS4+Eyh5drQJc0Q==\r\n
-----END CERTIFICATE-----
Now our cloud service was able to fetch the certificate that was needed to verify the receipt.

Receipt verification: failure and solution


I failed to get the verification of the example receipt to work. I spent a "few" hours to verify the logic and retested with different scenarios. Then finally I got it: the receipt was in wrong format, as all hash calculations were based on a receipt where there were no newlines or spaces between the XML nodes. I don't know if I missed something while reading the Microsoft Dev Center pages or XMLSig recommendation's canonicalization definitions, but also the Microsoft beta server sent the receipt in this format.

Real format of Receipt:

<receipt certificateid="b809e47cd0110a4db043b3f73e83acd917fe1336" receiptdate="2012-08-30T23:10:05Z" receiptdeviceid="4e362949-acc3-fe3a-e71b-89893eb4f528" version="1.0"><appreceipt appid="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" id="8ffa256d-eca8-712a-7cf8-cbf5522df24b" licensetype="Full" purchasedate="2012-06-04T23:07:24Z"><productreceipt appid="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" expirationdate="2012-09-02T23:08:49Z" id="6bbf4366-6fb2-8be8-7947-92fd5f683530" productid="Product1" producttype="Durable" purchasedate="2012-08-30T23:08:52Z"><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"></transform></transforms><digestmethod algorithm="http://www.w3.org/2001/04/xmlenc#sha256"><digestvalue>cdiU06eD8X/w1aGCHeaGCG9w/kWZ8I099rw4mmPpvdU=</digestvalue></digestmethod></reference></signaturemethod></canonicalizationmethod></signedinfo>  <signaturevalue>SjRIxS/2r2P6ZdgaR9bwUSa6ZItYYFpKLJZrnAa3zkMylbiWjh9oZGGng2p6/gtBHC2dSTZlLbqnysJjl7mQp/A3wKaIkzjyRXv3kxoVaSV0pkqiPt04cIfFTP0JZkE5QD/vYxiWjeyGp1dThEM2RV811sRWvmEs/hHhVxb32e8xCLtpALYx3a9lW51zRJJN0eNdPAvNoiCJlnogAoTToUQLHs72I1dECnSbeNPXiG7klpy5boKKMCZfnVXXkneWvVFtAA1h2sB7ll40LEHO4oYN6VzD+uKd76QOgGmsu9iGVyRvvmMtahvtL1/pxoxsTRedhKq6zrzCfT8qfh3C1w==</signaturevalue></signature></productreceipt></appreceipt></receipt>


YESH! Now the receipt verification worked with example receipt.

Windows Phone Store service & beta testing certificate


Nothing new on the planet, but we ran into more problems when we tried to verify that the receipt was received during beta testing. The problem was that the certification fetch failed for certification ID "A656B9B1B3AA509EEA30222E6D5E7DBDA9822DCD". I tried to search for a solution by using search engines and certificate ID as a search keyword, but no luck.

Then we sent a question to the community (Discussion Thread) and finally there was an answer that helped us. I could get a certificate for "A656B9B1B3AA509EEA30222E6D5E7DBDA9822DCD" from inside one sample In-app purchase receipt verification. I admit that having visited this page before, but I somehow had missed / not read the example so well. In fact in In-app purchase receipt verification page there is a statement:

Note:
The IapReceiptProduction.cer certificate file is used to verify a live in-app purchase receipt from the Windows Phone Store service.

My opinion is that Windows Phone Store service certificate is not "visible" enough and searches with keyword "A656B9B1B3AA509EEA30222E6D5E7DBDA9822DCD" will not give good results to resolve the problems I had.

We still have a one open topic. During beta testing, there was received a Receipt, that was somehow malformed, as there was missing " from XML node arguments (argument was in next format: NameOfArgument=realdata"). Our parser did not like it and when I added "-mark manually, hash calculations did fail. I have to check the issue in some point, and hopefully I manage to solve this issue soon.

Conclusion

Maybe nowadays there is a well-implemented and optimized XML parser, which runs smoothly in the newest hardware, but in general I would not like to use XML in cloud service / backend / server-side, as it is not so lightweight as well-defined "binary" formats.

This is not the only feature where Microsoft uses the XML approach and in fact it seems that Microsoft's approach to these issues is XML-driven. This is my current view, but let's see what future brings.

From the receipt verification point of view it is not such a big deal to use XML and XMLSig, as the receipt verification is not performed so often and should not cause problems to the cloud service. Hopefully our lightway implementation works for a long time and is robust if/when there are changes to the receipts. Anyway we had to also implement a backup system for the case where the receipt verification fails, so users won't lose their purchases.

Links:



If you have any questions related to this topic, just ask it via comments..

No comments:

Post a Comment