我有一台运行在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的示例应用,或者指出我在上面的例子中做错了什么?
答案 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);
}