diff --git a/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java b/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java index c1a505cf5efa..97fc040d1488 100644 --- a/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java +++ b/cli/central-client/src/main/java/org/ballerinalang/central/client/CentralAPIClient.java @@ -51,12 +51,20 @@ import org.ballerinalang.central.client.model.ToolResolutionCentralResponse; import org.ballerinalang.central.client.model.ToolSearchResult; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; import java.net.Proxy; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.file.Path; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -65,6 +73,9 @@ import java.util.concurrent.TimeUnit; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; @@ -142,14 +153,17 @@ public class CentralAPIClient { private static final String ERR_PACKAGE_UN_DEPRECATE = "error: failed to undo deprecation of the package: "; private static final String ERR_PACKAGE_RESOLUTION = "error: while connecting to central: "; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - // System property name for enabling central verbose - public static final String SYS_PROP_CENTRAL_VERBOSE_ENABLED = "CENTRAL_VERBOSE_ENABLED"; + private static final MediaType JSON_CONTENT_TYPE = MediaType.parse("application/json"); + private static final int DEFAULT_CONNECT_TIMEOUT = 60; private static final int DEFAULT_READ_TIMEOUT = 60; private static final int DEFAULT_WRITE_TIMEOUT = 60; private static final int DEFAULT_CALL_TIMEOUT = 0; private static final int MAX_RETRY = 1; public static final String CONNECTION_RESET = "Connection reset"; + private static final String ENV_CENTRAL_VERBOSE_ENABLED = "CENTRAL_VERBOSE_ENABLED"; + private static final String ENV_TRUSTSTORE_PATH = "BALLERINA_TRUSTSTORE_PATH"; + private static final String ENV_TRUSTSTORE_PASSWORD = "BALLERINA_TRUSTSTORE_PASSWORD"; private final String baseUrl; private final Proxy proxy; @@ -163,13 +177,15 @@ public class CentralAPIClient { private final int writeTimeout; private final int callTimeout; private final int maxRetries; + private final String trustStorePath; + private final String trustStorePassword; public CentralAPIClient(String baseUrl, Proxy proxy, String accessToken) { this.outStream = System.out; this.baseUrl = baseUrl; this.proxy = proxy; this.accessToken = accessToken; - this.verboseEnabled = Boolean.parseBoolean(System.getenv(SYS_PROP_CENTRAL_VERBOSE_ENABLED)); + this.verboseEnabled = Boolean.parseBoolean(System.getenv(ENV_CENTRAL_VERBOSE_ENABLED)); this.proxyUsername = ""; this.proxyPassword = ""; this.connectTimeout = DEFAULT_CONNECT_TIMEOUT; @@ -177,6 +193,8 @@ public CentralAPIClient(String baseUrl, Proxy proxy, String accessToken) { this.writeTimeout = DEFAULT_WRITE_TIMEOUT; this.callTimeout = DEFAULT_CALL_TIMEOUT; this.maxRetries = MAX_RETRY; + this.trustStorePath = System.getenv(ENV_TRUSTSTORE_PATH); + this.trustStorePassword = System.getenv(ENV_TRUSTSTORE_PASSWORD); } public CentralAPIClient(String baseUrl, Proxy proxy, String accessToken, boolean verboseEnabled, int maxRetries, @@ -193,6 +211,8 @@ public CentralAPIClient(String baseUrl, Proxy proxy, String accessToken, boolean this.writeTimeout = DEFAULT_WRITE_TIMEOUT; this.callTimeout = DEFAULT_CALL_TIMEOUT; this.maxRetries = maxRetries; + this.trustStorePath = System.getenv(ENV_TRUSTSTORE_PATH); + this.trustStorePassword = System.getenv(ENV_TRUSTSTORE_PASSWORD); } public CentralAPIClient(String baseUrl, Proxy proxy, String proxyUsername, String proxyPassword, @@ -202,7 +222,7 @@ public CentralAPIClient(String baseUrl, Proxy proxy, String proxyUsername, Strin this.baseUrl = baseUrl; this.proxy = proxy; this.accessToken = accessToken; - this.verboseEnabled = Boolean.parseBoolean(System.getenv(SYS_PROP_CENTRAL_VERBOSE_ENABLED)); + this.verboseEnabled = Boolean.parseBoolean(System.getenv(ENV_CENTRAL_VERBOSE_ENABLED)); this.proxyUsername = proxyUsername; this.proxyPassword = proxyPassword; this.connectTimeout = connectionTimeout; @@ -210,6 +230,8 @@ public CentralAPIClient(String baseUrl, Proxy proxy, String proxyUsername, Strin this.writeTimeout = writeTimeout; this.callTimeout = callTimeout; this.maxRetries = maxRetries; + this.trustStorePath = System.getenv(ENV_TRUSTSTORE_PATH); + this.trustStorePassword = System.getenv(ENV_TRUSTSTORE_PASSWORD); } /** @@ -1591,8 +1613,8 @@ public JsonObject getConnector(ConnectorInfo connector, String supportedPlatform * * @return the client */ - protected OkHttpClient getClient() { - OkHttpClient okHttpClient = new OkHttpClient.Builder() + protected OkHttpClient getClient() throws CentralClientException { + OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(connectTimeout, TimeUnit.SECONDS) .readTimeout(readTimeout, TimeUnit.SECONDS) .writeTimeout(writeTimeout, TimeUnit.SECONDS) @@ -1600,8 +1622,30 @@ protected OkHttpClient getClient() { .followRedirects(false) .retryOnConnectionFailure(true) .proxy(this.proxy) - .addInterceptor(new CustomRetryInterceptor(this.maxRetries)) - .build(); + .addInterceptor(new CustomRetryInterceptor(this.maxRetries)); + if (this.trustStorePath != null && this.trustStorePassword != null) { + try { + KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream keys = new FileInputStream(trustStorePath)) { + truststore.load(keys, trustStorePassword.toCharArray()); + } + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(truststore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); + SSLContext.setDefault(sslContext); + + builder.sslSocketFactory(sslContext.getSocketFactory(), + (X509TrustManager) trustManagerFactory.getTrustManagers()[0]); + } catch (CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException | + KeyManagementException e) { + throw new CentralClientException(e.getMessage()); + } + } + + OkHttpClient okHttpClient = builder.build(); if ((!(this.proxyUsername).isEmpty() && !(this.proxyPassword).isEmpty())) { Authenticator proxyAuthenticator = (route, response) -> { diff --git a/cli/central-client/src/test/java/org/ballerinalang/central/client/TestCustomRetryInterceptor.java b/cli/central-client/src/test/java/org/ballerinalang/central/client/TestCustomRetryInterceptor.java index 7d80f0c9769f..07a906705e12 100644 --- a/cli/central-client/src/test/java/org/ballerinalang/central/client/TestCustomRetryInterceptor.java +++ b/cli/central-client/src/test/java/org/ballerinalang/central/client/TestCustomRetryInterceptor.java @@ -23,6 +23,7 @@ import okhttp3.Response; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import org.ballerinalang.central.client.exceptions.CentralClientException; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -49,7 +50,7 @@ public class TestCustomRetryInterceptor { @BeforeClass - public void setUp() { + public void setUp() throws CentralClientException { this.console = new ByteArrayOutputStream(); PrintStream outStream = new PrintStream(this.console); CentralAPIClient centralAPIClient = new CentralAPIClient("https://localhost:9090/registry",