You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

OkHttpClientBuilder.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonarqube.ws.client;
  21. import java.io.FileInputStream;
  22. import java.io.IOException;
  23. import java.net.HttpURLConnection;
  24. import java.net.Proxy;
  25. import java.security.GeneralSecurityException;
  26. import java.security.KeyStore;
  27. import java.security.KeyStoreException;
  28. import java.security.NoSuchAlgorithmException;
  29. import java.security.NoSuchProviderException;
  30. import java.security.UnrecoverableKeyException;
  31. import java.security.cert.CertificateException;
  32. import java.util.Arrays;
  33. import java.util.concurrent.TimeUnit;
  34. import javax.annotation.Nullable;
  35. import javax.net.ssl.KeyManager;
  36. import javax.net.ssl.KeyManagerFactory;
  37. import javax.net.ssl.SSLContext;
  38. import javax.net.ssl.SSLSocketFactory;
  39. import javax.net.ssl.TrustManager;
  40. import javax.net.ssl.TrustManagerFactory;
  41. import javax.net.ssl.X509TrustManager;
  42. import okhttp3.ConnectionSpec;
  43. import okhttp3.Credentials;
  44. import okhttp3.Interceptor;
  45. import okhttp3.OkHttpClient;
  46. import okhttp3.Request;
  47. import okhttp3.Response;
  48. import static java.nio.charset.StandardCharsets.UTF_8;
  49. import static java.util.Arrays.asList;
  50. import static org.sonarqube.ws.WsUtils.nullToEmpty;
  51. /**
  52. * Helper to build an instance of {@link okhttp3.OkHttpClient} that
  53. * correctly supports HTTPS and proxy authentication. It also handles
  54. * sending of User-Agent header.
  55. */
  56. public class OkHttpClientBuilder {
  57. private static final String NONE = "NONE";
  58. private static final String P11KEYSTORE = "PKCS11";
  59. private static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
  60. private String userAgent;
  61. private Proxy proxy;
  62. private String credentials;
  63. private String proxyLogin;
  64. private String proxyPassword;
  65. private Boolean followRedirects;
  66. private long connectTimeoutMs = -1;
  67. private long readTimeoutMs = -1;
  68. private SSLSocketFactory sslSocketFactory = null;
  69. private X509TrustManager sslTrustManager = null;
  70. private boolean acceptGzip = false;
  71. /**
  72. * Optional User-Agent. If set, then all the requests sent by the
  73. * {@link OkHttpClient} will include the header "User-Agent".
  74. */
  75. public OkHttpClientBuilder setUserAgent(@Nullable String s) {
  76. this.userAgent = s;
  77. return this;
  78. }
  79. /**
  80. * Optional SSL socket factory with which SSL sockets will be created to establish SSL connections.
  81. * If not set, a default SSL socket factory will be used, base d on the JVM's default key store.
  82. */
  83. public OkHttpClientBuilder setSSLSocketFactory(@Nullable SSLSocketFactory sslSocketFactory) {
  84. this.sslSocketFactory = sslSocketFactory;
  85. return this;
  86. }
  87. /**
  88. * Optional SSL trust manager used to validate certificates.
  89. * If not set, a default system trust manager will be used, based on the JVM's default truststore.
  90. */
  91. public OkHttpClientBuilder setTrustManager(@Nullable X509TrustManager sslTrustManager) {
  92. this.sslTrustManager = sslTrustManager;
  93. return this;
  94. }
  95. /**
  96. * Optional proxy. If set, then all the requests sent by the
  97. * {@link OkHttpClient} will reach the proxy. If not set,
  98. * then the system-wide proxy is used.
  99. */
  100. public OkHttpClientBuilder setProxy(@Nullable Proxy proxy) {
  101. this.proxy = proxy;
  102. return this;
  103. }
  104. /**
  105. * Login required for proxy authentication.
  106. */
  107. public OkHttpClientBuilder setProxyLogin(@Nullable String s) {
  108. this.proxyLogin = s;
  109. return this;
  110. }
  111. /**
  112. * This flag decides whether the client should accept GZIP encoding. Default is false.
  113. */
  114. public OkHttpClientBuilder acceptGzip(boolean acceptGzip) {
  115. this.acceptGzip = acceptGzip;
  116. return this;
  117. }
  118. /**
  119. * Password used for proxy authentication. It is ignored if
  120. * proxy login is not defined (see {@link #setProxyLogin(String)}).
  121. * It can be null or empty when login is defined.
  122. */
  123. public OkHttpClientBuilder setProxyPassword(@Nullable String s) {
  124. this.proxyPassword = s;
  125. return this;
  126. }
  127. /**
  128. * Sets the default connect timeout for new connections. A value of 0 means no timeout.
  129. * Default is defined by OkHttp (10 seconds in OkHttp 3.3).
  130. */
  131. public OkHttpClientBuilder setConnectTimeoutMs(long l) {
  132. if (l < 0) {
  133. throw new IllegalArgumentException("Connect timeout must be positive. Got " + l);
  134. }
  135. this.connectTimeoutMs = l;
  136. return this;
  137. }
  138. /**
  139. * Set credentials that will be passed on every request
  140. */
  141. public OkHttpClientBuilder setCredentials(String credentials) {
  142. this.credentials = credentials;
  143. return this;
  144. }
  145. /**
  146. * Sets the default read timeout for new connections. A value of 0 means no timeout.
  147. * Default is defined by OkHttp (10 seconds in OkHttp 3.3).
  148. */
  149. public OkHttpClientBuilder setReadTimeoutMs(long l) {
  150. if (l < 0) {
  151. throw new IllegalArgumentException("Read timeout must be positive. Got " + l);
  152. }
  153. this.readTimeoutMs = l;
  154. return this;
  155. }
  156. /**
  157. * Set if redirects should be followed or not.
  158. * Default is defined by OkHttp (true, follow redirects).
  159. */
  160. public OkHttpClientBuilder setFollowRedirects(Boolean followRedirects) {
  161. this.followRedirects = followRedirects;
  162. return this;
  163. }
  164. public OkHttpClient build() {
  165. OkHttpClient.Builder builder = new OkHttpClient.Builder();
  166. builder.proxy(proxy);
  167. if (connectTimeoutMs >= 0) {
  168. builder.connectTimeout(connectTimeoutMs, TimeUnit.MILLISECONDS);
  169. }
  170. if (readTimeoutMs >= 0) {
  171. builder.readTimeout(readTimeoutMs, TimeUnit.MILLISECONDS);
  172. }
  173. builder.addNetworkInterceptor(this::addHeaders);
  174. if(!acceptGzip) {
  175. builder.addNetworkInterceptor(new GzipRejectorInterceptor());
  176. }
  177. if (proxyLogin != null) {
  178. builder.proxyAuthenticator((route, response) -> {
  179. if (response.request().header(PROXY_AUTHORIZATION) != null) {
  180. // Give up, we've already attempted to authenticate.
  181. return null;
  182. }
  183. if (HttpURLConnection.HTTP_PROXY_AUTH == response.code()) {
  184. String credential = Credentials.basic(proxyLogin, nullToEmpty(proxyPassword), UTF_8);
  185. return response.request().newBuilder().header(PROXY_AUTHORIZATION, credential).build();
  186. }
  187. return null;
  188. });
  189. }
  190. if (followRedirects != null) {
  191. builder.followRedirects(followRedirects);
  192. builder.followSslRedirects(followRedirects);
  193. }
  194. ConnectionSpec tls = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
  195. .allEnabledTlsVersions()
  196. .allEnabledCipherSuites()
  197. .supportsTlsExtensions(true)
  198. .build();
  199. builder.connectionSpecs(asList(tls, ConnectionSpec.CLEARTEXT));
  200. X509TrustManager trustManager = sslTrustManager != null ? sslTrustManager : systemDefaultTrustManager();
  201. SSLSocketFactory sslFactory = sslSocketFactory != null ? sslSocketFactory : systemDefaultSslSocketFactory(trustManager);
  202. builder.sslSocketFactory(sslFactory, trustManager);
  203. return builder.build();
  204. }
  205. private Response addHeaders(Interceptor.Chain chain) throws IOException {
  206. Request.Builder newRequest = chain.request().newBuilder();
  207. if (userAgent != null) {
  208. newRequest.header("User-Agent", userAgent);
  209. }
  210. if (credentials != null) {
  211. newRequest.header("Authorization", credentials);
  212. }
  213. return chain.proceed(newRequest.build());
  214. }
  215. private static X509TrustManager systemDefaultTrustManager() {
  216. try {
  217. TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  218. trustManagerFactory.init((KeyStore) null);
  219. TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
  220. if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
  221. throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
  222. }
  223. return (X509TrustManager) trustManagers[0];
  224. } catch (GeneralSecurityException e) {
  225. // The system has no TLS. Just give up.
  226. throw new AssertionError(e);
  227. }
  228. }
  229. private static SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
  230. KeyManager[] defaultKeyManager;
  231. try {
  232. defaultKeyManager = getDefaultKeyManager();
  233. } catch (Exception e) {
  234. throw new IllegalStateException("Unable to get default key manager", e);
  235. }
  236. try {
  237. SSLContext sslContext = SSLContext.getInstance("TLS");
  238. sslContext.init(defaultKeyManager, new TrustManager[] {trustManager}, null);
  239. return sslContext.getSocketFactory();
  240. } catch (GeneralSecurityException e) {
  241. // The system has no TLS. Just give up.
  242. throw new AssertionError(e);
  243. }
  244. }
  245. private static void logDebug(String msg) {
  246. boolean debugEnabled = "all".equals(System.getProperty("javax.net.debug"));
  247. if (debugEnabled) {
  248. System.out.println(msg);
  249. }
  250. }
  251. /**
  252. * Inspired from sun.security.ssl.SSLContextImpl#getDefaultKeyManager()
  253. */
  254. private static synchronized KeyManager[] getDefaultKeyManager() throws KeyStoreException, NoSuchProviderException,
  255. IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
  256. final String defaultKeyStore = System.getProperty("javax.net.ssl.keyStore", "");
  257. String defaultKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
  258. String defaultKeyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider", "");
  259. logDebug("keyStore is : " + defaultKeyStore);
  260. logDebug("keyStore type is : " + defaultKeyStoreType);
  261. logDebug("keyStore provider is : " + defaultKeyStoreProvider);
  262. if (P11KEYSTORE.equals(defaultKeyStoreType) && !NONE.equals(defaultKeyStore)) {
  263. throw new IllegalArgumentException("if keyStoreType is " + P11KEYSTORE + ", then keyStore must be " + NONE);
  264. }
  265. KeyStore ks = null;
  266. String defaultKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
  267. char[] passwd = defaultKeyStorePassword.isEmpty() ? null : defaultKeyStorePassword.toCharArray();
  268. // Try to initialize key store.
  269. if (!defaultKeyStoreType.isEmpty()) {
  270. logDebug("init keystore");
  271. if (defaultKeyStoreProvider.isEmpty()) {
  272. ks = KeyStore.getInstance(defaultKeyStoreType);
  273. } else {
  274. ks = KeyStore.getInstance(defaultKeyStoreType, defaultKeyStoreProvider);
  275. }
  276. if (!defaultKeyStore.isEmpty() && !NONE.equals(defaultKeyStore)) {
  277. try (FileInputStream fs = new FileInputStream(defaultKeyStore)) {
  278. ks.load(fs, passwd);
  279. }
  280. } else {
  281. ks.load(null, passwd);
  282. }
  283. }
  284. // Try to initialize key manager.
  285. logDebug("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm());
  286. KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  287. if (P11KEYSTORE.equals(defaultKeyStoreType)) {
  288. // do not pass key passwd if using token
  289. kmf.init(ks, null);
  290. } else {
  291. kmf.init(ks, passwd);
  292. }
  293. return kmf.getKeyManagers();
  294. }
  295. }