Skip to content

Commit

Permalink
re-write intro offers and discounts
Browse files Browse the repository at this point in the history
Also:
Fixes #357
  • Loading branch information
jamesmontemagno committed Feb 10, 2022
1 parent bd438fb commit 83b98f5
Show file tree
Hide file tree
Showing 10 changed files with 615 additions and 5,733 deletions.
116 changes: 116 additions & 0 deletions docs/SecuringPurchases.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,122 @@ The simplest and easiest (not necessarily the most secure) way is to do the foll
* Save each value out and put them in your app
* Implement the interface with this funcationality:


```csharp
/// <summary>
/// Utility security class to verify the purchases
/// </summary>
[Preserve(AllMembers = true)]
public static class InAppBillingSecurity
{
/// <summary>
/// Verifies the purchase.
/// </summary>
/// <returns><c>true</c>, if purchase was verified, <c>false</c> otherwise.</returns>
/// <param name="publicKey">Public key.</param>
/// <param name="signedData">Signed data.</param>
/// <param name="signature">Signature.</param>
public static bool VerifyPurchase(string publicKey, string signedData, string signature)
{
if (signedData == null)
{
Console.WriteLine("Security. data is null");
return false;
}

if (!string.IsNullOrEmpty(signature))
{
var key = InAppBillingSecurity.GeneratePublicKey(publicKey);
var verified = InAppBillingSecurity.Verify(key, signedData, signature);

if (!verified)
{
Console.WriteLine("Security. Signature does not match data.");
return false;
}
}

return true;
}

/// <summary>
/// Generates the public key.
/// </summary>
/// <returns>The public key.</returns>
/// <param name="encodedPublicKey">Encoded public key.</param>
public static IPublicKey GeneratePublicKey(string encodedPublicKey)
{
try
{
var keyFactory = KeyFactory.GetInstance(KeyFactoryAlgorithm);
return keyFactory.GeneratePublic(new X509EncodedKeySpec(Android.Util.Base64.Decode(encodedPublicKey, 0)));
}
catch (NoSuchAlgorithmException e)
{
Console.WriteLine(e.Message);
throw new RuntimeException(e);
}
catch (Java.Lang.Exception e)
{
Console.WriteLine(e.Message);
throw new IllegalArgumentException();
}
}

/// <summary>
/// Verify the specified publicKey, signedData and signature.
/// </summary>
/// <param name="publicKey">Public key.</param>
/// <param name="signedData">Signed data.</param>
/// <param name="signature">Signature.</param>
public static bool Verify(IPublicKey publicKey, string signedData, string signature)
{
Console.WriteLine("Signature: {0}", signature);
try
{
var sign = Signature.GetInstance(SignatureAlgorithm);
sign.InitVerify(publicKey);
sign.Update(Encoding.UTF8.GetBytes(signedData));

if (!sign.Verify(Android.Util.Base64.Decode(signature, 0)))
{
Console.WriteLine("Security. Signature verification failed.");
return false;
}

return true;
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
}

return false;
}

/// <summary>
/// Simple string transform via:
/// http://stackoverflow.com/questions/11671865/how-to-protect-google-play-public-key-when-doing-inapp-billing
/// </summary>
/// <param name="key">key to transform</param>
/// <param name="i">XOR Offset</param>
/// <returns></returns>
public static string TransformString(string key, int i)
{
var chars = key.ToCharArray(); ;
for (var j = 0; j < chars.Length; j++)
chars[j] = (char)(chars[j] ^ i);
return new string(chars);
}

#pragma warning disable IDE1006 // Naming Styles
const string KeyFactoryAlgorithm = "RSA";
const string SignatureAlgorithm = "SHA1withRSA";
#pragma warning restore IDE1006 // Naming Styles
}
```

```csharp
public class Verify : IInAppBillingVerifyPurchase
{
Expand Down
5,568 changes: 11 additions & 5,557 deletions src/InAppBillingTests/InAppBillingTests.Android/Resources/Resource.designer.cs

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions src/Plugin.InAppBilling/Converters.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,30 @@ public static InAppBillingPurchase ToIABPurchase(this PurchaseHistoryRecord purc
State = PurchaseState.Unknown
};
}

public static InAppBillingProduct ToIAPProduct(this SkuDetails product)
{
return new InAppBillingProduct
{
Name = product.Title,
Description = product.Description,
CurrencyCode = product.PriceCurrencyCode,
LocalizedPrice = product.Price,
ProductId = product.Sku,
MicrosPrice = product.PriceAmountMicros,
AndroidExtras = new InAppBillingProductAndroidExtras
{
SubscriptionPeriod = product.SubscriptionPeriod,
LocalizedIntroductoryPrice = product.IntroductoryPrice,
MicrosIntroductoryPrice = product.IntroductoryPriceAmountMicros,
FreeTrialPeriod = product.FreeTrialPeriod,
IconUrl = product.IconUrl,
IntroductoryPriceCycles = product.IntroductoryPriceCycles,
IntroductoryPricePeriod = product.IntroductoryPricePeriod,
MicrosOriginalPriceAmount = product.OriginalPriceAmountMicros,
OriginalPrice = product.OriginalPrice
}
};
}
}
}
133 changes: 2 additions & 131 deletions src/Plugin.InAppBilling/InAppBilling.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,23 +165,7 @@ public async override Task<IEnumerable<InAppBillingProduct>> GetProductInfoAsync
ParseBillingResult(skuDetailsResult?.Result);


return skuDetailsResult.SkuDetails.Select(product => new InAppBillingProduct
{
Name = product.Title,
Description = product.Description,
CurrencyCode = product.PriceCurrencyCode,
LocalizedPrice = product.Price,
ProductId = product.Sku,
MicrosPrice = product.PriceAmountMicros,
LocalizedIntroductoryPrice = product.IntroductoryPrice,
MicrosIntroductoryPrice = product.IntroductoryPriceAmountMicros,
FreeTrialPeriod = product.FreeTrialPeriod,
IconUrl = product.IconUrl,
IntroductoryPriceCycles = product.IntroductoryPriceCycles,
IntroductoryPricePeriod = product.IntroductoryPricePeriod,
MicrosOriginalPriceAmount = product.OriginalPriceAmountMicros,
OriginalPrice = product.OriginalPrice
});
return skuDetailsResult.SkuDetails.Select(product => product.ToIAPProduct());
}


Expand Down Expand Up @@ -453,120 +437,7 @@ bool ParseBillingResult(BillingResult result)
BillingResponseCode.ItemUnavailable => throw new InAppBillingPurchaseException(PurchaseError.ItemUnavailable),
_ => false,
};
}

/// <summary>
/// Utility security class to verify the purchases
/// </summary>
[Preserve(AllMembers = true)]
public static class InAppBillingSecurity
{
/// <summary>
/// Verifies the purchase.
/// </summary>
/// <returns><c>true</c>, if purchase was verified, <c>false</c> otherwise.</returns>
/// <param name="publicKey">Public key.</param>
/// <param name="signedData">Signed data.</param>
/// <param name="signature">Signature.</param>
public static bool VerifyPurchase(string publicKey, string signedData, string signature)
{
if (signedData == null)
{
Console.WriteLine("Security. data is null");
return false;
}

if (!string.IsNullOrEmpty(signature))
{
var key = InAppBillingSecurity.GeneratePublicKey(publicKey);
var verified = InAppBillingSecurity.Verify(key, signedData, signature);

if (!verified)
{
Console.WriteLine("Security. Signature does not match data.");
return false;
}
}

return true;
}

/// <summary>
/// Generates the public key.
/// </summary>
/// <returns>The public key.</returns>
/// <param name="encodedPublicKey">Encoded public key.</param>
public static IPublicKey GeneratePublicKey(string encodedPublicKey)
{
try
{
var keyFactory = KeyFactory.GetInstance(KeyFactoryAlgorithm);
return keyFactory.GeneratePublic(new X509EncodedKeySpec(Android.Util.Base64.Decode(encodedPublicKey, 0)));
}
catch (NoSuchAlgorithmException e)
{
Console.WriteLine(e.Message);
throw new RuntimeException(e);
}
catch (Java.Lang.Exception e)
{
Console.WriteLine(e.Message);
throw new IllegalArgumentException();
}
}

/// <summary>
/// Verify the specified publicKey, signedData and signature.
/// </summary>
/// <param name="publicKey">Public key.</param>
/// <param name="signedData">Signed data.</param>
/// <param name="signature">Signature.</param>
public static bool Verify(IPublicKey publicKey, string signedData, string signature)
{
Console.WriteLine("Signature: {0}", signature);
try
{
var sign = Signature.GetInstance(SignatureAlgorithm);
sign.InitVerify(publicKey);
sign.Update(Encoding.UTF8.GetBytes(signedData));

if (!sign.Verify(Android.Util.Base64.Decode(signature, 0)))
{
Console.WriteLine("Security. Signature verification failed.");
return false;
}

return true;
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
}

return false;
}

/// <summary>
/// Simple string transform via:
/// http://stackoverflow.com/questions/11671865/how-to-protect-google-play-public-key-when-doing-inapp-billing
/// </summary>
/// <param name="key">key to transform</param>
/// <param name="i">XOR Offset</param>
/// <returns></returns>
public static string TransformString(string key, int i)
{
var chars = key.ToCharArray(); ;
for (var j = 0; j < chars.Length; j++)
chars[j] = (char)(chars[j] ^ i);
return new string(chars);
}

#pragma warning disable IDE1006 // Naming Styles
const string KeyFactoryAlgorithm = "RSA";
const string SignatureAlgorithm = "SHA1withRSA";
#pragma warning restore IDE1006 // Naming Styles

}
}
}
}

Loading

0 comments on commit 83b98f5

Please sign in to comment.