diff --git a/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/RegistryResources.java b/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/RegistryResources.java index 3b60fd6dd46..d7b23ba472c 100644 --- a/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/RegistryResources.java +++ b/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/RegistryResources.java @@ -160,6 +160,24 @@ public static final class SecurityManagement { public static final String SERVER_INTERNAL_PRIVATE_KEY_PASSWORD = "Security.InternalKeyStore.KeyPassword"; public static final String SERVER_INTERNAL_KEYSTORE_TYPE = "Security.InternalKeyStore.Type"; + // Custom KeyStore related constants. + public static final class CustomKeyStore { + + public static final String CUSTOM_KEYSTORE_PREFIX = "CUSTOM/"; + + public static final String ELEM_SECURITY = "Security"; + public static final String ELEM_CUSTOM_KEYSTORES = "CustomKeyStores"; + + public static final String PROP_LOCATION = "Location"; + public static final String PROP_TYPE = "Type"; + public static final String PROP_PASSWORD = "Password"; + public static final String PROP_KEY_ALIAS = "KeyAlias"; + public static final String PROP_KEY_PASSWORD = "KeyPassword"; + } + + // carbon.home location placeholder. + public static final String CARBON_HOME_PLACEHOLDER = "${carbon.home}"; + //generated pub. key - multitenancy scenario public static final String TENANT_PUBKEY_RESOURCE = RegistryResources.ROOT + "security/pub-key"; diff --git a/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreManager.java b/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreManager.java index 2f33229c786..3f87162dfb6 100644 --- a/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreManager.java +++ b/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreManager.java @@ -17,10 +17,13 @@ */ package org.wso2.carbon.core.util; +import org.apache.axiom.om.OMElement; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.CarbonException; import org.wso2.carbon.base.api.ServerConfigurationService; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.core.RegistryResources; import org.wso2.carbon.core.internal.CarbonCoreDataHolder; import org.wso2.carbon.registry.api.Registry; @@ -31,11 +34,17 @@ import org.wso2.carbon.utils.CarbonUtils; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.security.Key; import java.security.KeyStore; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; @@ -55,7 +64,8 @@ public class KeyStoreManager { private static Log log = LogFactory.getLog(KeyStoreManager.class); private Registry registry = null; - private ConcurrentHashMap loadedKeyStores = null; + private ConcurrentHashMap tenantKeyStores = null; + private ConcurrentHashMap customKeyStores = null; private int tenantId = MultitenantConstants.SUPER_TENANT_ID; private ServerConfigurationService serverConfigService; @@ -73,7 +83,8 @@ private KeyStoreManager(int tenantId, ServerConfigurationService serverConfigSer RegistryService registryService) { this.serverConfigService = serverConfigService; this.registryService = registryService; - loadedKeyStores = new ConcurrentHashMap(); + tenantKeyStores = new ConcurrentHashMap(); + customKeyStores = new ConcurrentHashMap(); this.tenantId = tenantId; try { registry = registryService.getGovernanceSystemRegistry(tenantId); @@ -101,6 +112,8 @@ public RegistryService getRegistryService() { * @return KeyStoreManager instance for that tenant */ public static KeyStoreManager getInstance(int tenantId) { + + log.debug("KeyStoreManager Instace created for teant id: " + tenantId); return getInstance(tenantId, CarbonCoreDataHolder.getInstance(). getServerConfigurationService(), CryptoUtil.lookupRegistryService()); } @@ -118,92 +131,57 @@ public static KeyStoreManager getInstance(int tenantId, } /** - * Get the key store object for the given key store name + * Get the key store object for the given key store name. * - * @param keyStoreName key store name - * @return KeyStore object - * @throws Exception If there is not a key store with the given name + * @param keyStoreName Key store name. + * @return KeyStore object. + * @throws Exception If there is an error when retriving the given keystore. */ public KeyStore getKeyStore(String keyStoreName) throws Exception { + if (StringUtils.isEmpty(keyStoreName)) { + throw new SecurityException("Key store name is null or empty."); + } + if (KeyStoreUtil.isPrimaryStore(keyStoreName)) { return getPrimaryKeyStore(); } - if (isCachedKeyStoreValid(keyStoreName)) { - return loadedKeyStores.get(keyStoreName).getKeyStore(); + if (KeyStoreUtil.isCustomKeyStore(keyStoreName)) { + return getCustomKeyStore(keyStoreName); } - String path = RegistryResources.SecurityManagement.KEY_STORES + "/" + keyStoreName; - if (registry.resourceExists(path)) { - org.wso2.carbon.registry.api.Resource resource = registry.get(path); - byte[] bytes = (byte[]) resource.getContent(); - KeyStore keyStore = KeyStore.getInstance(resource - .getProperty(RegistryResources.SecurityManagement.PROP_TYPE)); - CryptoUtil cryptoUtil = CryptoUtil.getDefaultCryptoUtil(); - String encryptedPassword = resource - .getProperty(RegistryResources.SecurityManagement.PROP_PASSWORD); - String password = new String(cryptoUtil.base64DecodeAndDecrypt(encryptedPassword)); - ByteArrayInputStream stream = new ByteArrayInputStream(bytes); - keyStore.load(stream, password.toCharArray()); - KeyStoreBean keyStoreBean = new KeyStoreBean(keyStore, resource.getLastModified()); - resource.discard(); - - if (loadedKeyStores.containsKey(keyStoreName)) { - loadedKeyStores.replace(keyStoreName, keyStoreBean); - } else { - loadedKeyStores.put(keyStoreName, keyStoreBean); - } - return keyStore; - } else { - throw new SecurityException("Key Store with a name : " + keyStoreName + " does not exist."); - } + return getTenantKeyStore(keyStoreName); } /** - * This method loads the private key of a given key store + * This method loads the private key of a given key store. * - * @param keyStoreName name of the key store - * @param alias alias of the private key - * @return private key corresponding to the alias + * @param keyStoreName Name of the key store. + * @param alias Alias of the private key. + * @return Private key corresponding to the alias. + * @throws Exception If there is an error when retriving the private key from given keystore. */ public Key getPrivateKey(String keyStoreName, String alias) { + + if (StringUtils.isEmpty(keyStoreName)) { + throw new SecurityException("Key store name is null or empty."); + } + try { if (KeyStoreUtil.isPrimaryStore(keyStoreName)) { return getDefaultPrivateKey(); } - String path = RegistryResources.SecurityManagement.KEY_STORES + "/" + keyStoreName; - org.wso2.carbon.registry.api.Resource resource; - KeyStore keyStore; - - if (registry.resourceExists(path)) { - resource = registry.get(path); - } else { - throw new SecurityException("Given Key store is not available in registry : " + keyStoreName); + if (KeyStoreUtil.isCustomKeyStore(keyStoreName)) { + return getCustomKeyStorePrivateKey(keyStoreName); } - CryptoUtil cryptoUtil = CryptoUtil.getDefaultCryptoUtil(); - String encryptedPassword = resource - .getProperty(RegistryResources.SecurityManagement.PROP_PRIVATE_KEY_PASS); - String privateKeyPasswd = new String(cryptoUtil.base64DecodeAndDecrypt(encryptedPassword)); - - if (isCachedKeyStoreValid(keyStoreName)) { - keyStore = loadedKeyStores.get(keyStoreName).getKeyStore(); - return keyStore.getKey(alias, privateKeyPasswd.toCharArray()); - } else { - byte[] bytes = (byte[]) resource.getContent(); - String keyStorePassword = new String(cryptoUtil.base64DecodeAndDecrypt(resource.getProperty( - RegistryResources.SecurityManagement.PROP_PASSWORD))); - keyStore = KeyStore.getInstance(resource - .getProperty(RegistryResources.SecurityManagement.PROP_TYPE)); - ByteArrayInputStream stream = new ByteArrayInputStream(bytes); - keyStore.load(stream, keyStorePassword.toCharArray()); - - KeyStoreBean keyStoreBean = new KeyStoreBean(keyStore, resource.getLastModified()); - updateKeyStoreCache(keyStoreName, keyStoreBean); - return keyStore.getKey(alias, privateKeyPasswd.toCharArray()); + // Primary key store or custom key stores does not expect the key alias. + if (StringUtils.isEmpty(alias)) { + throw new SecurityException("Private key alias is null or empty."); } + return getTenantPrivateKey(keyStoreName, alias); } catch (Exception e) { log.error("Error loading the private key from the key store : " + keyStoreName); throw new SecurityException("Error loading the private key from the key store : " + @@ -211,6 +189,35 @@ public Key getPrivateKey(String keyStoreName, String alias) { } } + /** + * This method loads the public certificate of a given key store. + * + * @param keyStoreName Name of the key store. + * @param alias Alias of the certificate. + * @return Public Certificate corresponding to the alias. + * @throws Exception If there is an error when retriving the public certificate from given keystore. + */ + public Certificate getCertificate(String keyStoreName, String alias) throws Exception { + + if (StringUtils.isEmpty(keyStoreName)) { + throw new SecurityException("Key store name is null or empty."); + } + + if (KeyStoreUtil.isPrimaryStore(keyStoreName)) { + return getDefaultPrimaryCertificate(); + } + + if (KeyStoreUtil.isCustomKeyStore(keyStoreName)) { + return getCustomKeyStoreCertificate(keyStoreName); + } + + // Primary or custom key store methods does not expect alias. + if (StringUtils.isEmpty(alias)) { + throw new SecurityException("Certificate alias is null or empty."); + } + return getTenantCertificate(keyStoreName, alias); + } + /** * Get the key store password of the given key store resource * @@ -226,7 +233,6 @@ public String getPassword(Resource resource) throws Exception { } - /** * Get the key store password for the given key store name. * Note: Caching has been not implemented for this method @@ -299,7 +305,7 @@ public void updateKeyStore(String name, KeyStore keyStore) throws Exception { registry.put(path, resource); resource.discard(); - updateKeyStoreCache(name, new KeyStoreBean(keyStore, new Date())); + updateTenantKeyStoreCache(name, new KeyStoreBean(keyStore, new Date())); } /** @@ -310,6 +316,10 @@ public void updateKeyStore(String name, KeyStore keyStore) throws Exception { * than tenant 0 */ public KeyStore getPrimaryKeyStore() throws Exception { + + if (log.isDebugEnabled()) { + log.debug("Loading primary key store."); + } if (tenantId == MultitenantConstants.SUPER_TENANT_ID) { if (primaryKeyStore == null) { @@ -341,6 +351,48 @@ public KeyStore getPrimaryKeyStore() throws Exception { } } + /** + * Load the requested tenant keystore from registry. + * + * @param keyStoreName Name of the Tenant KeyStore. + * @return Key store object. + * @throws Exception Exception if failed to retrive the given keystore from registry. + */ + private KeyStore getTenantKeyStore(String keyStoreName) throws Exception { + + if (log.isDebugEnabled()) { + log.debug("Loading tenant key store : " + keyStoreName); + } + if (isCachedTenantKeyStoreValid(keyStoreName)) { + return tenantKeyStores.get(keyStoreName).getKeyStore(); + } + + String path = RegistryResources.SecurityManagement.KEY_STORES + "/" + keyStoreName; + if (registry.resourceExists(path)) { + org.wso2.carbon.registry.api.Resource resource = registry.get(path); + byte[] bytes = (byte[]) resource.getContent(); + KeyStore keyStore = KeyStore.getInstance(resource + .getProperty(RegistryResources.SecurityManagement.PROP_TYPE)); + CryptoUtil cryptoUtil = CryptoUtil.getDefaultCryptoUtil(); + String encryptedPassword = resource + .getProperty(RegistryResources.SecurityManagement.PROP_PASSWORD); + String password = new String(cryptoUtil.base64DecodeAndDecrypt(encryptedPassword)); + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + keyStore.load(stream, password.toCharArray()); + KeyStoreBean keyStoreBean = new KeyStoreBean(keyStore, resource.getLastModified()); + resource.discard(); + + if (tenantKeyStores.containsKey(keyStoreName)) { + tenantKeyStores.replace(keyStoreName, keyStoreBean); + } else { + tenantKeyStores.put(keyStoreName, keyStoreBean); + } + return keyStore; + } else { + throw new SecurityException("Key Store with a name : " + keyStoreName + " does not exist."); + } + } + /** * Load the internal key store, this is allowed only for the super tenant * @@ -418,6 +470,38 @@ public KeyStore getRegistryKeyStore() throws Exception { } } + /** + * Load custom key stores configured in Carbon.xml file. + * Custom key store files should reside in /repository/resources/security/ + * + * @param keyStoreName Name of the custom key store. Must start with the custom key store prefix. + * @return Key store object + * @throws Exception Exception if failed to retrive the given key store. + */ + private KeyStore getCustomKeyStore(String keyStoreName) throws Exception { + + if (log.isDebugEnabled()) { + log.debug("Loading custom key store : " + keyStoreName); + } + if (customKeyStores.containsKey(keyStoreName)) { + return customKeyStores.get(keyStoreName); + } + + OMElement config = KeyStoreUtil.getCustomKeyStoreConfigElement(keyStoreName, this.getServerConfigService()); + + String location = KeyStoreUtil.getCustomKeyStoreConfig( + config, RegistryResources.SecurityManagement.CustomKeyStore.PROP_LOCATION); + String type = KeyStoreUtil.getCustomKeyStoreConfig( + config, RegistryResources.SecurityManagement.CustomKeyStore.PROP_TYPE); + String password = KeyStoreUtil.getCustomKeyStoreConfig( + config, RegistryResources.SecurityManagement.CustomKeyStore.PROP_PASSWORD); + + KeyStore keyStore = loadKeyStoreFromFileSystem(location, password, type); + customKeyStores.put(keyStoreName, keyStore); + + return keyStore; + } + /** * Get the default private key, only allowed for tenant 0 * @@ -425,6 +509,10 @@ public KeyStore getRegistryKeyStore() throws Exception { * @throws Exception Carbon Exception for tenants other than tenant 0 */ public PrivateKey getDefaultPrivateKey() throws Exception { + + if (log.isDebugEnabled()) { + log.debug("Loading primary key store private key."); + } if (tenantId == MultitenantConstants.SUPER_TENANT_ID) { ServerConfigurationService config = this.getServerConfigService(); String password = config @@ -437,6 +525,73 @@ public PrivateKey getDefaultPrivateKey() throws Exception { "available only for the super tenant."); } + /** + * This method loads the private key of a given tenant key store. + * + * @param keyStoreName Name of the tenant key store. + * @param alias Alias of the private key. + * @return Private key corresponding to the alias. + */ + private PrivateKey getTenantPrivateKey(String keyStoreName, String alias) throws Exception { + + if (log.isDebugEnabled()) { + log.debug("Loading private key from tenant key store : " + keyStoreName + " alias: " + alias); + } + String path = RegistryResources.SecurityManagement.KEY_STORES + "/" + keyStoreName; + org.wso2.carbon.registry.api.Resource resource; + KeyStore keyStore; + + if (registry.resourceExists(path)) { + resource = registry.get(path); + } else { + throw new SecurityException("Given Key store is not available in registry : " + keyStoreName); + } + + CryptoUtil cryptoUtil = CryptoUtil.getDefaultCryptoUtil(); + String encryptedPassword = resource + .getProperty(RegistryResources.SecurityManagement.PROP_PRIVATE_KEY_PASS); + String privateKeyPasswd = new String(cryptoUtil.base64DecodeAndDecrypt(encryptedPassword)); + + if (isCachedTenantKeyStoreValid(keyStoreName)) { + keyStore = tenantKeyStores.get(keyStoreName).getKeyStore(); + } else { + byte[] bytes = (byte[]) resource.getContent(); + String keyStorePassword = new String(cryptoUtil.base64DecodeAndDecrypt(resource.getProperty( + RegistryResources.SecurityManagement.PROP_PASSWORD))); + keyStore = KeyStore.getInstance(resource + .getProperty(RegistryResources.SecurityManagement.PROP_TYPE)); + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + keyStore.load(stream, keyStorePassword.toCharArray()); + + KeyStoreBean keyStoreBean = new KeyStoreBean(keyStore, resource.getLastModified()); + updateTenantKeyStoreCache(keyStoreName, keyStoreBean); + } + + return (PrivateKey) keyStore.getKey(alias, privateKeyPasswd.toCharArray()); + } + + /** + * Get the private key of a given custom keystore. + * + * @param keyStoreName Name of the custom key store. Must include the prefix "CustomKeyStore/". + * @return Private key from custom keystore corresponding to the alias. + * @throws Exception If failed to retrive the requested private key. + */ + private PrivateKey getCustomKeyStorePrivateKey(String keyStoreName) throws Exception { + + OMElement config = KeyStoreUtil.getCustomKeyStoreConfigElement(keyStoreName, this.getServerConfigService()); + + String password = KeyStoreUtil.getCustomKeyStoreConfig( + config, RegistryResources.SecurityManagement.CustomKeyStore.PROP_PASSWORD); + String alias = KeyStoreUtil.getCustomKeyStoreConfig( + config, RegistryResources.SecurityManagement.CustomKeyStore.PROP_KEY_ALIAS); + + if (log.isDebugEnabled()) { + log.debug("Loading private key from custom key store : " + keyStoreName + " alias: " + alias); + } + return (PrivateKey) getCustomKeyStore(keyStoreName).getKey(alias, password.toCharArray()); + } + /** * Get default pub. key * @@ -477,6 +632,10 @@ public String getPrimaryPrivateKeyPasssword() throws CarbonException { * @throws Exception Permission denied for accessing primary key store */ public X509Certificate getDefaultPrimaryCertificate() throws Exception { + + if (log.isDebugEnabled()) { + log.debug("Loading primary key store public certificate."); + } if (tenantId == MultitenantConstants.SUPER_TENANT_ID) { ServerConfigurationService config = this.getServerConfigService(); String alias = config @@ -487,13 +646,43 @@ public X509Certificate getDefaultPrimaryCertificate() throws Exception { "available only for the super tenant."); } - private boolean isCachedKeyStoreValid(String keyStoreName) { + /** + * This method is used to get public certificates of tenant keystores. + * + * @param keyStoreName Name of the tenant key store. + * @param alias Public certificate alias. + * @return Public certificate of a given tenant keystore. + * @throws Exception If failed to retrive the requested public certificate from the tenant key store. + */ + private Certificate getTenantCertificate(String keyStoreName, String alias) throws Exception { + + return getTenantKeyStore(keyStoreName).getCertificate(alias); + } + + /** + * This method is used to get public certificates of custom keystores. + * + * @param keyStoreName Custom key store name. + * @return Public certificate of a given custom keystore + * @throws Exception If failed to retrive the requested public certificate from the custom key store. + */ + private Certificate getCustomKeyStoreCertificate(String keyStoreName) throws Exception { + + OMElement config = KeyStoreUtil.getCustomKeyStoreConfigElement(keyStoreName, this.getServerConfigService()); + String alias = KeyStoreUtil.getCustomKeyStoreConfig( + config, RegistryResources.SecurityManagement.CustomKeyStore.PROP_KEY_ALIAS); + + return getCustomKeyStore(keyStoreName).getCertificate(alias); + } + + private boolean isCachedTenantKeyStoreValid(String keyStoreName) { + String path = RegistryResources.SecurityManagement.KEY_STORES + "/" + keyStoreName; boolean cachedKeyStoreValid = false; try { - if (loadedKeyStores.containsKey(keyStoreName)) { + if (tenantKeyStores.containsKey(keyStoreName)) { org.wso2.carbon.registry.api.Resource metaDataResource = registry.get(path); - KeyStoreBean keyStoreBean = loadedKeyStores.get(keyStoreName); + KeyStoreBean keyStoreBean = tenantKeyStores.get(keyStoreName); if (keyStoreBean.getLastModifiedDate().equals(metaDataResource.getLastModified())) { cachedKeyStoreValid = true; } @@ -512,15 +701,17 @@ private boolean isCachedKeyStoreValid(String keyStoreName) { return cachedKeyStoreValid; } - private void updateKeyStoreCache(String keyStoreName, KeyStoreBean keyStoreBean) { - if (loadedKeyStores.containsKey(keyStoreName)) { - loadedKeyStores.replace(keyStoreName, keyStoreBean); + private void updateTenantKeyStoreCache(String keyStoreName, KeyStoreBean keyStoreBean) { + + if (tenantKeyStores.containsKey(keyStoreName)) { + tenantKeyStores.replace(keyStoreName, keyStoreBean); } else { - loadedKeyStores.put(keyStoreName, keyStoreBean); + tenantKeyStores.put(keyStoreName, keyStoreBean); } } public KeyStore loadKeyStoreFromFileSystem(String keyStorePath, String password, String type) { + CarbonUtils.checkSecurity(); String absolutePath = new File(keyStorePath).getAbsolutePath(); FileInputStream inputStream = null; diff --git a/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreUtil.java b/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreUtil.java index a594bfd04f4..70d9ee52038 100644 --- a/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreUtil.java +++ b/core/org.wso2.carbon.core/src/main/java/org/wso2/carbon/core/util/KeyStoreUtil.java @@ -17,15 +17,22 @@ */ package org.wso2.carbon.core.util; +import org.apache.axiom.om.OMElement; import org.apache.axis2.AxisFault; +import org.apache.axis2.util.XMLUtils; +import org.wso2.carbon.CarbonException; import org.wso2.carbon.base.api.ServerConfigurationService; import org.wso2.carbon.core.RegistryResources; import org.wso2.carbon.core.internal.CarbonCoreDataHolder; +import org.wso2.carbon.utils.ServerConstants; import java.io.File; import java.security.KeyStore; import java.security.cert.Certificate; +import java.util.Arrays; import java.util.Enumeration; +import java.util.Iterator; +import javax.xml.namespace.QName; public class KeyStoreUtil { @@ -111,4 +118,121 @@ public static Certificate getCertificate(String alias, KeyStore store) throws Ax } } + /** + * This method checks if the given key store name is for a custom key store by checking the prefix. + * + * @param keyStoreName Custom key store name. + * @return Boolean value indicating if the given name is for a custom key store. + */ + public static boolean isCustomKeyStore(String keyStoreName) { + + return keyStoreName.startsWith(RegistryResources.SecurityManagement.CustomKeyStore.CUSTOM_KEYSTORE_PREFIX); + } + + /** + * This method builds the QName object for a configuration within the carbon.xml namespace. + * + * @param localPart Local part of the configuration. + * @return QName object. + */ + public static QName getQNameWithCarbonNS(String localPart) { + + return new QName(ServerConstants.CARBON_SERVER_XML_NAMESPACE, localPart); + } + + /** + * This method returns the OMElement object corresponding to the custom key store configuration in Carbon.xml file. + * + * @param keyStoreName Name of the custom key store. + * @param serverConfiguration ServerConfigurationService instance. + * @return OMElement object for the requested configuration. + * @throws CarbonException If failed to retrieve the requested configuration. + */ + public static OMElement getCustomKeyStoreConfigElement( + String keyStoreName, ServerConfigurationService serverConfiguration) throws CarbonException { + + // Remove prefix if exists. + if (KeyStoreUtil.isCustomKeyStore(keyStoreName)) { + keyStoreName = keyStoreName.substring( + RegistryResources.SecurityManagement.CustomKeyStore.CUSTOM_KEYSTORE_PREFIX.length()); + } + + OMElement config = null; + try { + OMElement customKeyStoresConfigElem = XMLUtils.toOM(serverConfiguration.getDocumentElement()) + .getFirstChildWithName(KeyStoreUtil.getQNameWithCarbonNS( + RegistryResources.SecurityManagement.CustomKeyStore.ELEM_SECURITY)) + .getFirstChildWithName(KeyStoreUtil.getQNameWithCarbonNS( + RegistryResources.SecurityManagement.CustomKeyStore.ELEM_CUSTOM_KEYSTORES)); + + Iterator iterator = customKeyStoresConfigElem.getChildElements(); + while (iterator.hasNext()) { + OMElement customKeyStoreConfig = iterator.next(); + String location = customKeyStoreConfig.getFirstChildWithName(KeyStoreUtil.getQNameWithCarbonNS( + RegistryResources.SecurityManagement.CustomKeyStore.PROP_LOCATION)).getText(); + + if (location.endsWith(keyStoreName)) { + config = customKeyStoreConfig; + break; + } + } + } catch (Exception e) { + throw new CarbonException("Error occurred while reading custom keystore configuration.", e); + } + + if (config == null) { + throw new CarbonException("No configuration found for custom key store : " + keyStoreName); + } + + return config; + } + + /** + * This method returns the requested key store property value from the configuration. + * + * @param config OMElement object of configuration. + * @param propertyName Property name. + * @return Configuration value as String. + * @throws CarbonException If failed to retrieve the requested configuration. + */ + public static String getCustomKeyStoreConfig(OMElement config, String propertyName) throws CarbonException { + + validateKeyStoreConfigName(propertyName); + + String configValue = config.getFirstChildWithName(getQNameWithCarbonNS(propertyName)).getText(); + + if (RegistryResources.SecurityManagement.CustomKeyStore.PROP_LOCATION.equals(propertyName)) { + // Replace "{$carbon.home}" placeholder with proper location path + if (configValue.startsWith(RegistryResources.SecurityManagement.CARBON_HOME_PLACEHOLDER)) { + configValue = configValue.replace(RegistryResources.SecurityManagement.CARBON_HOME_PLACEHOLDER, ""); + configValue = new File(".").getAbsolutePath() + configValue; + } else { + throw new CarbonException("Invalid key store location: " + configValue); + } + } + + return configValue; + } + + /** + * This method validates the requested key store configuration name. + * Only 'Location', 'Type', 'Password', 'KeyAlias' and 'KeyPassword' are valid key store configuration names. + * + * @param propertyName Requested key store configuration name. + * @throws CarbonException If the requested key store configuration is invalid. + */ + public static void validateKeyStoreConfigName(String propertyName) throws CarbonException { + + String[] keyStoreProperties = { + RegistryResources.SecurityManagement.CustomKeyStore.PROP_LOCATION, + RegistryResources.SecurityManagement.CustomKeyStore.PROP_TYPE, + RegistryResources.SecurityManagement.CustomKeyStore.PROP_PASSWORD, + RegistryResources.SecurityManagement.CustomKeyStore.PROP_KEY_ALIAS, + RegistryResources.SecurityManagement.CustomKeyStore.PROP_KEY_PASSWORD + }; + + if (!Arrays.asList(keyStoreProperties).contains(propertyName)) { + throw new CarbonException("Requested key store configuration is invalid."); + } + } } diff --git a/core/org.wso2.carbon.core/src/test/java/org/wso2/carbon/core/util/KeyStoreUtilTest.java b/core/org.wso2.carbon.core/src/test/java/org/wso2/carbon/core/util/KeyStoreUtilTest.java new file mode 100644 index 00000000000..34d131487f4 --- /dev/null +++ b/core/org.wso2.carbon.core/src/test/java/org/wso2/carbon/core/util/KeyStoreUtilTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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 org.wso2.carbon.core.util; + +import org.apache.axiom.om.OMElement; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.wso2.carbon.CarbonException; +import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.utils.ServerConstants; + +import javax.xml.namespace.QName; + +import java.nio.file.Paths; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +public class KeyStoreUtilTest { + + private static final String basedir = Paths.get("").toAbsolutePath().toString(); + private static final String testDir = Paths.get(basedir, "src", "test", "resources").toString(); + + @DataProvider(name = "KeyStoreNameDataProvider") + public Object[][] keyStoreNameDataProvider() { + + return new Object[][] { + {"CUSTOM/myKeyStore", true}, + {"CUSTOM/@#$_keyStore", true}, + {"abcd", false}, + {"", false}, + {" ", false}, + }; + } + + @Test(dataProvider = "KeyStoreNameDataProvider") + public void testIsCustomKeyStore(String keyStoreName, boolean expectedResult) { + + assertEquals(KeyStoreUtil.isCustomKeyStore(keyStoreName), expectedResult); + } + + @DataProvider(name = "QNameWithCarbonNSDataProvider") + public String[] qNameWithCarbonNSDataProvider() { + + return new String[] { + "localPart", + "", + }; + } + + @Test(dataProvider = "QNameWithCarbonNSDataProvider") + public void testGetQNameWithCarbonNS(String localPart) { + + QName qName = KeyStoreUtil.getQNameWithCarbonNS(localPart); + assertEquals(ServerConstants.CARBON_SERVER_XML_NAMESPACE, qName.getNamespaceURI()); + assertEquals(localPart, qName.getLocalPart()); + } + + @DataProvider(name = "CustomKeyStoreConfigDataProvider") + public String[][] customKeyStoreConfigDataProvider() { + + return new String[][] { + {"custom.jks", "Location", basedir + "/./repository/resources/security/custom.jks"}, + {"custom.jks", "Type", "JKS"}, + {"custom.jks", "Password", "customPassword"}, + {"custom.jks", "KeyAlias", "customAlias"}, + {"custom.jks", "KeyPassword", "customKeyPass"}, + {"testKey.jks", "Location", basedir + "/./repository/resources/security/testKey.jks"}, + {"testKey.jks", "Type", "JKS"}, + {"testKey.jks", "Password", "testPass"}, + {"testKey.jks", "KeyAlias", "testKey"}, + {"testKey.jks", "KeyPassword", "testKeyPass"} + }; + } + + // This test covers both getCustomKeyStoreConfigElement and getCustomKeyStoreConfig methods + @Test(dataProvider = "CustomKeyStoreConfigDataProvider") + public void testReadCustomKeyStoreConfigs(String keyStoreName, String configName, String expectedValue) { + + try { + String serverConfigPath = Paths.get(testDir, "carbon.xml").toString(); + ServerConfiguration serverConfiguration = ServerConfiguration.getInstance(); + serverConfiguration.forceInit(serverConfigPath); + OMElement keyStoreConfigElement = KeyStoreUtil.getCustomKeyStoreConfigElement(keyStoreName, serverConfiguration); + assertEquals(KeyStoreUtil.getCustomKeyStoreConfig(keyStoreConfigElement, configName), expectedValue); + } catch (Exception e) { + fail(); + } + } + + @DataProvider(name = "CorrectKeyStoreConfigDataProvider") + public String[] correctKeyStoreConfigDataProvider() { + + return new String[] { + "Location", + "Type", + "Password", + "KeyAlias", + "KeyPassword" + }; + } + + @Test(dataProvider = "CorrectKeyStoreConfigDataProvider") + public void testValidateCorrectKeyStoreConfigNames(String configName) { + + try { + KeyStoreUtil.validateKeyStoreConfigName(configName); + } catch (CarbonException e) { + fail(); + } + } + + @DataProvider(name = "IncorrectKeyStoreConfigDataProvider") + public String[] incorrectKeyStoreConfigDataProvider() { + + return new String[] { + "ABCD", + "", + " " + }; + } + + @Test(dataProvider = "IncorrectKeyStoreConfigDataProvider", expectedExceptions = CarbonException.class) + public void testValidateIncorrectKeyStoreConfigName(String configName) throws Exception { + + KeyStoreUtil.validateKeyStoreConfigName(configName); + } +} diff --git a/core/org.wso2.carbon.core/src/test/resources/carbon.xml b/core/org.wso2.carbon.core/src/test/resources/carbon.xml new file mode 100644 index 00000000000..3a375a3edcc --- /dev/null +++ b/core/org.wso2.carbon.core/src/test/resources/carbon.xml @@ -0,0 +1,657 @@ + + + + + + + + ${product.name} + + + ${product.key} + + + ${product.version} + + + + + + + + + local:/${carbon.context}/services/ + + + + + + + ${default.server.role} + + + + + + + org.wso2.carbon + + + / + + + + + + + + + 15 + + + + + + + + + 0 + + + + + 9999 + + 11111 + + + + + + 10389 + + 8000 + + + + + + 10500 + + + + + + + + + org.wso2.carbon.tomcat.jndi.CarbonJavaURLContextFactory + + + + + + + + + java + + + + + + + + + + false + + + false + + + 600 + + + + false + + + + + + + + 30 + + + + + + + + + 15 + + + + + + ${carbon.home}/repository/deployment/server/ + + + 15 + + + ${carbon.home}/repository/conf/axis2/axis2.xml + + + 30000 + + + ${carbon.home}/repository/deployment/client/ + + ${carbon.home}/repository/conf/axis2/axis2_client.xml + + true + + + + + + + + + + admin + Default Administrator Role + + + user + Default User Role + + + + + + + + + + + + + ${carbon.home}/repository/resources/security/wso2carbon.p12 + + PKCS12 + + wso2carbon + + wso2carbon + + wso2carbon + + + + + PKCS12 + + + + + + ${carbon.home}/repository/resources/security/client-truststore.p12 + + PKCS12 + + wso2carbon + + + // Custom KeyStore configurations for KeyStoreUtilTest test cases. + + + + ${carbon.home}/repository/resources/security/custom.jks + + JKS + + customPassword + + customAlias + + customKeyPass + + + + ${carbon.home}/repository/resources/security/testKey.jks + + JKS + + testPass + + testKey + + testKeyPass + + + + + + + + + + + + + + + + + + + + UserManager + + + false + + + + + + true + allow + + + + + + + + + claim_mgt_menu + identity_mgt_emailtemplate_menu + identity_security_questions_menu + + + + ${carbon.home}/tmp/work + + + + + + true + + + 10 + + + 30 + + + + + + 100 + + + + dbs + + org.wso2.carbon.ui.transports.fileupload.DBSFileUploadExecutor + + + + + + + + + + info + org.wso2.carbon.core.transports.util.InfoProcessor + + + wsdl + org.wso2.carbon.core.transports.util.Wsdl11Processor + + + wsdl2 + org.wso2.carbon.core.transports.util.Wsdl20Processor + + + xsd + org.wso2.carbon.core.transports.util.XsdProcessor + + + + + + false + false + true + svn + http://svnrepo.example.com/repos/ + username + password + true + + + + + + + + + + + + + + + ${require.carbon.servlet} + + + true + + + + + + + default repository + http://product-dist.wso2.com/p2/carbon/releases/wilkes/ + + + + + + + + true + + + + + + true + + diff --git a/core/org.wso2.carbon.core/src/test/resources/testng.xml b/core/org.wso2.carbon.core/src/test/resources/testng.xml index 9e60b4c8c4c..212781ea3e5 100644 --- a/core/org.wso2.carbon.core/src/test/resources/testng.xml +++ b/core/org.wso2.carbon.core/src/test/resources/testng.xml @@ -24,6 +24,7 @@ + \ No newline at end of file diff --git a/distribution/kernel/carbon-home/repository/resources/conf/templates/repository/conf/carbon.xml.j2 b/distribution/kernel/carbon-home/repository/resources/conf/templates/repository/conf/carbon.xml.j2 index 8c51678080b..974389bd98d 100644 --- a/distribution/kernel/carbon-home/repository/resources/conf/templates/repository/conf/carbon.xml.j2 +++ b/distribution/kernel/carbon-home/repository/resources/conf/templates/repository/conf/carbon.xml.j2 @@ -554,6 +554,25 @@ {{keystore.saml.key_password}} + {% if keystore.custom is defined %} + + + {% for keystore in keystore.custom %} + + + ${carbon.home}/repository/resources/security/{{keystore.file_name}} + + {{keystore.type}} + + {{keystore.password}} + + {{keystore.alias}} + + {{keystore.key_password}} + + {% endfor %} + + {% endif %} {{keystore.userstore_password_encryption}}