Retrofit not working with maps api of sha256 test url - android

I have used retrofit for google Places and direction API integration.Recently I got update from Google Maps for Work Support Team that In order to make sure your applications are not impacted you need to verify that the HTTPS client you are using supports SHA-256.
They have provided one test url(https://cert-test.sandbox.google.com) to validate that http-client is compatible or not
I have used https://cert-test.sandbox.google.com to validate with Retrofit but It is failing and giving me exception as mentioned below:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x1027ce0: Failure in SSL library, usually a protocol error
To be clear, I have used plain okhttpclient with this integration.
please do needful if anybody have fix for it.

Looks that server is using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 cipher. It only started being supported in API 20 (L Preview). You can see a list of supported ciphers by API level on the SSLSocket docs.
Try running your test on a 5.0 or above device. For example, the following code is successful on a device running 5.0, but gets an SSL exception on 4.4.4 --
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().get().url("https://cert-test.sandbox.google.com/")
.build();
response = client.newCall(request).execute();
Log.d(TAG, "code = " + response.code());
but the same is also true for the equivalent URLConnection code --
HttpsURLConnection con = (HttpsURLConnection) new URL("https://cert-test.sandbox.google.com/").openConnection();
con.connect();
Log.d(TAG, "code = " + con.getResponseCode());
The issue is not retrofit or okhttp, but the limitations of the default security providers provided on older phones.
You can work around it by installing a new provider. Google makes one available through google play services, and is easy to install. One line (plus exception handling, etc).
try {
ProviderInstaller.installIfNeeded(this);
} catch (GooglePlayServicesRepairableException e) {
// Fix it
} catch (GooglePlayServicesNotAvailableException e) {
// Skip it
}
For a full example, see "Updating Your Security Provider to Protect Against SSL Exploits" from Google.
Doing this will allow the above two code blocks to run on lower API versions if the phone has Google Play installed.

Related

android trust manager vulnerability

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.

Log the SSL Certificate programmatically

I have 2 devices in the wild that are not able to connect to my TLS v1.2 endpoint. All others seem able to, including browsers, PostMan and iOS devices.
The devices are running Android 5 & 7 (so there should not be a problem with the TLS v1.2 support).
Note: This is not a self-signed certificate. It is signed by Amazon.
Immediate thoughts were:
Android fragmentation - perhaps the devices (one is a Kindle Fire 7)
are not including the correct certificates into the OS. It wouldn't
be the first time that a device manufacturer made an odd decision
that breaks functionality.
API is being accessed via a proxy, and there actually is a Man-In-The-Middle, correctly being detected.
Fixing (1) means bundling our certificate, and leads to the usual problems when our cert expires.
I would prefer to get the user to install a debug build that confirms whether (1) or (2) is the problem. Such build would inspect the SSL Certificate provided by the server/proxy, and log that back to me.
Web Frameworks:
Retrofit v2.3.0
OkHttp v3.9.1
Question:
How do I inspect the information of the SSL Certificate that the device is seeing when hitting my endpoint?
Update per comment from #SangeetSuresh:
It turns out there are 2 different exceptions being thrown.
The Kindle Fire 7" Tablet (KFAUWI, OS 5.1.1) is throwing the one I have started to investigate, which this question is meant to have focused on. i.e. basic SSL failure.
java.security.cert.CertPathValidatorException:
Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:331)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:232)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)
The LG device (LG-SP200, OS 7.1.2) is having the connection closed by the peer, which should be addressed under a new question if not solved here:
javax.net.ssl.SSLHandshakeException:
Connection closed by peer
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(NativeCrypto.java)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:360)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:299)
Robby Cornelissen provided the basic answer in a comment referencing the OkHttp Response:
the information should be available from
response.handshake().peerCertificates().
A simple Interceptor was implemented to inspect the certificates, given a valid handshake:
private static class SslCertificateLogger implements Interceptor {
public static final String TAG = "SSL";
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
Log.d(TAG, "<-- HTTP FAILED: " + e);
throw e;
}
Handshake handshake = response.handshake();
if (handshake == null) {
Log.d(TAG, "no handshake");
return response;
}
Log.d(TAG, "handshake success");
List<Certificate> certificates = handshake.peerCertificates();
if (certificates == null) {
Log.d(TAG, "no peer certificates");
return response;
}
String s;
for (Certificate certificate : certificates) {
s = certificate.toString();
Log.d(TAG, s);
}
return response;
}
}
This gets added to the OkHttpClient as per normal:
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new SslCertificateLogger())
.build();
A similar solution was proposed by Sangeet Suresh that references the Retrofit Response object:
response?.raw()?.handshake() I think this will help you
Here the important information being the fact that Retrofit gives access to the raw OkHttp response in this manner.
This would not be used in an Interceptor but rather at a higher level, in the actual Retrofit handling code, after getting a Retrofit Response<> from the API.
Converting his Kotlin solution back to Java could yield something like this:
okhttp3.Response raw = httpResponse.raw();
if (raw != null) {
Handshake handshake = raw.handshake();
if (handshake != null) {
List<Certificate> certificates = handshake.peerCertificates();
if (certificates != null) {
for (Certificate certificate : certificates) {
Log.d(TAG, certificate.toString());
}
}
}
}
Both solutions work fine, provided the handshake() is not null i.e. when the handshake succeeds.
Given that this is an investigation into failed handshakes, a further step was required to "trust all certificates" (NB debug builds only!).
This has been documented many times - here is one such version:
Unsafe SSL Client (do not do this in production)

Android nv-websocket-client with SNI

I am using android nv-websocket-client library (both 2.0 and 1.31 versions) and I am trying to open a wss: connection; however, the connection fails with 503 Service not available error message.
Upon investigating I found that HAProxy requires the clients to use the SNI extension, otherwise such error is returned regardless of the Host: header (I am using HAProxy as a loadbalancer).
Upon investigating further (with tcpdump/wireshark) I found that the client does not send SNI, a wrong certificate is returned (for a different domain), yet the client continues with the TLS connection and actually sends the HTTP request (as if no certificate checking was performed?).
My code is basically:
ws = new WebSocketFactory().createSocket(wsurl);
ws.addHeader("Authorization", "Bearer " + Config.getToken());
ws.addListener(this);
ws.connectAsynchronously();
I didn't find an easy way to set up the SSLSocketFactory, however it seems to me that the code in nv-websocket-client just uses the SSLSocketFactory.getDefault(), which should be correct? SSLCertificateSocketFactory seems to be deprecated in favour of this approach.
Am I missing some key piece about SSL setup, is this and Android bug or is this and Android 'feature'?

okhttp doesn't validate pins correctly

Using com.squareup.retrofit2:retrofit:2.0.1 with com.squareup.okhttp3:okhttp:3.2.0 on an AVD with Android 6.0.
I'm trying to implement public key pinning using a self signed certificate that is signed by a Root CA. That Root CA is in the system CA trust store.
Using the example provided by okhttp wiki with some small changes:
OkHttpClient client = new OkHttpClient.Builder().certificatePinner(
new CertificatePinner.Builder()
.add(pinningUrl, "sha256/invalidPIN")
.build()).build();
Request request = new Request.Builder()
.url(pinningUrl)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
What happens is that response.isSuccessful returns true, no exception is thrown, although the pin isn't correct. The only thing that is done correctly is the validation of the certificate with the Root CAs in systems CA trust store.
What I've found to be working, is adding this line before the for loop. But that isn't the right approach because the request is already sent, the pinning should work before TLS negotiation is finished. Also this line isn't mentioned in any sample code I've found.
client.certificatePinner().check(pinningUrl, response.handshake().peerCertificates());
throws
javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Is there a bug in the sample code provided by okhttp or am I doing something wrong?
You’re configuring it incorrectly. Replace pinningUrl with the hostname of the pinning URL. For example, you want example.com instead of http://example.com/. If you’d like to send a PR to make hostname validation more strict, it would be quite welcome.

Android - youtube api r52 - "access not configured"

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.

Categories

Resources