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 + ")");
}
}
}
}