i' having trouble implementing in app billing in my android app.
i'm getting a purchase signature verification failed.
In a first time i tough it was the base64 key but i checked it many times and i'm still getting the error, then after i took a look at the Security.java file and i found this method which i edited for get some informations about what was wrong:
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
if(TextUtils.isEmpty(signedData)) Log.d(TAG, "SIGNED DATA EMPTY");
if(TextUtils.isEmpty(base64PublicKey)) Log.d(TAG, "KEY IS EMPTY");
if(TextUtils.isEmpty(signature)) Log.d(TAG, "SIGNATURE IS EMPTY");
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
And i'm getting "signature is empty".
Even after i follow the steps below:
-Sign the apk with my release key
-upload it as a draft
-install it on a device with "adb -d install app.apk"
I'm testing with real purchases.
Thanks.
Edit The purchase flow is fine, i get the error when i call queryInventoryAsync
You can use the test SKU's to do testing, as explained here. These are:
android.test.purchased
android.test.canceled
android.test.refunded
android.test.item_unavailable
These purchases will be successful (at least the android.test.purchased) even in test and debug scenario's, without the need to cancel the purchase.
In the verifyPurchase I changed return false to:
Log.e(TAG, "Purchase verification failed: missing data.");
if (BuildConfig.DEBUG) {
return true;
}
return false;
but you should be aware to use this only in test scenario's.
This will return true, if you have a debug build, and the signature data is missing. Since the BuildConfig.DEBUG will be false in a production build this should be OK. But better is to remove this code after everything is debugged.
Replace your verifyPurchase() method with below one. Use old code that given below, google developer are trying to solve this error in the near future but before they updated their code you should prefer below code.
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (signedData == null) {
Log.e(TAG, "data is null");
return false;
}
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {
PublicKey key = Security.generatePublicKey(base64PublicKey);
verified = Security.verify(key, signedData, signature);
if (!verified) {
Log.w(TAG, "signature does not match data.");
return false;
}
}
return true;
}
check this link for more information:
In App billing not working after update - Google Store
Use try to replace OLD CODE method verifyPurchase() method in your project. But It should be only happens when you are trying to purchase test products. Let me know for the real products purchase also after using this code.
Edit:
Why it happens because we will not get any signature while we are using dummy product like "android.test.purchased". So in the old code it is working good because we were return true even if signature is not given and for the New code we are returning false.
more information about the signature data null or blank from link1 and link2
So I suggest you just replace old code method verifyPurchase() instead of New Code method.
I think may be New Code will work fine for the real product but not in the dummy product. But yet I have not tested for the real product.
or
use GvS's answer for the test purchases it also the good solution for the new code.
Hope it will solve your problem.
Make sure that you are logged in with the right user on your phone or e.g. add your phone's google account as a test user in the developer console.
http://developer.android.com/google/play/billing/billing_testing.html#billing-testing-static:
In some cases, the reserved items may return signed static responses, which lets you test signature verification in your application. The reserved items only return signed responses if the user running the application has a developer or test account.
set return value to true In
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
return true;
}
after tesing undo the change
Related
Please help, I have the below implementation in MainApplication.java file. I am checking fixed hostnames and then returning true or false accordingly.
Error Insecure hostname verifier
Security Your app is using an unsafe implementation of hostname
verifier. Please see this Google Help Centre article for details,
including the deadline for fixing the vulnerability.
Ld/a/a/a/a/c/e$1; Ld/a/a/a/a/c/f$1; sv:deadline:12/10/2020
#Override
public void onCreate() {
super.onCreate();
hostnameVerifier();
MobileCore.setApplication(this);
SoLoader.init(this, /* native exopackage */ false);
ReactNativeFirebaseApp.setApplicationContext(this);
}
private void hostnameVerifier(){
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
String hostList[] = {"in.appcenter.ms", "graph.facebook.com",
"assets.adobedtm.com", "codepushupdates.azureedge.net", "app-measurement.com",
"forter.com", "dmn1", "dmn2", "dmn3", "quantummetric.com", "urbanairship.com", "demdex.net", "search.unbxd.io",
"monetate.net", "bazaarvoice.com", "google.com", "stylitics.com", "getcandid.com","braintreegateway.com"
};
for (String host : hostList) {
if (host.contains(arg0) || arg0.endsWith(host)) {
return true;
}
}
return false;
}
});
}
What should I change, please help.
The security alert is correct. Your code is putting users at risk and should be rejected from the App Store.
Why do you need this code at all? Remove it and only connect to servers when you get valid certificates back.
On google play console go to Release Management -> Select apk version -> Security tab. There you will see list of security issues with that apk along with class in your code that's causing that security issue where ever possible.
If you do not see a class name and rather see some encoded code in the security warning message, upload another build by disabling whatever code compress tool you maybe using. In my case it was proguard, I disabled it and got the library name. FYI - THe library was Braintree in my case
I've been going through the AWS Amplify docs and tutorials for how to use Amplify and Cognito identity pools together with UNauthenticated users. The example given by the Amplify docs is:
Amplify.Auth.fetchAuthSession(
result -> {
AWSCognitoAuthSession cognitoAuthSession = (AWSCognitoAuthSession) result;
switch(cognitoAuthSession.getIdentityId().getType()) {
case SUCCESS:
Log.i("AuthQuickStart", "IdentityId: " + cognitoAuthSession.getIdentityId().getValue());
break;
case FAILURE:
Log.i("AuthQuickStart", "IdentityId not present because: " + cognitoAuthSession.getIdentityId().getError().toString());
}
},
error -> Log.e("AuthQuickStart", error.toString())
);
But in practice when I use this code - I get an error printed out in LogCat:
AuthQuickStart: FAILURE IdentityId not present because: AmplifyException {message=You are currently signed out., cause=null, recoverySuggestion=Please sign in and reattempt the operation.}
Note: I did configure AWS Cognito to support Unauthenticaed users!
I've also looked everywhere for the Amplify Android API doc to see what other APIs are supported - couldn't find any Android API docs.
And looking into the AWS Amplify.Auth methods i could not find ANY function that deals with unauthenticated users
Question:
Any clue how can i use Amplify (Android) and have AWS credentials via AWS Cognito for unauthenticated users ???
This is David from the Amplify Android team. I was actually just looking into this the other day and currently there's a hack that's required to make unauth users work.
After setting up unauth/guest users through the CLI (as you mentioned you had) you have to call the getAWSCredentials method on the underlying escape hatch once for the app to get it to work.
Here's a code snippet I'd written that you can run after Amplify.configure (and again, this only needs to be run once per app install):
AWSMobileClient mobileClient = (AWSMobileClient) Amplify.Auth.getPlugin("awsCognitoAuthPlugin").getEscapeHatch();
mobileClient.getAWSCredentials(new Callback<AWSCredentials>() {
#Override
public void onResult(AWSCredentials result) {
// Now you'll see the Identity ID and AWSCredentials in the resulting auth session object.
Amplify.Auth.fetchAuthSession(
result2 -> Log.i(TAG, result2.toString()),
error -> Log.e(TAG, error.toString()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
#Override
public void onError(Exception e) {
// Handle the error however is best for your app
}
});
I'm working on a solution to avoid this hack right now and adding a documentation section on Unauth users to our site but in the meantime this should get it working for you.
Again note you only have to do this once and from then on out, it should just work when you call fetchAuthSession.
UPDATE: The non patched (official) version:
Amplify.Auth.fetchAuthSession(
result -> {
AWSCognitoAuthSession cognitoAuthSession = (AWSCognitoAuthSession) result;
switch (cognitoAuthSession.getIdentityId().getType()) {
case SUCCESS:
Log.i(TAG, "identity: " + cognitoAuthSession.getIdentityId().getValue());
Log.i(TAG, "credentials: " + cognitoAuthSession.getAWSCredentials().getValue(););
break;
case FAILURE:
Log.i(TAG, "FAILURE IdentityId not present because: " + cognitoAuthSession.getIdentityId().getError().toString());
}
},
error -> Log.e(TAG, "UNAUTH USERS ERR: " + error.toString()));
You wont be able to retrieve an authenticated session unless you have a logged in user.
If your Identity Pool (not User Pool) is configured for unauthenticated or guest users you can make a simple call to the GetId endpoint:
GetId
Generates (or retrieves) a Cognito ID. Supplying multiple logins will create an implicit linked account.
This is a public API. You do not need any credentials to call this API.
Request Syntax
{
"AccountId": "string",
"IdentityPoolId": "string",
"Logins": {
"string" : "string"
}
}
https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html
You should then be able to use the ID to retrieve a token using:
GetOpenIdToken
Gets an OpenID token, using a known Cognito ID. This known Cognito ID is returned by GetId. You can optionally add additional logins for the identity. Supplying multiple logins creates an implicit link.
The OpenID token is valid for 10 minutes.
This is a public API. You do not need any credentials to call this API.
Request Syntax
{
"IdentityId": "string",
"Logins": {
"string" : "string"
}
}
https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetOpenIdToken.html
To begin with, I'm working on a Unity Game where I'm authenticating user when the game starts. My build environment is android. I'm using Firebase authentication for Google Play Games Services to authenticate user.
When the game starts in my android device or emulator, it is able to authenticate Play Games Services as well as able to connect with Firebase (I'm getting analytics data). However, when I pass the PlayGames AuthCode into Firebase.Auth Credentials, it stops executing the code (I've debug log for it). It does not throw any error in LogCat except
Firebase | server_auth_code
I tried searching web for different issues, but nothing. I checked my keys in player setting, firebase settings, OAuth 2.0 credentials on my Google API console and even check keys from my Google Play Console (which I'm not using at this stage). I have even checked my test users email addresses in Game Services and tried multiple google play games account. But issue still persist.
I'm using similar script in my other unity project where authentication works like a charm. I tried to use same script here and ended up with this issue: here. However, I solved it by removing all the packages and re-importing them into unity and changed my call functions in the script. Now, I'm stuck at this issue.
Here is cs file:
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using UnityEngine.SocialPlatforms;
using System.Threading.Tasks;
public class SetFirebase : MonoBehaviour
{
string authCode;
void Start()
{
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder().
RequestServerAuthCode(false /* Don't force refresh */).Build();
PlayGamesPlatform.InitializeInstance(config);
PlayGamesPlatform.Activate();
Social.localUser.Authenticate((bool success) =>
{
if (success)
{
authCode = PlayGamesPlatform.Instance.GetServerAuthCode();
Debug.Log("PlayGames successfully authenticated!");
Debug.Log("AuthCode: " + authCode);
}
else
{
Debug.Log("PlayGames SignIn Failed");
}
});
Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
var dependencyStatus = task.Result;
if (dependencyStatus == Firebase.DependencyStatus.Available)
{
Debug.Log("Firebase Ready!!!");
RunFirebase();
}
else
{
Debug.LogError(System.String.Format("Could not resolve all Firebase dependencies: {0}", dependencyStatus));
}
});
}
private void RunFirebase(){
Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
Debug.Log("init firebase auth ");
Firebase.Auth.Credential credential = Firebase.Auth.PlayGamesAuthProvider.GetCredential(authCode);
Debug.Log(" passed auth code ");
auth.SignInWithCredentialAsync(credential).ContinueWith(task =>
{
if (task.IsCanceled)
{
Debug.LogError("SignInOnClick was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogError("SignInOnClick encountered an error: " + task.Exception);
return;
}
Firebase.Auth.FirebaseUser newUser = task.Result;
Debug.LogFormat("SignInOnClick: User signed in successfully: {0} ({1})", newUser.DisplayName, newUser.UserId);
});
}
}
My LogCat executes everything till "init firebase auth" but does not execute "passed auth code" so I know there is some issue in passing the credentials. It also does not run anything inside auth.SignInWithCredentialAsync(credential).
Any help or suggestion would be highly appreciated. Thank you.
There are two things I may suggest:
1) Replace ContinueWith with ContinueWithOnMainThread. This is a Firebase Extension that will guarantee that your logic runs on the main Unity thread (which tends to resolve many Unity specific issues). I go into more detail about that here.
2) Your logic may have a race condition between the Authenticate callback and the CheckAndFixDependenciesAsync continuation. These will not necessarily run in the order that you see them in your logic.
If I were building this system, I might prefer using Coroutines and a custom yield instruction:
class Authenticate : CustomYieldInstruction
{
private bool _keepWaiting = true;
public override bool keepWaiting => _keepWaiting;
public Authenticate(Social.ILocalUser user) {
user.Authenticate((bool success)=>{
/* old authentication code here */
_keepWaiting = false;
});
}
}
Then in a coroutine have something like:
private IEnumerator InitializeCoroutine() {
/* old authentication code */
// I'm ignoring error checking for now, but it shouldn't be hard to figure in.
// I'm mostly going from memory now anyway
// start both authentication processes in parallel
var authenticate = new Authenticate(Social.localUser);
var firebaseDependenciesTask = FirebaseApp.CheckAndFixDependenciesAsync();
// wait on social
yield return authenticate;
// wait on Firebase. If it finished in the meantime this should just fall through
yield return new WaitUntil(()=>firebaseDependenciesTask.IsComplete);
RunFirebase();
}
This way my logic looks roughly synchronous whilst still maintaining the asynchronosity (spell check claims that I made up that word) of the systems you're depending on and you avoid threading related issues that arise when using ContinueWith.
Let me know if that helps!
--Patrick
I'm having some difficulties consuming a web service that is available only over https.
I have read posts from several other people who is having issues with achieving this as well, but none of the answers I have seen so far has fixed the problem for me, so I will try to explain my issue here and hope some of you know how to get past this obstacle.
I'm using Xamarin Studio 6.1.1 developing for Android specifically.
I have set the "HttpClient Implementation" under "Android Build" for the project to "AndroidClientHandler" (which appears to be the latest implementation and should support TLS 1.2).
I have added a web reference (not as WCF) to the web service and supplied the login information when prompted... So far everything is going as expected.
Note: I have tested the web service from a console application in Visual Studio and it works as expected.
However, when I attempt to call one of the methods of the web service I get the same error that I can see so many others have encountered before me which is this "Error: TrustFailure (The authentication or decryption has failed.)".
I have tried several of the previous posted solutions but nothing seems to help.
1.A) providing the callback function for ServicePointManager:
ServicePointManager.ServerCertificateValidationCallback += CertificateValidationCallBack;
1.B) the callback function:
private static bool CertificateValidationCallBack(
object sender,
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
if (chain != null && chain.ChainStatus != null)
{
foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus)
{
if ((certificate.Subject == certificate.Issuer) &&
(status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot))
{
// Self-signed certificates with an untrusted root are valid.
continue;
}
else
{
if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError)
{
// If there are any other errors in the certificate chain, the certificate is invalid,
// so the method returns false.
return false;
}
}
}
}
// When processing reaches this line, the only errors in the certificate chain are
// untrusted root errors for self-signed certificates. These certificates are valid
// for default Exchange server installations, so return true.
return true;
}
else
{
// In all other cases, return false.
return false;
}
}
2) Creating an instance of the AesCryptoServiceProvider:
System.Security.Cryptography.AesCryptoServiceProvider b = new System.Security.Cryptography.AesCryptoServiceProvider();
If anyone can has a solution this the apparently pretty common problem, please don't hesitate to let me know, I only have so much hair...
kind regards,
Aidal
Possible known bug. Search this here for "https": https://releases.xamarin.com
[Mono], [Xamarin.iOS], [Xamarin.Android], [Xamarin.Mac] – 43566 –
“TrustFailure (The authentication or decryption has failed.) … Invalid
certificate received from server.” with “Error code: 0x5” or “Error
code: 0xffffffff800b010f” when attempting to access HTTPS servers on
ports other than 443
Bug reference: https://bugzilla.xamarin.com/show_bug.cgi?id=44708
I refered google play android api to check the purchase and consumption status of an in-app item.
For some orders, I can get right result,but some return the error as below:
error: {
errors: [
{
domain: "global",
reason: "purchaseTokenNotFound",
message: "The purchase token was not found.",
locationType: "parameter",
location: "token"
}
],
code: 404,
message: "The purchase token was not found."
}
Purchase token is provided by google, does it can be faked?
I found if I cancel the google order, then check the purchase status,it will return the purchase token was not found. if not, i will get the right purchase status.
Hope somebody can help.
If you are selling the same INAPP product to the same user muliple times within a short period, then it's very likely that all access tokens except the last purchase will return a 404 code.
For example:
john#example.com went to your app and purchased com.example.test.product a few times, you will probaly notice within your records (or Google Wallet Merchant account) that it's the same user buying the product.
When you go to check the last purchase from this user, then the following is likely to appear
{
kind: "androidpublisher#inappPurchase",
purchaseTime: "1409823171827",
purchaseState: "0",
consumptionState: "1",
developerPayload: "My Product | Ref | 1409823162466"
}
and yet if you were to check his previous tokens, then it's very likely that his purchases will return 404!
I had read somewhere (can't remember where) that the purchase token created for each purchase is basically based on the inapp product and google user. Therefore, it's very likely that each purchase will "destroy" any previous purchase token created for the same user.
Hope this explanation helps. I am constantly having this problem everyday when my server is attempting to connect to the Google API and check the transactions. Perhaps one day somebody will read this and provide a solution :)
The documents are misleading. You don't need to use this API to verify purchases.
Mobile app have INAPP_PURCHASE_DATA and INAPP_DATA_SIGNATURE from getBuyIntent method.
You can verify the purchase with the signature and your public key.
https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntent
You can find the public key on Google Play Developer Console -> YOUR_APP -> service and API
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
)
//replace const below with your own.
const (
pubKeyPEM=`-----BEGIN PUBLIC KEY-----
Some thing like this
-----END PUBLIC KEY-----`
data = `purchase data from getBuyIntent API`
sign = `purchase data signature from getBuyIntent API`
)
func Panic(err error) {
if err != nil {
panic(err)
}
}
func main() {
PEMBlock, _ := pem.Decode([]byte(pubKeyPEM))
if PEMBlock == nil {
Panic(fmt.Errorf("Could not parse Public Key PEM"))
}
if PEMBlock.Type != "PUBLIC KEY" {
Panic(fmt.Errorf("Found wrong key type"))
}
pubkey, err := x509.ParsePKIXPublicKey(PEMBlock.Bytes)
if err != nil {
Panic(err)
}
// compute the sha1
h := sha1.New()
h.Write([]byte(data))
// decode b64 signature
signature, err := base64.StdEncoding.DecodeString(sign)
Panic(err)
// Verify
err = rsa.VerifyPKCS1v15(pubkey.(*rsa.PublicKey), crypto.SHA1, h.Sum(nil), signature)
Panic(err)
// It verified!
fmt.Println("OK")
}