Tsahi Deri
Tsahi Deri

Reputation: 581

Android WebView handle onReceivedClientCertRequest

I'm developing an Android app using Client Certificate Authentication within WebView. The certificate (cert.pfx) and password are embedded in the application.

When executing Client Certificate Authentication request with ajax call in the WebView, the following function getting called :

@Override
public void onReceivedClientCertRequest(WebView view, final ClientCertRequest request) {}

As I understend I need to call :

request.proceed(PrivateKey privateKey, X509Certificate[] chain)

Any idea how to create the PrivateKey and X509Certificate objects from the embedded certificate in order to proceed with the request. BTW, is this the correct way to implement Client Certificate Authentication on Android app ? if no, please advice.

Upvotes: 14

Views: 14088

Answers (3)

Marcos Vasconcelos
Marcos Vasconcelos

Reputation: 18276

This is how you select from the user and answer for the ClientCertRequest (in Kotlin)

    override fun onReceivedClientCertRequest(view: WebView?, request: ClientCertRequest?) {
        try {
            KeyChain.choosePrivateKeyAlias(this, {
                if (it == null)
                    return@choosePrivateKeyAlias

                try {
                    val certs = KeyChain.getCertificateChain(this, it)
                    val pKey = KeyChain.getPrivateKey(this, it)
                    request?.proceed(pKey, certs)
                } catch (e: Exception) {
                    request?.cancel()
                    e.printStackTrace()
                }
            }, null, null, null, -1, null)
        } catch (e: Throwable) {
            request?.cancel()
            e.printStackTrace()
        }
    }

Upvotes: 0

aeroxr1
aeroxr1

Reputation: 1074

I encountered the same problem, and I have resolved in this way :

private void initPrivateKeyAndX509Certificate(int clientCertResourceId, String clientCertPassword) throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
        try {
            InputStream certificateFileStream = null;
            if (clientCertResourceId > 0) {
                certificateFileStream = new ByteArrayInputStream(Utility.readbyteFromResources(clientCertResourceId, MYApplication.getAppContext()));
            }

            if (certificateFileStream != null) {
                KeyStore keyStore = KeyStore.getInstance("PKCS12");
                keyStore.load(certificateFileStream, clientCertPassword != null ? clientCertPassword.toCharArray() : null);

                Enumeration<String> aliases = keyStore.aliases();

                while (aliases.hasMoreElements()) {
                    String alias = aliases.nextElement();
                    Key key = keyStore.getKey(alias, clientCertPassword.toCharArray());
                    if (key instanceof PrivateKey) {
                        mPrivateKey = (PrivateKey) key;
                        Certificate[] arrayOfCertificate = keyStore.getCertificateChain(alias);
                        mCertificates = new X509Certificate[arrayOfCertificate.length];
                        for (int i = 0; i < mCertificates.length; i++) {
                            mCertificates[i] = ((X509Certificate) arrayOfCertificate[i]);
                        }
                        break;
                    }
                }

                certificateFileStream.close();
            }

        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            throw e;
        }
    }

Where the clientCertResourceId is the resourceId related of the p12 which is in raw folder. Then I have overrided the onReveicedClientCertRequest in this way :

@Override
    public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
        if (mPrivateKey != null && mCertificates != null && mCertificates.length != 0) {
            request.proceed(mPrivateKey, mCertificates);
        } else {
            request.cancel();
        }
    }

I wondering if this way to do is the correct one, but currently it works

Upvotes: 1

Tsahi Deri
Tsahi Deri

Reputation: 581

Solved it using KeyStore to obtain the PrivateKey and X509Certificate objects :

    private X509Certificate[] mCertificates;
    private PrivateKey mPrivateKey;

    private void loadCertificateAndPrivateKey() {
          try {
                InputStream certificateFileStream = getClass().getResourceAsStream("/assets/cert.pfx");

                KeyStore keyStore = KeyStore.getInstance("PKCS12");
                String password = "password";
                keyStore.load(certificateFileStream, password != null ? password.toCharArray() : null);

                Enumeration<String> aliases = keyStore.aliases();
                String alias = aliases.nextElement();

                Key key = keyStore.getKey(alias, password.toCharArray());
                if (key instanceof PrivateKey) {
                    mPrivateKey = (PrivateKey)key;
                    Certificate cert = keyStore.getCertificate(alias);
                    mCertificates = new X509Certificate[1];
                    mCertificates[0] = (X509Certificate)cert;
                 }

                 certificateFileStream.close();

            } catch (Exception e) {
                 Log.e(TAG, e.getMessage());
         }
    }


    private WebViewClient mWebViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return false;
        }

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

        @Override
        public void onReceivedClientCertRequest(WebView view, final ClientCertRequest request) {
            if (mCertificates == null || mPrivateKey == null) {
                loadCertificateAndPrivateKey();
            } 
            request.proceed(mPrivateKey, mCertificates);
        }
    };

Upvotes: 21

Related Questions