123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- /*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
- package org.sonarqube.ws.client;
-
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.net.HttpURLConnection;
- import java.net.Proxy;
- import java.security.GeneralSecurityException;
- import java.security.KeyStore;
- import java.security.KeyStoreException;
- import java.security.NoSuchAlgorithmException;
- import java.security.NoSuchProviderException;
- import java.security.UnrecoverableKeyException;
- import java.security.cert.CertificateException;
- import java.util.Arrays;
- import java.util.concurrent.TimeUnit;
- import javax.annotation.Nullable;
- import javax.net.ssl.KeyManager;
- import javax.net.ssl.KeyManagerFactory;
- import javax.net.ssl.SSLContext;
- import javax.net.ssl.SSLSocketFactory;
- import javax.net.ssl.TrustManager;
- import javax.net.ssl.TrustManagerFactory;
- import javax.net.ssl.X509TrustManager;
- import okhttp3.ConnectionSpec;
- import okhttp3.Credentials;
- import okhttp3.Interceptor;
- import okhttp3.OkHttpClient;
- import okhttp3.Request;
- import okhttp3.Response;
-
- import static java.nio.charset.StandardCharsets.UTF_8;
- import static java.util.Arrays.asList;
- import static org.sonarqube.ws.WsUtils.nullToEmpty;
-
- /**
- * Helper to build an instance of {@link okhttp3.OkHttpClient} that
- * correctly supports HTTPS and proxy authentication. It also handles
- * sending of User-Agent header.
- */
- public class OkHttpClientBuilder {
-
- private static final String NONE = "NONE";
- private static final String P11KEYSTORE = "PKCS11";
- private static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
-
- private String userAgent;
- private Proxy proxy;
- private String credentials;
- private String proxyLogin;
- private String proxyPassword;
- private Boolean followRedirects;
- private long connectTimeoutMs = -1;
- private long readTimeoutMs = -1;
- private SSLSocketFactory sslSocketFactory = null;
- private X509TrustManager sslTrustManager = null;
- private boolean acceptGzip = false;
-
- /**
- * Optional User-Agent. If set, then all the requests sent by the
- * {@link OkHttpClient} will include the header "User-Agent".
- */
- public OkHttpClientBuilder setUserAgent(@Nullable String s) {
- this.userAgent = s;
- return this;
- }
-
- /**
- * Optional SSL socket factory with which SSL sockets will be created to establish SSL connections.
- * If not set, a default SSL socket factory will be used, base d on the JVM's default key store.
- */
- public OkHttpClientBuilder setSSLSocketFactory(@Nullable SSLSocketFactory sslSocketFactory) {
- this.sslSocketFactory = sslSocketFactory;
- return this;
- }
-
- /**
- * Optional SSL trust manager used to validate certificates.
- * If not set, a default system trust manager will be used, based on the JVM's default truststore.
- */
- public OkHttpClientBuilder setTrustManager(@Nullable X509TrustManager sslTrustManager) {
- this.sslTrustManager = sslTrustManager;
- return this;
- }
-
- /**
- * Optional proxy. If set, then all the requests sent by the
- * {@link OkHttpClient} will reach the proxy. If not set,
- * then the system-wide proxy is used.
- */
- public OkHttpClientBuilder setProxy(@Nullable Proxy proxy) {
- this.proxy = proxy;
- return this;
- }
-
- /**
- * Login required for proxy authentication.
- */
- public OkHttpClientBuilder setProxyLogin(@Nullable String s) {
- this.proxyLogin = s;
- return this;
- }
-
- /**
- * This flag decides whether the client should accept GZIP encoding. Default is false.
- */
- public OkHttpClientBuilder acceptGzip(boolean acceptGzip) {
- this.acceptGzip = acceptGzip;
- return this;
- }
-
- /**
- * Password used for proxy authentication. It is ignored if
- * proxy login is not defined (see {@link #setProxyLogin(String)}).
- * It can be null or empty when login is defined.
- */
- public OkHttpClientBuilder setProxyPassword(@Nullable String s) {
- this.proxyPassword = s;
- return this;
- }
-
- /**
- * Sets the default connect timeout for new connections. A value of 0 means no timeout.
- * Default is defined by OkHttp (10 seconds in OkHttp 3.3).
- */
- public OkHttpClientBuilder setConnectTimeoutMs(long l) {
- if (l < 0) {
- throw new IllegalArgumentException("Connect timeout must be positive. Got " + l);
- }
- this.connectTimeoutMs = l;
- return this;
- }
-
- /**
- * Set credentials that will be passed on every request
- */
- public OkHttpClientBuilder setCredentials(String credentials) {
- this.credentials = credentials;
- return this;
- }
-
- /**
- * Sets the default read timeout for new connections. A value of 0 means no timeout.
- * Default is defined by OkHttp (10 seconds in OkHttp 3.3).
- */
- public OkHttpClientBuilder setReadTimeoutMs(long l) {
- if (l < 0) {
- throw new IllegalArgumentException("Read timeout must be positive. Got " + l);
- }
- this.readTimeoutMs = l;
- return this;
- }
-
- /**
- * Set if redirects should be followed or not.
- * Default is defined by OkHttp (true, follow redirects).
- */
- public OkHttpClientBuilder setFollowRedirects(Boolean followRedirects) {
- this.followRedirects = followRedirects;
- return this;
- }
-
- public OkHttpClient build() {
- OkHttpClient.Builder builder = new OkHttpClient.Builder();
- builder.proxy(proxy);
- if (connectTimeoutMs >= 0) {
- builder.connectTimeout(connectTimeoutMs, TimeUnit.MILLISECONDS);
- }
- if (readTimeoutMs >= 0) {
- builder.readTimeout(readTimeoutMs, TimeUnit.MILLISECONDS);
- }
- builder.addNetworkInterceptor(this::addHeaders);
- if(!acceptGzip) {
- builder.addNetworkInterceptor(new GzipRejectorInterceptor());
- }
- if (proxyLogin != null) {
- builder.proxyAuthenticator((route, response) -> {
- if (response.request().header(PROXY_AUTHORIZATION) != null) {
- // Give up, we've already attempted to authenticate.
- return null;
- }
- if (HttpURLConnection.HTTP_PROXY_AUTH == response.code()) {
- String credential = Credentials.basic(proxyLogin, nullToEmpty(proxyPassword), UTF_8);
- return response.request().newBuilder().header(PROXY_AUTHORIZATION, credential).build();
- }
- return null;
- });
- }
- if (followRedirects != null) {
- builder.followRedirects(followRedirects);
- builder.followSslRedirects(followRedirects);
- }
-
- ConnectionSpec tls = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
- .allEnabledTlsVersions()
- .allEnabledCipherSuites()
- .supportsTlsExtensions(true)
- .build();
- builder.connectionSpecs(asList(tls, ConnectionSpec.CLEARTEXT));
-
- X509TrustManager trustManager = sslTrustManager != null ? sslTrustManager : systemDefaultTrustManager();
- SSLSocketFactory sslFactory = sslSocketFactory != null ? sslSocketFactory : systemDefaultSslSocketFactory(trustManager);
- builder.sslSocketFactory(sslFactory, trustManager);
-
- return builder.build();
- }
-
- private Response addHeaders(Interceptor.Chain chain) throws IOException {
- Request.Builder newRequest = chain.request().newBuilder();
- if (userAgent != null) {
- newRequest.header("User-Agent", userAgent);
- }
- if (credentials != null) {
- newRequest.header("Authorization", credentials);
- }
- return chain.proceed(newRequest.build());
- }
-
- private static X509TrustManager systemDefaultTrustManager() {
- try {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init((KeyStore) null);
- TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
- if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
- throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
- }
- return (X509TrustManager) trustManagers[0];
- } catch (GeneralSecurityException e) {
- // The system has no TLS. Just give up.
- throw new AssertionError(e);
- }
- }
-
- private static SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
- KeyManager[] defaultKeyManager;
- try {
- defaultKeyManager = getDefaultKeyManager();
- } catch (Exception e) {
- throw new IllegalStateException("Unable to get default key manager", e);
- }
- try {
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(defaultKeyManager, new TrustManager[] {trustManager}, null);
- return sslContext.getSocketFactory();
- } catch (GeneralSecurityException e) {
- // The system has no TLS. Just give up.
- throw new AssertionError(e);
- }
- }
-
- private static void logDebug(String msg) {
- boolean debugEnabled = "all".equals(System.getProperty("javax.net.debug"));
- if (debugEnabled) {
- System.out.println(msg);
- }
- }
-
- /**
- * Inspired from sun.security.ssl.SSLContextImpl#getDefaultKeyManager()
- */
- private static synchronized KeyManager[] getDefaultKeyManager() throws KeyStoreException, NoSuchProviderException,
- IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
- final String defaultKeyStore = System.getProperty("javax.net.ssl.keyStore", "");
- String defaultKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
- String defaultKeyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider", "");
-
- logDebug("keyStore is : " + defaultKeyStore);
- logDebug("keyStore type is : " + defaultKeyStoreType);
- logDebug("keyStore provider is : " + defaultKeyStoreProvider);
-
- if (P11KEYSTORE.equals(defaultKeyStoreType) && !NONE.equals(defaultKeyStore)) {
- throw new IllegalArgumentException("if keyStoreType is " + P11KEYSTORE + ", then keyStore must be " + NONE);
- }
-
- KeyStore ks = null;
- String defaultKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
- char[] passwd = defaultKeyStorePassword.isEmpty() ? null : defaultKeyStorePassword.toCharArray();
-
- // Try to initialize key store.
- if (!defaultKeyStoreType.isEmpty()) {
- logDebug("init keystore");
- if (defaultKeyStoreProvider.isEmpty()) {
- ks = KeyStore.getInstance(defaultKeyStoreType);
- } else {
- ks = KeyStore.getInstance(defaultKeyStoreType, defaultKeyStoreProvider);
- }
- if (!defaultKeyStore.isEmpty() && !NONE.equals(defaultKeyStore)) {
- try (FileInputStream fs = new FileInputStream(defaultKeyStore)) {
- ks.load(fs, passwd);
- }
- } else {
- ks.load(null, passwd);
- }
- }
-
- // Try to initialize key manager.
- logDebug("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm());
- KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
-
- if (P11KEYSTORE.equals(defaultKeyStoreType)) {
- // do not pass key passwd if using token
- kmf.init(ks, null);
- } else {
- kmf.init(ks, passwd);
- }
-
- return kmf.getKeyManagers();
- }
-
- }
|