简体   繁体   English

Android 客户端似乎没有发送证书(尝试相互身份验证)

[英]Android client doesn't appear to send certificate (trying mutual authentication)

I'm trying to get an Android app to mutually authenticate to my own IoT server.我正在尝试让 Android 应用程序对我自己的物联网服务器进行相互身份验证。 The client doesn't seem to send a certificate, verified also against https://server.cryptomix.com/secure .客户端似乎没有发送证书,也针对https://server.cryptomix.com/secure进行了验证。

Simple test application:简单的测试应用:

package info.backx.danny.alarm.jsontestapplication;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.google.android.material.snackbar.Snackbar;

import org.apache.commons.io.IOUtils;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;

public class FirstFragment extends Fragment {
    JsonTestApplication app;

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState
    ) {
        app = (JsonTestApplication) getActivity().getApplication();
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false);
    }

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        view.findViewById(R.id.button_first).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                NavHostFragment.findNavController(FirstFragment.this)
                        .navigate(R.id.action_FirstFragment_to_SecondFragment);
            }
        });
        view.findViewById(R.id.hitme).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // DoRPC(view);
                DoRPC5(view);
            }
        });
    }

    final String myhostname = "lite2.dannybackx.dns-cloud.net";
//    final String rpc_url = "https://lite2.dannybackx.dns-cloud.net:4435/json";
//    final String rpc_url = "http://lite2.dannybackx.dns-cloud.net/json";
    final String http_url = "http://lite2.dannybackx.dns-cloud.net/json";

    final String test_json = "{ \"status\" : \"night\", \"name\" : \"hp\" }";

    final String rpc_url = "https://server.cryptomix.com/secure";
/*
 * Option 1 in https://stackoverflow.com/questions/59000913/mutual-authentication-using-retrofit-android
 *  but edited to use resource files
 *
 * I (09:45:07.098) esp_https_server: performing session handshake
 * E (09:45:09.093) esp-tls-mbedtls: mbedtls_ssl_handshake returned -29824 SSL - No client certification received from the client, but required by the authentication mode
 * E (09:45:09.098) esp_https_server: esp_tls_create_server_session failed
 * W (09:45:09.106) httpd: httpd_accept_conn: session creation failed
 * W (09:45:09.113) httpd: httpd_server: error accepting new connection
 *
 * E/DoRPC: DoRPC query {"status":"night","name":"hp"}, error java.net.SocketException: Connection reset
 * Disconnected from the target VM, address: 'localhost:38789', transport: 'socket'
 */

    public void DoRPC5(View view) {
        SSLSocketFactory socketFactory = null;
        try {
            char[] pass = "secret".toCharArray();
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

            // InputStream trustedCertificateAsInputStream = Files.newInputStream(Paths.get("badssl-server-certificate.pem"), StandardOpenOption.READ);
            // Certificate trustedCertificate = certificateFactory.generateCertificate(trustedCertificateAsInputStream);
            Certificate trustedCertificate = certificateFactory.generateCertificate(
                    app.getResources().openRawResource(R.raw.chain));
            KeyStore trustStore = createEmptyKeyStore(pass);
            trustStore.setCertificateEntry("trust-server", trustedCertificate);

            //String pk = new String(Files.readAllBytes(Paths.get("private-key-badssl.pem")), Charset.defaultCharset());
            String pk = IOUtils.toString(app.getResources().openRawResource(R.raw.privkey));
            // IOUtils.closeQuietly(is); // don't forget to close your streams
            String privateKeyContent = pk
                    .replace("-----BEGIN PRIVATE KEY-----", "")
                    .replaceAll(System.lineSeparator(), "")
                    .replace("-----END PRIVATE KEY-----", "");

            byte[] privateKeyAsBytes = Base64.getDecoder().decode(privateKeyContent);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyAsBytes);

            // InputStream certificateChainAsInputStream = Files.newInputStream(Paths.get("certificate-chain-badssl.pem"), StandardOpenOption.READ);
            // Certificate certificateChain = certificateFactory.generateCertificate(certificateChainAsInputStream);
            Certificate certificateChain = certificateFactory.generateCertificate(
                    app.getResources().openRawResource(R.raw.fullchain));

            KeyStore identityStore = createEmptyKeyStore(pass);
            // identityStore.setKeyEntry("client", keyFactory.generatePrivate(keySpec), "".toCharArray(), new Certificate[]{certificateChain});
            identityStore.setKeyEntry("client", keyFactory.generatePrivate(keySpec),
                    "".toCharArray(), new Certificate[]{certificateChain});

            // trustedCertificateAsInputStream.close();
            // certificateChainAsInputStream.close();

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(identityStore, pass);
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagers, trustManagers, null);

            socketFactory = sslContext.getSocketFactory();

        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        RequestQueue queue = Volley.newRequestQueue(app,
                new HurlStack(null, socketFactory));

        try {
            JSONObject reqParam = new JSONObject(test_json);
            JsonObjectRequest jor = new JsonObjectRequest(Request.Method.PUT, rpc_url, reqParam,
                    new Response.Listener<JSONObject>() {
                        @Override
                        public void onResponse(JSONObject response) {
                            Snackbar.make(view, response.toString(), Snackbar.LENGTH_LONG)
                                    .setAction("Action", null).show();
                            Log.i("DoRPC", "Query " + reqParam.toString()
                                    + ", reply " + response.toString());
                        }
                    }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Snackbar.make(view, error.toString(), Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                    Log.e("DoRPC", "DoRPC query " + reqParam.toString()
                            + ", error " + error.getLocalizedMessage());
                }
            });
            queue.add(jor);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        try {
            JSONObject reqParam = new JSONObject(test_json);
            StringRequest sr = new StringRequest(Request.Method.PUT, rpc_url, 
                    new Response.Listener<String>() {
                        @Override
                        public void onResponse(String response) {
                            Snackbar.make(view, response.toString(), Snackbar.LENGTH_LONG)
                                    .setAction("Action", null).show();
                            Log.i("DoRPC", "Query " + reqParam.toString()
                                    + ", reply " + response.toString());
                        }
                    }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Snackbar.make(view, error.toString(), Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                    Log.e("DoRPC", "DoRPC query " + reqParam.toString()
                            + ", error " + error.getLocalizedMessage());
                }
            });
            queue.add(sr);
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    public static KeyStore createEmptyKeyStore(char[] keyStorePassword) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, keyStorePassword);
        return keyStore;
    }

    /*
     * Authenticated query
     * https://medium.com/android-bits/android-security-tip-public-key-pinning-with-volley-library-fb85bf761857
     *
     * Not looked into yet :
     * https://stackoverflow.com/questions/39553999/how-can-i-make-android-volley-perform-https-request-using-a-certificate-self-si
     *
     * Note a way to test the server :
     * curl -X PUT --cert fullchain.pem --key privkey.pem https://lite2.dannybackx.dns-cloud.net:4435/json -d '{ "status" : "night", "name" : "hp"}'
     *
     * Note : this works but is not mutually authenticated.
     */
    public void DoRPC(View view) {
        RequestQueue queue = Volley.newRequestQueue(app,
                new HurlStack(null, getSocketFactory()));

        try {
            JSONObject reqParam = new JSONObject(test_json);
            JsonObjectRequest jor = new JsonObjectRequest(Request.Method.PUT, rpc_url, reqParam,
                    new Response.Listener<JSONObject>() {
                        @Override
                        public void onResponse(JSONObject response) {
                            Snackbar.make(view, response.toString(), Snackbar.LENGTH_LONG)
                                    .setAction("Action", null).show();
                            Log.i("DoRPC", "Query " + reqParam.toString()
                                    + ", reply " + response.toString());
                        }
                    }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Snackbar.make(view, error.toString(), Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                    Log.e("DoRPC", "DoRPC query " + reqParam.toString()
                            + ", error " + error.getLocalizedMessage());
                }
            });
            queue.add(jor);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private SSLSocketFactory getSocketFactory() {
        CertificateFactory cf = null;
        try {
            cf = CertificateFactory.getInstance("X.509");
            InputStream caInput = getResources().openRawResource(R.raw.mycert);
            Certificate ca;
            try {
                ca = cf.generateCertificate(caInput);
                Log.e("CERT", "ca=" + ((X509Certificate) ca).getSubjectDN());
            } finally {
                caInput.close();
            }

            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    Log.e("CipherUsed", session.getCipherSuite());
                    return hostname.compareTo(myhostname)==0; //The Hostname of your server.
                }
            };

            HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
            SSLContext context = SSLContext.getInstance("TLS");

            context.init(null, tmf.getTrustManagers(), null);
            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());

            SSLSocketFactory sf = context.getSocketFactory();
            return sf;
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }

        return  null;
    }
}

What am I missing?我错过了什么?

Thanks, Danny谢谢,丹尼

Found it, the identityStore.setKeyEntry() should also have pass as password (3rd parameter), not the empty string.找到它, identityStore.setKeyEntry()也应该作为密码(第三个参数)传递,而不是空字符串。

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

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