Skip to content

Commit e7ef2df

Browse files
committed
SCANNERAPI-179 Upgrade OkHttpIO to 3.11.0
Recreation of certificates to make them works
1 parent 33a903a commit e7ef2df

38 files changed

Lines changed: 1159 additions & 249 deletions

api/src/main/java/org/sonarsource/scanner/api/internal/OkHttpClientFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import okhttp3.ConnectionSpec;
4141
import okhttp3.Credentials;
4242
import okhttp3.OkHttpClient;
43+
import okhttp3.internal.tls.OkHostnameVerifier;
4344
import org.sonarsource.scanner.api.internal.cache.Logger;
4445

4546
import static java.util.Arrays.asList;
@@ -68,8 +69,10 @@ static OkHttpClient create(Logger logger) {
6869
.supportsTlsExtensions(true)
6970
.build();
7071
okHttpClientBuilder.connectionSpecs(asList(tls, ConnectionSpec.CLEARTEXT));
72+
7173
X509TrustManager systemDefaultTrustManager = systemDefaultTrustManager();
7274
okHttpClientBuilder.sslSocketFactory(systemDefaultSslSocketFactory(systemDefaultTrustManager, logger), systemDefaultTrustManager);
75+
okHttpClientBuilder.hostnameVerifier(OkHostnameVerifier.INSTANCE);
7376

7477
// OkHttp detect 'http.proxyHost' java property, but credentials should be filled
7578
final String proxyUser = System.getProperty("http.proxyUser", "");
@@ -148,6 +151,8 @@ private static synchronized KeyManager[] getDefaultKeyManager(Logger logger) {
148151
logger.debug("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm());
149152
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
150153

154+
// FIXME : The password for opening keys inside the JKS is not mandatory to be the same
155+
// as opening the JKS KeyStore
151156
if (P11KEYSTORE.equals(defaultKeyStoreType)) {
152157
// do not pass key passwd if using token
153158
kmf.init(ks, null);

api/src/test/java/org/sonarsource/scanner/api/internal/OkHttpClientFactoryTest.java

Lines changed: 147 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,58 @@
1919
*/
2020
package org.sonarsource.scanner.api.internal;
2121

22-
import okhttp3.ConnectionSpec;
23-
import okhttp3.OkHttpClient;
22+
import java.io.FileInputStream;
23+
import java.io.IOException;
24+
import java.net.URISyntaxException;
25+
import java.nio.file.Path;
26+
import java.nio.file.Paths;
27+
import java.security.KeyStore;
28+
import java.security.SecureRandom;
2429
import java.util.List;
30+
import javax.net.ssl.KeyManagerFactory;
31+
import javax.net.ssl.SSLContext;
32+
import javax.net.ssl.SSLHandshakeException;
2533
import javax.net.ssl.SSLSocketFactory;
34+
import javax.net.ssl.TrustManagerFactory;
35+
import okhttp3.ConnectionSpec;
36+
import okhttp3.OkHttpClient;
37+
import okhttp3.Request;
38+
import okhttp3.Response;
39+
import okhttp3.mockwebserver.Dispatcher;
40+
import okhttp3.mockwebserver.MockResponse;
41+
import okhttp3.mockwebserver.MockWebServer;
42+
import okhttp3.mockwebserver.RecordedRequest;
43+
import org.junit.Rule;
2644
import org.junit.Test;
45+
import org.junit.experimental.theories.DataPoint;
46+
import org.junit.experimental.theories.Theories;
47+
import org.junit.experimental.theories.Theory;
48+
import org.junit.rules.ExpectedException;
49+
import org.junit.runner.RunWith;
2750
import org.sonarsource.scanner.api.internal.cache.Logger;
2851

52+
import static java.lang.String.format;
2953
import static org.assertj.core.api.Assertions.assertThat;
54+
import static org.assertj.core.api.Assertions.fail;
3055
import static org.mockito.Mockito.mock;
3156

57+
@RunWith(Theories.class)
3258
public class OkHttpClientFactoryTest {
3359

60+
@DataPoint
61+
public static final String KEYSTORE_CLIENT_WITH_CA = "/client-with-ca.jks";
62+
@DataPoint
63+
public static final String KEYSTORE_CLIENT_WITH_CERTIFICATE = "/client-with-certificate.jks";
64+
65+
private static final String KEYSTORE_PASSWORD = "abcdef";
66+
private static final String KEYSTORE_FILE = "/server.jks";
67+
private static final Logger logger = mock(Logger.class);
68+
69+
@Rule
70+
public ExpectedException expectedException = ExpectedException.none();
71+
3472
@Test
3573
public void support_tls_versions_of_java8() {
36-
Logger logger = mock(Logger.class);
3774
OkHttpClient underTest = OkHttpClientFactory.create(logger);
3875

3976
assertTlsAndClearTextSpecifications(underTest);
@@ -52,4 +89,111 @@ private void assertTlsAndClearTextSpecifications(OkHttpClient client) {
5289
assertThat(connectionSpecs.get(1).tlsVersions()).isNull();
5390
assertThat(connectionSpecs.get(1).isTls()).isFalse();
5491
}
92+
93+
@Test
94+
public void test_with_external_http_server() throws IOException {
95+
Response response = call("http://www.google.com");
96+
assertThat(response.code()).isEqualTo(200);
97+
assertThat(response.body().string()).contains("doctype html");
98+
}
99+
100+
@Test
101+
public void test_with_external_https_server_with_correct_certificate() throws IOException {
102+
Response response = call("https://www.google.com");
103+
assertThat(response.code()).isEqualTo(200);
104+
assertThat(response.body().string()).contains("doctype html");
105+
}
106+
107+
@Theory
108+
public void when_overriding_truststore_known_websites_are_failing(String clientKeyStore) throws IOException, URISyntaxException {
109+
try {
110+
Path clientTruststore = Paths.get(getClass().getResource(clientKeyStore).toURI()).toAbsolutePath();
111+
System.setProperty("javax.net.ssl.trustStore", clientTruststore.toString());
112+
System.setProperty("javax.net.ssl.trustStorePassword", KEYSTORE_PASSWORD);
113+
114+
expectedException.expect(SSLHandshakeException.class);
115+
call("https://www.google.com");
116+
} finally {
117+
// Ensure to not keeping this property for other tests
118+
System.clearProperty("javax.net.ssl.trustStore");
119+
System.clearProperty("javax.net.ssl.trustStorePassword");
120+
}
121+
}
122+
123+
@Theory
124+
public void test_with_custom_https_server(String clientKeyStore) throws Exception {
125+
try (MockWebServer server = buildTLSServer()) {
126+
String url = format("https://localhost:%d/", server.getPort());
127+
128+
// First test without any truststore is expecting to fail
129+
try {
130+
call(url);
131+
fail("Must have failed with an IOException");
132+
} catch (IOException e) {
133+
assertThat(e).hasMessageContaining("unable to find valid certification path to requested target");
134+
}
135+
136+
// Add the truststore
137+
Path clientTruststore = Paths.get(getClass().getResource(clientKeyStore).toURI()).toAbsolutePath();
138+
System.setProperty("javax.net.ssl.trustStore", clientTruststore.toString());
139+
System.setProperty("javax.net.ssl.trustStorePassword", KEYSTORE_PASSWORD);
140+
141+
Response response = call(url);
142+
assertThat(response.code()).isEqualTo(200);
143+
assertThat(response.body().string()).contains("OK");
144+
} finally {
145+
// Ensure to not keeping this property for other tests
146+
System.clearProperty("javax.net.ssl.trustStore");
147+
System.clearProperty("javax.net.ssl.trustStorePassword");
148+
}
149+
}
150+
151+
private static Response call(String url) throws IOException {
152+
return OkHttpClientFactory.create(logger).newCall(
153+
new Request.Builder()
154+
.url(url)
155+
.get()
156+
.build())
157+
.execute();
158+
}
159+
160+
/**
161+
* Build a MockWebServer with a dispatcher always sending 200 response with OK
162+
* This webserver will use respond only to https protocol
163+
*/
164+
private MockWebServer buildTLSServer() throws Exception {
165+
MockWebServer server = new MockWebServer();
166+
server.setDispatcher(new Dispatcher() {
167+
@Override
168+
public MockResponse dispatch(RecordedRequest request) {
169+
return new MockResponse().setResponseCode(200).setBody("OK");
170+
}
171+
});
172+
173+
// JKS file storing the private key and TLS certificate
174+
Path serverCertificate = Paths.get(getClass().getResource(KEYSTORE_FILE).toURI()).toAbsolutePath();
175+
176+
// Load the KeyStore
177+
KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
178+
FileInputStream stream = new FileInputStream(serverCertificate.toFile());
179+
serverKeyStore.load(stream, KEYSTORE_PASSWORD.toCharArray());
180+
181+
// Load the KeyManager from the KeyStore
182+
String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
183+
KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);
184+
kmf.init(serverKeyStore, "".toCharArray());
185+
186+
// Add the "Keys" (ie. private key and TLS certificate to the TrustManager
187+
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(kmfAlgorithm);
188+
trustManagerFactory.init(serverKeyStore);
189+
190+
// Create the SocketFactory using the TrustManager so the private key and certificate
191+
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
192+
sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
193+
194+
// Let's use it for the WebServer
195+
server.useHttps(sslContext.getSocketFactory(), false);
196+
197+
return server;
198+
}
55199
}

api/src/test/resources/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Those files are copied from its/it-test/src/test/resources/SSLTest.
2+
Symlink are not used because they will make tests failing.
1.5 KB
Binary file not shown.
1.44 KB
Binary file not shown.

api/src/test/resources/client.jks

3.77 KB
Binary file not shown.
1.49 KB
Binary file not shown.

api/src/test/resources/server.jks

3.83 KB
Binary file not shown.

its/it-tests/src/test/java/com/sonar/scanner/api/it/SSLTest.java

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,29 @@
4747
import org.junit.Before;
4848
import org.junit.ClassRule;
4949
import org.junit.Test;
50+
import org.junit.experimental.theories.DataPoint;
51+
import org.junit.experimental.theories.Theories;
52+
import org.junit.experimental.theories.Theory;
53+
import org.junit.runner.RunWith;
5054

5155
import static org.assertj.core.api.Assertions.assertThat;
5256

57+
@RunWith(Theories.class)
5358
public class SSLTest {
5459

55-
private static final String CLIENT_KEYSTORE = "/SSLTest/clientkeystore.jks";
56-
private static final String CLIENT_KEYSTORE_PWD = "clientp12pwd";
60+
private static final String JKS_PASSWORD = "abcdef";
5761

58-
private static final String CLIENT_TRUSTSTORE = "/SSLTest/clienttruststore.jks";
59-
private static final String CLIENT_TRUSTSTORE_PWD = "clienttruststorepwd";
62+
// This truststore contains only the CA used to sign the server certificate
63+
@DataPoint
64+
public static final String CLIENT_TRUSTSTORE_WITH_CA = "/SSLTest/client-with-ca.jks";
6065

61-
private static final String SERVER_TRUSTSTORE = "/SSLTest/servertruststore.jks";
62-
private static final String SERVER_TRUSTSTORE_PWD = "servertruststorepwd";
66+
// This truststore contains only the server certificate
67+
@DataPoint
68+
public static final String CLIENT_TRUSTSTORE_WITH_CERTIFICATE = "/SSLTest/client-with-certificate.jks";
6369

64-
private static final String SERVER_KEYSTORE = "/SSLTest/serverkeystore.jks";
65-
private static final String SERVER_KEYSTORE_PWD = "serverkeystorepwd";
70+
private static final String SERVER_TRUSTSTORE = "/SSLTest/server-with-client-ca.jks";
71+
private static final String SERVER_KEYSTORE = "/SSLTest/server.jks";
72+
private static final String CLIENT_KEYSTORE = "/SSLTest/client.jks";
6673

6774
private static Server server;
6875
private static int httpsPort;
@@ -109,18 +116,19 @@ private static void startSSLTransparentReverseProxy(boolean requireClientAuth) t
109116
server.addConnector(http);
110117

111118
Path serverKeyStore = Paths.get(SSLTest.class.getResource(SERVER_KEYSTORE).toURI()).toAbsolutePath();
112-
String serverKeyPassword = "serverp12pwd";
113-
Path serverTrustStore = Paths.get(SSLTest.class.getResource(SERVER_TRUSTSTORE).toURI()).toAbsolutePath();
114119
assertThat(serverKeyStore).exists();
115-
assertThat(serverTrustStore).exists();
116120

117121
// SSL Context Factory
118122
SslContextFactory sslContextFactory = new SslContextFactory();
119123
sslContextFactory.setKeyStorePath(serverKeyStore.toString());
120-
sslContextFactory.setKeyStorePassword(SERVER_KEYSTORE_PWD);
121-
sslContextFactory.setKeyManagerPassword(serverKeyPassword);
122-
sslContextFactory.setTrustStorePath(serverTrustStore.toString());
123-
sslContextFactory.setTrustStorePassword(SERVER_TRUSTSTORE_PWD);
124+
sslContextFactory.setKeyStorePassword(JKS_PASSWORD);
125+
sslContextFactory.setKeyManagerPassword("");
126+
if ( requireClientAuth) {
127+
Path serverTrustStore = Paths.get(SSLTest.class.getResource(SERVER_TRUSTSTORE).toURI()).toAbsolutePath();
128+
sslContextFactory.setTrustStorePath(serverTrustStore.toString());
129+
assertThat(serverTrustStore).exists();
130+
sslContextFactory.setTrustStorePassword(JKS_PASSWORD);
131+
}
124132
sslContextFactory.setNeedClientAuth(requireClientAuth);
125133
sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA",
126134
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
@@ -165,41 +173,67 @@ public void simple_analysis_with_server_and_client_certificate() throws Exceptio
165173
assertThat(buildResult.getLastStatus()).isNotEqualTo(0);
166174
assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
167175

168-
Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE).toURI()).toAbsolutePath();
176+
Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE_WITH_CA).toURI()).toAbsolutePath();
169177
assertThat(clientTruststore).exists();
170178
Path clientKeystore = Paths.get(SSLTest.class.getResource(CLIENT_KEYSTORE).toURI()).toAbsolutePath();
171179
assertThat(clientKeystore).exists();
172180

173181
Map<String, String> params = new HashMap<>();
182+
// In the truststore we have the CA allowing to connect to local TLS server
174183
params.put("javax.net.ssl.trustStore", clientTruststore.toString());
175-
params.put("javax.net.ssl.trustStorePassword", CLIENT_TRUSTSTORE_PWD);
184+
params.put("javax.net.ssl.trustStorePassword", JKS_PASSWORD);
185+
// The KeyStore is storing the certificate to identify the user
176186
params.put("javax.net.ssl.keyStore", clientKeystore.toString());
177-
params.put("javax.net.ssl.keyStorePassword", CLIENT_KEYSTORE_PWD);
187+
params.put("javax.net.ssl.keyStorePassword", JKS_PASSWORD);
178188

179189
buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params);
180190
assertThat(buildResult.getLastStatus()).isEqualTo(0);
181191
}
182192

193+
@Test
194+
public void simple_analysis_with_server_and_without_client_certificate_is_failing() throws Exception {
195+
startSSLTransparentReverseProxy(true);
196+
SimpleScanner scanner = new SimpleScanner();
197+
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort);
198+
199+
assertThat(buildResult.getLastStatus()).isNotEqualTo(0);
200+
assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
201+
202+
Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE_WITH_CA).toURI()).toAbsolutePath();
203+
assertThat(clientTruststore).exists();
204+
Path clientKeystore = Paths.get(SSLTest.class.getResource(CLIENT_KEYSTORE).toURI()).toAbsolutePath();
205+
assertThat(clientKeystore).exists();
206+
207+
Map<String, String> params = new HashMap<>();
208+
// In the truststore we have the CA allowing to connect to local TLS server
209+
params.put("javax.net.ssl.trustStore", clientTruststore.toString());
210+
params.put("javax.net.ssl.trustStorePassword", JKS_PASSWORD);
211+
// Voluntary missing client keystore
212+
213+
buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params);
214+
assertThat(buildResult.getLastStatus()).isEqualTo(1);
215+
assertThat(buildResult.getLogs()).contains("bad_certificate");
216+
}
217+
183218
private static Path project(String projectName) {
184219
return Paths.get("..", "projects", projectName);
185220
}
186221

187-
@Test
188-
public void simple_analysis_with_server_certificate() throws Exception {
222+
@Theory
223+
public void simple_analysis_with_server_certificate(String clientTrustStore) throws Exception {
189224
startSSLTransparentReverseProxy(false);
190225
SimpleScanner scanner = new SimpleScanner();
191226

192227
BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort);
193228
assertThat(buildResult.getLastStatus()).isNotEqualTo(0);
194229
assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
195230

196-
Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE).toURI()).toAbsolutePath();
197-
String truststorePassword = CLIENT_TRUSTSTORE_PWD;
231+
Path clientTruststore = Paths.get(SSLTest.class.getResource(clientTrustStore).toURI()).toAbsolutePath();
198232
assertThat(clientTruststore).exists();
199233

200234
Map<String, String> params = new HashMap<>();
201235
params.put("javax.net.ssl.trustStore", clientTruststore.toString());
202-
params.put("javax.net.ssl.trustStorePassword", truststorePassword);
236+
params.put("javax.net.ssl.trustStorePassword", JKS_PASSWORD);
203237

204238
buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params);
205239
assertThat(buildResult.getLastStatus()).isEqualTo(0);

0 commit comments

Comments
 (0)