WebView: 如何在实现 onReceivedSslError 时避免来自 GooglePlay 的安全警报

我有一个链接,将在 WebView打开。问题是,在我像下面这样覆盖 onReceivedSslError之前,它无法打开:

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}

我收到了来自 Google Play 的安全警报:

安全警报 您的应用程序具有 WebViewClient.onReceivedSslError 处理程序的不安全实现。具体来说,该实现忽略所有 SSL 证书验证错误,使您的应用程序容易受到中间人攻击。攻击者可以更改受影响的 WebView 的内容,读取传输的数据(例如登录凭据) ,并使用 JavaScript 在应用程序中执行代码。

若要正确处理 SSL 证书验证,请在服务器提供的证书满足要求时更改代码以调用 SslErrorHandler.process () ,否则调用 SslErrorHandler.Cancer.ase ()。一个包含受影响的应用程序和类别的电子邮件警报已经发送到您的开发人员帐户地址。

请尽快处理这个漏洞并增加升级后的 APK 的版本号。有关 SSL 错误处理程序的详细信息,请参阅开发人员帮助中心中的文档。对于其他技术问题,您可以发布到 https://www.stackoverflow.com/questions,并使用标签“ android-security”和“ SslErrorHandler”如果您正在使用第三方库,这是负责,请通知第三方,并与他们一起解决这个问题。

要确认您已经正确地升级,请将更新后的版本上传到 Developer Console,并在5小时后检查。如果应用程序没有正确升级,我们将显示一个警告。

请注意,虽然这些特定的问题可能不会影响每个使用 WebView SSL 的应用程序,但是最好保持所有安全补丁的最新状态。有漏洞的应用程序使用户暴露于危险的风险中,可能被认为是危险的产品,违反了内容政策和开发者分发协议的第4.4节。

请确保所有发布的应用程序符合开发者分发协议和内容政策。如果您有问题或关注,请通过 Google Play 开发者帮助中心与我们的支持团队联系。

如果我删除 onReceivedSslError (handler.proceed()),那么页面将不会打开。

有没有什么办法可以打开 WebView页面,避免安全警报?

63820 次浏览

修正了我只是禁用 AuthorizationWebViewClient中定义的 onReceivedSslError函数。在这种情况下,如果出现 SSL 错误,将调用 handler.cancel。但是,它可以很好地使用 One Drive SSL 证书。在 Android 2.3.7,Android 5.1上测试。

若要正确处理 SSL 证书验证,请将代码更改为 提供的证书时调用 SslErrorHandler.process () 服务器满足您的期望,并调用 否则取消()。

正如电子邮件中所说,onReceivedSslError应该处理用户进入一个有无效证书的页面,比如一个通知对话框。你不应该直接进行。

例如,我添加了一个警告对话框,使用户已经确认,似乎谷歌不再显示警告。


@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();
}

更多关于邮件的解释。

具体来说,该实现将忽略所有 SSL 证书验证 错误,使您的应用程序容易受到中间人攻击。

电子邮件说,默认实现忽略了一个重要的 SSL 安全问题。所以我们需要在我们自己的应用程序中使用 WebView 来处理它。用警告对话框通知用户是一种简单的方法。

您可以使用 SslError 来显示关于此证书的错误的一些信息,并且可以在对话框中写入类型错误的字符串。

@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);    }

GoogleSecurityAlert: X509TrustManager 接口的不安全实现报道,Google Play 从2016年7月11日起将不再支持 X509TrustManager:

你好,Google Play 开发者,

本邮件末尾列出的应用程序使用不安全的 接口 X509TrustManager 的实现 实现忽略所有 SSL 证书验证错误时 建立到远程主机的 HTTPS 连接,从而使您的 应用程序容易受到中间人攻击。攻击者可以阅读 传输数据(例如登录凭据) ,甚至更改数据 在 HTTPS 连接上传输。如果受影响的数量超过20 应用程序在您的帐户,请检查开发者控制台为一个完整的 名单。

若要正确处理 SSL 证书验证,请更改 自定义 X509TrustManager 接口的 checkServerTrusted 方法 无论何时,都会引发 CericateException 或 IllegalArgumentException 服务器提供的证书不符合您的 对于技术问题,您可以发布到 Stack Overflow 并使用“ android-security”和“ TrustManager”标签

请尽快解决这个问题并增加 升级后的 APK 的版本号。从2016年5月17日开始,谷歌 播放将阻止发布任何新的应用程序或包含 接口 X509TrustManager 的不安全实现。

要确认您做了正确的更改,请提交更新后的版本 你的应用程序到开发者控制台,并在五个小时后检查回来。 如果应用程序没有正确升级,我们将显示一个警告。

虽然这些特定的问题可能不会影响每个应用程序 实现 TrustManager 时,最好不要忽略 SSL 证书 验证错误。应用程序的漏洞暴露用户的风险 折衷方法可能被视为危险产品,违反 内容策略和开发者分发的第4.4节 同意。

...

我需要在向用户显示任何消息之前检查我们的信任库,所以我这样做了:

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();
}
}
}

到目前为止,提出的解决方案只是绕过了安全检查,因此它们是不安全的。

我的建议是将证书嵌入到 App 中,当出现 SslError 错误时,检查服务器证书是否与嵌入的证书之一匹配。

下面是步骤:

  1. 从网站检索证书。

    • 在 Safari 上打开网站
    • 点击网站名称附近的挂锁图标
    • 点击显示证书
    • 将证书拖放到文件夹中

https://www.markbrilman.nl/2012/03/howto-save-a-certificate-via-safari-on-mac/

  1. 将证书(. cer 文件)复制到应用程序的 res/raw 文件夹中

  2. 在代码中,通过调用 loadSSLSecurities ()加载证书

    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();
    }
    }
    
  3. 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);
    }
    
    
    
    
    });
    

在我的情况下: 这个错误发生在我们尝试更新上传的 apk 时 进入 Google Play 商店,得到 SSL 错误: 然后我使用了以下代码

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();
}
}

我有同样的问题,并尝试了以下所有上述建议。

  1. 实现 onReceivedSslError () ,为用户提供机会 当出现 SSL 错误时,可以决定 handler 发生了
  2. 实现 onReceivedSslError ()以调用 handler.Canc() ; 只要有 没有考虑用户的决定就发生了 SSL 问题。
  3. 实现 onReceivedSslError ()以在本地验证 SSL 证书 除了检查 error.getPrimaryError ()并为用户提供 只有在 SSL 证书是有效的
  4. 删除 onReceivedSslError ()的实现并只让 发生 Android 默认行为。

即使在尝试了以上所有尝试之后,Google Play 仍然在发送相同的通知邮件,提到相同的错误和旧的 APK 版本(尽管在以上所有尝试中,我们都改变了 Gradle 中的版本代码和版本名称)

我们遇到了很大的麻烦,通过邮件联系了 Google 技术支持

“我们正在上传更高版本的 APK,但审查结果 说同样的错误提到旧的错误 APK 版本 为什么?”

几天后,谷歌支持回复我们的请求如下。

请注意,您必须完全替换您的 生产轨道。这意味着 < strong > 你必须全面推出一个更高的版本 版本以停用版本12 .

在游戏控制台或任何论坛中都没有找到或提到突出显示的点。

根据经典的 Google Play 视图中的指导方针,我们检查了产品跟踪,其中既有 bug 版本,也有最新的 bug 修复版本,但 bug 修复版本的推出百分比是20% 。所以,把它全面推出,然后错误的版本从生产轨道消失。经过24小时以上的回顾时间版本已经回来了。

注意 : 当我们遇到这个问题的时候,Google 刚刚移动到他们的游戏控制台的新 UI 版本,它错过了以前的 UI 版本或者经典视图中的一些视图。因为我们使用的是最新的视图,所以我们无法注意到正在发生的情况。简单地说,Google 回顾了 APK 以前的错误版本,因为新版本还没有全面推出。