SSL Error handler [duplicate] - android

I have a link which will open in WebView. The problem is it cannot be open until I override onReceivedSslError like this:
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
I am getting security alert from Google Play saying:
Security alert
Your application has an unsafe implementation of the WebViewClient.onReceivedSslError handler. Specifically, the implementation ignores all SSL certificate validation errors, making your app vulnerable to man-in-the-middle attacks. An attacker could change the affected WebView's content, read transmitted data (such as login credentials), and execute code inside the app using JavaScript.
To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise. An email alert containing the affected app(s) and class(es) has been sent to your developer account address.
Please address this vulnerability as soon as possible and increment the version number of the upgraded APK. For more information about the SSL error handler, please see our documentation in the Developer Help Center. For other technical questions, you can post to https://www.stackoverflow.com/questions and use the tags “android-security” and “SslErrorHandler.” If you are using a 3rd party library that’s responsible for this, please notify the 3rd party and work with them to address the issue.
To confirm that you've upgraded correctly, upload the updated version to the Developer Console and check back after five hours. If the app hasn't been correctly upgraded, we will display a warning.
Please note, while these specific issues may not affect every app that uses WebView SSL, it's best to stay up to date on all security patches. Apps with vulnerabilities that expose users to risk of compromise may be considered dangerous products in violation of the Content Policy and section 4.4 of the Developer Distribution Agreement.
Please ensure all apps published are compliant with the Developer Distribution Agreement and Content Policy. If you have questions or concerns, please contact our support team through the Google Play Developer Help Center.
If I remove onReceivedSslError (handler.proceed()), then page won't open.
Is there any way I can open the page in WebView and avoid security alert?

To properly handle SSL certificate validation, change your code to
invoke SslErrorHandler.proceed() whenever the certificate presented by
the server meets your expectations, and invoke
SslErrorHandler.cancel() otherwise.
As email said, onReceivedSslError should handle user is going to a page with invalid cert, such like a notify dialog. You should not proceed it directly.
For example, I add an alert dialog to make user have confirmed and seems Google no longer shows warning.
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.notification_error_ssl_cert_invalid);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
More explain about the email.
Specifically, the implementation ignores all SSL certificate validation
errors, making your app vulnerable to man-in-the-middle attacks.
The email says the default implement ignored an important SSL security problem. So we need to handle it in our own app which used WebView. Notify user with a alert dialog is a simple way.

The proposed solutions so far just bypass the security check, so they are not safe.
What I suggest is to embed the certificate(s) in the App, and when a SslError occurs, check that the server certificate matches one of the embedded certificates.
So here are the steps:
Retrieve the certificate from the website.
Open the site on Safari
Click on the padlock icon near the website name
Click on Show Certificate
Drag and drop the certificate in a folder
see https://www.markbrilman.nl/2012/03/howto-save-a-certificate-via-safari-on-mac/
Copy the certificate (.cer file) into the res/raw folder of your app
In your code, load the certificate(s) by calling loadSSLCertificates()
private static final int[] CERTIFICATES = {
R.raw.my_certificate, // you can put several certificates
};
private ArrayList<SslCertificate> certificates = new ArrayList<>();
private void loadSSLCertificates() {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
for (int rawId : CERTIFICATES) {
InputStream inputStream = getResources().openRawResource(rawId);
InputStream certificateInput = new BufferedInputStream(inputStream);
try {
Certificate certificate = certificateFactory.generateCertificate(certificateInput);
if (certificate instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) certificate;
SslCertificate sslCertificate = new SslCertificate(x509Certificate);
certificates.add(sslCertificate);
} else {
Log.w(TAG, "Wrong Certificate format: " + rawId);
}
} catch (CertificateException exception) {
Log.w(TAG, "Cannot read certificate: " + rawId);
} finally {
try {
certificateInput.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (CertificateException e) {
e.printStackTrace();
}
}
When a SslError occurs, check that the server certificate matches one embedded certificate. Note that it is not possible to directly compare certificates, so I use SslCertificate.saveState to put the certificate data into a Bundle, and then I compare all the bundle entries.
webView.setWebViewClient(new WebViewClient() {
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
// Checks Embedded certificates
SslCertificate serverCertificate = error.getCertificate();
Bundle serverBundle = SslCertificate.saveState(serverCertificate);
for (SslCertificate appCertificate : certificates) {
if (TextUtils.equals(serverCertificate.toString(), appCertificate.toString())) { // First fast check
Bundle appBundle = SslCertificate.saveState(appCertificate);
Set<String> keySet = appBundle.keySet();
boolean matches = true;
for (String key : keySet) {
Object serverObj = serverBundle.get(key);
Object appObj = appBundle.get(key);
if (serverObj instanceof byte[] && appObj instanceof byte[]) { // key "x509-certificate"
if (!Arrays.equals((byte[]) serverObj, (byte[]) appObj)) {
matches = false;
break;
}
} else if ((serverObj != null) && !serverObj.equals(appObj)) {
matches = false;
break;
}
}
if (matches) {
handler.proceed();
return;
}
}
}
handler.cancel();
String message = "SSL Error " + error.getPrimaryError();
Log.w(TAG, message);
}
});

I needed to check our truststore before show any message to the user so I did this:
public class MyWebViewClient extends WebViewClient {
private static final String TAG = MyWebViewClient.class.getCanonicalName();
Resources resources;
Context context;
public MyWebViewClient(Resources resources, Context context){
this.resources = resources;
this.context = context;
}
#Override
public void onReceivedSslError(WebView v, final SslErrorHandler handler, SslError er){
// first check certificate with our truststore
// if not trusted, show dialog to user
// if trusted, proceed
try {
TrustManagerFactory tmf = TrustManagerUtil.getTrustManagerFactory(resources);
for(TrustManager t: tmf.getTrustManagers()){
if (t instanceof X509TrustManager) {
X509TrustManager trustManager = (X509TrustManager) t;
Bundle bundle = SslCertificate.saveState(er.getCertificate());
X509Certificate x509Certificate;
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
}
X509Certificate[] x509Certificates = new X509Certificate[1];
x509Certificates[0] = x509Certificate;
trustManager.checkServerTrusted(x509Certificates, "ECDH_RSA");
}
}
Log.d(TAG, "Certificate from " + er.getUrl() + " is trusted.");
handler.proceed();
}catch(Exception e){
Log.d(TAG, "Failed to access " + er.getUrl() + ". Error: " + er.getPrimaryError());
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
String message = "SSL Certificate error.";
switch (er.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "O certificado não é confiável.";
break;
case SslError.SSL_EXPIRED:
message = "O certificado expirou.";
break;
case SslError.SSL_IDMISMATCH:
message = "Hostname inválido para o certificado.";
break;
case SslError.SSL_NOTYETVALID:
message = "O certificado é inválido.";
break;
}
message += " Deseja continuar mesmo assim?";
builder.setTitle("Erro");
builder.setMessage(message);
builder.setPositiveButton("Sim", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("Não", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
}
}

Fix which works for me is just disable onReceivedSslError function defined in AuthorizationWebViewClient. In this case handler.cancel will be called in case of SSL error. However it works good with One Drive SSL certificates. Tested on Android 2.3.7, Android 5.1.

According to Google Security Alert: Unsafe implementation of the interface X509TrustManager, Google Play won't support X509TrustManager from 11th July 2016:
Hello Google Play Developer,
Your app(s) listed at the end of this email use an unsafe
implementation of the interface X509TrustManager. Specifically, the
implementation ignores all SSL certificate validation errors when
establishing an HTTPS connection to a remote host, thereby making your
app vulnerable to man-in-the-middle attacks. An attacker could read
transmitted data (such as login credentials) and even change the data
transmitted on the HTTPS connection. If you have more than 20 affected
apps in your account, please check the Developer Console for a full
list.
To properly handle SSL certificate validation, change your code in the
checkServerTrusted method of your custom X509TrustManager interface to
raise either CertificateException or IllegalArgumentException whenever
the certificate presented by the server does not meet your
expectations. For technical questions, you can post to Stack Overflow
and use the tags “android-security” and “TrustManager.”
Please address this issue as soon as possible and increment the
version number of the upgraded APK. Beginning May 17, 2016, Google
Play will block publishing of any new apps or updates containing the
unsafe implementation of the interface X509TrustManager.
To confirm you’ve made the correct changes, submit the updated version
of your app to the Developer Console and check back after five hours.
If the app hasn’t been correctly upgraded, we will display a warning.
While these specific issues may not affect every app with the
TrustManager implementation, it’s best not to ignore SSL certificate
validation errors. Apps with vulnerabilities that expose users to risk
of compromise may be considered dangerous products in violation of the
Content Policy and section 4.4 of the Developer Distribution
Agreement.
...

I had the same issue and tried all the above-mentioned suggestions as below.
Implement onReceivedSslError() by giving the chance to the user to
decide handler.proceed(); or handler.cancel(); when a SSL error
occurred
Implement onReceivedSslError() to call handler.cancel(); whenever a
SSL issue occurred without considering user's decision.
Implement onReceivedSslError() to verify SSL certificate locally
addition to checking error.getPrimaryError() and providing the user
to decide handler.proceed(); or handler.cancel(); only if the SSL
certificate is valid. If not just call handler.cancel();
Removing the implementation of onReceivedSslError() and just let to
happen Android default behavior.
Even after trying all the above attempts, Google Play was keeping sending the same notification mail mentioning the same error and the old APK version (Even though in the all above attempts we changed both version code and version name in the Gradle)
We were in huge trouble and contacted Google Support via mail and asked
"We are uploading the higher versions of the APK but the review result
says the same error mentioning the old buggy APK version. What's the
reason for that ?"
After a few days, google support replied to our request as follows.
Please note that you must completely replace version 12 in your
Production track. It means that you'll have to full rollout a higher
version in order to deactivate version 12.
The highlighted point was never found or mentioned in the play console or any forum.
According to that guideline in the classic Google play view, we checked the production track and there were both buggy versions and the latest bug fixed version but the bug fix version's rollout percentage is 20%. So, turned it to full rollout then the buggy version disappeared from the production track. After more than 24 hour review time version has come back.
NOTE: When we had this issue Google has just moved to a new UI version of their play console and it had missed some views in the previous UI version or classic view. Since we were using the latest view we couldn't notice what was happening. Simply what happened was Google reviewed the same previous buggy version of the APK since the new one was not full roll-out.

You can use SslError for show, some information about the error of this certificated, and you can write in your dialog the string of the type error.
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final SslErrorHandler handlerFinal;
handlerFinal = handler;
int mensaje ;
switch(error.getPrimaryError()) {
case SslError.SSL_DATE_INVALID:
mensaje = R.string.notification_error_ssl_date_invalid;
break;
case SslError.SSL_EXPIRED:
mensaje = R.string.notification_error_ssl_expired;
break;
case SslError.SSL_IDMISMATCH:
mensaje = R.string.notification_error_ssl_idmismatch;
break;
case SslError.SSL_INVALID:
mensaje = R.string.notification_error_ssl_invalid;
break;
case SslError.SSL_NOTYETVALID:
mensaje = R.string.notification_error_ssl_not_yet_valid;
break;
case SslError.SSL_UNTRUSTED:
mensaje = R.string.notification_error_ssl_untrusted;
break;
default:
mensaje = R.string.notification_error_ssl_cert_invalid;
}
AppLogger.e("OnReceivedSslError handel.proceed()");
View.OnClickListener acept = new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
handlerFinal.proceed();
}
};
View.OnClickListener cancel = new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
handlerFinal.cancel();
}
};
View.OnClickListener listeners[] = {cancel, acept};
dialog = UiUtils.showDialog2Buttons(activity, R.string.info, mensaje, R.string.popup_custom_cancelar, R.string.popup_custom_cancelar, listeners); }

In my situation:This error occured when we try to updated apk uploaded
into the Google Play store,and getting SSL Error:
Then i have used following code
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
try {
progressDialog.dismiss();
} catch (WindowManager.BadTokenException e) {
e.printStackTrace();
}
super.onPageFinished(view, url);
}
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(PayNPayWebActivity.this);
builder.setMessage(R.string.notification_error_ssl_cert_invalid);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
}

Related

Android App is not approved with SSL Error Handler

I released a production Android app last month, but I had trouble with the SSL Error Handler.
I followed Stackoverfollow's and Google's tutorials, however Google doesn't still approve my app (note: this QA is not a duplicate).
https://support.google.com/faqs/answer/7071387
SSL Error Handler WebView Android
My code is implemented the following:
Any Fragment or Activity that uses WebViewClient, I've controlled SSL Error like this
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
LogI("onReceivedSslError: " + error.getCertificate());
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
AlertDialog alertDialog = builder.create();
String message;
switch (error.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted.";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired.";
break;
case SslError.SSL_IDMISMATCH:
message = "The certificate Hostname mismatch.";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid.";
break;
case SslError.SSL_DATE_INVALID:
message = "The date of the certificate is invalid.";
break;
default:
message = "A generic error occurred.";
break;
}
message += " Do you want to continue anyway?";
alertDialog.setTitle("SSL Certificate Error");
alertDialog.setMessage(message);
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", (dialog, which) -> handler.proceed());
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", (dialog, which) -> handler.cancel());
alertDialog.show();
}
So, Why is my app not approved? What should I do next?
Thank you for your advice!
Update 1:
I released my app in 2019 and updated it many times (there was no problem). But from 2021/5 I've got this problem.
May these errors are from old APKs, AABs version, remove/deactivate it before submitting new APKs, AABs
you have to call either handler.cancel(); (thats for your case) or super.onReceivedSslError(view, handler, error); straight inside onReceivedSslError. HERE you have some doc, in which:
The host application must call either SslErrorHandler#cancel or SslErrorHandler#proceed
and also
Application overrides of this method may display custom error pages or silently log issues, but it is strongly recommended to always call SslErrorHandler#cancel and never allow proceeding past errors.
without any of these calls some Google bot which checks apps may think, that you are disabling any SSL validation at all, which may be insecure for user
This is the code I used in my App and it was accepted. The only difference I am seeing is the try-catch block. My suggestion will be to try a simpler version on Play Store first, then update that with the specific type error message.
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
try{
final AlertDialog.Builder builder = new AlertDialog.Builder(PaymentActivity.this);
builder.setMessage(R.string.notification_error_ssl_cert_invalid);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}catch (Exception e){
e.printStackTrace();
}
}

SslError.SSL_INVALID: What is "A generic error occurred."?

Since I update my WebView-based app to comply with Google's requirement to remove the catchall SslErrorHandler.proceed():
Please address this vulnerability as soon as possible and increment
the version number of the upgraded APK. To properly handle SSL
certificate validation, change your code to invoke
SslErrorHandler.proceed() whenever the certificate presented by the
server meets your expectations, and invoke SslErrorHandler.cancel()
otherwise.
Using this solution:
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
String message = "SSL Certificate error.";
switch (error.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted.";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired.";
break;
case SslError.SSL_IDMISMATCH:
message = "The certificate Hostname mismatch.";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid.";
break;
}
message += " Do you want to continue anyway?";
builder.setTitle("SSL Certificate Error");
builder.setMessage(message);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
My app keeps encountering SslError.SSL_INVALID ("A generic error occurred.") when accessing nytimes.com.
This doesn't happen with other sites, like wikipedia.org for example.
Clearly, there is something different about nytimes that triggers an SslError. But it's not an invalid or expired cert, so I can't fix this.
What can I possibly do to troubleshoot and fix this problem?
BTW, I have tried tips 1-4 in this article (resetting my smartphone to factory settings, is just too much and unacceptable for me). To no avail.
Any ideas?

WebView: how to avoid security alert from Google Play upon implementation of onReceivedSslError

I have a link which will open in WebView. The problem is it cannot be open until I override onReceivedSslError like this:
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
I am getting security alert from Google Play saying:
Security alert
Your application has an unsafe implementation of the WebViewClient.onReceivedSslError handler. Specifically, the implementation ignores all SSL certificate validation errors, making your app vulnerable to man-in-the-middle attacks. An attacker could change the affected WebView's content, read transmitted data (such as login credentials), and execute code inside the app using JavaScript.
To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise. An email alert containing the affected app(s) and class(es) has been sent to your developer account address.
Please address this vulnerability as soon as possible and increment the version number of the upgraded APK. For more information about the SSL error handler, please see our documentation in the Developer Help Center. For other technical questions, you can post to https://www.stackoverflow.com/questions and use the tags “android-security” and “SslErrorHandler.” If you are using a 3rd party library that’s responsible for this, please notify the 3rd party and work with them to address the issue.
To confirm that you've upgraded correctly, upload the updated version to the Developer Console and check back after five hours. If the app hasn't been correctly upgraded, we will display a warning.
Please note, while these specific issues may not affect every app that uses WebView SSL, it's best to stay up to date on all security patches. Apps with vulnerabilities that expose users to risk of compromise may be considered dangerous products in violation of the Content Policy and section 4.4 of the Developer Distribution Agreement.
Please ensure all apps published are compliant with the Developer Distribution Agreement and Content Policy. If you have questions or concerns, please contact our support team through the Google Play Developer Help Center.
If I remove onReceivedSslError (handler.proceed()), then page won't open.
Is there any way I can open the page in WebView and avoid security alert?
To properly handle SSL certificate validation, change your code to
invoke SslErrorHandler.proceed() whenever the certificate presented by
the server meets your expectations, and invoke
SslErrorHandler.cancel() otherwise.
As email said, onReceivedSslError should handle user is going to a page with invalid cert, such like a notify dialog. You should not proceed it directly.
For example, I add an alert dialog to make user have confirmed and seems Google no longer shows warning.
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.notification_error_ssl_cert_invalid);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
More explain about the email.
Specifically, the implementation ignores all SSL certificate validation
errors, making your app vulnerable to man-in-the-middle attacks.
The email says the default implement ignored an important SSL security problem. So we need to handle it in our own app which used WebView. Notify user with a alert dialog is a simple way.
The proposed solutions so far just bypass the security check, so they are not safe.
What I suggest is to embed the certificate(s) in the App, and when a SslError occurs, check that the server certificate matches one of the embedded certificates.
So here are the steps:
Retrieve the certificate from the website.
Open the site on Safari
Click on the padlock icon near the website name
Click on Show Certificate
Drag and drop the certificate in a folder
see https://www.markbrilman.nl/2012/03/howto-save-a-certificate-via-safari-on-mac/
Copy the certificate (.cer file) into the res/raw folder of your app
In your code, load the certificate(s) by calling loadSSLCertificates()
private static final int[] CERTIFICATES = {
R.raw.my_certificate, // you can put several certificates
};
private ArrayList<SslCertificate> certificates = new ArrayList<>();
private void loadSSLCertificates() {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
for (int rawId : CERTIFICATES) {
InputStream inputStream = getResources().openRawResource(rawId);
InputStream certificateInput = new BufferedInputStream(inputStream);
try {
Certificate certificate = certificateFactory.generateCertificate(certificateInput);
if (certificate instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) certificate;
SslCertificate sslCertificate = new SslCertificate(x509Certificate);
certificates.add(sslCertificate);
} else {
Log.w(TAG, "Wrong Certificate format: " + rawId);
}
} catch (CertificateException exception) {
Log.w(TAG, "Cannot read certificate: " + rawId);
} finally {
try {
certificateInput.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (CertificateException e) {
e.printStackTrace();
}
}
When a SslError occurs, check that the server certificate matches one embedded certificate. Note that it is not possible to directly compare certificates, so I use SslCertificate.saveState to put the certificate data into a Bundle, and then I compare all the bundle entries.
webView.setWebViewClient(new WebViewClient() {
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
// Checks Embedded certificates
SslCertificate serverCertificate = error.getCertificate();
Bundle serverBundle = SslCertificate.saveState(serverCertificate);
for (SslCertificate appCertificate : certificates) {
if (TextUtils.equals(serverCertificate.toString(), appCertificate.toString())) { // First fast check
Bundle appBundle = SslCertificate.saveState(appCertificate);
Set<String> keySet = appBundle.keySet();
boolean matches = true;
for (String key : keySet) {
Object serverObj = serverBundle.get(key);
Object appObj = appBundle.get(key);
if (serverObj instanceof byte[] && appObj instanceof byte[]) { // key "x509-certificate"
if (!Arrays.equals((byte[]) serverObj, (byte[]) appObj)) {
matches = false;
break;
}
} else if ((serverObj != null) && !serverObj.equals(appObj)) {
matches = false;
break;
}
}
if (matches) {
handler.proceed();
return;
}
}
}
handler.cancel();
String message = "SSL Error " + error.getPrimaryError();
Log.w(TAG, message);
}
});
I needed to check our truststore before show any message to the user so I did this:
public class MyWebViewClient extends WebViewClient {
private static final String TAG = MyWebViewClient.class.getCanonicalName();
Resources resources;
Context context;
public MyWebViewClient(Resources resources, Context context){
this.resources = resources;
this.context = context;
}
#Override
public void onReceivedSslError(WebView v, final SslErrorHandler handler, SslError er){
// first check certificate with our truststore
// if not trusted, show dialog to user
// if trusted, proceed
try {
TrustManagerFactory tmf = TrustManagerUtil.getTrustManagerFactory(resources);
for(TrustManager t: tmf.getTrustManagers()){
if (t instanceof X509TrustManager) {
X509TrustManager trustManager = (X509TrustManager) t;
Bundle bundle = SslCertificate.saveState(er.getCertificate());
X509Certificate x509Certificate;
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
}
X509Certificate[] x509Certificates = new X509Certificate[1];
x509Certificates[0] = x509Certificate;
trustManager.checkServerTrusted(x509Certificates, "ECDH_RSA");
}
}
Log.d(TAG, "Certificate from " + er.getUrl() + " is trusted.");
handler.proceed();
}catch(Exception e){
Log.d(TAG, "Failed to access " + er.getUrl() + ". Error: " + er.getPrimaryError());
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
String message = "SSL Certificate error.";
switch (er.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "O certificado não é confiável.";
break;
case SslError.SSL_EXPIRED:
message = "O certificado expirou.";
break;
case SslError.SSL_IDMISMATCH:
message = "Hostname inválido para o certificado.";
break;
case SslError.SSL_NOTYETVALID:
message = "O certificado é inválido.";
break;
}
message += " Deseja continuar mesmo assim?";
builder.setTitle("Erro");
builder.setMessage(message);
builder.setPositiveButton("Sim", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("Não", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
}
}
Fix which works for me is just disable onReceivedSslError function defined in AuthorizationWebViewClient. In this case handler.cancel will be called in case of SSL error. However it works good with One Drive SSL certificates. Tested on Android 2.3.7, Android 5.1.
According to Google Security Alert: Unsafe implementation of the interface X509TrustManager, Google Play won't support X509TrustManager from 11th July 2016:
Hello Google Play Developer,
Your app(s) listed at the end of this email use an unsafe
implementation of the interface X509TrustManager. Specifically, the
implementation ignores all SSL certificate validation errors when
establishing an HTTPS connection to a remote host, thereby making your
app vulnerable to man-in-the-middle attacks. An attacker could read
transmitted data (such as login credentials) and even change the data
transmitted on the HTTPS connection. If you have more than 20 affected
apps in your account, please check the Developer Console for a full
list.
To properly handle SSL certificate validation, change your code in the
checkServerTrusted method of your custom X509TrustManager interface to
raise either CertificateException or IllegalArgumentException whenever
the certificate presented by the server does not meet your
expectations. For technical questions, you can post to Stack Overflow
and use the tags “android-security” and “TrustManager.”
Please address this issue as soon as possible and increment the
version number of the upgraded APK. Beginning May 17, 2016, Google
Play will block publishing of any new apps or updates containing the
unsafe implementation of the interface X509TrustManager.
To confirm you’ve made the correct changes, submit the updated version
of your app to the Developer Console and check back after five hours.
If the app hasn’t been correctly upgraded, we will display a warning.
While these specific issues may not affect every app with the
TrustManager implementation, it’s best not to ignore SSL certificate
validation errors. Apps with vulnerabilities that expose users to risk
of compromise may be considered dangerous products in violation of the
Content Policy and section 4.4 of the Developer Distribution
Agreement.
...
I had the same issue and tried all the above-mentioned suggestions as below.
Implement onReceivedSslError() by giving the chance to the user to
decide handler.proceed(); or handler.cancel(); when a SSL error
occurred
Implement onReceivedSslError() to call handler.cancel(); whenever a
SSL issue occurred without considering user's decision.
Implement onReceivedSslError() to verify SSL certificate locally
addition to checking error.getPrimaryError() and providing the user
to decide handler.proceed(); or handler.cancel(); only if the SSL
certificate is valid. If not just call handler.cancel();
Removing the implementation of onReceivedSslError() and just let to
happen Android default behavior.
Even after trying all the above attempts, Google Play was keeping sending the same notification mail mentioning the same error and the old APK version (Even though in the all above attempts we changed both version code and version name in the Gradle)
We were in huge trouble and contacted Google Support via mail and asked
"We are uploading the higher versions of the APK but the review result
says the same error mentioning the old buggy APK version. What's the
reason for that ?"
After a few days, google support replied to our request as follows.
Please note that you must completely replace version 12 in your
Production track. It means that you'll have to full rollout a higher
version in order to deactivate version 12.
The highlighted point was never found or mentioned in the play console or any forum.
According to that guideline in the classic Google play view, we checked the production track and there were both buggy versions and the latest bug fixed version but the bug fix version's rollout percentage is 20%. So, turned it to full rollout then the buggy version disappeared from the production track. After more than 24 hour review time version has come back.
NOTE: When we had this issue Google has just moved to a new UI version of their play console and it had missed some views in the previous UI version or classic view. Since we were using the latest view we couldn't notice what was happening. Simply what happened was Google reviewed the same previous buggy version of the APK since the new one was not full roll-out.
You can use SslError for show, some information about the error of this certificated, and you can write in your dialog the string of the type error.
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final SslErrorHandler handlerFinal;
handlerFinal = handler;
int mensaje ;
switch(error.getPrimaryError()) {
case SslError.SSL_DATE_INVALID:
mensaje = R.string.notification_error_ssl_date_invalid;
break;
case SslError.SSL_EXPIRED:
mensaje = R.string.notification_error_ssl_expired;
break;
case SslError.SSL_IDMISMATCH:
mensaje = R.string.notification_error_ssl_idmismatch;
break;
case SslError.SSL_INVALID:
mensaje = R.string.notification_error_ssl_invalid;
break;
case SslError.SSL_NOTYETVALID:
mensaje = R.string.notification_error_ssl_not_yet_valid;
break;
case SslError.SSL_UNTRUSTED:
mensaje = R.string.notification_error_ssl_untrusted;
break;
default:
mensaje = R.string.notification_error_ssl_cert_invalid;
}
AppLogger.e("OnReceivedSslError handel.proceed()");
View.OnClickListener acept = new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
handlerFinal.proceed();
}
};
View.OnClickListener cancel = new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
handlerFinal.cancel();
}
};
View.OnClickListener listeners[] = {cancel, acept};
dialog = UiUtils.showDialog2Buttons(activity, R.string.info, mensaje, R.string.popup_custom_cancelar, R.string.popup_custom_cancelar, listeners); }
In my situation:This error occured when we try to updated apk uploaded
into the Google Play store,and getting SSL Error:
Then i have used following code
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
try {
progressDialog.dismiss();
} catch (WindowManager.BadTokenException e) {
e.printStackTrace();
}
super.onPageFinished(view, url);
}
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(PayNPayWebActivity.this);
builder.setMessage(R.string.notification_error_ssl_cert_invalid);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
}

android Google Play Warning: SSL Error Handler Vulnerability

I use the gorbin/ASNE SDK in my app. I recently received an email from Google with the following subject : "Google Play Warning: SSL Error Handler Vulnerability". In this email, Google explains that my app has an ["unsafe implementation of the WebViewClient.onReceivedSslError handler"]
and they recommended me to ["To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise"]
here's my implementation of the method :
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
any help please ?
To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise.
For example, I add an alert dialog to make user have confirmed and seems Google no longer shows warning.
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
String message = "SSL Certificate error.";
switch (error.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted.";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired.";
break;
case SslError.SSL_IDMISMATCH:
message = "The certificate Hostname mismatch.";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid.";
break;
}
message += " Do you want to continue anyway?";
builder.setTitle("SSL Certificate Error");
builder.setMessage(message);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
After this changes it will not show warning. Reference
the solution is to remove onReceivedSslError.
I was using backendless library old version compile 'com.backendless:backendless:3.0.11' so i update to latest version compile 'com.backendless:backendless:3.0.24' and issue solved.

Android WebView blocks redirect from https to http

I have a solution where my Android WebView needs to first open a https url, then it will be redirected to a http url (it might be trying a http POST from the https site). This is not working, and my Android debug log says:
02-20 11:04:45.079 8538-8538/? E/WebViewCallback﹕ Blocked URL: [blocked] The page at 'https://xxx/' was loaded over HTTPS, but is submitting data to an insecure location at 'http://yyy': this content should also be submitted over HTTPS.
Are there any configuration options in the WebView that will allow this behaviour?
More info: it seems like a behaviour change in the Android SDK. A client compiled a long time ago does this without any complaints.
There was a change in default WebView settings for mixed http/https content in Lollipop (API 20). See https://datatheorem.github.io/android/2014/12/20/webviews-andorid-lollipop/ for more details.
To allow https to redirect to http you need to set the mixed content mode to MIXED_CONTENT_ALWAYS_ALLOW
if (Build.VERSION.SDK_INT >= 21) {
webview.getSettings().setMixedContentMode( WebSettings.MIXED_CONTENT_ALWAYS_ALLOW );
}
Note that setting MIXED_CONTENT_ALWAYS_ALLOW is bad from security point of view, and as you note in your answer, it is better to support https on both sites.
But for those that don't have control over the sites, this should work.
You can ignore ssl error by overriding onReceivedSslError() method.
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); // Ignore SSL certificate errors
}
Hope it will be work for you.
From my research I don't think it is possible to disable this feature. I will support https in both sites instead. Safest anyway.
Its worked for me
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.webView.getContext());
AlertDialog alertDialog = builder.create();
String message = "Certificate error.";
switch (error.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted.";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired.";
break;
case SslError.SSL_IDMISMATCH:
message = "The certificate Hostname mismatch.";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid.";
break;
}
message += " Do you want to continue anyway?";
alertDialog.setTitle("SSL Certificate Error");
alertDialog.setMessage(message);
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.d("CHECK", "Button ok pressed");
// Ignore SSL certificate errors
handler.proceed();
}
});
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.d("CHECK", "Button cancel pressed");
handler.cancel();
}
});
alertDialog.show();

Categories

Resources