Brak opisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CrossPlatformValidator.cs 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using UnityEngine.Purchasing;
  5. namespace UnityEngine.Purchasing.Security
  6. {
  7. /// <summary>
  8. /// Security exception for when the store is not supported by the <c>CrossPlatformValidator</c>.
  9. /// </summary>
  10. public class StoreNotSupportedException : IAPSecurityException
  11. {
  12. /// <summary>
  13. /// Constructs an instance with a message.
  14. /// </summary>
  15. /// <param name="message"> The message that describes the error. </param>
  16. public StoreNotSupportedException(string message) : base(message)
  17. {
  18. }
  19. }
  20. /// <summary>
  21. /// Security exception for an invalid App bundle ID.
  22. /// </summary>
  23. public class InvalidBundleIdException : IAPSecurityException { }
  24. /// <summary>
  25. /// Security exception for invalid receipt Data.
  26. /// </summary>
  27. public class InvalidReceiptDataException : IAPSecurityException { }
  28. /// <summary>
  29. /// Security exception for a missing store secret.
  30. /// </summary>
  31. public class MissingStoreSecretException : IAPSecurityException
  32. {
  33. /// <summary>
  34. /// Constructs an instance with a message.
  35. /// </summary>
  36. /// <param name="message"> The message that describes the error. </param>
  37. public MissingStoreSecretException(string message) : base(message)
  38. {
  39. }
  40. }
  41. /// <summary>
  42. /// Security exception for an invalid public key.
  43. /// </summary>
  44. public class InvalidPublicKeyException : IAPSecurityException
  45. {
  46. /// <summary>
  47. /// Constructs an instance with a message.
  48. /// </summary>
  49. /// <param name="message"> The message that describes the error. </param>
  50. public InvalidPublicKeyException(string message) : base(message)
  51. {
  52. }
  53. }
  54. /// <summary>
  55. /// A generic exception for <c>CrossPlatformValidator</c> issues.
  56. /// </summary>
  57. public class GenericValidationException : IAPSecurityException
  58. {
  59. /// <summary>
  60. /// Constructs an instance with a message.
  61. /// </summary>
  62. /// <param name="message"> The message that describes the error. </param>
  63. public GenericValidationException(string message) : base(message)
  64. {
  65. }
  66. }
  67. /// <summary>
  68. /// Class that validates receipts on multiple platforms that support the Security module.
  69. /// Note that this currently only supports GooglePlay and Apple platforms.
  70. /// </summary>
  71. public class CrossPlatformValidator
  72. {
  73. private GooglePlayValidator google;
  74. private AppleValidator apple;
  75. private string googleBundleId, appleBundleId;
  76. /// <summary>
  77. /// Constructs an instance and checks the validity of the certification keys
  78. /// which only takes input parameters for the supported platforms and uses a common bundle ID for Apple and GooglePlay.
  79. /// </summary>
  80. /// <param name="googlePublicKey"> The GooglePlay public key. </param>
  81. /// <param name="appleRootCert"> The Apple certification key. </param>
  82. /// <param name="appBundleId"> The bundle ID for all platforms. </param>
  83. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert,
  84. string appBundleId) : this(googlePublicKey, appleRootCert, null, appBundleId, appBundleId, null)
  85. {
  86. }
  87. /// <summary>
  88. /// Constructs an instance and checks the validity of the certification keys
  89. /// which uses a common bundle ID for Apple and GooglePlay.
  90. /// </summary>
  91. /// <param name="googlePublicKey"> The GooglePlay public key. </param>
  92. /// <param name="appleRootCert"> The Apple certification key. </param>
  93. /// <param name="unityChannelPublicKey_not_used"> The Unity Channel public key. Not used because Unity Channel is no longer supported. </param>
  94. /// <param name="appBundleId"> The bundle ID for all platforms. </param>
  95. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, byte[] unityChannelPublicKey_not_used,
  96. string appBundleId)
  97. : this(googlePublicKey, appleRootCert, null, appBundleId, appBundleId, appBundleId)
  98. {
  99. }
  100. /// <summary>
  101. /// Constructs an instance and checks the validity of the certification keys
  102. /// which only takes input parameters for the supported platforms.
  103. /// </summary>
  104. /// <param name="googlePublicKey"> The GooglePlay public key. </param>
  105. /// <param name="appleRootCert"> The Apple certification key. </param>
  106. /// <param name="googleBundleId"> The GooglePlay bundle ID. </param>
  107. /// <param name="appleBundleId"> The Apple bundle ID. </param>
  108. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert,
  109. string googleBundleId, string appleBundleId)
  110. : this(googlePublicKey, appleRootCert, null, googleBundleId, appleBundleId, null)
  111. {
  112. }
  113. /// <summary>
  114. /// Constructs an instance and checks the validity of the certification keys.
  115. /// </summary>
  116. /// <param name="googlePublicKey"> The GooglePlay public key. </param>
  117. /// <param name="appleRootCert"> The Apple certification key. </param>
  118. /// <param name="unityChannelPublicKey_not_used"> The Unity Channel public key. Not used because Unity Channel is no longer supported. </param>
  119. /// <param name="googleBundleId"> The GooglePlay bundle ID. </param>
  120. /// <param name="appleBundleId"> The Apple bundle ID. </param>
  121. /// <param name="xiaomiBundleId_not_used"> The Xiaomi bundle ID. Not used because Xiaomi is no longer supported. </param>
  122. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, byte[] unityChannelPublicKey_not_used,
  123. string googleBundleId, string appleBundleId, string xiaomiBundleId_not_used)
  124. {
  125. try
  126. {
  127. if (null != googlePublicKey)
  128. {
  129. google = new GooglePlayValidator(googlePublicKey);
  130. }
  131. if (null != appleRootCert)
  132. {
  133. apple = new AppleValidator(appleRootCert);
  134. }
  135. }
  136. catch (Exception ex)
  137. {
  138. throw new InvalidPublicKeyException("Cannot instantiate self with an invalid public key. (" +
  139. ex.ToString() + ")");
  140. }
  141. this.googleBundleId = googleBundleId;
  142. this.appleBundleId = appleBundleId;
  143. }
  144. /// <summary>
  145. /// Validates a receipt.
  146. /// </summary>
  147. /// <param name="unityIAPReceipt"> The receipt to be validated. </param>
  148. /// <returns> An array of receipts parsed from the validation process </returns>
  149. public IPurchaseReceipt[] Validate(string unityIAPReceipt)
  150. {
  151. try
  152. {
  153. var wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(unityIAPReceipt);
  154. if (null == wrapper)
  155. {
  156. throw new InvalidReceiptDataException();
  157. }
  158. var store = (string)wrapper["Store"];
  159. var payload = (string)wrapper["Payload"];
  160. switch (store)
  161. {
  162. case "GooglePlay":
  163. {
  164. if (null == google)
  165. {
  166. throw new MissingStoreSecretException(
  167. "Cannot validate a Google Play receipt without a Google Play public key.");
  168. }
  169. var details = (Dictionary<string, object>)MiniJson.JsonDecode(payload);
  170. var json = (string)details["json"];
  171. var sig = (string)details["signature"];
  172. var result = google.Validate(json, sig);
  173. // [IAP-1696] Check googleBundleId if packageName is present inside the signed receipt.
  174. // packageName can be missing when the GPB v1 getPurchaseHistory API is used to fetch.
  175. if (!string.IsNullOrEmpty(result.packageName) &&
  176. !googleBundleId.Equals(result.packageName))
  177. {
  178. throw new InvalidBundleIdException();
  179. }
  180. return new IPurchaseReceipt[] { result };
  181. }
  182. case "AppleAppStore":
  183. case "MacAppStore":
  184. {
  185. if (null == apple)
  186. {
  187. throw new MissingStoreSecretException(
  188. "Cannot validate an Apple receipt without supplying an Apple root certificate");
  189. }
  190. var r = apple.Validate(Convert.FromBase64String(payload));
  191. if (!appleBundleId.Equals(r.bundleID))
  192. {
  193. throw new InvalidBundleIdException();
  194. }
  195. return r.inAppPurchaseReceipts.ToArray();
  196. }
  197. default:
  198. {
  199. throw new StoreNotSupportedException("Store not supported: " + store);
  200. }
  201. }
  202. }
  203. catch (IAPSecurityException ex)
  204. {
  205. throw ex;
  206. }
  207. catch (Exception ex)
  208. {
  209. throw new GenericValidationException("Cannot validate due to unhandled exception. (" + ex + ")");
  210. }
  211. }
  212. }
  213. }