简体   繁体   English

如果证书是从特定的自签名CA签名的,请检查WebViewClient的onReceivedSslError()方法

[英]Check in the onReceivedSslError() method of a WebViewClient if a certificate is signed from a specific self-signed CA

I would like to override the onReceivedSslError() of a WebViewClient . 我想覆盖WebViewClientonReceivedSslError() Here I want to check if the error.getCertificate() certificate is signed from a self-signed CA and, only in this case , call the handler.proceed() . 在这里,我想检查error.getCertificate()证书是否是从自签名CA签名的,并且仅在这种情况下 ,调用handler.proceed() In pseudo-code: 在伪代码中:

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    SslCertificate serverCertificate = error.getCertificate();

    if (/* signed from my self-signed CA */) {
        handler.proceed();
    }
    else {
        super.onReceivedSslError(view, handler, error);
    }
}

The public key of my CA is saved in a BouncyCastle resource called rootca.bks . 我的CA的公钥保存在名为rootca.bks的BouncyCastle资源中。 How can I do? 我能怎么做?

I think you can try as the following: 我想你可以尝试如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    try {
        WebView webView = (WebView) findViewById(R.id.webView);
        if (webView != null) {
            // Get cert from raw resource...
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream caInput = getResources().openRawResource(R.raw.rootca); // stored at \app\src\main\res\raw
            final Certificate certificate = cf.generateCertificate(caInput);
            caInput.close();

            String url = "https://www.yourserver.com";
            webView.setWebViewClient(new WebViewClient() {                    
                @Override
                public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                    // Get cert from SslError
                    SslCertificate sslCertificate = error.getCertificate();
                    Certificate cert = getX509Certificate(sslCertificate);
                    if (cert != null && certificate != null){
                        try {
                            // Reference: https://developer.android.com/reference/java/security/cert/Certificate.html#verify(java.security.PublicKey)
                            cert.verify(certificate.getPublicKey()); // Verify here...
                            handler.proceed();
                        } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
                            super.onReceivedSslError(view, handler, error);
                            e.printStackTrace();
                        }
                    } else {
                        super.onReceivedSslError(view, handler, error);
                    }
                }
            });

            webView.loadUrl(url);
        }
    } catch (Exception e){
        e.printStackTrace();
    }
}

// credits to @Heath Borders at http://stackoverflow.com/questions/20228800/how-do-i-validate-an-android-net-http-sslcertificate-with-an-x509trustmanager
private Certificate getX509Certificate(SslCertificate sslCertificate){
    Bundle bundle = SslCertificate.saveState(sslCertificate);
    byte[] bytes = bundle.getByteArray("x509-certificate");
    if (bytes == null) {
        return null;
    } else {
        try {
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
            return certFactory.generateCertificate(new ByteArrayInputStream(bytes));
        } catch (CertificateException e) {
            return null;
        }
    }
}

If failed validation, logcat will have some information such as java.security.SignatureException: Signature was not verified... 如果验证失败,logcat将有一些信息,如java.security.SignatureException: Signature was not verified...

If success, here's a screenshot: 如果成功,这是一个截图:

BNK的截图

I think this should work ( SSL_IDMISMATCH means "Hostname mismatch"). 我认为这应该有效( SSL_IDMISMATCH意味着“主机名不匹配”)。

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    SslCertificate serverCertificate = error.getCertificate();

    if (error.hasError(SSL_UNTRUSTED)) {
        // Check if Cert-Domain equals the Uri-Domain
        String certDomain = serverCertificate.getIssuedTo().getCName();
        if(certDomain.equals(new URL(error.getUrl()).getHost())) {
          handler.proceed();
        }
    }
    else {
        super.onReceivedSslError(view, handler, error);
    }
}

If "hasError()" is not working, try error.getPrimaryError() == SSL_IDMISMATCH 如果“hasError()”不起作用,请尝试error.getPrimaryError() == SSL_IDMISMATCH

Check Documentation of SslError for all error-types. 检查所有错误类型的SslError文档

EDIT: I tested the function on my own self-cert server (its a Xampp), and I got Error #3. 编辑:我在我自己的自我证书服务器(它的Xampp)上测试了该功能,我得到了错误#3。 That means you have to check for error.hasError(SslError.SSL_UNTRUSTED) for a self-signed cert. 这意味着您必须为自签名证书检查error.hasError(SslError.SSL_UNTRUSTED)

based on documentation: 基于文档:

Have you tried using the method getIssuedBy().getDName() of class SslCertificate. 您是否尝试过使用类SslCertificate的getIssuedBy().getDName()方法。 This method returns a String representing "The entity that issued this certificate". 此方法返回表示“颁发此证书的实体”的String。

Take a look here: http://developer.android.com/reference/android/net/http/SslCertificate.html#getIssuedBy() 看看这里: http//developer.android.com/reference/android/net/http/SslCertificate.html#getIssuedBy()

Then you just need to know wich string is returned when it is self signed. 然后你只需要知道在自签名时返回字符串。

EDIT: I think that if it is selfsigned, that should return empty string, and if not, it would return the entity 编辑:我认为如果它是自签名的,那应该返回空字符串,如果没有,它将返回实体

Regards 问候

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM