Skip to content

Commit 04db63d

Browse files
authored
chore(all): Enable TLSv1.2 support for older devices (#3258)
* Enable and enforce TLS 1.2 on older devices
1 parent 3c79c49 commit 04db63d

File tree

8 files changed

+278
-10
lines changed

8 files changed

+278
-10
lines changed

aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/util/AuthHttpClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717

1818
package com.amazonaws.mobileconnectors.cognitoauth.util;
1919

20-
import android.content.Context;
21-
20+
import com.amazonaws.http.TLS12SocketFactory;
2221
import com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthClientException;
2322
import com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthServiceException;
2423

@@ -46,6 +45,7 @@ public String httpPost(final URL uri, final Map<String, String> headerParams, fi
4645
}
4746

4847
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) uri.openConnection();
48+
TLS12SocketFactory.fixTLSPre21(httpsURLConnection);
4949
DataOutputStream httpOutputStream = null;
5050
BufferedReader br = null;
5151
try {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
*
3+
* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License").
6+
* * You may not use this file except in compliance with the License.
7+
* * A copy of the License is located at
8+
* *
9+
* * http://aws.amazon.com/apache2.0
10+
* *
11+
* * or in the "license" file accompanying this file. This file is distributed
12+
* * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
13+
* * express or implied. See the License for the specific language governing
14+
* * permissions and limitations under the License.
15+
*
16+
*/
17+
18+
package com.amazonaws.http;
19+
20+
import com.amazonaws.logging.LogFactory;
21+
22+
import javax.net.ssl.HandshakeCompletedEvent;
23+
import javax.net.ssl.HandshakeCompletedListener;
24+
import javax.net.ssl.SSLSession;
25+
26+
public class LoggingHandshakeCompletedListener implements HandshakeCompletedListener {
27+
28+
private static final com.amazonaws.logging.Log log =
29+
LogFactory.getLog(LoggingHandshakeCompletedListener.class);
30+
@Override
31+
public void handshakeCompleted(HandshakeCompletedEvent event) {
32+
try {
33+
SSLSession session = event.getSession();
34+
String protocol = session.getProtocol();
35+
String cipherSuite = session.getCipherSuite();
36+
37+
log.debug("Protocol: " + protocol + ", CipherSuite: " + cipherSuite);
38+
} catch (Exception exception) {
39+
log.debug("Failed to log connection protocol/cipher suite", exception);
40+
}
41+
}
42+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amazonaws.http;
16+
17+
import android.os.Build;
18+
19+
import androidx.annotation.NonNull;
20+
import androidx.annotation.Nullable;
21+
22+
import java.io.IOException;
23+
import java.net.InetAddress;
24+
import java.net.Socket;
25+
import java.net.UnknownHostException;
26+
import java.security.KeyManagementException;
27+
import java.security.NoSuchAlgorithmException;
28+
29+
import javax.net.ssl.HttpsURLConnection;
30+
import javax.net.ssl.SSLContext;
31+
import javax.net.ssl.SSLSocket;
32+
import javax.net.ssl.SSLSocketFactory;
33+
34+
/**
35+
* Although this has public access, it is intended for internal use and should not be used directly by host
36+
* applications. The behavior of this may change without warning.
37+
*/
38+
public class TLS12SocketFactory extends SSLSocketFactory {
39+
40+
private static final Object contextLock = new Object();
41+
private static final String[] SUPPORTED_PROTOCOLS =
42+
new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
43+
private static SSLContext sslContext = null;
44+
private final SSLSocketFactory delegate;
45+
private LoggingHandshakeCompletedListener handshakeCompletedListener;
46+
47+
@Nullable
48+
public static TLS12SocketFactory createTLS12SocketFactory() {
49+
return createTLS12SocketFactory(null);
50+
}
51+
52+
@Nullable
53+
public static TLS12SocketFactory createTLS12SocketFactory(
54+
@Nullable SSLContext sslContext
55+
) {
56+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
57+
try {
58+
return new TLS12SocketFactory(sslContext);
59+
} catch (Exception e) {
60+
//
61+
}
62+
}
63+
return null;
64+
}
65+
66+
public static void fixTLSPre21(@NonNull HttpsURLConnection connection) {
67+
fixTLSPre21(connection, createTLS12SocketFactory());
68+
}
69+
70+
public static void fixTLSPre21(
71+
@NonNull HttpsURLConnection connection,
72+
@Nullable TLS12SocketFactory tls12SocketFactory
73+
) {
74+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP &&
75+
tls12SocketFactory != null) {
76+
try {
77+
connection.setSSLSocketFactory(tls12SocketFactory);
78+
} catch (Exception e) {
79+
// Failed to enabled TLS1.2 on < Android 21 device
80+
}
81+
}
82+
}
83+
84+
private TLS12SocketFactory(@Nullable SSLContext customSSLContext)
85+
throws KeyManagementException, NoSuchAlgorithmException {
86+
87+
if (customSSLContext != null) {
88+
delegate = customSSLContext.getSocketFactory();
89+
} else {
90+
// Cache SSLContext due to weight and hold static
91+
synchronized (contextLock) {
92+
if (sslContext == null) {
93+
sslContext = SSLContext.getInstance("TLS");
94+
sslContext.init(null, null, null);
95+
}
96+
}
97+
delegate = sslContext.getSocketFactory();
98+
}
99+
this.handshakeCompletedListener = new LoggingHandshakeCompletedListener();
100+
}
101+
102+
@Override
103+
public String[] getDefaultCipherSuites() {
104+
return delegate.getDefaultCipherSuites();
105+
}
106+
107+
@Override
108+
public String[] getSupportedCipherSuites() {
109+
return delegate.getSupportedCipherSuites();
110+
}
111+
112+
@Override
113+
public Socket createSocket() throws IOException {
114+
SSLSocket socket = (SSLSocket) delegate.createSocket();
115+
socket.addHandshakeCompletedListener(handshakeCompletedListener);
116+
return updateTLSProtocols(socket);
117+
}
118+
119+
@Override
120+
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
121+
SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose);
122+
socket.addHandshakeCompletedListener(handshakeCompletedListener);
123+
return updateTLSProtocols(socket);
124+
}
125+
126+
@Override
127+
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
128+
SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
129+
socket.addHandshakeCompletedListener(handshakeCompletedListener);
130+
return updateTLSProtocols(socket);
131+
}
132+
133+
@Override
134+
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
135+
SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort);
136+
socket.addHandshakeCompletedListener(handshakeCompletedListener);
137+
return updateTLSProtocols(socket);
138+
}
139+
140+
@Override
141+
public Socket createSocket(InetAddress host, int port) throws IOException {
142+
SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
143+
socket.addHandshakeCompletedListener(handshakeCompletedListener);
144+
return updateTLSProtocols(socket);
145+
}
146+
147+
@Override
148+
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
149+
SSLSocket socket = (SSLSocket) delegate.createSocket(address, port, localAddress, localPort);
150+
socket.addHandshakeCompletedListener(handshakeCompletedListener);
151+
return updateTLSProtocols(socket);
152+
}
153+
154+
private Socket updateTLSProtocols(Socket socket) {
155+
if(socket instanceof SSLSocket) {
156+
try {
157+
((SSLSocket) socket).setEnabledProtocols(SUPPORTED_PROTOCOLS);
158+
} catch (Exception e) {
159+
// TLS 1.2 may not be supported on device
160+
}
161+
}
162+
return socket;
163+
}
164+
}

aws-android-sdk-core/src/main/java/com/amazonaws/http/UrlHttpClient.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,24 @@ public class UrlHttpClient implements HttpClient {
5555
private static final int BUFFER_SIZE_MULTIPLIER = 8;
5656
private final ClientConfiguration config;
5757

58+
// SocketFactory for Pre SDK 21 devices to enforce TLS 1.2
59+
private final TLS12SocketFactory tls12SocketFactory;
60+
61+
// Cached SSLContext for connections using custom TrustManagers.
62+
private SSLContext customTrustSSLContext = null;
63+
64+
// SocketFactory for Pre SDK 21 devices to enforce TLS 1.2 that also holds custom TrustManagers.
65+
private TLS12SocketFactory customTrustTls12SocketFactory;
66+
5867
/**
5968
* Constructor.
6069
* @param config the client config.
6170
*/
6271
public UrlHttpClient(ClientConfiguration config) {
6372
this.config = config;
73+
74+
// will return null if SDK >= 21
75+
tls12SocketFactory = TLS12SocketFactory.createTLS12SocketFactory();
6476
}
6577

6678
@Override
@@ -279,26 +291,35 @@ void configureConnection(HttpRequest request, HttpURLConnection connection) {
279291

280292
if (config.getTrustManager() != null) {
281293
enableCustomTrustManager(https);
294+
} else if (tls12SocketFactory != null) {
295+
TLS12SocketFactory.fixTLSPre21(https, tls12SocketFactory);
282296
}
283297
}
284298
}
285299

286-
private SSLContext sc = null;
287-
288300
private void enableCustomTrustManager(HttpsURLConnection connection) {
289-
if (sc == null) {
301+
if (customTrustSSLContext == null) {
290302
final TrustManager[] customTrustManagers = new TrustManager[] {
291303
config.getTrustManager()
292304
};
293305
try {
294-
sc = SSLContext.getInstance("TLS");
295-
sc.init(null, customTrustManagers, null);
306+
customTrustSSLContext = SSLContext.getInstance("TLS");
307+
customTrustSSLContext.init(null, customTrustManagers, null);
308+
309+
if (customTrustTls12SocketFactory == null) {
310+
customTrustTls12SocketFactory = TLS12SocketFactory
311+
.createTLS12SocketFactory(customTrustSSLContext);
312+
}
296313
} catch (final GeneralSecurityException e) {
297314
throw new RuntimeException(e);
298315
}
299316
}
300317

301-
connection.setSSLSocketFactory(sc.getSocketFactory());
318+
if (customTrustTls12SocketFactory != null) {
319+
connection.setSSLSocketFactory(customTrustTls12SocketFactory);
320+
} else {
321+
connection.setSSLSocketFactory(customTrustSSLContext.getSocketFactory());
322+
}
302323
}
303324

304325
/*

aws-android-sdk-core/src/main/java/com/amazonaws/util/HttpUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.amazonaws.ClientConfiguration;
1919
import com.amazonaws.Request;
2020
import com.amazonaws.http.HttpMethodName;
21+
import com.amazonaws.http.TLS12SocketFactory;
2122

2223
import java.io.IOException;
2324
import java.io.InputStream;
@@ -31,6 +32,8 @@
3132
import java.util.regex.Matcher;
3233
import java.util.regex.Pattern;
3334

35+
import javax.net.ssl.HttpsURLConnection;
36+
3437
/**
3538
* HTTP utils class.
3639
*/
@@ -289,6 +292,9 @@ public static InputStream fetchFile(
289292
final URL url = uri.toURL();
290293
// TODO: support proxy?
291294
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
295+
if (connection instanceof HttpsURLConnection) {
296+
TLS12SocketFactory.fixTLSPre21((HttpsURLConnection) connection);
297+
}
292298
connection.setConnectTimeout(getConnectionTimeout(config));
293299
connection.setReadTimeout(getSocketTimeout(config));
294300
connection.addRequestProperty("User-Agent", getUserAgent(config));

aws-android-sdk-iot/src/main/java/com/amazonaws/mobileconnectors/iot/AWSIotMqttManager.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.amazonaws.AmazonClientException;
2525
import com.amazonaws.SDKGlobalConfiguration;
2626
import com.amazonaws.auth.AWSCredentialsProvider;
27+
import com.amazonaws.http.TLS12SocketFactory;
2728
import com.amazonaws.regions.Region;
2829
import com.amazonaws.util.StringUtils;
2930
import com.amazonaws.util.VersionInfoUtils;
@@ -229,10 +230,17 @@ public boolean isMetricsEnabled() {
229230
return metricsIsEnabled;
230231
}
231232
/**
232-
* Holds client socket factory. Set upon initial connect then reused on
233+
* Holds client socket factory for keystore connect. Set upon initial connect then reused on
233234
* reconnect.
234235
*/
235236
private SocketFactory clientSocketFactory;
237+
238+
239+
/**
240+
* Holds cached SocketFactory for non-keystore connect calls on Android versions < 21
241+
* Set upon initial connect then reused on reconnect.
242+
*/
243+
private TLS12SocketFactory tls12SocketFactory;
236244
/**
237245
* Holds client provided AWS credentials provider.
238246
* Set upon initial connect.
@@ -1138,6 +1146,7 @@ private void customAuthConnect(final MqttConnectOptions options) {
11381146
private void mqttConnect(MqttConnectOptions options) {
11391147
LOGGER.debug("ready to do mqtt connect");
11401148

1149+
fixTLSPre21(options);
11411150
options.setCleanSession(cleanSession);
11421151
options.setKeepAliveInterval(userKeepAlive);
11431152

@@ -1324,6 +1333,8 @@ void reconnectToSession() {
13241333
handleConnectionFailure(new IllegalStateException("Unexpected value: " + authMode));
13251334
}
13261335

1336+
fixTLSPre21(options);
1337+
13271338
setupCallbackForMqttClient();
13281339
try {
13291340
++autoReconnectsAttempted;
@@ -2055,4 +2066,18 @@ enum AuthenticationMode {
20552066
public boolean getSessionPresent() {
20562067
return sessionPresent;
20572068
}
2069+
2070+
/**
2071+
* Injects a SocketFactory that supports TLSv1.2 on pre 21 devices.
2072+
* If a SocketFactory is already specified (ex keystore connect uses its own), call is ignored.
2073+
* @param options for connect call
2074+
*/
2075+
private void fixTLSPre21(MqttConnectOptions options) {
2076+
if (options.getSocketFactory() == null &&
2077+
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
2078+
) {
2079+
this.tls12SocketFactory = TLS12SocketFactory.createTLS12SocketFactory();
2080+
options.setSocketFactory(tls12SocketFactory);
2081+
}
2082+
}
20582083
}

aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/internal/oauth2/OAuth2Client.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import androidx.browser.customtabs.CustomTabsSession;
1313
import android.util.Log;
1414

15+
import com.amazonaws.http.TLS12SocketFactory;
1516
import com.amazonaws.internal.keyvaluestore.AWSKeyValueStore;
1617
import com.amazonaws.mobile.client.AWSMobileClient;
1718
import com.amazonaws.mobile.client.Callback;
@@ -495,6 +496,7 @@ public static String httpPost(final URL uri, final Map<String, String> headerPar
495496
}
496497

497498
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) uri.openConnection();
499+
TLS12SocketFactory.fixTLSPre21(httpsURLConnection);
498500
DataOutputStream httpOutputStream = null;
499501
BufferedReader br = null;
500502
try {

0 commit comments

Comments
 (0)