无法让 JellyBean 与 TLSv1.2 服务器通信

Can't get JellyBean to communicate with TLSv1.2 server

我在 Google 云上有一个 Apache/Coyote 服务器 运行,只有 TLS 1.2,我们的一个客户设备运行 JellyBean 4.1.2 API 16。它让这个设备正常工作很重要,但我一直在尝试在旧 Android 版本上启用 TLS 1.2。它适用于较新的设备。

我尝试了几种不同的方法。 1 - 标准 Android HTTPS,带有 BouncyCastle 安全提供程序 2 - 自定义 SSLSocketFactory 3 - 使用 TLSProtocol

的 BouncyCastle HTTPS 请求

最接近的结果是第 3 个,但文档很少,所以我无法完成。有人告诉我,我应该能够让它与选项 1 一起工作,但一直无法弄清楚。

我会在下面粘贴我的代码,如果有人能纠正我,我将不胜感激。

这是标准的 android 方式,但还要注意我正在设置 socketfactory,例如 urlConnection.setSSLSocketFactory(new TLSSocketFactory());

private void useAndroidHTTP() {
    Security.insertProviderAt(new BouncyCastleProvider(), 1);
    HttpsURLConnection conn = getConnection(FULL_URL);
    InputStream is = conn.getInputStream();
    String result = readStream(is);
    Log.d(TAG, "Result: " + result);
}

private HttpsURLConnection getConnection(String url) throws MalformedURLException {
    URL request_url = new URL(url);
    listProviders();
    urlConnection = (HttpsURLConnection) request_url.openConnection();
    urlConnection.setSSLSocketFactory(new TLSSocketFactory());
    listProviders();
    urlConnection.setRequestMethod("GET");
    urlConnection.setReadTimeout(15 * 1000);
    urlConnection.setConnectTimeout(15 * 1000);
    urlConnection.connect()

    return urlConnection;
}

我还尝试了在网上找到的多个示例 SocketFactory。例如:

public class TLSSocketFactory extends javax.net.ssl.SSLSocketFactory {

    private javax.net.ssl.SSLSocketFactory delegate;

    static {
        Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
    }

    public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext context = SSLContext.getInstance("TLSv1.2");
        context.init(null, null, null);
        delegate = context.getSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return new String[] {
                "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
                "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
                "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
        };
//        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket() throws IOException {
        return enableTLSOnSocket(delegate.createSocket());
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return enableTLSOnSocket(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return enableTLSOnSocket(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
    }

    private Socket enableTLSOnSocket(Socket socket) {
        if (socket != null && (socket instanceof SSLSocket)) {
            ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"});
        }
        return socket;
    }
}

另一个:

public class SimpleSSLSocketFactory extends javax.net.ssl.SSLSocketFactory {

    private javax.net.ssl.SSLSocketFactory delegate;

    public SimpleSSLSocketFactory() {

        try {
            Security.insertProviderAt(new BouncyCastleProvider(), 1);
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, // KeyManager not required
                    new TrustManager[]{new DummyTrustManager()},
                    new java.security.SecureRandom());
            delegate = sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            System.out.println(e);
        } catch (KeyManagementException e) {
            System.out.println(e);
        }
    }

    public static SocketFactory getDefault() {
        return new SimpleSSLSocketFactory();
    }

    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
        return delegate.createSocket(socket, host, port, autoClose);
    }

    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j) throws IOException {
        return delegate.createSocket(inaddr, i, inaddr2, j);
    }

    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
        return delegate.createSocket(inaddr, i);
    }

    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
        return delegate.createSocket(s, i, inaddr, j);
    }

    public Socket createSocket(String s, int i) throws IOException {
        return delegate.createSocket(s, i);
    }

    public String[] getDefaultCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    private static class DummyTrustManager implements X509TrustManager {

        public boolean isClientTrusted(X509Certificate[] cert) {
            return true;
        }

        public boolean isServerTrusted(X509Certificate[] cert) {
            try {
                cert[0].checkValidity();
                return true;
            } catch (CertificateExpiredException e) {
                return false;
            } catch (CertificateNotYetValidException e) {
                return false;
            }
        }

        public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException {
        }

        public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
}

我还听说 PayPal 应用程序对较旧的 android 设备使用 TLS1.2,这给了我希望,但当然它不起作用。 (我怀疑他们支持旧的 TLS 版本和 1.2)

public class PaypalSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory internalSSLSocketFactory;

    public PaypalSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, null, null);
        internalSSLSocketFactory = context.getSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return internalSSLSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return internalSSLSocketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
    }

    private Socket enableTLSOnSocket(Socket socket) {
        if (socket instanceof SSLSocket) {
            ArrayList<String> supportedProtocols =
                    new ArrayList<>(Arrays.asList(((SSLSocket) socket).getSupportedProtocols()));
            supportedProtocols.retainAll(Arrays.asList("TLSv1.2"));

            ((SSLSocket) socket).setEnabledProtocols(supportedProtocols.toArray(
                    new String[supportedProtocols.size()]));
        }
        return socket;
    }
}

然后我尝试了 SpongyCastleBouncyCastle 的 Android 版本。

    private static void useSpongyCastle() throws IOException {
        Security.insertProviderAt(new BouncyCastleProvider(), 1);
        java.security.SecureRandom secureRandom = new java.security.SecureRandom();
//        Socket socket = new Socket(java.net.InetAddress.getByName(DOMAIN), 443);
        Socket socket = new Socket(DOMAIN, 443);

        TlsClientProtocol protocol = new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(), secureRandom);
        DefaultTlsClient client = new DefaultTlsClient() {
            public TlsAuthentication getAuthentication() throws IOException {
                TlsAuthentication auth = new TlsAuthentication() {
                    // Capture the server certificate information!
                    public void notifyServerCertificate(Certificate serverCertificate) throws IOException {
                    }

                    public TlsCredentials getClientCredentials(CertificateRequest certificateRequest) throws IOException {
                        return null;
                    }
                };
                return auth;
            }
        };
        protocol.connect(client);

        java.io.OutputStream output = protocol.getOutputStream();
        output.write("GET / HTTP/1.1\r\n".getBytes("UTF-8"));
        output.write(("Host: " + FULL_URL + "\r\n").getBytes("UTF-8"));

        //Get auth
        Log.d(TAG, "AUTH: " + BasicAuthentication.getAuthenticationHeader("myuser", "mypass"));
        output.write(("Authorization: " + BasicAuthentication.getAuthenticationHeader("myuser", "mypass")).getBytes());

//        output.write("Content-Type: application/json".getBytes());
//        output.write("Accept: application/json".getBytes());

        output.write("Connection: close\r\n".getBytes("UTF-8")); // So the server will close socket immediately.
        output.write("\r\n".getBytes("UTF-8")); // HTTP1.1 requirement: last line must be empty line.
        output.flush();

        java.io.InputStream input = protocol.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        String line;
        StringBuilder sb = new StringBuilder();
        try {
            while ((line = reader.readLine()) != null) {
                Log.d(TAG, "--> " + line);
                sb.append(line).append("\n");
            }
        } catch (TlsNoCloseNotifyException e) {
            Log.d(TAG, "End of stream");
        }

        String result = sb.toString();
        Log.d(TAG, "Result: " + result);
        listProviders();
    }

上述所有方法,除了 SpongyCastle,都会给我一个 SSLHandshakeException。 SpongyCastle 给了我一个 HTTP 400。我认为 "HOST" header 有一些问题,因为我的服务器在 sub-domain 上并且不是以 www 开头。这是一个猜测,所以我不确定。

所以现在我处于要么非常接近,要么完全走错路的阶段。

谁能给我一个适用于 TLS v1.2 的示例应用程序,或者指出我在上述示例中做错了什么?

这里的解决方案是使用标准 Android HTTP 库并将此方法添加到我的自定义 SSLSocketFactory:

private static void setupSecurityForTLS() {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        Log.d(TAG, "Adding new security provider");
        Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
        Security.insertProviderAt(new BouncyCastleProvider(), 2);
    }
}

并在我初始化套接字之前调用它。

您还可以将这两行添加到 class 顶部的静态初始化程序,例如:

static {
        Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
        Security.insertProviderAt(new BouncyCastleProvider(), 2);
}