All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.trino.client.OkHttpUtil Maven / Gradle / Ivy

There is a newer version: 464
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.trino.client;

import com.google.common.base.CharMatcher;
import com.google.common.base.StandardSystemProperty;
import com.google.common.collect.ImmutableList;
import com.google.common.net.HostAndPort;
import io.trino.client.auth.kerberos.DelegatedConstrainedContextProvider;
import io.trino.client.auth.kerberos.DelegatedUnconstrainedContextProvider;
import io.trino.client.auth.kerberos.GSSContextProvider;
import io.trino.client.auth.kerberos.LoginBasedUnconstrainedContextProvider;
import io.trino.client.auth.kerberos.SpnegoHandler;
import io.trino.client.uri.LoggingLevel;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.internal.tls.LegacyHostnameVerifier;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import org.ietf.jgss.GSSCredential;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.CookieManager;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.net.HttpHeaders.USER_AGENT;
import static java.net.Proxy.Type.HTTP;
import static java.net.Proxy.Type.SOCKS;
import static java.util.Collections.list;
import static java.util.Objects.requireNonNull;

public final class OkHttpUtil
{
    // Mac KeyStore. See JDK documentation for Apple Provider.
    private static final String KEYSTORE_MACOS = "KeychainStore";

    // Windows KeyStores. See JDK documentation for MSCAPI Provider.
    private static final String KEYSTORE_WINDOWS_MY = "Windows-MY-CURRENTUSER";
    private static final String KEYSTORE_WINDOWS_ROOT = "Windows-ROOT-CURRENTUSER";

    private OkHttpUtil() {}

    public static Interceptor userAgent(String userAgent)
    {
        return chain -> chain.proceed(chain.request().newBuilder()
                .header(USER_AGENT, userAgent)
                .build());
    }

    public static Interceptor basicAuth(String user, String password)
    {
        requireNonNull(user, "user is null");
        requireNonNull(password, "password is null");
        if (user.contains(":")) {
            throw new ClientException("Illegal character ':' found in username");
        }

        String credential = Credentials.basic(user, password);
        return chain -> chain.proceed(chain.request().newBuilder()
                .header(AUTHORIZATION, credential)
                .build());
    }

    public static Interceptor tokenAuth(String accessToken)
    {
        requireNonNull(accessToken, "accessToken is null");
        checkArgument(CharMatcher.inRange((char) 33, (char) 126).matchesAllOf(accessToken));

        return chain -> chain.proceed(chain.request().newBuilder()
                .addHeader(AUTHORIZATION, "Bearer " + accessToken)
                .build());
    }

    public static void setupTimeouts(OkHttpClient.Builder clientBuilder, int timeout, TimeUnit unit)
    {
        clientBuilder
                .connectTimeout(timeout, unit)
                .readTimeout(timeout, unit)
                .writeTimeout(timeout, unit);
    }

    public static void setupCookieJar(OkHttpClient.Builder clientBuilder)
    {
        clientBuilder.cookieJar(new JavaNetCookieJar(new CookieManager()));
    }

    public static void disableHttp2(OkHttpClient.Builder clientBuilder)
    {
        // Disable HTTP/2 as it's not tested nor supported
        clientBuilder.protocols(ImmutableList.of(Protocol.HTTP_1_1, Protocol.HTTP_1_1));
    }

    public static void setupSocksProxy(OkHttpClient.Builder clientBuilder, Optional socksProxy)
    {
        setupProxy(clientBuilder, socksProxy, SOCKS);
    }

    public static void setupHttpProxy(OkHttpClient.Builder clientBuilder, Optional httpProxy)
    {
        setupProxy(clientBuilder, httpProxy, HTTP);
    }

    public static void setupProxy(OkHttpClient.Builder clientBuilder, Optional proxy, Proxy.Type type)
    {
        proxy.map(OkHttpUtil::toUnresolvedAddress)
                .map(address -> new Proxy(type, address))
                .ifPresent(clientBuilder::proxy);
    }

    public static void setupHttpLogging(OkHttpClient.Builder clientBuilder, LoggingLevel level)
    {
        switch (level) {
            case NONE:
                return;

            case BODY:
                clientBuilder.addNetworkInterceptor(
                        new HttpLoggingInterceptor(System.err::println)
                                .setLevel(Level.BODY));
                break;
            case BASIC:
                clientBuilder.addNetworkInterceptor(
                        new HttpLoggingInterceptor(System.err::println)
                                .setLevel(Level.BASIC));
                break;
            case HEADERS:
                clientBuilder.addNetworkInterceptor(
                        new HttpLoggingInterceptor(System.err::println)
                                .setLevel(Level.HEADERS));
                break;
        }
    }

    private static InetSocketAddress toUnresolvedAddress(HostAndPort address)
    {
        return InetSocketAddress.createUnresolved(address.getHost(), address.getPort());
    }

    public static void setupInsecureSsl(OkHttpClient.Builder clientBuilder)
    {
        try {
            X509TrustManager trustAllCerts = new X509TrustManager()
            {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                {
                    throw new UnsupportedOperationException("checkClientTrusted should not be called");
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType)
                {
                    // skip validation of server certificate
                }

                @Override
                public X509Certificate[] getAcceptedIssuers()
                {
                    return new X509Certificate[0];
                }
            };

            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, new TrustManager[] {trustAllCerts}, new SecureRandom());

            clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts);
            clientBuilder.hostnameVerifier((hostname, session) -> true);
        }
        catch (GeneralSecurityException e) {
            throw new ClientException("Error setting up SSL: " + e.getMessage(), e);
        }
    }

    public static void setupSsl(
            OkHttpClient.Builder clientBuilder,
            Optional keyStorePath,
            Optional keyStorePassword,
            Optional keyStoreType,
            boolean useSystemKeyStore,
            Optional trustStorePath,
            Optional trustStorePassword,
            Optional trustStoreType,
            boolean useSystemTrustStore)
    {
        if (!keyStorePath.isPresent() && !useSystemKeyStore && !trustStorePath.isPresent() && !useSystemTrustStore) {
            return;
        }

        try {
            // load KeyStore if configured and get KeyManagers
            KeyStore keyStore = null;
            KeyManager[] keyManagers = null;
            char[] keyManagerPassword = null;

            if (useSystemKeyStore) {
                keyStore = loadSystemKeyStore(keyStoreType);
            }
            else if (keyStorePath.isPresent()) {
                try {
                    // attempt to read the key store as a PEM file
                    keyStore = PemReader.loadKeyStore(new File(keyStorePath.get()), new File(keyStorePath.get()), keyStorePassword);
                    // for PEM encoded keys, the password is used to decrypt the specific key (and does not protect the keystore itself)
                    keyManagerPassword = new char[0];
                }
                catch (IOException | GeneralSecurityException ignored) {
                    keyManagerPassword = keyStorePassword.map(String::toCharArray).orElse(null);

                    keyStore = KeyStore.getInstance(keyStoreType.orElseGet(KeyStore::getDefaultType));
                    try (InputStream in = new FileInputStream(keyStorePath.get())) {
                        keyStore.load(in, keyManagerPassword);
                    }
                }
                validateCertificates(keyStore);
            }

            if (keyStore != null) {
                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                keyManagerFactory.init(keyStore, keyManagerPassword);
                keyManagers = keyManagerFactory.getKeyManagers();
            }

            // load TrustStore if configured, otherwise use KeyStore
            KeyStore trustStore = keyStore;
            if (useSystemTrustStore) {
                trustStore = loadSystemTrustStore(trustStoreType);
            }
            else if (trustStorePath.isPresent()) {
                trustStore = loadTrustStore(new File(trustStorePath.get()), trustStorePassword, trustStoreType);
            }

            // create TrustManagerFactory
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);

            // get X509TrustManager
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new RuntimeException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
            }
            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

            // create SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagers, new TrustManager[] {trustManager}, null);

            clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
            clientBuilder.hostnameVerifier(LegacyHostnameVerifier.INSTANCE);
        }
        catch (GeneralSecurityException | IOException e) {
            throw new ClientException("Error setting up SSL: " + e.getMessage(), e);
        }
    }

    private static void validateCertificates(KeyStore keyStore)
            throws GeneralSecurityException
    {
        for (String alias : list(keyStore.aliases())) {
            if (!keyStore.isKeyEntry(alias)) {
                continue;
            }
            Certificate certificate = keyStore.getCertificate(alias);
            if (!(certificate instanceof X509Certificate)) {
                continue;
            }

            try {
                ((X509Certificate) certificate).checkValidity();
            }
            catch (CertificateExpiredException e) {
                throw new CertificateExpiredException("KeyStore certificate is expired: " + e.getMessage());
            }
            catch (CertificateNotYetValidException e) {
                throw new CertificateNotYetValidException("KeyStore certificate is not yet valid: " + e.getMessage());
            }
        }
    }

    private static KeyStore loadTrustStore(File trustStorePath, Optional trustStorePassword, Optional trustStoreType)
            throws IOException, GeneralSecurityException
    {
        KeyStore trustStore = KeyStore.getInstance(trustStoreType.orElseGet(KeyStore::getDefaultType));
        try {
            // attempt to read the trust store as a PEM file
            List certificateChain = PemReader.readCertificateChain(trustStorePath);
            if (!certificateChain.isEmpty()) {
                trustStore.load(null, null);
                for (X509Certificate certificate : certificateChain) {
                    X500Principal principal = certificate.getSubjectX500Principal();
                    trustStore.setCertificateEntry(principal.getName(), certificate);
                }
                return trustStore;
            }
        }
        catch (IOException | GeneralSecurityException ignored) {
        }

        try (InputStream in = new FileInputStream(trustStorePath)) {
            trustStore.load(in, trustStorePassword.map(String::toCharArray).orElse(null));
        }
        return trustStore;
    }

    private static KeyStore loadSystemKeyStore(Optional keyStoreType)
            throws IOException, GeneralSecurityException
    {
        return loadSystemStore(keyStoreType, KEYSTORE_MACOS, KEYSTORE_WINDOWS_MY);
    }

    private static KeyStore loadSystemTrustStore(Optional trustStoreType)
            throws IOException, GeneralSecurityException
    {
        return loadSystemStore(trustStoreType, KEYSTORE_MACOS, KEYSTORE_WINDOWS_ROOT);
    }

    private static KeyStore loadSystemStore(Optional storeType, String mac, String windows)
            throws IOException, GeneralSecurityException
    {
        String osName = Optional.ofNullable(StandardSystemProperty.OS_NAME.value()).orElse("");
        Optional systemStoreType = storeType;
        if (!systemStoreType.isPresent()) {
            if (osName.contains("Windows")) {
                systemStoreType = Optional.of(windows);
            }
            else if (osName.contains("Mac")) {
                systemStoreType = Optional.of(mac);
            }
        }

        KeyStore store = KeyStore.getInstance(systemStoreType.orElseGet(KeyStore::getDefaultType));
        store.load(null, null);
        return store;
    }

    public static void setupKerberos(
            OkHttpClient.Builder clientBuilder,
            String servicePrincipalPattern,
            String remoteServiceName,
            boolean useCanonicalHostname,
            Optional principal,
            Optional kerberosConfig,
            Optional keytab,
            Optional credentialCache,
            boolean delegatedKerberos,
            Optional gssCredential)
    {
        GSSContextProvider contextProvider;
        if (delegatedKerberos) {
            contextProvider = getDelegatedGSSContextProvider(gssCredential);
        }
        else {
            contextProvider = new LoginBasedUnconstrainedContextProvider(principal, kerberosConfig, keytab, credentialCache);
        }
        SpnegoHandler handler = new SpnegoHandler(servicePrincipalPattern, remoteServiceName, useCanonicalHostname, contextProvider);
        clientBuilder.addInterceptor(handler);
        clientBuilder.authenticator(handler);
    }

    public static void setupAlternateHostnameVerification(OkHttpClient.Builder clientBuilder, String alternativeHostname)
    {
        clientBuilder.hostnameVerifier((hostname, session) -> LegacyHostnameVerifier.INSTANCE.verify(alternativeHostname, session));
    }

    private static GSSContextProvider getDelegatedGSSContextProvider(Optional gssCredential)
    {
        return gssCredential.map(DelegatedConstrainedContextProvider::new)
                .map(gssCred -> (GSSContextProvider) gssCred)
                .orElse(new DelegatedUnconstrainedContextProvider());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy