Skip to content

Commit

Permalink
Merge pull request #381 from Hakky54/main
Browse files Browse the repository at this point in the history
Support for custom ciphers and protocols for https requests
  • Loading branch information
ryber authored Nov 4, 2020
2 parents 2eac583 + c700a20 commit e71dd10
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 15 deletions.
2 changes: 1 addition & 1 deletion build/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="FileLength">
<!-- This can't ignore javadocs so it's kind of stupid -->
<property name="max" value="1000"/>
<property name="max" value="1100"/>
</module>

<!-- Checks for whitespace -->
Expand Down
38 changes: 38 additions & 0 deletions unirest/src/main/java/kong/unirest/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private Registry<ConnectionSocketFactory> 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;
}
Expand Down
97 changes: 84 additions & 13 deletions unirest/src/test/java/BehaviorTests/CertificateTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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/")
Expand All @@ -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/")
Expand All @@ -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();
Expand All @@ -92,15 +95,83 @@ 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();
assertEquals(200, response);
}

@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();
Expand All @@ -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();
Expand Down Expand Up @@ -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: " +
Expand All @@ -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: " +
Expand All @@ -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: " +
Expand All @@ -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: " +
Expand All @@ -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");
Expand All @@ -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");
Expand Down

0 comments on commit e71dd10

Please sign in to comment.