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

时间:2018-03-16 14:42:45

标签: android tls1.2 android-4.1-jelly-bean

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

我尝试过几种不同的方法。 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应用程序使用TLS1.2用于较旧的Android设备,这给了我希望,但当然它不起作用。 (我怀疑它们支持较旧的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;
    }
}

然后我尝试了SpongyCastle,Android版BouncyCastle

    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”标题存在一些问题,因为我的服务器位于子域并且不以www开头。这是猜测,所以我不确定。

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

任何人都可以给我一个适用于TLS v1.2的示例应用,或者指出我在上面的例子中做错了什么?

1 个答案:

答案 0 :(得分:0)

这里的解决方案是使用标准的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);
    }
}

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

您还可以将2行添加到类顶部的静态初始化器中,例如:

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