繁体   English   中英

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

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

我正在尝试让 Android 应用程序对我自己的物联网服务器进行相互身份验证。 客户端似乎没有发送证书,也针对https://server.cryptomix.com/secure进行了验证。

简单的测试应用:

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

我错过了什么?

谢谢,丹尼

找到它, identityStore.setKeyEntry()也应该作为密码(第三个参数)传递,而不是空字符串。

暂无
暂无

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

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