I received a very disturbing email from google:
We reviewed app name, with package name, and found that your app
uses software that contains security vulnerabilities for users. Apps
with these vulnerabilities can expose user information or damage a
user's device, and may be considered to be in violation of our
Malicious Behavior policy.
Below is the list of issues and the corresponding APK versions that
were detected in your recent submission. Please migrate your apps to
use the updated software as soon as possible and increment the version
number of the upgraded APK.
Vulnerability APK Version(s) Deadline to fix TrustManager You can find
more information about TrustManager in this Google Help Center
article.
486 September 14, 2020 Vulnerability APK Version(s) Deadline to fix To
confirm you've upgraded correctly, submit the updated version of your
app to the Play Console and check back after five hours. We'll show a
warning message if the app hasn't been updated correctly.
While these vulnerabilities may not affect every app, it's best to
stay up to date on all security patches.
If you have technical questions about the vulnerability, you can post
to Stack Overflow and use the tag "android-security." For
clarification on steps you need to take to resolve this issue, you can
contact our developer support team.
Best,
The Google Play Team
The only new thing added on app version 486 is an RSA encryption I added to some data.
This encryption is made in the following way: I manually generated an RSA key pair, stored the private key in my server and deployed the public key together with the apk.
For some requests in the app, I use the public key to encrypt the data before sending it through post request using URLConnection on the server side I decrypt it and process, then I send the response back to the user UNENCRYPTED.
so take in consideration the following:
0- there are only two requests in the app that use this technic
1- this update was made to ensure all the requests which arrive at the server came from my official app since last week I got 3 DoS attacks
2- those request existed for ages and always used standard android HTTPSUrlConnection without any extra encryption... what I made now was add an extra layer of encryption (how could it make the app less secure?)
3- the data transmitted is completely inoffensive
I know what a MITM attack is and I have done it for ages to reverse engineer some apps, I can't make this type of attack against my app without modifying the compiled code
That being said, how can I solve this problem without downgrade the work i took one week to implement?
this is the code:
post.put("p", "test");
post.put("s", Encrypter.encrypt(userid + "|" + did));
final String data = SimpleJsonDeserializer.getDefaultJson().toJson(post);
FirebaseCrashlytics.getInstance().log(data);
conn = (HttpsURLConnection) new URL(App.CLOUD_FUNCTIONS_HOST + "warsaw?v=2").openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.addRequestProperty("accept", "application/json");
conn.addRequestProperty("content-type", "application/json; charset=utf-8");
conn.addRequestProperty("content-length", data.length() + "");
conn.getOutputStream().write(data.getBytes());
conn.getOutputStream().flush();
conn.getOutputStream().close();
this is the Encrypter class (which is the new added on 486)
public final class Encrypter {
private static Cipher cipher;
public static void initialize(#NonNull final Context c) {
if (cipher == null) {
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode("my public key base64 coded".getBytes(), Base64.DEFAULT));
KeyFactory kf = KeyFactory.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, kf.generatePublic(spec));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeySpecException | InvalidKeyException e) {
App.shouldNeverHappen(e);
}
}
}
public static String encrypt(#NonNull final String s) throws IllegalBlockSizeException {
if (s.isEmpty() || s.length() > 240)
throw new IllegalBlockSizeException("Data must not be longer than 245 bytes");
try {
return new String(Base64.encode(cipher.doFinal(s.getBytes()), Base64.DEFAULT));
} catch (BadPaddingException e) {
App.shouldNeverHappen(e);
return "";
}
}
}
The only way to use "inject" a malicious key would modify the compiled code (which is IMPOSSIBLE to prevent) but even doing this the attacker would only be able to modify the post data signature in the requests SENT
what would be useless for any sort of phishing attack
I'm 100% sure this kind of warning is flagged by some automatical code inspection (which google spend ZERO work on re-evaluating manually), so since I got this problem, i've deployed several versions of my app just "masking around" the code to try not to be flagged on this automatic code inspection... not successful so far.
Related
I'm trying to implement Google Cloud's Translation API using only a key as authentication in my Android app. I don't have a server or anything to improve security, so I wanted to restrict the API key to only be used by my Android app by specifying the app package name and SHA-1 hash. Here are the settings I used in the Cloud API & Services page:
To ensure the information was correct, I have the Gradle App ID set to the specified package name:
I ran the provided keytool command in windows for the SHA-1 hash:
Gradle's signing report tool also returns the same hash:
When I try to call a simple GET request for supported languages, the response is always a 403 Forbidden error. However, when I remove the restriction of only Android apps and the package/hash setting, the key works. Am I clearly doing something wrong here or forgetting something? When I even log BuildConfig.getPackage(), it returns the same package name. Both ways for getting the SHA-1 hash returned the same hash. I'm not sure what is going wrong.
Finally figured it out a couple days ago. Just sending the API request from the app package does not mean it's encoded with that information somewhere for the API endpoint to know it's from an authorized accessor.
The requests need two header properties to specify the source package and signature, in the form of:
{
"X-Android-Package": "package.name.here",
"X-Android-Cert": "debug:or:release:signature:here"
}
This is nowhere in Google documentation. I have no idea why, and it's frustrating that this is apparently something everyone needs to know when using pure REST instead of Google's client libraries.
One thing this means is that it's not a good idea to hard code these values in, since someone could decompile the apk and get the authorized acccessor credentials for these headers. But you can make it more difficult by using several available functions to get this information.
Getting the package name is simple:
context.getPackageName()
Signature requires some work
public String getSignature(Context context) {
PackageInfo info = null;
try {
info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
Signature[] sigHistory = info.signingInfo.getSigningCertificateHistory();
byte[] signature = sigHistory[0].toByteArray();
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] digest = md.digest(signature);
StringBuilder sha1Builder = new StringBuilder();
for (byte b : digest) sha1Builder.append(String.format("%02x", b));
return sha1Builder.toString();
} catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
Make sure to read the SigningInfo documentation to use it correctly.
From there, set the request headers. I have been using Apache's Http Clients:
HttpGet getReq = new HttpGet(uri);
getReq.setHeader("X-Android-Package", context.getPackageName());
getReq.addHeader("X-Android-Cert", getSignature(context));
I would like to ask the following. We have a mobile app both for Android & iOS that exchanges data with a .NET server.
For Android the ksoap2 library is used, while for iOS the Alamofire with AEXML libraries are used.
We would like to enable encryption for the communication between the server and the apps, specifically Message Security with Mutual Certificates (https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-mutual-certificates)
I am not able to find any information how either the Android or the iOS client could encrypt/decrypt the requests/responses.
Can you please provide any relative information?
Thanks in advance!
For the iOS Part.
By default, Alamofire will evaluate the certificate chain provided by the server using Apple's built in validation provided by the Security framework.
While this guarantees the certificate chain is valid, it does not prevent man-in-the-middle (MITM) attacks or other potential vulnerabilities.
In order to mitigate MITM attacks, applications dealing with sensitive customer data or financial information should use certificate or public key pinning provided by the ServerTrustPolicy.
ServerTrustPolicy
The ServerTrustPolicy enumeration evaluates the server trust generally provided by an URLAuthenticationChallenge when connecting to a server over a secure HTTPS connection.
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
)
There are many different cases of server trust evaluation giving you complete control over the validation process:
performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to validate the host provided by the challenge.
pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned certificates match one of the server certificates.
pinPublicKeys: Uses the pinned public keys to validate the server trust.
The server trust is considered valid if one of the pinned public keys match one of the server certificate public keys.
disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
customEvaluation: Uses the associated closure to evaluate the validity of the server trust thus giving you complete control over the validation process. Use with caution.
AlamoFire documentation
For Android part, i am not experienced with but i came across someone asking about the same thing and got an answer with
what you need to do is only to install your certificate into the webserver and call the webservice URL like https://my.webservice.url/ instead of http://my.webservice.url/.
If your certificate is a self-signed certificate, means you did not
bought it from a certificate authority, you will need to set the
SSLSocketFactory. You can check the project wiki on how to do that:
http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks#How_to_set_the_SSLSocketFactory_on_a_https_connection__in_order
Check Here.
This might be Helpful too
UPDATE: i've found this framework SOAPEEngine
this one.
Supports Basic, Digest and NTLM Authentication, WS-Security, Client side Certificate and custom security header.
you check its example for more clarifications too.
Message encryption with WCF is done through the WS-Security protocol, by setting the security attribute mode to Message. As you have undoubtedly realized by now, WS-Security is not exactly popular on the Android and iOS platforms, mostly due to it having been superseded by other technologies (like HTTPS,) so your choices in terms of existing libraries are not abundant. The fact that not even Microsoft-owned Xamarin supports it says a lot.
First, a word on WS-Security, This protocol provides three main means of enhancing message security:
Authentication through security tokens
Signing SOAP messages
Encryption of SOAP messages
So a conforming implementation should really provide all three of these functions, but we are mostly interested in encryption here, as from the question and comments it seems like you have the authentication part working.
Therefore, assuming we are looking for a mobile platform library providing minimal WCF compatibility with WS-Security signing and encryption:
Android
On Android, the closes to your needs is WSS-Client for Android. This project:
... implements the OASIS Web Service Security (WSS) standard for
Android platforms and makes XML Encryption and XML Signature available
for tablets and smartphones.
Note that this is GPL-licensed software. The readme says to contact the author for commercial license details. However, it seems to do what you're looking for. Once you have negotiated the key exchange with the server, to encrypt a previously constructed SOAP message using the SOAPUtil class you would do something like:
SOAPMessage message = SOAPUtil.createSOAPMessage();
//... Populate the message (possibly with another library)
SecCrypto serverCrypto = new SecCrypto(serverCertificate, null);
SecCrypto clientCrypto = new SecCrypto(clientPublicKey, clientPrivateKey);
SOAPMessage securedMessage = SOAPUtil.secureSOAPMessage(message, new HashMap<String,String>(SecConstants.REQ_ENCRYPT_SIGN, "yes"), clientCrypto, serverCrypto);
//...
SOAPMessage returnedSecuredMessage = SOAPUtil.sendSOAPMessage(context, securedMessage, endpoint, cryptoParams);
SOAPMessage returnedMessage = SOAPUtil.validateSOAPMessage(returnedSecuredMessage, new HashMap<String,String>(SecConstants.RES_DECRYPT_VERIFY, "yes", decCrypto);
Nevertheless, be prepared to do quite a bit of configuration work and debugging to make it match your server's needs.
If you are looking for a more current and actively developed product, Quasar Development offers a WS-Security implementation for Android.
iOS
Things look a lot more bleak on the iOS side. There are a few libraries claiming varying degrees of support for WSS, but none of them seem to match your needs:
At first SOAPEngine looks the most promising, as it claims
support for WS-Security. However, in a footnote it says that it has a
limitation that it only supports the WCF basicHttpBinding. This
would actually be OK if true. The binding used in the sample code you
linked to in the question is wsHttpBinding however it's important
to note that both wsHttpBinding and basicHttpBinding have support
for encryption though WS-Security. The difference is that
wsHttpBinding supports WS-Security by default (whereas it needs to
be enabled with basicHttpBinding) and it also supports
WS-ReliableMessaging and some other features you may or may not
care about. But the basicHttpBinding is the one intended for
compatibility with other technologies. So in order to have
WS-Security encryption on your WCF server and maximize compatibility
with other technologies at the same time, it would be OK to use
basicHttpBinding and enable WS-Security signing and encryption by
setting the mode security attribute to Message. With the
Message attribute, from the docs:
Security is provided using SOAP message security. By default, the body
is encrypted and signed. For this binding, the system requires that
the server certificate be provided to the client out of band. The only
valid ClientCredentialType for this binding is Certificate.
But this is of no use as SOAPEngine does not have any support for
encrypting messages (or at least I could not find any support for it
in the API). The only WS-Security function it supports is
authentication. So the claim that it supports WS-Security seems
misleading as the support is quite limited.
ServiceNow offers very limited support for WS-Security. It only
supports verifying server signatures. No encryption or signing on the
client side.
Chilkat has some rudimentary XML support and there is sample
code for WS-Security authentication. I didn't see any support or
sample code for WS-Security encryption.
Therefore for iOS, to the best of my knowledge, your two options are:
Pick one of the existing libraries that best matches your other needs
and reach out to the developer and see if you can get them to add the
WS-Security features you need.
Implement the bare minimum features
you need yourself. The spec is actually not that complicated and
there is sample code out there (for non-mobile platforms) that you
can use as guide, like WSS4J for example.
In Android:
I use kasoap2 to call the web services, but before the call, to enable mutual authentication with client certificate you need to initialize a SSLContext with the client authentication keys (KeyManager).
To do that you have to load your certificate and the corresponding password in a KeyStore object, my certificate is a *.pfx file. I cerate a "PKCS12" KeyStore instance. Then you need a KeyManagerFactory object to obtain the KeyManager array. I use a "PKIX" KeyManagerFactory instance. The KeyManager array is needed to init the SSLContext.
Here is an example:
public void enableMutualAuthentication(String filename, String password) {
try {
// InputStream to read the certificate file
FileInputStream cert = new FileInputStream(filename);
char[] pass = password.toCharArray();
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(cert ,pass);
cert.close();
KeyManagerFactory keymanagerfactory = javax.net.ssl.KeyManagerFactory.getInstance("PKIX");
keymanagerfactory.init(keystore, pass);
KeyManager[] keymanagers = keymanagerfactory.getKeyManagers();
// This is not for the mutual authentication.
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
// Install the mutual authentication manager
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(keymanagers, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
}
Check those links, was what help me most.
https://chariotsolutions.com/blog/post/https-with-client-certificates-on/
http://callistaenterprise.se/blogg/teknik/2011/11/24/android-tlsssl-mutual-authentication/
I am particularly new to App development and have learnt lately about the entire Signing process of an apk, why it's mandatory and it's importance to prevent unauthorized and tampering of the app.
One of the famous checks for Signature refers to using the PackageManager class to do signature verification. Is there any other method which checks for the META-INF directory in the apk itself for tampering or other abusive activities to verify the App is not tampered and intact with its original signature?
Best practice is Server Side Tampering Detection which can not be patched.
Here is how:
Use Android SafetyNet. This is how Android Pay validates itself.
The basic flow is:
Your server generates a nonce that it sends to the client app.
The app sends a verification request with the nonce via Google Play Services.
SafetyNet verifies that the local device is unmodified and passed the CTS.
A Google-signed response ("attestation") is returned to your app with a pass/fail result and information about your app's APK (hash and sigining certificate).
Your app sends the attestation to your server.
Your server validates the nonce and APK signature, and then submits the attestation to a Google server for verification. Google checks the attestation signature and tells you if it is genuine.
If this passes, you can be fairly confident that the user is running a genuine version of your app on an unmodified system. The app should get an attestation when it starts up and send it along to your sever with every transaction request.
Note, however, this means:
Users who have rooted their phone will not pass these checks
Users who have installed custom or third-party ROM/firmware/OS (eg Cyanogen) will not pass these checks
Users who do not have access to Google Play Services (eg Amazon
devices, people in China) will not pass these checks
...and therefore will be unable to use your app. Your company needs to make a business decision as to whether or not these restrictions (and the accompanying upset users) are acceptable.
Finally, realize that this is not an entirely airtight solution. With root access and perhaps Xposed, it is possible to modify the SafetyNet library to lie to Google's servers, telling them the "right" answers to get a verification pass result that Google signs. In reality, SafetyNet just moves the goalposts and makes it harder for malicious actors. Since these checks ultimately have to run on a device out of your control, it is indeed impossible to design an entirely secure system.
Read an excellent analysis of how the internals of SafetyNet work here.
This Code Get CRC Checksum of Classes.dex file and compare it with provided one.
private void crcTest() throws IOException {
boolean modified = false;
// required dex crc value stored as a text string.
// it could be any invisible layout element
long dexCrc = Long.parseLong(Main.MyContext.getString(R.string.dex_crc));
ZipFile zf = new ZipFile(Main.MyContext.getPackageCodePath());
ZipEntry ze = zf.getEntry("classes.dex");
if ( ze.getCrc() != dexCrc ) {
// dex has been modified
modified = true;
}
else {
// dex not tampered with
modified = false;
}
}
I have this scenario where my App needs to make requests towards a secure server (NON http(s), actually it is about SIP protocol but the question should apply to any non http(s) protocol), and I need be able to tell if the server is considered trusted, based on the System Default Trusted certificates installed in my Android device's keystore.
The problem is that after checking all the APIs Android provides for certificates (like KeyStore, KeyChain, etc) I haven't been able to find a solution.
Seems that each app, even though it can gain access to the System Default keystore of the device, it can only access it's own resources, not global, even when we are talking about TrustedCertificateEntry-type entries.
Is there anything I'm missing here?
Seems like a pretty valid use case for non-https authentication
Best regards,
Antonis
Finally, managed to find a way to do this, so let me share in case this can be useful to others. Turns out Android gives access to system wide trusted certificates. The detail here (and the reason it didn't work for me previously) was the keystore 'type' identifier that I used:
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
Which I believe was trying to find actual keys, which off course shouldn't be shared. So after some digging I found that there's a separate type, AndroidCAStore, which did the trick for me. So here's a working code excerpt, that just prints out certificates:
try {
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
ks.load(null);
try {
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
Certificate cert = ks.getCertificate(aliases.nextElement());
Log.e(TAG, "Certificate: " + cert.toString());
}
}
catch(Exception e) {
e.printStackTrace();
}
}
catch (IOException|NoSuchAlgorithmException|KeyStoreException|CertificateException e) {
e.printStackTrace();
}
I am building a simple test android app, where I am trying to get data about a particular video. I have taken the SHA1 fingerprint of my debug keystore and created a google api key with it (and my package name). I have activated the youtube api service for it.
This is my code:
YouTube youtube = new YouTube.Builder(new NetHttpTransport(), new GsonFactory(), new HttpRequestInitializer() {
public void initialize(HttpRequest request) throws IOException {}
}).setApplicationName("MyApp").build();
try {
YouTube.Videos.List listVideosRequest = youtube.videos().list("snippet,contentDetails");
listVideosRequest.setId("A3PDXmYoF5U");
listVideosRequest.setKey(GoogleAPIKey.DEBUG_GOOGLE_API_KEY);
VideoListResponse youtubeResponse = listVideosRequest.execute();
List<Video> youtubeVideos = youtubeResponse.getItems();
return youtubeVideos;
} catch (IOException e) {
Log.e("MyApp", e.getLocalizedMessage());
return null;
}
So, i always get an exception: 403, Access not configured.
I am running out of ideas, what could be wrong.
Anyone have successfully used the youtube api for android yet with an api key?
Update:
I just deeply debugged the google libraries in order to find, what the actual request looks like, that is sent to google. It is this:
https://www.googleapis.com/youtube/v3/videos?id=A3PDXmYoF5U&key=[my-debug-google-api-key]&part=snippet,contentDetails
with these headers set:
Accept-Encoding: gzip
User-Agent: MyApp Google-HTTP-Java-Client/1.15.0-rc (gzip)
I don't see anything wrong there.
Ok, after a lot of reading I found this google doc :
You must send an authorization token for every insert, update, and delete request. You must also send an authorization token for any request that retrieves the authenticated user's private data.
In addition, some API methods for retrieving resources may support parameters that require authorization or may contain additional metadata when requests are authorized. For example, a request to retrieve a user's uploaded videos may also contain private videos if the request is authorized by that specific user.
So conclusion is simple - you should use API key only for reading non-private data while videos also may be private. In my case I've tried to load Channels anonymously - and it works when I requesting only channel_id. When I've requested for contentDetails (which contains link to uploads) - I've also got 403 error.