无法让 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;
}
}
然后我尝试了 SpongyCastle
,BouncyCastle
的 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);
}
我在 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;
}
}
然后我尝试了 SpongyCastle
,BouncyCastle
的 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);
}