using System; using System.Linq; using System.Collections.Generic; using UnityEngine.Purchasing; namespace UnityEngine.Purchasing.Security { /// /// Security exception for when the store is not supported by the CrossPlatformValidator. /// public class StoreNotSupportedException : IAPSecurityException { /// /// Constructs an instance with a message. /// /// The message that describes the error. public StoreNotSupportedException(string message) : base(message) { } } /// /// Security exception for an invalid App bundle ID. /// public class InvalidBundleIdException : IAPSecurityException { } /// /// Security exception for invalid receipt Data. /// public class InvalidReceiptDataException : IAPSecurityException { } /// /// Security exception for a missing store secret. /// public class MissingStoreSecretException : IAPSecurityException { /// /// Constructs an instance with a message. /// /// The message that describes the error. public MissingStoreSecretException(string message) : base(message) { } } /// /// Security exception for an invalid public key. /// public class InvalidPublicKeyException : IAPSecurityException { /// /// Constructs an instance with a message. /// /// The message that describes the error. public InvalidPublicKeyException(string message) : base(message) { } } /// /// A generic exception for CrossPlatformValidator issues. /// public class GenericValidationException : IAPSecurityException { /// /// Constructs an instance with a message. /// /// The message that describes the error. public GenericValidationException(string message) : base(message) { } } /// /// Class that validates receipts on multiple platforms that support the Security module. /// Note that this currently only supports GooglePlay and Apple platforms. /// public class CrossPlatformValidator { private GooglePlayValidator google; private AppleValidator apple; private string googleBundleId, appleBundleId; /// /// Constructs an instance and checks the validity of the certification keys /// which only takes input parameters for the supported platforms and uses a common bundle ID for Apple and GooglePlay. /// /// The GooglePlay public key. /// The Apple certification key. /// The bundle ID for all platforms. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, string appBundleId) : this(googlePublicKey, appleRootCert, null, appBundleId, appBundleId, null) { } /// /// Constructs an instance and checks the validity of the certification keys /// which uses a common bundle ID for Apple and GooglePlay. /// /// The GooglePlay public key. /// The Apple certification key. /// The Unity Channel public key. Not used because Unity Channel is no longer supported. /// The bundle ID for all platforms. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, byte[] unityChannelPublicKey_not_used, string appBundleId) : this(googlePublicKey, appleRootCert, null, appBundleId, appBundleId, appBundleId) { } /// /// Constructs an instance and checks the validity of the certification keys /// which only takes input parameters for the supported platforms. /// /// The GooglePlay public key. /// The Apple certification key. /// The GooglePlay bundle ID. /// The Apple bundle ID. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, string googleBundleId, string appleBundleId) : this(googlePublicKey, appleRootCert, null, googleBundleId, appleBundleId, null) { } /// /// Constructs an instance and checks the validity of the certification keys. /// /// The GooglePlay public key. /// The Apple certification key. /// The Unity Channel public key. Not used because Unity Channel is no longer supported. /// The GooglePlay bundle ID. /// The Apple bundle ID. /// The Xiaomi bundle ID. Not used because Xiaomi is no longer supported. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, byte[] unityChannelPublicKey_not_used, string googleBundleId, string appleBundleId, string xiaomiBundleId_not_used) { try { if (null != googlePublicKey) { google = new GooglePlayValidator(googlePublicKey); } if (null != appleRootCert) { apple = new AppleValidator(appleRootCert); } } catch (Exception ex) { throw new InvalidPublicKeyException("Cannot instantiate self with an invalid public key. (" + ex.ToString() + ")"); } this.googleBundleId = googleBundleId; this.appleBundleId = appleBundleId; } /// /// Validates a receipt. /// /// The receipt to be validated. /// An array of receipts parsed from the validation process public IPurchaseReceipt[] Validate(string unityIAPReceipt) { try { var wrapper = (Dictionary)MiniJson.JsonDecode(unityIAPReceipt); if (null == wrapper) { throw new InvalidReceiptDataException(); } var store = (string)wrapper["Store"]; var payload = (string)wrapper["Payload"]; switch (store) { case "GooglePlay": { if (null == google) { throw new MissingStoreSecretException( "Cannot validate a Google Play receipt without a Google Play public key."); } var details = (Dictionary)MiniJson.JsonDecode(payload); var json = (string)details["json"]; var sig = (string)details["signature"]; var result = google.Validate(json, sig); // [IAP-1696] Check googleBundleId if packageName is present inside the signed receipt. // packageName can be missing when the GPB v1 getPurchaseHistory API is used to fetch. if (!string.IsNullOrEmpty(result.packageName) && !googleBundleId.Equals(result.packageName)) { throw new InvalidBundleIdException(); } return new IPurchaseReceipt[] { result }; } case "AppleAppStore": case "MacAppStore": { if (null == apple) { throw new MissingStoreSecretException( "Cannot validate an Apple receipt without supplying an Apple root certificate"); } var r = apple.Validate(Convert.FromBase64String(payload)); if (!appleBundleId.Equals(r.bundleID)) { throw new InvalidBundleIdException(); } return r.inAppPurchaseReceipts.ToArray(); } default: { throw new StoreNotSupportedException("Store not supported: " + store); } } } catch (IAPSecurityException ex) { throw ex; } catch (Exception ex) { throw new GenericValidationException("Cannot validate due to unhandled exception. (" + ex + ")"); } } } }