diff --git a/build/checkstyle.xml b/build/checkstyle.xml
index b81e43399..492bca3b4 100644
--- a/build/checkstyle.xml
+++ b/build/checkstyle.xml
@@ -63,7 +63,7 @@
-
+
diff --git a/unirest/src/main/java/kong/unirest/Config.java b/unirest/src/main/java/kong/unirest/Config.java
index a64851e4a..16b36283d 100644
--- a/unirest/src/main/java/kong/unirest/Config.java
+++ b/unirest/src/main/java/kong/unirest/Config.java
@@ -79,6 +79,8 @@ public class Config {
private UniMetric metrics = new NoopMetric();
private long ttl = -1;
private SSLContext sslContext;
+ private String[] ciphers;
+ private String[] protocols;
private CompoundInterceptor interceptor = new CompoundInterceptor();
private HostnameVerifier hostnameVerifier;
private String defaultBaseUrl;
@@ -105,6 +107,8 @@ private void setDefaults() {
keystore = null;
keystorePassword = null;
sslContext = null;
+ ciphers = null;
+ protocols = null;
interceptor = new CompoundInterceptor();
this.objectMapper = Optional.of(new JsonObjectMapper());
@@ -256,6 +260,26 @@ public Config hostnameVerifier(HostnameVerifier value) {
return this;
}
+ /**
+ * Set a custom array of ciphers
+ * @param values the array of ciphers
+ * @return this config object
+ */
+ public Config ciphers(String... values) {
+ this.ciphers = values;
+ return this;
+ }
+
+ /**
+ * Set a custom array of protocols
+ * @param values the array of protocols
+ * @return this config object
+ */
+ public Config protocols(String... values) {
+ this.protocols = values;
+ return this;
+ }
+
private void verifySecurityConfig(Object thing) {
if(thing != null){
throw new UnirestConfigException("You may only configure a SSLContext OR a Keystore, but not both");
@@ -970,6 +994,20 @@ public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}
+ /**
+ * @return the ciphers for the SSL connection configuration
+ */
+ public String[] getCiphers() {
+ return ciphers;
+ }
+
+ /**
+ * @return the protocols for the SSL connection configuration
+ */
+ public String[] getProtocols() {
+ return protocols;
+ }
+
/**
* @return the default base URL
*/
diff --git a/unirest/src/main/java/kong/unirest/apache/SecurityConfig.java b/unirest/src/main/java/kong/unirest/apache/SecurityConfig.java
index a9027ce59..448b3118c 100644
--- a/unirest/src/main/java/kong/unirest/apache/SecurityConfig.java
+++ b/unirest/src/main/java/kong/unirest/apache/SecurityConfig.java
@@ -106,7 +106,7 @@ private Registry createDisabledSSLContext() throws Exce
private SSLConnectionSocketFactory getSocketFactory() {
if(sslSocketFactory == null) {
- sslSocketFactory = new SSLConnectionSocketFactory(createSslContext(), getHostnameVerifier());
+ sslSocketFactory = new SSLConnectionSocketFactory(createSslContext(), config.getProtocols(), config.getCiphers(), getHostnameVerifier());
}
return sslSocketFactory;
}
diff --git a/unirest/src/test/java/BehaviorTests/CertificateTests.java b/unirest/src/test/java/BehaviorTests/CertificateTests.java
index c652967b3..73eff61e4 100644
--- a/unirest/src/test/java/BehaviorTests/CertificateTests.java
+++ b/unirest/src/test/java/BehaviorTests/CertificateTests.java
@@ -25,8 +25,10 @@
package BehaviorTests;
+import kong.unirest.GetRequest;
import kong.unirest.TestUtil;
import kong.unirest.Unirest;
+import kong.unirest.UnirestException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
@@ -53,13 +55,14 @@
import java.security.KeyStore;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
@Disabled // dont normally run these because they depend on badssl.com
-public class CertificateTests extends BddTest {
+class CertificateTests extends BddTest {
@Test
- public void canDoClientCertificates() throws Exception {
+ void canDoClientCertificates() throws Exception {
Unirest.config().clientCertificateStore(readStore(), "badssl.com");
Unirest.get("https://client.badssl.com/")
@@ -70,7 +73,7 @@ public void canDoClientCertificates() throws Exception {
@Test
- public void canLoadKeyStoreByPath() {
+ void canLoadKeyStoreByPath() {
Unirest.config().clientCertificateStore("src/test/resources/certs/badssl.com-client.p12", "badssl.com");
Unirest.get("https://client.badssl.com/")
@@ -80,7 +83,7 @@ public void canLoadKeyStoreByPath() {
}
@Test
- public void loadWithSSLContext() throws Exception {
+ void loadWithSSLContext() throws Exception {
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
.build();
@@ -92,7 +95,75 @@ public void loadWithSSLContext() throws Exception {
}
@Test
- public void canSetHoestNameVerifyer() throws Exception {
+ void loadWithSSLContextAndProtocol() throws Exception {
+ SSLContext sslContext = SSLContexts.custom()
+ .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
+ .build();
+
+ Unirest.config().sslContext(sslContext).protocols("TLSv1.2");
+
+ int response = Unirest.get("https://client.badssl.com/").asEmpty().getStatus();
+ assertEquals(200, response);
+ }
+
+ @Test
+ void loadWithSSLContextAndCipher() throws Exception {
+ SSLContext sslContext = SSLContexts.custom()
+ .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
+ .build();
+
+ Unirest.config().sslContext(sslContext).ciphers("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384");
+
+ int response = Unirest.get("https://client.badssl.com/").asEmpty().getStatus();
+ assertEquals(200, response);
+ }
+
+ @Test
+ void loadWithSSLContextAndCipherAndProtocol() throws Exception {
+ SSLContext sslContext = SSLContexts.custom()
+ .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
+ .build();
+
+ Unirest.config()
+ .sslContext(sslContext)
+ .protocols("TLSv1.2")
+ .ciphers("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384");
+
+ int response = Unirest.get("https://client.badssl.com/").asEmpty().getStatus();
+ assertEquals(200, response);
+ }
+
+ @Test
+ void sslHandshakeFailsWhenServerIsReceivingAnUnsupportedCipher() throws Exception {
+ SSLContext sslContext = SSLContexts.custom()
+ .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
+ .build();
+
+ Unirest.config()
+ .sslContext(sslContext)
+ .protocols("TLSv1.2")
+ .ciphers("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
+
+ GetRequest request = Unirest.get("https://client.badssl.com/");
+ assertThrows(UnirestException.class, request::asEmpty);
+ }
+
+ @Test
+ void clientPreventsToUseUnsafeProtocol() throws Exception {
+ SSLContext sslContext = SSLContexts.custom()
+ .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
+ .build();
+
+ Unirest.config()
+ .sslContext(sslContext)
+ .protocols("SSLv3");
+
+ GetRequest request = Unirest.get("https://client.badssl.com/");
+ assertThrows(UnirestException.class, request::asEmpty);
+ }
+
+ @Test
+ void canSetHoestNameVerifyer() throws Exception {
Unirest.config().hostnameVerifier(new NoopHostnameVerifier());
int response = Unirest.get("https://badssl.com/").asEmpty().getStatus();
@@ -100,7 +171,7 @@ public void canSetHoestNameVerifyer() throws Exception {
}
@Test
- public void rawApacheClientCert() throws Exception {
+ void rawApacheClientCert() throws Exception {
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
.build();
@@ -117,7 +188,7 @@ public void rawApacheClientCert() throws Exception {
}
@Test
- public void rawApacheWithConnectionManager() throws Exception {
+ void rawApacheWithConnectionManager() throws Exception {
SSLContext sc = SSLContexts.custom()
.loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password
.build();
@@ -151,7 +222,7 @@ public void rawApacheWithConnectionManager() throws Exception {
}
@Test
- public void badName() {
+ void badName() {
fails("https://wrong.host.badssl.com/",
SSLPeerUnverifiedException.class,
"javax.net.ssl.SSLPeerUnverifiedException: " +
@@ -162,7 +233,7 @@ public void badName() {
}
@Test
- public void expired() {
+ void expired() {
fails("https://expired.badssl.com/",
SSLHandshakeException.class,
"javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: " +
@@ -172,7 +243,7 @@ public void expired() {
}
@Test
- public void selfSigned() {
+ void selfSigned() {
fails("https://self-signed.badssl.com/",
SSLHandshakeException.class,
"javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: " +
@@ -183,7 +254,7 @@ public void selfSigned() {
}
@Test
- public void badNameAsync() {
+ void badNameAsync() {
failsAsync("https://wrong.host.badssl.com/",
SSLPeerUnverifiedException.class,
"javax.net.ssl.SSLPeerUnverifiedException: " +
@@ -194,7 +265,7 @@ public void badNameAsync() {
}
@Test
- public void expiredAsync() {
+ void expiredAsync() {
failsAsync("https://expired.badssl.com/",
SSLHandshakeException.class,
"javax.net.ssl.SSLHandshakeException: General SSLEngine problem");
@@ -203,7 +274,7 @@ public void expiredAsync() {
}
@Test
- public void selfSignedAsync() {
+ void selfSignedAsync() {
failsAsync("https://self-signed.badssl.com/",
SSLHandshakeException.class,
"javax.net.ssl.SSLHandshakeException: General SSLEngine problem");