From 455917f21e0ea3d73411f7b38b9e14ad3006b662 Mon Sep 17 00:00:00 2001 From: pasindu Date: Wed, 26 Apr 2023 20:35:56 +0530 Subject: [PATCH] Implement method for Https ssl trustmanager and improve exception --- .../rest/api/APIApplicationServicesImpl.java | 54 +---- .../rest/api/PublisherRESTAPIServices.java | 21 +- .../UnexpectedResponseException.java | 31 +++ .../rest/api/util/HttpsTrustManagerUtils.java | 229 ++++++++++++++++++ .../publisher/APIPublisherServiceImpl.java | 7 +- 5 files changed, 280 insertions(+), 62 deletions(-) create mode 100644 components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/exceptions/UnexpectedResponseException.java create mode 100644 components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/util/HttpsTrustManagerUtils.java diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java index 0fc1f4792ea..abf383b501b 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/APIApplicationServicesImpl.java @@ -19,6 +19,7 @@ package io.entgra.device.mgt.core.apimgt.extension.rest.api; import com.google.gson.Gson; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.util.HttpsTrustManagerUtils; import org.json.JSONObject; import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIApplicationKey; import io.entgra.device.mgt.core.apimgt.extension.rest.api.constants.Constants; @@ -48,7 +49,7 @@ import java.util.concurrent.TimeUnit; public class APIApplicationServicesImpl implements APIApplicationServices { private static final Log log = LogFactory.getLog(APIApplicationServicesImpl.class); - private static final OkHttpClient client = getOkHttpClient(); + private static final OkHttpClient client = new OkHttpClient(HttpsTrustManagerUtils.getSSLClient().newBuilder()); private static final Gson gson = new Gson(); private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); String msg = null; @@ -133,55 +134,4 @@ public class APIApplicationServicesImpl implements APIApplicationServices { throw new APIServicesException(e); } } - - protected static OkHttpClient getOkHttpClient() { - X509TrustManager trustAllCerts = new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[0]; - } - - public void checkClientTrusted( - java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted( - java.security.cert.X509Certificate[] certs, String authType) { - } - }; - OkHttpClient okHttpClient = new OkHttpClient.Builder() - .connectTimeout(300, TimeUnit.SECONDS) - .writeTimeout(300, TimeUnit.SECONDS) - .readTimeout(300, TimeUnit.SECONDS) - .connectionPool(new ConnectionPool(500, 500, TimeUnit.SECONDS)) - .sslSocketFactory(getSimpleTrustedSSLSocketFactory(), trustAllCerts) - .hostnameVerifier((hostname, sslSession) -> true).build(); - return okHttpClient; - } - - private static SSLSocketFactory getSimpleTrustedSSLSocketFactory() { - try { - TrustManager[] trustAllCerts = new TrustManager[]{ - new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted( - java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted( - java.security.cert.X509Certificate[] certs, String authType) { - } - } - }; - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - return sc.getSocketFactory(); - } catch (KeyManagementException | NoSuchAlgorithmException e) { - log.error("Error while creating the SSL socket factory due to " + e.getMessage(), e); - return null; - } - - } } diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java index 60d93890b2e..c74b227289b 100644 --- a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/PublisherRESTAPIServices.java @@ -23,6 +23,8 @@ import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIApplicationKey import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.AccessTokenInfo; import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIServicesException; import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.BadRequestException; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.UnexpectedResponseException; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.util.HttpsTrustManagerUtils; import io.entgra.device.mgt.core.apimgt.extension.rest.api.util.ScopeUtils; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -38,17 +40,15 @@ import org.wso2.carbon.apimgt.api.model.Scope; import java.io.IOException; -import static io.entgra.device.mgt.core.apimgt.extension.rest.api.APIApplicationServicesImpl.getOkHttpClient; - public class PublisherRESTAPIServices { private static final Log log = LogFactory.getLog(PublisherRESTAPIServices.class); - private static final OkHttpClient client = getOkHttpClient(); + private static final OkHttpClient client = new OkHttpClient(HttpsTrustManagerUtils.getSSLClient().newBuilder()); private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String host = System.getProperty(Constants.IOT_CORE_HOST); private static final String port = System.getProperty(Constants.IOT_CORE_HTTPS_PORT); public JSONObject getScopes(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo) - throws APIServicesException, BadRequestException { + throws APIServicesException, BadRequestException, UnexpectedResponseException { String getAllScopesUrl = Constants.HTTPS_PROTOCOL + Constants.SCHEME_SEPARATOR + host + Constants.COLON + port + Constants.GET_ALL_SCOPES; @@ -76,7 +76,8 @@ public class PublisherRESTAPIServices { log.error(msg); throw new BadRequestException(msg); } else { - return null; + String msg = "Response : " + response.code() + response.body(); + throw new UnexpectedResponseException(msg); } } catch (IOException e) { String msg = "Error occurred while processing the response"; @@ -86,7 +87,7 @@ public class PublisherRESTAPIServices { } public boolean isSharedScopeNameExists(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, String key) - throws APIServicesException, BadRequestException { + throws APIServicesException, BadRequestException, UnexpectedResponseException { String keyValue = new String(Base64.encodeBase64((key).getBytes())).replace(Constants.QUERY_KEY_VALUE_SEPARATOR, Constants.EMPTY_STRING); @@ -115,7 +116,8 @@ public class PublisherRESTAPIServices { log.error(msg); throw new BadRequestException(msg); } else { - return false; + String msg = "Response : " + response.code() + response.body(); + throw new UnexpectedResponseException(msg); } } catch (IOException e) { String msg = "Error occurred while processing the response"; @@ -125,7 +127,7 @@ public class PublisherRESTAPIServices { } public boolean updateSharedScope(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, Scope scope) - throws APIServicesException, BadRequestException { + throws APIServicesException, BadRequestException, UnexpectedResponseException { String updateScopeUrl = Constants.HTTPS_PROTOCOL + Constants.SCHEME_SEPARATOR + host + Constants.COLON + port + Constants.GET_SCOPE + scope.getId(); @@ -161,7 +163,8 @@ public class PublisherRESTAPIServices { log.error(msg); throw new BadRequestException(msg); } else { - return false; + String msg = "Response : " + response.code() + response.body(); + throw new UnexpectedResponseException(msg); } } catch (IOException e) { String msg = "Error occurred while processing the response"; diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/exceptions/UnexpectedResponseException.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/exceptions/UnexpectedResponseException.java new file mode 100644 index 00000000000..898caba26e8 --- /dev/null +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/exceptions/UnexpectedResponseException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you 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. + */ + +/** + * Custom exception class for handling unexpected server response exceptions. + */ +package io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions; + +public class UnexpectedResponseException extends Exception { + + private static final long serialVersionUID = -2387103750774855056L; + + public UnexpectedResponseException(String errorMessage) { + super(errorMessage); + } +} diff --git a/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/util/HttpsTrustManagerUtils.java b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/util/HttpsTrustManagerUtils.java new file mode 100644 index 00000000000..8b47d77a343 --- /dev/null +++ b/components/apimgt-extensions/io.entgra.device.mgt.core.apimgt.extension.rest.api/src/main/java/io/entgra/device/mgt/core/apimgt/extension/rest/api/util/HttpsTrustManagerUtils.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you 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.entgra.device.mgt.core.apimgt.extension.rest.api.util; + +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.base.ServerConfiguration; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.SSLSession; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.HostnameVerifier; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; + +import java.security.*; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class HttpsTrustManagerUtils { + + private static final Log log = LogFactory.getLog(HttpsTrustManagerUtils.class); + private static final String KEY_STORE_TYPE = "JKS"; + /** + * Default truststore type of the client + */ + private static final String TRUST_STORE_TYPE = "JKS"; + /** + * Default keymanager type of the client + */ + private static final String KEY_MANAGER_TYPE = "SunX509"; //Default Key Manager Type + /** + * Default trustmanager type of the client + */ + private static final String TRUST_MANAGER_TYPE = "SunX509"; //Default Trust Manager Type + private static final String SSLV3 = "SSLv3"; + private static final String DEFAULT_HOST = "localhost"; + private static final String DEFAULT_HOST_IP = "127.0.0.1"; + private static final int TIMEOUT = 100; + + public static OkHttpClient getSSLClient() { + + boolean isIgnoreHostnameVerification = Boolean.parseBoolean(System.getProperty("org.wso2" + + ".ignoreHostnameVerification")); + OkHttpClient okHttpClient; + final String proxyHost = System.getProperty("http.proxyHost"); + final String proxyPort = System.getProperty("http.proxyPort"); + final String nonProxyHostsValue = System.getProperty("http.nonProxyHosts"); + + final ProxySelector proxySelector = new ProxySelector() { + @Override + public List select(URI uri) { + List proxyList = new ArrayList<>(); + String host = uri.getHost(); + + if (!StringUtils.isEmpty(host)) { + if (host.startsWith(DEFAULT_HOST_IP) || host.startsWith(DEFAULT_HOST) || StringUtils + .isEmpty(nonProxyHostsValue) || StringUtils.contains(nonProxyHostsValue, host) || + StringUtils.isEmpty(proxyHost) || StringUtils.isEmpty(proxyPort)) { + proxyList.add(Proxy.NO_PROXY); + } else { + proxyList.add(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress(proxyHost, Integer.parseInt(proxyPort)))); + } + } else { + log.error("Host is null. Host could not be empty or null"); + } + return proxyList; + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + throw new UnsupportedOperationException("Not supported yet."); + } + }; + + X509TrustManager trustAllCerts = new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[0]; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + }; + + if (isIgnoreHostnameVerification) { + okHttpClient = new OkHttpClient.Builder() + .connectTimeout(TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(TIMEOUT, TimeUnit.SECONDS) + .readTimeout(TIMEOUT, TimeUnit.SECONDS) + .connectionPool(new ConnectionPool(TIMEOUT, TIMEOUT, TimeUnit.SECONDS)) + .sslSocketFactory(getSimpleTrustedSSLSocketFactory(), trustAllCerts) + .hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + }).proxySelector(proxySelector).build(); + return okHttpClient; + } else { + SSLSocketFactory trustedSSLSocketFactory = getTrustedSSLSocketFactory(); + okHttpClient = new OkHttpClient.Builder() + .connectTimeout(TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(TIMEOUT, TimeUnit.SECONDS) + .readTimeout(TIMEOUT, TimeUnit.SECONDS) + .connectionPool(new ConnectionPool(TIMEOUT, TIMEOUT, TimeUnit.SECONDS)) + .sslSocketFactory(trustedSSLSocketFactory) + .proxySelector(proxySelector).build(); + return okHttpClient; + } + } + + private static SSLSocketFactory getSimpleTrustedSSLSocketFactory() { + try { + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new SecureRandom()); + return sc.getSocketFactory(); + } catch (KeyManagementException | NoSuchAlgorithmException e) { + log.error("Error while creating the SSL socket factory due to " + e.getMessage(), e); + return null; + } + } + + private static SSLSocketFactory getTrustedSSLSocketFactory() { + try { + String keyStorePassword = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Password"); + String keyStoreLocation = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Location"); + String trustStorePassword = ServerConfiguration.getInstance().getFirstProperty( + "Security.TrustStore.Password"); + String trustStoreLocation = ServerConfiguration.getInstance().getFirstProperty( + "Security.TrustStore.Location"); + KeyStore keyStore = loadKeyStore(keyStoreLocation, keyStorePassword, KEY_STORE_TYPE); + KeyStore trustStore = loadTrustStore(trustStoreLocation, trustStorePassword); + + return initSSLConnection(keyStore, keyStorePassword, trustStore); + } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException + | CertificateException | IOException | UnrecoverableKeyException e) { + log.error("Error while creating the SSL socket factory due to " + e.getMessage(), e); + return null; + } + } + + private static SSLSocketFactory initSSLConnection(KeyStore keyStore, String keyStorePassword, KeyStore trustStore) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, KeyManagementException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_MANAGER_TYPE); + keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TRUST_MANAGER_TYPE); + trustManagerFactory.init(trustStore); + + // Create and initialize SSLContext for HTTPS communication + SSLContext sslContext = SSLContext.getInstance(SSLV3); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + SSLContext.setDefault(sslContext); + return sslContext.getSocketFactory(); + } + + private static KeyStore loadKeyStore(String keyStorePath, String ksPassword, String type) + throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + InputStream fileInputStream = null; + try { + char[] keypassChar = ksPassword.toCharArray(); + KeyStore keyStore = KeyStore.getInstance(type); + fileInputStream = new FileInputStream(keyStorePath); + keyStore.load(fileInputStream, keypassChar); + return keyStore; + } finally { + if (fileInputStream != null) { + fileInputStream.close(); + } + } + } + + private static KeyStore loadTrustStore(String trustStorePath, String tsPassword) + throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + return loadKeyStore(trustStorePath, tsPassword, TRUST_STORE_TYPE); + } +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java index fb4509858d2..e4a63f456a0 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.webapp.publisher/src/main/java/org/wso2/carbon/apimgt/webapp/publisher/APIPublisherServiceImpl.java @@ -25,6 +25,7 @@ import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIApplicationKey import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.AccessTokenInfo; import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIServicesException; import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.BadRequestException; +import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.UnexpectedResponseException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -468,7 +469,11 @@ public class APIPublisherServiceImpl implements APIPublisherService { String errorMsg = "Error while calling Publisher REST APIs"; log.error(errorMsg, e); throw new APIManagerPublisherException(e); - } finally { + } catch (UnexpectedResponseException e) { + String errorMsg = "Unexpected response from the server"; + log.error(errorMsg, e); + throw new APIManagerPublisherException(e); + }finally { PrivilegedCarbonContext.endTenantFlow(); } }