Skip to content

Commit d8a5060

Browse files
committed
AsyncSSLSocketWrapper: Perform handshake before allowing read/write.
1 parent eb633ea commit d8a5060

File tree

3 files changed

+85
-158
lines changed

3 files changed

+85
-158
lines changed

AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java

Lines changed: 59 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -28,129 +28,28 @@
2828
import javax.net.ssl.X509TrustManager;
2929

3030
public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket {
31+
public interface HandshakeCallback {
32+
public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket);
33+
}
34+
3135
static SSLContext defaultSSLContext;
3236

3337
AsyncSocket mSocket;
3438
BufferedDataEmitter mEmitter;
3539
BufferedDataSink mSink;
36-
boolean mUnwrapping = false;
40+
boolean mUnwrapping;
41+
SSLEngine engine;
42+
boolean finishedHandshake;
43+
private int mPort;
44+
private String mHost;
45+
private boolean mWrapping;
3746
HostnameVerifier hostnameVerifier;
38-
39-
/*
40-
private static void initTLS_1_2() {
41-
try {
42-
defaultSSLContext = SSLContext.getInstance("TLSv1.2");
43-
}
44-
catch (NoSuchAlgorithmException e) {
45-
}
46-
}
47-
48-
private static void initTLS_1_1() {
49-
try {
50-
defaultSSLContext = SSLContext.getInstance("TLSv1.1");
51-
}
52-
catch (NoSuchAlgorithmException e) {
53-
}
54-
}
55-
56-
private static void initTLS() {
57-
try {
58-
defaultSSLContext = SSLContext.getInstance("TLS");
59-
}
60-
catch (NoSuchAlgorithmException e) {
61-
}
62-
}
63-
64-
static {
65-
try {
66-
initTLS_1_2();
67-
if (defaultSSLContext == null)
68-
initTLS_1_1();
69-
if (defaultSSLContext == null)
70-
initTLS();
71-
if (defaultSSLContext == null)
72-
defaultSSLContext = SSLContext.getInstance("SSL");
73-
// critical extension 2.5.29.15 is implemented improperly prior to 4.0.3.
74-
// https://code.google.com/p/android/issues/detail?id=9307
75-
// https://groups.google.com/forum/?fromgroups=#!topic/netty/UCfqPPk5O4s
76-
// certs that use this extension will throw in Cipher.java.
77-
// fallback is to use a custom SSLContext, and hack around the x509 extension.
78-
TrustManager[] trustManagers = null;
79-
if (Build.VERSION.SDK_INT <= 15) {
80-
trustManagers = new TrustManager[] { new X509TrustManager() {
81-
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
82-
return new X509Certificate[0];
83-
}
84-
85-
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
86-
}
87-
88-
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
89-
for (X509Certificate cert : certs) {
90-
if (cert != null && cert.getCriticalExtensionOIDs() != null)
91-
cert.getCriticalExtensionOIDs().remove("2.5.29.15");
92-
}
93-
}
94-
} };
95-
}
96-
defaultSSLContext.init(null, trustManagers, null);
97-
}
98-
catch (Exception ex) {
99-
ex.printStackTrace();
100-
}
101-
}
102-
103-
// android SSL cipher suites were downgraded (!!) for some derpy reason.
104-
// Paranoid people would be wise to enable the original/secure suites.
105-
// http://op-co.de/blog/posts/android_ssl_downgrade/
106-
public static final String RECOMMENDED_CIPHERS[] = {
107-
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
108-
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
109-
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
110-
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
111-
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
112-
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
113-
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
114-
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
115-
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
116-
"TLS_RSA_WITH_AES_128_CBC_SHA",
117-
"TLS_RSA_WITH_AES_256_CBC_SHA",
118-
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
119-
"SSL_RSA_WITH_RC4_128_SHA",
120-
"SSL_RSA_WITH_RC4_128_MD5",
121-
"TLS_RSA_WITH_AES_256_CBC_SHA256",
122-
};
123-
124-
public static final String RECOMMENDED_PROTOCOLS[] = {
125-
"TLSv1"
126-
};
127-
128-
public static void setupRecommendedEngineSecurity(SSLEngine engine) {
129-
LinkedHashSet<String> ciphers = new LinkedHashSet<String>(Arrays.asList(engine.getSupportedCipherSuites()));
130-
ciphers.addAll(Arrays.asList(engine.getSupportedCipherSuites()));
131-
LinkedHashSet<String> protocols = new LinkedHashSet<String>();
132-
protocols.addAll(Arrays.asList(engine.getSupportedProtocols()));
133-
134-
ArrayList<String> enabledCiphers = new ArrayList<String>();
135-
for (String cipher: RECOMMENDED_CIPHERS) {
136-
if (ciphers.contains(cipher))
137-
enabledCiphers.add(cipher);
138-
}
139-
140-
ArrayList<String> enabledProtocols = new ArrayList<String>();
141-
for (String protocol: RECOMMENDED_PROTOCOLS) {
142-
if (protocols.contains(protocol))
143-
enabledProtocols.add(protocol);
144-
}
145-
146-
enabledCiphers.addAll(Arrays.asList(engine.getEnabledCipherSuites()));
147-
enabledProtocols.addAll(Arrays.asList(engine.getEnabledProtocols()));
148-
// engine.setEnabledCipherSuites(enabledCiphers.toArray(new String[enabledCiphers.size()]));
149-
// engine.setEnabledProtocols(enabledProtocols.toArray(new String[enabledProtocols.size()]));
150-
// engine.setEnabledCipherSuites(RECOMMENDED_CIPHERS);
151-
engine.setEnabledProtocols(new String[] {"SSL"});
152-
}
153-
*/
47+
HandshakeCallback handshakeCallback;
48+
X509Certificate[] peerCertificates;
49+
WritableCallback mWriteableCallback;
50+
DataCallback mDataCallback;
51+
TrustManager[] trustManagers;
52+
boolean clientMode;
15453

15554
static {
15655
// following is the "trust the system" certs setup
@@ -195,19 +94,25 @@ public static SSLEngine createDefaultSSLEngine() {
19594
return defaultSSLContext.createSSLEngine();
19695
}
19796

198-
@Override
199-
public void end() {
200-
mSocket.end();
201-
}
202-
203-
public AsyncSSLSocketWrapper(AsyncSocket socket, String host, int port) {
204-
this(socket, host, port, createDefaultSSLEngine(), null, null, true);
97+
public static void handshake(AsyncSocket socket,
98+
String host, int port,
99+
SSLEngine sslEngine,
100+
TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode,
101+
HandshakeCallback callback) {
102+
AsyncSSLSocketWrapper wrapper = new AsyncSSLSocketWrapper(socket, host, port, sslEngine, trustManagers, verifier, clientMode);
103+
wrapper.handshakeCallback = callback;
104+
try {
105+
wrapper.engine.beginHandshake();
106+
wrapper.handleHandshakeStatus(wrapper.engine.getHandshakeStatus());
107+
} catch (SSLException e) {
108+
wrapper.report(e);
109+
}
205110
}
206111

207-
TrustManager[] trustManagers;
208-
boolean clientMode;
209-
210-
public AsyncSSLSocketWrapper(AsyncSocket socket, String host, int port, SSLEngine sslEngine, TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode) {
112+
private AsyncSSLSocketWrapper(AsyncSocket socket,
113+
String host, int port,
114+
SSLEngine sslEngine,
115+
TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode) {
211116
mSocket = socket;
212117
hostnameVerifier = verifier;
213118
this.clientMode = clientMode;
@@ -277,7 +182,7 @@ else if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
277182
bb.addFirst(b);
278183
b = ByteBufferList.EMPTY_BYTEBUFFER;
279184
}
280-
handleResult(res);
185+
handleHandshakeStatus(res.getHandshakeStatus());
281186
if (b.remaining() == remaining && before == transformed.remaining()) {
282187
bb.addFirst(b);
283188
break;
@@ -308,32 +213,30 @@ void addToPending(ByteBufferList out, ByteBuffer mReadTmp) {
308213
}
309214

310215

311-
SSLEngine engine;
312-
boolean finishedHandshake = false;
313-
314-
private String mHost;
216+
@Override
217+
public void end() {
218+
mSocket.end();
219+
}
315220

316221
public String getHost() {
317222
return mHost;
318223
}
319224

320-
private int mPort;
321-
322225
public int getPort() {
323226
return mPort;
324227
}
325228

326-
private void handleResult(SSLEngineResult res) {
327-
if (res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
229+
private void handleHandshakeStatus(HandshakeStatus status) {
230+
if (status == HandshakeStatus.NEED_TASK) {
328231
final Runnable task = engine.getDelegatedTask();
329232
task.run();
330233
}
331234

332-
if (res.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
235+
if (status == HandshakeStatus.NEED_WRAP) {
333236
write(ByteBufferList.EMPTY_BYTEBUFFER);
334237
}
335238

336-
if (res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
239+
if (status == HandshakeStatus.NEED_UNWRAP) {
337240
mEmitter.onDataAvailable();
338241
}
339242

@@ -380,6 +283,11 @@ private void handleResult(SSLEngineResult res) {
380283
throw e;
381284
}
382285
}
286+
else {
287+
finishedHandshake = true;
288+
}
289+
handshakeCallback.onHandshakeCompleted(null, this);
290+
handshakeCallback = null;
383291
if (mWriteableCallback != null)
384292
mWriteableCallback.onWriteable();
385293
mEmitter.onDataAvailable();
@@ -403,7 +311,6 @@ private void writeTmp(ByteBuffer mWriteTmp) {
403311
assert !mWriteTmp.hasRemaining();
404312
}
405313

406-
private boolean mWrapping = false;
407314

408315
int calculateAlloc(int remaining) {
409316
// alloc 50% more than we need for writing
@@ -445,7 +352,7 @@ public void write(ByteBuffer bb) {
445352
else {
446353
mWriteTmp = ByteBufferList.obtain(calculateAlloc(bb.remaining()));
447354
}
448-
handleResult(res);
355+
handleHandshakeStatus(res.getHandshakeStatus());
449356
}
450357
catch (SSLException e) {
451358
report(e);
@@ -489,7 +396,7 @@ public void write(ByteBufferList bb) {
489396
}
490397
else {
491398
mWriteTmp = ByteBufferList.obtain(calculateAlloc(bb.remaining()));
492-
handleResult(res);
399+
handleHandshakeStatus(res.getHandshakeStatus());
493400
}
494401
}
495402
catch (SSLException e) {
@@ -501,8 +408,6 @@ public void write(ByteBufferList bb) {
501408
mWrapping = false;
502409
}
503410

504-
WritableCallback mWriteableCallback;
505-
506411
@Override
507412
public void setWriteableCallback(WritableCallback handler) {
508413
mWriteableCallback = handler;
@@ -514,13 +419,21 @@ public WritableCallback getWriteableCallback() {
514419
}
515420

516421
private void report(Exception e) {
422+
final HandshakeCallback hs = handshakeCallback;
423+
if (hs != null) {
424+
handshakeCallback = null;
425+
mSocket.setDataCallback(new NullDataCallback());
426+
mSocket.end();
427+
mSocket.close();
428+
hs.onHandshakeCompleted(e, null);
429+
return;
430+
}
431+
517432
CompletedCallback cb = getEndCallback();
518433
if (cb != null)
519434
cb.onCompleted(e);
520435
}
521436

522-
DataCallback mDataCallback;
523-
524437
@Override
525438
public void setDataCallback(DataCallback callback) {
526439
mDataCallback = callback;
@@ -596,8 +509,6 @@ public DataEmitter getDataEmitter() {
596509
return mSocket;
597510
}
598511

599-
X509Certificate[] peerCertificates;
600-
601512
@Override
602513
public X509Certificate[] getPeerCertificates() {
603514
return peerCertificates;

AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.os.Build;
55
import android.text.TextUtils;
66

7+
import com.koushikdutta.async.AsyncSSLSocket;
78
import com.koushikdutta.async.AsyncSSLSocketWrapper;
89
import com.koushikdutta.async.AsyncSocket;
910
import com.koushikdutta.async.LineEmitter;
@@ -70,17 +71,25 @@ protected SSLEngine createConfiguredSSLEngine(String host, int port) {
7071
return sslEngine;
7172
}
7273

74+
protected void tryHandshake(final ConnectCallback callback, AsyncSocket socket, final Uri uri, final int port) {
75+
AsyncSSLSocketWrapper.handshake(socket, uri.getHost(), port,
76+
createConfiguredSSLEngine(uri.getHost(), port),
77+
trustManagers, hostnameVerifier, true, new AsyncSSLSocketWrapper.HandshakeCallback() {
78+
@Override
79+
public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket) {
80+
callback.onConnectCompleted(e, socket);
81+
}
82+
});
83+
}
84+
7385
@Override
7486
protected ConnectCallback wrapCallback(final ConnectCallback callback, final Uri uri, final int port, final boolean proxied) {
7587
return new ConnectCallback() {
7688
@Override
7789
public void onConnectCompleted(Exception ex, final AsyncSocket socket) {
7890
if (ex == null) {
7991
if (!proxied) {
80-
callback.onConnectCompleted(null,
81-
new AsyncSSLSocketWrapper(socket, uri.getHost(), port,
82-
createConfiguredSSLEngine(uri.getHost(), port),
83-
trustManagers, hostnameVerifier, true));
92+
tryHandshake(callback, socket, uri, port);
8493
}
8594
else {
8695
// this SSL connection is proxied, must issue a CONNECT request to the proxy server
@@ -112,10 +121,7 @@ public void onStringAvailable(String s) {
112121
socket.setDataCallback(null);
113122
socket.setEndCallback(null);
114123
if (TextUtils.isEmpty(s.trim())) {
115-
callback.onConnectCompleted(null,
116-
new AsyncSSLSocketWrapper(socket, uri.getHost(), port,
117-
createConfiguredSSLEngine(uri.getHost(), port),
118-
trustManagers, hostnameVerifier, true));
124+
tryHandshake(callback, socket, uri, port);
119125
}
120126
else {
121127
callback.onConnectCompleted(new IOException("unknown second status line"), socket);

0 commit comments

Comments
 (0)