简体   繁体   English

Android 应用程序客户端与 java 服务器的相互 TLS

[英]Android app client Mutual TLS with java server

I'm trying to send https requests to my server using mutual TLS.我正在尝试使用双向 TLS 向我的服务器发送 https 请求。 The server I got working successfully with TLS.我成功使用 TLS 的服务器。 But I can't figure out how to do this on the client-side (Android app).但我不知道如何在客户端(Android 应用程序)上执行此操作。 I use spring on the java server.我在 java 服务器上使用 spring。 Requests from android app are made using HttpsUrlConnection() .来自 android 应用程序的请求是使用HttpsUrlConnection()的。

I managed to be able to call HttpsUrlConnection() this how my code looks:我设法能够以我的代码的外观调用HttpsUrlConnection()

public void test() {
        try {
            URL url = new URL(this.apiUrl);
            HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
            urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
            InputStream in = urlConnection.getInputStream();
            System.out.print(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

My server is configured to use TLSv1.2 protocol.我的服务器配置为使用TLSv1.2协议。 Running test() throws this error:运行test()会引发此错误:

W/System.err: javax.net.ssl.SSLHandshakeException: Handshake failed
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:288)
        at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:196)
        at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:153)
        at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116)
        at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186)
        at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128)
        at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
        at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289)
        at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:248)
        at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getInputStream(DelegatingHttpsURLConnection.java:211)
W/System.err:     at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:30)
        at nl.management.finance.client.RaboClient.test(RaboClient.java:64)
        at nl.management.finance.MainActivity$RESTTask.doInBackground(MainActivity.java:31)
        at nl.management.finance.MainActivity$RESTTask.doInBackground(MainActivity.java:25)
        at android.os.AsyncTask$3.call(AsyncTask.java:378)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:289)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
    Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x703daa2ff448: Failure in SSL library, usually a protocol error
    error:10000412:SSL routines:OPENSSL_internal:SSLV3_ALERT_BAD_CERTIFICATE (external/boringssl/src/ssl/tls_record.cc:587 0x703daa2b1148:0x00000001)
        at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
        at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:387)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
        ... 22 more

Why do I see SSLV3 in the stacktrace?为什么我在堆栈跟踪中看到 SSLV3? Is it not using TLSv1.2?不使用 TLSv1.2 吗? Wireshark shows this https://ibb.co/27mpG4r Wireshark 展示了这个https://ibb.co/27mpG4r

This code (from @Hakan54) makes the SSLContext :这段代码(来自@Hakan54)使SSLContext

public class SSLTrustManagerHelper {

    private InputStream keyStore;
    private String keyStorePassword;
    private InputStream trustStore;
    private String trustStorePassword;

    public SSLTrustManagerHelper(InputStream keyStore,
                                 String keyStorePassword,
                                 InputStream trustStore,
                                 String trustStorePassword) throws ClientException {
        if (keyStore == null || keyStorePassword.trim().isEmpty() || trustStore == null || trustStorePassword.trim().isEmpty()) {
            throw new ClientException("TrustStore or KeyStore details are empty, which are required to be present when SSL is enabled");
        }

        this.keyStore = keyStore;
        this.keyStorePassword = keyStorePassword;
        this.trustStore = trustStore;
        this.trustStorePassword = trustStorePassword;
    }

    public SSLContext clientSSLContext() throws ClientException {
        try {
            TrustManagerFactory trustManagerFactory = getTrustManagerFactory(trustStore, trustStorePassword);
            KeyManagerFactory keyManagerFactory = getKeyManagerFactory(keyStore, keyStorePassword);
            this.keyStore.close();
            this.trustStore.close();

            return getSSLContext(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers());
        } catch (UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException | KeyManagementException e) {
            e.printStackTrace();
            throw new ClientException(e);
        }
    }

    private static SSLContext getSSLContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(keyManagers, trustManagers, null);
        return sslContext;
    }

    private static KeyManagerFactory getKeyManagerFactory(InputStream keystore, String keystorePassword) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, ClientException {
        KeyStore keyStore = loadKeyStore(keystore, keystorePassword);
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
        return keyManagerFactory;
    }

    private static TrustManagerFactory getTrustManagerFactory(InputStream truststore, String truststorePassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, ClientException {
        KeyStore trustStore = loadKeyStore(truststore, truststorePassword);
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);
        return trustManagerFactory;
    }

    private static KeyStore loadKeyStore(InputStream keystoreStream, String keystorePassword) throws ClientException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
        if (keystoreStream == null) {
            throw new ClientException("keystore was null.");
        }

        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(keystoreStream, keystorePassword.toCharArray());
        return keystore;
    }

}

What you are looking for is mutual authentication based on certificates.您正在寻找的是基于证书的相互身份验证。 Both the server and the client needs to trust each other to communicate.服务器和客户端都需要相互信任才能进行通信。 And if the server just trust that specific client only it shouldn't be possible for any other client to do a request.如果服务器只信任该特定客户端,那么任何其他客户端都不应该发出请求。

The above example looks okay, but it will be easier to configure with the example below:上面的示例看起来不错,但是使用下面的示例配置起来会更容易:

import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class SSLTrustManagerHelper {

    private String keyStore;
    private String keyStorePassword;
    private String trustStore;
    private String trustStorePassword;

    public SSLTrustManagerHelper(String keyStore,
                                 String keyStore,
                                 String keyStorePassword,
                                 String trustStore,
                                 String trustStorePassword) {
        if (isBlank(keyStore) || isBlank(keyStorePassword) || isBlank(trustStore) || isBlank(trustStorePassword)) {
            throw new ClientException("TrustStore or KeyStore details are empty, which are required to be present when SSL is enabled");
        }

        this.keyStore = keyStore;
        this.keyStorePassword = keyStorePassword;
        this.trustStore = trustStore;
        this.trustStorePassword = trustStorePassword;
    }

    public SSLContext clientSSLContext() {
        try {
            TrustManagerFactory trustManagerFactory = getTrustManagerFactory(trustStore, trustStorePassword);
            KeyManagerFactory keyManagerFactory = getKeyManagerFactory(keyStore, keyStorePassword);

            return getSSLContext(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers());
        } catch (UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException | KeyManagementException e) {
            throw new ClientException(e);
        }
    }

    private static SSLContext getSSLContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(keyManagers, trustManagers, null);
        return sslContext;
    }

    private static KeyManagerFactory getKeyManagerFactory(String keystorePath, String keystorePassword) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException {
        KeyStore keyStore = loadKeyStore(keystorePath, keystorePassword);
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
        return keyManagerFactory;
    }

    private static TrustManagerFactory getTrustManagerFactory(String truststorePath, String truststorePassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        KeyStore trustStore = loadKeyStore(truststorePath, truststorePassword);
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);
        return trustManagerFactory;
    }

    private static KeyStore loadKeyStore(String keystorePath, String keystorePassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        try(InputStream keystoreInputStream = SSLTrustManagerHelper.class.getClassLoader().getResourceAsStream(keystorePath)) {
            if (isNull(keystoreInputStream)) {
                throw new ClientException(String.format("Could not find the keystore file with the given location %s", keystorePath));
            }

            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            keystore.load(keystoreInputStream, keystorePassword.toCharArray());
            return keystore;
        }
    }

}

Here you need to provide the location of the keystore and truststore, and also the passwords.在这里,您需要提供密钥库和信任库的位置以及密码。 The public class will provide you the ssl context which you can load into your http client.公共 class 将为您提供 ssl 上下文,您可以将其加载到 http 客户端中。

Make sure you have a client keystore with private and public key, and a truststore where you have the public key of the server.确保您有一个带有私钥和公钥的客户端密钥库,以及一个您拥有服务器公钥的信任库。 And make sure that the server has the public key of the client in its truststore.并确保服务器在其信任库中有客户端的公钥。 You also need to provide your server an additional properties in the application.yml file which enforces the server to validate the client.您还需要在application.yml文件中为您的服务器提供一个附加属性,该文件强制服务器验证客户端。 The property is: client-auth: need属性是: client-auth: need

See here a full example of setting up mutual authentication for server and client including example project spring-boot-mutual-tls-sll请参阅此处为服务器和客户端设置相互身份验证的完整示例,包括示例项目spring-boot-mutual-tls-sll

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

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