Skip to content

Commit 2ae4c7c

Browse files
author
Sefa Ilkimen
committed
refactor cert handling for Android (preparing for v2 changes)
1 parent 32fdf49 commit 2ae4c7c

File tree

4 files changed

+145
-163
lines changed

4 files changed

+145
-163
lines changed

src/android/com/github/kevinsawicki/http/HttpRequest.java

Lines changed: 121 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ public class HttpRequest {
263263
*/
264264
public static final String PARAM_CHARSET = "charset";
265265

266+
public static final String CERT_MODE_DEFAULT = "default";
267+
268+
public static final String CERT_MODE_PINNED = "pinned";
269+
270+
public static final String CERT_MODE_TRUSTALL = "trustall";
271+
266272
private static final String BOUNDARY = "00content0boundary00";
267273

268274
private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary="
@@ -272,13 +278,13 @@ public class HttpRequest {
272278

273279
private static final String[] EMPTY_STRINGS = new String[0];
274280

275-
private static SSLSocketFactory PINNED_FACTORY;
281+
private static SSLSocketFactory SOCKET_FACTORY;
276282

277-
private static SSLSocketFactory TRUSTED_FACTORY;
283+
private static String CURRENT_CERT_MODE = CERT_MODE_DEFAULT;
278284

279285
private static ArrayList<Certificate> PINNED_CERTS;
280286

281-
private static HostnameVerifier TRUSTED_VERIFIER;
287+
private static HostnameVerifier HOSTNAME_VERIFIER;
282288

283289
private static String getValidCharset(final String charset) {
284290
if (charset != null && charset.length() > 0)
@@ -287,63 +293,107 @@ private static String getValidCharset(final String charset) {
287293
return CHARSET_UTF8;
288294
}
289295

290-
private static SSLSocketFactory getPinnedFactory()
291-
throws HttpRequestException {
292-
if (PINNED_FACTORY != null) {
293-
return PINNED_FACTORY;
296+
/**
297+
* Configure SSL cert handling for all future HTTPS connections
298+
*
299+
* @param mode
300+
*/
301+
public static void setSSLCertMode(String mode) {
302+
try {
303+
if (mode == CERT_MODE_TRUSTALL) {
304+
SOCKET_FACTORY = createSocketFactory(getNoopTrustManagers());
305+
} else if (mode == CERT_MODE_PINNED) {
306+
SOCKET_FACTORY = createSocketFactory(getPinnedTrustManagers());
307+
} else {
308+
SOCKET_FACTORY = null;
309+
}
310+
311+
CURRENT_CERT_MODE = mode;
312+
} catch(IOException e) {
313+
throw new HttpRequestException(e);
314+
}
315+
}
316+
317+
/**
318+
* Configure host name verification for all future HTTPS connections
319+
*
320+
* @param enabled
321+
*/
322+
public static void setHostnameVerification(boolean enabled) {
323+
if (enabled) {
324+
HOSTNAME_VERIFIER = null;
294325
} else {
295-
IOException e = new IOException("You must add at least 1 certificate in order to pin to certificates");
296-
throw new HttpRequestException(e);
326+
HOSTNAME_VERIFIER = getTrustedVerifier();
297327
}
298328
}
299329

300-
private static SSLSocketFactory getTrustedFactory()
301-
throws HttpRequestException {
302-
if (TRUSTED_FACTORY == null) {
303-
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
330+
private static TrustManager[] getPinnedTrustManagers() throws IOException {
331+
if (PINNED_CERTS == null) {
332+
throw new IOException("You must add at least 1 certificate in order to pin to certificates");
333+
}
304334

305-
public X509Certificate[] getAcceptedIssuers() {
306-
return new X509Certificate[0];
307-
}
335+
try {
336+
String keyStoreType = KeyStore.getDefaultType();
337+
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
338+
keyStore.load(null, null);
308339

309-
public void checkClientTrusted(X509Certificate[] chain, String authType) {
310-
// Intentionally left blank
311-
}
340+
for (int i = 0; i < PINNED_CERTS.size(); i++) {
341+
keyStore.setCertificateEntry("CA" + i, PINNED_CERTS.get(i));
342+
}
312343

313-
public void checkServerTrusted(X509Certificate[] chain, String authType) {
314-
// Intentionally left blank
315-
}
316-
} };
317-
try {
318-
SSLContext context = SSLContext.getInstance("TLS");
319-
context.init(null, trustAllCerts, new SecureRandom());
344+
// Create a TrustManager that trusts the CAs in our KeyStore
345+
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
346+
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
347+
tmf.init(keyStore);
320348

321-
if (android.os.Build.VERSION.SDK_INT < 20) {
322-
TRUSTED_FACTORY = new TLSSocketFactory(context);
323-
} else {
324-
TRUSTED_FACTORY = context.getSocketFactory();
325-
}
326-
} catch (GeneralSecurityException e) {
327-
IOException ioException = new IOException(
328-
"Security exception configuring SSL context");
329-
ioException.initCause(e);
330-
throw new HttpRequestException(ioException);
331-
}
349+
return tmf.getTrustManagers();
350+
} catch (GeneralSecurityException e) {
351+
IOException ioException = new IOException("Security exception configuring SSL trust managers");
352+
ioException.initCause(e);
353+
throw new HttpRequestException(ioException);
332354
}
355+
}
356+
357+
private static TrustManager[] getNoopTrustManagers() {
358+
return new TrustManager[] { new X509TrustManager() {
359+
public X509Certificate[] getAcceptedIssuers() {
360+
return new X509Certificate[0];
361+
}
333362

334-
return TRUSTED_FACTORY;
363+
public void checkClientTrusted(X509Certificate[] chain, String authType) {
364+
// Intentionally left blank
365+
}
366+
367+
public void checkServerTrusted(X509Certificate[] chain, String authType) {
368+
// Intentionally left blank
369+
}
370+
}};
335371
}
336372

337-
private static HostnameVerifier getTrustedVerifier() {
338-
if (TRUSTED_VERIFIER == null)
339-
TRUSTED_VERIFIER = new HostnameVerifier() {
373+
private static SSLSocketFactory createSocketFactory(TrustManager[] trustManagers)
374+
throws HttpRequestException {
375+
try {
376+
SSLContext context = SSLContext.getInstance("TLS");
377+
context.init(null, trustManagers, new SecureRandom());
340378

341-
public boolean verify(String hostname, SSLSession session) {
342-
return true;
343-
}
344-
};
379+
if (android.os.Build.VERSION.SDK_INT < 20) {
380+
return new TLSSocketFactory(context);
381+
} else {
382+
return context.getSocketFactory();
383+
}
384+
} catch (GeneralSecurityException e) {
385+
IOException ioException = new IOException("Security exception configuring SSL context");
386+
ioException.initCause(e);
387+
throw new HttpRequestException(ioException);
388+
}
389+
}
345390

346-
return TRUSTED_VERIFIER;
391+
private static HostnameVerifier getTrustedVerifier() {
392+
return new HostnameVerifier() {
393+
public boolean verify(String hostname, SSLSession session) {
394+
return true;
395+
}
396+
};
347397
}
348398

349399
private static StringBuilder addPathSeparator(final String baseUrl,
@@ -453,32 +503,15 @@ public static void setConnectionFactory(final ConnectionFactory connectionFactor
453503
* @throws IOException
454504
*/
455505
public static void addCert(Certificate ca) throws GeneralSecurityException, IOException {
456-
if (PINNED_CERTS == null) {
457-
PINNED_CERTS = new ArrayList<Certificate>();
458-
}
459-
PINNED_CERTS.add(ca);
460-
String keyStoreType = KeyStore.getDefaultType();
461-
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
462-
keyStore.load(null, null);
463-
464-
for (int i = 0; i < PINNED_CERTS.size(); i++) {
465-
keyStore.setCertificateEntry("CA" + i, PINNED_CERTS.get(i));
466-
}
467-
468-
// Create a TrustManager that trusts the CAs in our KeyStore
469-
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
470-
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
471-
tmf.init(keyStore);
506+
if (PINNED_CERTS == null) {
507+
PINNED_CERTS = new ArrayList<Certificate>();
508+
}
472509

473-
// Create an SSLContext that uses our TrustManager
474-
SSLContext sslContext = SSLContext.getInstance("TLS");
475-
sslContext.init(null, tmf.getTrustManagers(), null);
510+
PINNED_CERTS.add(ca);
476511

477-
if (android.os.Build.VERSION.SDK_INT < 20) {
478-
PINNED_FACTORY = new TLSSocketFactory(sslContext);
479-
} else {
480-
PINNED_FACTORY = sslContext.getSocketFactory();
481-
}
512+
if (CURRENT_CERT_MODE == CERT_MODE_PINNED) {
513+
SOCKET_FACTORY = createSocketFactory(getPinnedTrustManagers());
514+
}
482515
}
483516

484517
/**
@@ -1632,6 +1665,7 @@ public HttpRequest(final CharSequence url, final String method)
16321665
throw new HttpRequestException(e);
16331666
}
16341667
this.requestMethod = method;
1668+
this.setupSecurity();
16351669
}
16361670

16371671
/**
@@ -1645,6 +1679,23 @@ public HttpRequest(final URL url, final String method)
16451679
throws HttpRequestException {
16461680
this.url = url;
16471681
this.requestMethod = method;
1682+
this.setupSecurity();
1683+
}
1684+
1685+
private void setupSecurity() {
1686+
final HttpURLConnection connection = getConnection();
1687+
1688+
if (!(connection instanceof HttpsURLConnection)) {
1689+
return;
1690+
}
1691+
1692+
if (SOCKET_FACTORY != null) {
1693+
((HttpsURLConnection) connection).setSSLSocketFactory(SOCKET_FACTORY);
1694+
}
1695+
1696+
if (HOSTNAME_VERIFIER != null) {
1697+
((HttpsURLConnection) connection).setHostnameVerifier(HOSTNAME_VERIFIER);
1698+
}
16481699
}
16491700

16501701
private Proxy createProxy() {
@@ -3351,58 +3402,6 @@ public HttpRequest form(final Map<?, ?> values, final String charset)
33513402
return this;
33523403
}
33533404

3354-
/**
3355-
* Configure HTTPS connection to trust only certain certificates
3356-
* <p>
3357-
* This method throws an exception if the current request is not a HTTPS request
3358-
*
3359-
* @return this request
3360-
* @throws HttpRequestException
3361-
*/
3362-
public HttpRequest pinToCerts() throws HttpRequestException {
3363-
final HttpURLConnection connection = getConnection();
3364-
if (connection instanceof HttpsURLConnection) {
3365-
((HttpsURLConnection) connection).setSSLSocketFactory(getPinnedFactory());
3366-
} else {
3367-
IOException e = new IOException("You must use a https url to use ssl pinning");
3368-
throw new HttpRequestException(e);
3369-
}
3370-
return this;
3371-
}
3372-
3373-
/**
3374-
* Configure HTTPS connection to trust all certificates
3375-
* <p>
3376-
* This method does nothing if the current request is not a HTTPS request
3377-
*
3378-
* @return this request
3379-
* @throws HttpRequestException
3380-
*/
3381-
public HttpRequest trustAllCerts() throws HttpRequestException {
3382-
final HttpURLConnection connection = getConnection();
3383-
if (connection instanceof HttpsURLConnection)
3384-
((HttpsURLConnection) connection)
3385-
.setSSLSocketFactory(getTrustedFactory());
3386-
return this;
3387-
}
3388-
3389-
/**
3390-
* Configure HTTPS connection to trust all hosts using a custom
3391-
* {@link HostnameVerifier} that always returns <code>true</code> for each
3392-
* host verified
3393-
* <p>
3394-
* This method does nothing if the current request is not a HTTPS request
3395-
*
3396-
* @return this request
3397-
*/
3398-
public HttpRequest trustAllHosts() {
3399-
final HttpURLConnection connection = getConnection();
3400-
if (connection instanceof HttpsURLConnection)
3401-
((HttpsURLConnection) connection)
3402-
.setHostnameVerifier(getTrustedVerifier());
3403-
return this;
3404-
}
3405-
34063405
/**
34073406
* Get the {@link URL} of this request's connection
34083407
*

src/android/com/synconset/cordovahttp/CordovaHttp.java

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@
3838
abstract class CordovaHttp {
3939
protected static final String TAG = "CordovaHTTP";
4040
protected static final String[] ACCEPTED_CHARSETS = new String[] { HttpRequest.CHARSET_UTF8, HttpRequest.CHARSET_LATIN1 };
41-
42-
private static AtomicBoolean sslPinning = new AtomicBoolean(false);
43-
private static AtomicBoolean acceptAllCerts = new AtomicBoolean(false);
44-
private static AtomicBoolean validateDomainName = new AtomicBoolean(true);
4541
private static AtomicBoolean disableRedirect = new AtomicBoolean(false);
4642

4743
private String urlString;
@@ -64,24 +60,6 @@ public CordovaHttp(String urlString, Object params, String serializerName, JSONO
6460
this.callbackContext = callbackContext;
6561
}
6662

67-
public static void enableSSLPinning(boolean enable) {
68-
sslPinning.set(enable);
69-
if (enable) {
70-
acceptAllCerts.set(false);
71-
}
72-
}
73-
74-
public static void acceptAllCerts(boolean accept) {
75-
acceptAllCerts.set(accept);
76-
if (accept) {
77-
sslPinning.set(false);
78-
}
79-
}
80-
81-
public static void validateDomainName(boolean accept) {
82-
validateDomainName.set(accept);
83-
}
84-
8563
public static void disableRedirect(boolean disable) {
8664
disableRedirect.set(disable);
8765
}
@@ -122,20 +100,6 @@ protected CallbackContext getCallbackContext() {
122100
return this.callbackContext;
123101
}
124102

125-
protected HttpRequest setupSecurity(HttpRequest request) {
126-
if (acceptAllCerts.get()) {
127-
request.trustAllCerts();
128-
}
129-
if (!validateDomainName.get()) {
130-
request.trustAllHosts();
131-
}
132-
if (sslPinning.get()) {
133-
request.pinToCerts();
134-
}
135-
136-
return request;
137-
}
138-
139103
protected HttpRequest setupRedirect(HttpRequest request) {
140104
if (disableRedirect.get()) {
141105
request.followRedirects(false);
@@ -222,7 +186,6 @@ protected HashMap<String, Object> getMapFromJSONObject(JSONObject object) throws
222186

223187
protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException {
224188
this.setupRedirect(request);
225-
this.setupSecurity(request);
226189

227190
request.readTimeout(this.getRequestTimeout());
228191
request.acceptCharset(ACCEPTED_CHARSETS);

0 commit comments

Comments
 (0)