WebViewClient
のonReceivedSslError()
をオーバーライドしたいと思います。ここで、error.getCertificate()
証明書が自己署名CAから署名されているかどうかを確認し、この場合のみでhandler.proceed()
を呼び出します。疑似コード:
@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);
}
}
私のCAの公開鍵は、rootca.bks
というBouncyCastleリソースに保存されています。どのようにできるのか?
私はあなたが次のように試すことができると思います:
@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;
}
}
}
検証に失敗した場合、logcatにはJava.security.SignatureException: Signature was not verified...
などの情報が含まれます
成功した場合のスクリーンショットは次のとおりです。
これはうまくいくと思います(_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);
}
}
_
「hasError()」が機能しない場合は、error.getPrimaryError() == SSL_IDMISMATCH
を試してください
すべてのエラータイプについて、 SslErrorのドキュメント を確認します。
EDIT:自分の自己証明書サーバー(Xampp)で関数をテストしたところ、エラー#3が発生しました。つまり、自己署名証明書のerror.hasError(SslError.SSL_UNTRUSTED)
を確認する必要があります。
ドキュメントに基づく:
クラスSslCertificateのメソッドgetIssuedBy().getDName()
を使用してみましたか?このメソッドは、「この証明書を発行したエンティティ」を表す文字列を返します。
ここを見てください: http://developer.Android.com/reference/Android/net/http/SslCertificate.html#getIssuedBy()
次に、自己署名された文字列が返されることを知る必要があります。
編集:それが自己署名されている場合は空の文字列を返し、そうでない場合はエンティティを返すと思います
よろしく