From f8e8c2652d04a8b1716c71302664c80e285204e4 Mon Sep 17 00:00:00 2001 From: thumimku Date: Tue, 10 Dec 2024 08:36:18 +0530 Subject: [PATCH 1/7] methods for create and call context specific tenanted keystores --- .../core/IdentityKeyStoreResolver.java | 56 ++- .../util/IdentityKeyStoreResolverUtil.java | 29 ++ .../identity/core/util/IdentityUtil.java | 119 +++++-- .../org.wso2.carbon.security.mgt/pom.xml | 11 +- .../internal/SecurityMgtServiceComponent.java | 4 + .../service/IdentityKeyStoreGenerator.java | 9 + .../IdentityKeyStoreGeneratorImpl.java | 319 ++++++++++++++++++ pom.xml | 15 + 8 files changed, 538 insertions(+), 24 deletions(-) create mode 100644 components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java create mode 100644 components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java index 1c05a79b5182..a9145147fff3 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java @@ -246,6 +246,40 @@ public Key getPrivateKey(String tenantDomain, InboundProtocol inboundProtocol) return getPrivateKey(tenantDomain); } + /** + * Return Public Certificate of the Primary or tenant keystore according to given tenant domain. + * + * @param tenantDomain Tenant domain. + * @return Public Certificate of Primary or tenant keystore. + * @throws IdentityKeyStoreResolverException the exception in the IdentityKeyStoreResolver class. + */ + private Certificate getCertificate(String tenantDomain, String context) throws IdentityKeyStoreResolverException { + + if (StringUtils.isBlank(context)) { + getCertificate(tenantDomain); + } + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + if (publicCerts.containsKey(String.valueOf(tenantId + "_" + context))) { + return publicCerts.get(String.valueOf(tenantId + "_" + context)); + } + + KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); + Certificate publicCert; + try { + String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain, context); + publicCert = keyStoreManager.getCertificate(tenantKeyStoreName, tenantDomain + "_"+ context); + + } catch (Exception e) { + throw new IdentityKeyStoreResolverException( + ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getCode(), + String.format(ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getDescription(), + tenantDomain), e); + } + + publicCerts.put(String.valueOf(tenantId + "_" + context), publicCert); + return publicCert; + } + /** * Return Public Certificate of the Primary or tenant keystore according to given tenant domain. * @@ -288,14 +322,18 @@ private Certificate getCertificate(String tenantDomain) throws IdentityKeyStoreR * @return Public Certificate of the Primary, tenant or custom keystore. * @throws IdentityKeyStoreResolverException the exception in the IdentityKeyStoreResolver class. */ - public Certificate getCertificate(String tenantDomain, InboundProtocol inboundProtocol) + public Certificate getCertificate(String tenantDomain, InboundProtocol inboundProtocol, String suffix) throws IdentityKeyStoreResolverException { + if (StringUtils.isEmpty(tenantDomain)) { throw new IdentityKeyStoreResolverException( ErrorMessages.ERROR_CODE_INVALID_ARGUMENT.getCode(), String.format(ErrorMessages.ERROR_CODE_INVALID_ARGUMENT.getDescription(), "Tenant domain")); } + if (suffix != null) { + return getCertificate(tenantDomain, suffix); + } if (inboundProtocol == null) { return getCertificate(tenantDomain); } @@ -326,13 +364,27 @@ public Certificate getCertificate(String tenantDomain, InboundProtocol inboundPr throw new IdentityKeyStoreResolverException( ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_CUSTOM_PUBLIC_CERTIFICATE.getCode(), String.format(ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_CUSTOM_PUBLIC_CERTIFICATE - .getDescription(), keyStoreName), e); + .getDescription(), keyStoreName), e); } } } return getCertificate(tenantDomain); } + /** + * Return Public Certificate of the Primary, tenant or custom keystore. + * + * @param tenantDomain Tenant domain. + * @param inboundProtocol Inbound authentication protocol of the application. + * @return Public Certificate of the Primary, tenant or custom keystore. + * @throws IdentityKeyStoreResolverException the exception in the IdentityKeyStoreResolver class. + */ + public Certificate getCertificate(String tenantDomain, InboundProtocol inboundProtocol) + throws IdentityKeyStoreResolverException { + + return getCertificate(tenantDomain, inboundProtocol, null); + } + /** * Return Public Key of the Primary or tenant keystore according to given tenant domain. * diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java index 98295af64c9b..500fe21e8eb2 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java @@ -38,12 +38,41 @@ public class IdentityKeyStoreResolverUtil { */ public static String buildTenantKeyStoreName(String tenantDomain) throws IdentityKeyStoreResolverException { + return buildTenantKeyStoreName(tenantDomain, null); + } + + /** + * Builds the keystore name for a given tenant domain and context. + * + * The tenant domain is sanitized by replacing dots (.) with hyphens (-) to ensure compatibility + * with keystore naming conventions. If a context is provided, it is appended to the sanitized + * tenant domain with an underscore (_). The method also appends the standard keystore file + * extension as defined in {@link IdentityKeyStoreResolverConstants}. + * + * @param tenantDomain The domain name of the tenant (e.g., "example.com"). + * @param context The optional context to append to the tenant keystore name. + * @return A sanitized and formatted keystore name for the tenant. + * @throws IdentityKeyStoreResolverException If the tenant domain is null, empty, or invalid. + */ + public static String buildTenantKeyStoreName(String tenantDomain, String context) + throws IdentityKeyStoreResolverException { + + // Validate tenantDomain argument if (StringUtils.isEmpty(tenantDomain)) { throw new IdentityKeyStoreResolverException( ErrorMessages.ERROR_CODE_INVALID_ARGUMENT.getCode(), String.format(ErrorMessages.ERROR_CODE_INVALID_ARGUMENT.getDescription(), "Tenant domain")); } + + // Sanitize tenant domain: replace '.' with '-' String ksName = tenantDomain.trim().replace(".", "-"); + + // Append context if provided + if (!StringUtils.isEmpty(context)) { + ksName = ksName + "_" + context.trim(); + } + + // Add the keystore extension return ksName + IdentityKeyStoreResolverConstants.KEY_STORE_EXTENSION; } diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java index 7d9a51f7e6cd..2b92d586f6b7 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java @@ -1969,6 +1969,50 @@ public static boolean isSCIM2UserMaxItemsPerPageEnabled() { return Boolean.parseBoolean(scim2UserMaxItemsPerPageEnabledProperty); } + /** + * Validates the provided signature for the given data using the public key of a specified tenant. + * + * The method retrieves the public key for the tenant from the certificate stored in the tenant's keystore. + * If a context is provided, the method attempts to retrieve the certificate within that context. + * + * @param data The data to validate the signature against. + * @param signature The signature to be validated. + * @param tenantDomain The domain name of the tenant whose public key should be used for validation. + * @param context The optional context for retrieving the tenant's certificate (can be null or blank). + * @return True if the signature is valid; false otherwise. + * @throws SignatureException If an error occurs while validating the signature or accessing tenant data. + */ + public static boolean validateSignatureFromTenant(String data, byte[] signature, String tenantDomain, + String context) throws SignatureException { + + // Retrieve tenant ID based on the tenant domain + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + try { + // Initialize the tenant's registry + IdentityTenantUtil.initializeRegistry(tenantId); + + // Retrieve the tenant's public key + PublicKey publicKey; + if (StringUtils.isBlank(context)) { + // Fetch certificate without context if context is null or blank + publicKey = IdentityKeyStoreResolver.getInstance() + .getCertificate(tenantDomain, null) + .getPublicKey(); + } else { + // Fetch certificate within the provided context + publicKey = IdentityKeyStoreResolver.getInstance() + .getCertificate(tenantDomain, null, context) + .getPublicKey(); + } + + // Validate the signature using the retrieved public key + return SignatureUtil.validateSignature(data, signature, publicKey); + } catch (IdentityException e) { + // Log and throw an exception if an error occurs + throw new SignatureException("Error while validating the signature for tenant: " + tenantDomain, e); + } + } + /** * Validates the signature of the given data for the specified tenant domain. * @@ -1981,48 +2025,81 @@ public static boolean isSCIM2UserMaxItemsPerPageEnabled() { public static boolean validateSignatureFromTenant(String data, byte[] signature, String tenantDomain) throws SignatureException { - int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); - try { - IdentityTenantUtil.initializeRegistry(tenantId); - PublicKey publicKey = IdentityKeyStoreResolver.getInstance().getCertificate(tenantDomain, null) - .getPublicKey(); - return SignatureUtil.validateSignature(data, signature, publicKey); - } catch (IdentityException e) { - throw new SignatureException("Error while validating the signature from tenant: " + tenantDomain, e); - } + return validateSignatureFromTenant(data, signature, tenantDomain, null); } /** - * Sign the given data for the specified tenant domain. + * Signs the given data using the private key of the specified tenant. + * + * For super tenant domains, the default private key is used. For other tenants, the method retrieves the private + * key from the tenant's keystore. If a context is provided, it will attempt to retrieve the private key associated + * with that context. * * @param data The data to be signed. - * @param tenantDomain The tenant domain to which the data belongs. - * @return The signature of the data. - * @throws SignatureException If an error occurs during the signature generation process. + * @param tenantDomain The domain name of the tenant whose private key will be used for signing. + * @param context The optional context for retrieving the tenant's private key (can be null or blank). + * @return A byte array containing the signature for the provided data. + * @throws SignatureException If an error occurs while retrieving the private key or signing the data. */ - public static byte[] signWithTenantKey(String data, String tenantDomain) throws SignatureException { + public static byte[] signWithTenantKey(String data, String tenantDomain, String context) throws SignatureException { + // Get tenant ID from tenant domain int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); PrivateKey privateKey; if (MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { try { - privateKey = keyStoreManager.getDefaultPrivateKey(); + String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain, context); + // Retrieve private key from the tenant's keystore + if (StringUtils.isBlank(context)) { + // Retrieve default private key for the super tenant + privateKey = keyStoreManager.getDefaultPrivateKey(); + } else { + privateKey = (PrivateKey) keyStoreManager.getPrivateKey(tenantKeyStoreName, + tenantDomain + "_" + context); + } + } catch (Exception e) { - throw new SignatureException(String.format(IdentityKeyStoreResolverConstants.ErrorMessages - .ERROR_CODE_ERROR_RETRIEVING_TENANT_PRIVATE_KEY.getDescription(), tenantDomain), - e); + throw new SignatureException(String.format( + IdentityKeyStoreResolverConstants.ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PRIVATE_KEY + .getDescription(), + tenantDomain), e); } } else { try { - String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain); + // Build tenant keystore name + String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain, context); + + // Initialize the tenant's registry IdentityTenantUtil.initializeRegistry(tenantId); - privateKey = (PrivateKey) keyStoreManager.getPrivateKey(tenantKeyStoreName, tenantDomain); + + // Retrieve private key from the tenant's keystore + if (StringUtils.isBlank(context)) { + privateKey = (PrivateKey) keyStoreManager.getPrivateKey(tenantKeyStoreName, tenantDomain); + } else { + privateKey = (PrivateKey) keyStoreManager.getPrivateKey(tenantKeyStoreName, + tenantDomain + "_" + context); + } } catch (IdentityException e) { - throw new SignatureException("Error while signing from private key of tenant: " + tenantDomain, e); + throw new SignatureException("Error while retrieving the private key for tenant: " + tenantDomain, e); } } + + // Sign the data with the retrieved private key return SignatureUtil.doSignature(data, privateKey); } + + /** + * Sign the given data for the specified tenant domain. + * + * @param data The data to be signed. + * @param tenantDomain The tenant domain to which the data belongs. + * @return The signature of the data. + * @throws SignatureException If an error occurs during the signature generation process. + */ + public static byte[] signWithTenantKey(String data, String tenantDomain) throws SignatureException { + + return signWithTenantKey(data, tenantDomain, null); + } } diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/pom.xml b/components/security-mgt/org.wso2.carbon.security.mgt/pom.xml index 2933841a8f9e..45674b4f0bfe 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/pom.xml +++ b/components/security-mgt/org.wso2.carbon.security.mgt/pom.xml @@ -106,6 +106,14 @@ org.wso2.orbit.javax.xml.bind jaxb-api + + org.wso2.orbit.org.bouncycastle + bcpkix-jdk18on + + + org.wso2.orbit.org.bouncycastle + bcprov-jdk18on + @@ -156,7 +164,8 @@ org.wso2.carbon.registry.core.*;version="${carbon.kernel.registry.imp.pkg.version}", org.wso2.carbon.registry.api;version="${carbon.kernel.registry.imp.pkg.version}", org.wso2.carbon.identity.core.*; version="${carbon.identity.package.import.version.range}", - org.wso2.carbon.identity.base; version="${carbon.identity.package.import.version.range}" + org.wso2.carbon.identity.base; version="${carbon.identity.package.import.version.range}", + org.bouncycastle.*;version="${org.bouncycastle.imp.pkg.version.range}", !org.wso2.carbon.security.internal, diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/internal/SecurityMgtServiceComponent.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/internal/SecurityMgtServiceComponent.java index fe1cea36fcaa..638d41d6d4af 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/internal/SecurityMgtServiceComponent.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/internal/SecurityMgtServiceComponent.java @@ -42,6 +42,8 @@ import org.wso2.carbon.security.SecurityServiceHolder; import org.wso2.carbon.security.keystore.KeyStoreManagementService; import org.wso2.carbon.security.keystore.KeyStoreManagementServiceImpl; +import org.wso2.carbon.security.keystore.service.IdentityKeyStoreGenerator; +import org.wso2.carbon.security.keystore.service.IdentityKeyStoreGeneratorImpl; import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.utils.ConfigurationContextService; @@ -66,6 +68,8 @@ protected void activate(ComponentContext ctxt) { BundleContext bundleCtx = ctxt.getBundleContext(); bundleCtx.registerService(KeyStoreManagementService.class.getName(), new KeyStoreManagementServiceImpl(), null); + bundleCtx.registerService(IdentityKeyStoreGenerator.class.getName(), new IdentityKeyStoreGeneratorImpl(), + null); try { addKeystores(); } catch (Exception e) { diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java new file mode 100644 index 000000000000..1852608615aa --- /dev/null +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java @@ -0,0 +1,9 @@ +package org.wso2.carbon.security.keystore.service; + +import org.wso2.carbon.security.keystore.KeyStoreManagementException; + +public interface IdentityKeyStoreGenerator { + + void generateContextKeyStore(String tenantDomain, String suffix) throws KeyStoreManagementException; + +} diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java new file mode 100644 index 000000000000..abc998925cbd --- /dev/null +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java @@ -0,0 +1,319 @@ +/* + * 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.security.keystore.service; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.core.util.CryptoUtil; +import org.wso2.carbon.core.util.KeyStoreManager; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.security.keystore.KeyStoreManagementException; +import org.wso2.carbon.utils.ServerConstants; +import org.wso2.carbon.utils.security.KeystoreUtils; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.security.*; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Implementation of the {@link IdentityKeyStoreGenerator} interface for generating and managing + * context-specific tenant key stores. This class provides functionality to create, manage, and store + * keystores in a multi-tenant environment, adhering to specific cryptographic requirements. + * + *

Key Features:

+ *
    + *
  • Generates keystores for tenants dynamically.
  • + *
  • Supports various cryptographic algorithms and key generation techniques.
  • + *
  • Handles secure persistence of keystores using {@link KeyStoreManager}.
  • + *
  • Ensures thread-safe operations with locking mechanisms for multi-tenant environments.
  • + *
  • Provides explainable methods for certificate creation, storage, and retrieval.
  • + *
+ * + *

Concurrency:

+ * This implementation uses {@link ReentrantLock} to handle concurrent access to keystores for + * specific tenants, ensuring thread safety and data integrity. + * + *

Usage:

+ * This class is intended to be used in environments where context-specific cryptographic needs + * must be met dynamically. + * + *

Exceptions:

+ * The methods in this class throw {@link KeyStoreManagementException} for errors encountered during + * keystore creation, management, or persistence. + */ +public class IdentityKeyStoreGeneratorImpl implements IdentityKeyStoreGenerator{ + + private static final Log LOG = LogFactory.getLog(IdentityKeyStoreGeneratorImpl.class); + private String tenantDomain; + private String password; + private KeyStoreManager keyStoreManager; + private static final String SIGNING_ALG = "Tenant.SigningAlgorithm"; + + // Supported signature algorithms for public certificate generation. + private static final String DSA_SHA1 = "SHA1withDSA"; + private static final String ECDSA_SHA1 = "SHA1withECDSA"; + private static final String ECDSA_SHA256 = "SHA256withECDSA"; + private static final String ECDSA_SHA384 = "SHA384withECDSA"; + private static final String ECDSA_SHA512 = "SHA512withECDSA"; + private static final String RSA_MD5 = "MD5withRSA"; + private static final String RSA_SHA1 = "SHA1withRSA"; + private static final String RSA_SHA256 = "SHA256withRSA"; + private static final String RSA_SHA384 = "SHA384withRSA"; + private static final String RSA_SHA512 = "SHA512withRSA"; + private static final String[] signatureAlgorithms = new String[]{ + DSA_SHA1, ECDSA_SHA1, ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512, RSA_MD5, RSA_SHA1, RSA_SHA256, + RSA_SHA384, RSA_SHA512 + }; + private static final ConcurrentHashMap tenantLocks = new ConcurrentHashMap<>(); + + /** + * This method first generates the keystore, then persist it in the gov.registry of that tenant + * + * @throws KeyStoreManagementException Error when generating or storing the keystore + */ + public void generateContextKeyStore(String tenantDomain, String suffix) throws KeyStoreManagementException { + + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + this.keyStoreManager = KeyStoreManager.getInstance(tenantId); + this.tenantDomain = tenantDomain; + ReentrantLock lock = tenantLocks.computeIfAbsent(tenantId, id -> new ReentrantLock()); + boolean lockAcquired = false; // Track if the lock was acquired + + try { + IdentityTenantUtil.initializeRegistry(tenantId); + if (isSecondaryKeyStoreExists(suffix)) { + return; // KeyStore already exists, no need to create again + } + + lock.lock(); // Acquire the lock + lockAcquired = true; // Mark the lock as acquired + + if (!isSecondaryKeyStoreExists(suffix)) { + // Create the KeyStore + password = generatePassword(); + KeyStore keyStore = KeystoreUtils.getKeystoreInstance( + KeystoreUtils.getKeyStoreFileType(tenantDomain)); + keyStore.load(null, password.toCharArray()); + generateSecondaryKeyPair(keyStore, suffix); + persistSecondaryKeyStore(keyStore, suffix); + } + + } catch (Exception e) { + String msg = "Error while instantiating a keystore"; + LOG.error(msg, e); + throw new KeyStoreManagementException(msg, e); + } finally { + if (lockAcquired) { // Only release the lock if it was acquired + lock.unlock(); + } + tenantLocks.remove(tenantId); // Clean up locks for this tenant if not needed anymore + } + } + + + private boolean isSecondaryKeyStoreExists(String suffix) { + + String keyStoreName = KeystoreUtils.getKeyStoreFileLocation(tenantDomain + "_" + suffix); + boolean isKeyStoreExists = false; + try { + keyStoreManager.getKeyStore(keyStoreName); + isKeyStoreExists = true; + } catch (Exception e) { + String msg = "Error while checking the existance of keystore. "; + LOG.error(msg + e.getMessage()); + } + return isKeyStoreExists; + } + + /** + * This method is used to generate a random password for the generated keystore + * + * @return generated password + */ + private String generatePassword() { + + SecureRandom random = new SecureRandom(); + String randString = new BigInteger(130, random).toString(12); + return randString.substring(randString.length() - 10); + } + + /** + * This method generates the keypair and stores it in the keystore + * + * @param keyStore A keystore instance + * @throws KeyStoreManagementException Error when generating key pair + */ + private void generateSecondaryKeyPair(KeyStore keyStore, String suffix) + throws KeyStoreManagementException { + try { + CryptoUtil.getDefaultCryptoUtil(); + //generate key pair + String keyGenerationAlgorithm = getKeyGenerationAlgorithm(); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyGenerationAlgorithm); + int keySize = getKeySize(keyGenerationAlgorithm); + if (keySize != 0) { + keyPairGenerator.initialize(keySize); + } + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + // Common Name and alias for the generated certificate + String commonName = "CN=" + tenantDomain +"_" + suffix + ", OU=None, O=None, L=None, C=None"; + + //generate certificates + X500Name distinguishedName = new X500Name(commonName); + + Date notBefore = new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30); + Date notAfter = new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365 * 10)); + + SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); + BigInteger serialNumber = BigInteger.valueOf(new SecureRandom().nextInt()); + + X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder( + distinguishedName, + serialNumber, + notBefore, + notAfter, + distinguishedName, + subPubKeyInfo + ); + + String algorithmName = getSignatureAlgorithm(); + JcaContentSignerBuilder signerBuilder = + new JcaContentSignerBuilder(algorithmName).setProvider(getJCEProvider()); + PrivateKey privateKey = keyPair.getPrivate(); + X509Certificate x509Cert = new JcaX509CertificateConverter().setProvider(getJCEProvider()) + .getCertificate(certificateBuilder.build(signerBuilder.build(privateKey))); + + //add private key to KS + keyStore.setKeyEntry(tenantDomain +"_" + suffix, keyPair.getPrivate(), password.toCharArray(), + new java.security.cert.Certificate[]{x509Cert}); + } catch (Exception ex) { + String msg = "Error while generating the secondary certificate for tenant :" + + tenantDomain + "."; + LOG.error(msg, ex); + throw new KeyStoreManagementException(msg, ex); + } + } + + private static String getKeyGenerationAlgorithm() { + + String signatureAlgorithm = getSignatureAlgorithm(); + // If the algorithm naming format is {digest}with{encryption}, we need to extract the encryption part. + int withIndex = signatureAlgorithm.indexOf("with"); + if (withIndex != -1 && withIndex + 4 < signatureAlgorithm.length()) { + return signatureAlgorithm.substring(withIndex + 4); + } else { + // The algorithm name is same as the encryption algorithm. + // This need to be updated if more algorithms are supported. + return signatureAlgorithm; + } + } + + private static String getSignatureAlgorithm() { + + String algorithm = ServerConfiguration.getInstance().getFirstProperty(SIGNING_ALG); + // Find in a list of supported signature algorithms. + for (String supportedAlgorithm : signatureAlgorithms) { + if (supportedAlgorithm.equalsIgnoreCase(algorithm)) { + return supportedAlgorithm; + } + } + return RSA_MD5; + } + + private static int getKeySize(String algorithm) { + + // Initialize the key size according to the FIPS standard. + // This need to be updated if more algorithms are supported. + if ("ECDSA".equalsIgnoreCase(algorithm)) { + return 384; + } else if ("RSA".equalsIgnoreCase(algorithm) || "DSA".equalsIgnoreCase(algorithm)) { + return 2048; + } + return 0; + } + + private static String getJCEProvider() { + + String provider = ServerConfiguration.getInstance().getFirstProperty(ServerConstants.JCE_PROVIDER); + if (!StringUtils.isBlank(provider)) { + return provider; + } + return ServerConstants.JCE_PROVIDER_BC; + } + + /** + * Persist the keystore in the gov.registry + * + * @param keyStore created Keystore of the tenant + * @throws KeyStoreManagementException Exception when storing the keystore in the registry + */ + private void persistSecondaryKeyStore(KeyStore keyStore, String suffix) + throws KeyStoreManagementException { + + String keyStoreName = generateSecondaryKSNameFromDomainName(suffix); + try { + char[] passwordChar = password.toCharArray(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + keyStore.store(outputStream, passwordChar); + outputStream.flush(); + outputStream.close(); + + keyStoreManager.addKeyStore(outputStream.toByteArray(), keyStoreName, + passwordChar, " ", KeystoreUtils.getKeyStoreFileType(tenantDomain), passwordChar); + } catch (SecurityException e) { + if (e.getMessage() != null && e.getMessage().contains("Key store " + keyStoreName + " already available")) { + + LOG.warn("Key store " + keyStoreName + " is already available, ignoring."); + } else { + + String msg = "Error when adding a keyStore"; + LOG.error(msg, e); + throw new KeyStoreManagementException(msg, e); + } + } catch (Exception e) { + + String msg = "Error when processing keystore/pub. cert to be stored in registry"; + LOG.error(msg, e); + throw new KeyStoreManagementException(msg, e); + } + } + + /** + * This method generates the key store file name from the Domain Name + * @return keystore name. + */ + private String generateSecondaryKSNameFromDomainName(String suffix){ + + String ksName = tenantDomain.trim().replace(".", "-"); + ksName = ksName + "_" + suffix; + return (ksName + KeystoreUtils.getExtensionByFileType(KeystoreUtils.StoreFileType.defaultFileType())); + } +} diff --git a/pom.xml b/pom.xml index ffac22766008..3db549b9a56b 100644 --- a/pom.xml +++ b/pom.xml @@ -922,6 +922,17 @@ ${version.javax.servlet} + + org.wso2.orbit.org.bouncycastle + bcprov-jdk18on + ${bcprov-jdk18.version} + + + org.wso2.orbit.org.bouncycastle + bcpkix-jdk18on + ${bcpkix-jdk18.version} + + org.opensaml @@ -1840,6 +1851,7 @@ ${project.version} [5.14.0, 8.0.0) + [0.0.0,2.0.0) 1.0.90 [1.0.0, 2.0.0) @@ -1950,6 +1962,9 @@ 1.4.1 [1.4.0,1.5.0) + 1.78.1.wso2v1 + 1.78.1.wso2v1 + 1.8.0 3.2.2.wso2v1 From dd985fbca16e48aaa031f0d5afde197d3ffb5cde Mon Sep 17 00:00:00 2001 From: thumimku Date: Tue, 10 Dec 2024 10:05:30 +0530 Subject: [PATCH 2/7] update doc --- .../core/IdentityKeyStoreResolver.java | 36 +++-- .../IdentityKeyStoreResolverConstants.java | 1 + .../util/IdentityKeyStoreResolverUtil.java | 3 +- .../identity/core/util/IdentityUtil.java | 6 +- .../carbon/security/SecurityConstants.java | 2 +- .../service/IdentityKeyStoreGenerator.java | 34 ++++- .../IdentityKeyStoreGeneratorImpl.java | 125 ++++++++++-------- 7 files changed, 134 insertions(+), 73 deletions(-) diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java index a9145147fff3..bd69a83123f4 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java @@ -247,27 +247,39 @@ public Key getPrivateKey(String tenantDomain, InboundProtocol inboundProtocol) } /** - * Return Public Certificate of the Primary or tenant keystore according to given tenant domain. + * Retrieves the public certificate for a given tenant domain and context. + *

+ * This method fetches the public certificate associated with a specific tenant domain and context. + * If the context is blank, it delegates the call to the overloaded + * {@code getCertificate(String tenantDomain)} method. + * The method first checks if the certificate is cached; if not, it retrieves the certificate from + * the KeyStoreManager, caches it, and then returns it. + *

* - * @param tenantDomain Tenant domain. - * @return Public Certificate of Primary or tenant keystore. - * @throws IdentityKeyStoreResolverException the exception in the IdentityKeyStoreResolver class. + * @param tenantDomain the tenant domain for which the certificate is requested. + * @param context the specific context for the tenant's certificate. If blank, the default certificate for the tenant is fetched. + * @return the public certificate for the specified tenant domain and context. + * @throws IdentityKeyStoreResolverException if there is an error while retrieving the certificate. */ + private Certificate getCertificate(String tenantDomain, String context) throws IdentityKeyStoreResolverException { if (StringUtils.isBlank(context)) { getCertificate(tenantDomain); } int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); - if (publicCerts.containsKey(String.valueOf(tenantId + "_" + context))) { - return publicCerts.get(String.valueOf(tenantId + "_" + context)); + if (publicCerts.containsKey(String.valueOf(tenantId + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context))) { + return publicCerts.get(String.valueOf(tenantId + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context)); } KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); Certificate publicCert; try { String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain, context); - publicCert = keyStoreManager.getCertificate(tenantKeyStoreName, tenantDomain + "_"+ context); + publicCert = keyStoreManager.getCertificate(tenantKeyStoreName, tenantDomain + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR+ context); } catch (Exception e) { throw new IdentityKeyStoreResolverException( @@ -276,7 +288,8 @@ private Certificate getCertificate(String tenantDomain, String context) throws I tenantDomain), e); } - publicCerts.put(String.valueOf(tenantId + "_" + context), publicCert); + publicCerts.put(String.valueOf(tenantId + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context), publicCert); return publicCert; } @@ -319,10 +332,11 @@ private Certificate getCertificate(String tenantDomain) throws IdentityKeyStoreR * * @param tenantDomain Tenant domain. * @param inboundProtocol Inbound authentication protocol of the application. + * @param context Context of the keystore. * @return Public Certificate of the Primary, tenant or custom keystore. * @throws IdentityKeyStoreResolverException the exception in the IdentityKeyStoreResolver class. */ - public Certificate getCertificate(String tenantDomain, InboundProtocol inboundProtocol, String suffix) + public Certificate getCertificate(String tenantDomain, InboundProtocol inboundProtocol, String context) throws IdentityKeyStoreResolverException { @@ -331,8 +345,8 @@ public Certificate getCertificate(String tenantDomain, InboundProtocol inboundPr ErrorMessages.ERROR_CODE_INVALID_ARGUMENT.getCode(), String.format(ErrorMessages.ERROR_CODE_INVALID_ARGUMENT.getDescription(), "Tenant domain")); } - if (suffix != null) { - return getCertificate(tenantDomain, suffix); + if (context != null) { + return getCertificate(tenantDomain, context); } if (inboundProtocol == null) { return getCertificate(tenantDomain); diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java index def4bdd725d6..6dee9b4b17c1 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java @@ -41,6 +41,7 @@ public class IdentityKeyStoreResolverConstants { // KeyStore Constants. public static final String KEY_STORE_EXTENSION = ".jks"; + public static final String KEY_STORE_CONTEXT_SEPARATOR = "--"; // Inbound Protocols. public static final String INBOUND_PROTOCOL_OAUTH = "oauth"; diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java index 500fe21e8eb2..8ea682561548 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java @@ -43,7 +43,6 @@ public static String buildTenantKeyStoreName(String tenantDomain) throws Identit /** * Builds the keystore name for a given tenant domain and context. - * * The tenant domain is sanitized by replacing dots (.) with hyphens (-) to ensure compatibility * with keystore naming conventions. If a context is provided, it is appended to the sanitized * tenant domain with an underscore (_). The method also appends the standard keystore file @@ -69,7 +68,7 @@ public static String buildTenantKeyStoreName(String tenantDomain, String context // Append context if provided if (!StringUtils.isEmpty(context)) { - ksName = ksName + "_" + context.trim(); + ksName = ksName + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context.trim(); } // Add the keystore extension diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java index 2b92d586f6b7..0642ed81e1c9 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java @@ -2057,7 +2057,8 @@ public static byte[] signWithTenantKey(String data, String tenantDomain, String privateKey = keyStoreManager.getDefaultPrivateKey(); } else { privateKey = (PrivateKey) keyStoreManager.getPrivateKey(tenantKeyStoreName, - tenantDomain + "_" + context); + tenantDomain + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context); } } catch (Exception e) { @@ -2079,7 +2080,8 @@ public static byte[] signWithTenantKey(String data, String tenantDomain, String privateKey = (PrivateKey) keyStoreManager.getPrivateKey(tenantKeyStoreName, tenantDomain); } else { privateKey = (PrivateKey) keyStoreManager.getPrivateKey(tenantKeyStoreName, - tenantDomain + "_" + context); + tenantDomain + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context); } } catch (IdentityException e) { throw new SignatureException("Error while retrieving the private key for tenant: " + tenantDomain, e); diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/SecurityConstants.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/SecurityConstants.java index 4f50c249076c..a5da81df780a 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/SecurityConstants.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/SecurityConstants.java @@ -131,7 +131,7 @@ public static class KeyStoreMgtConstants { public static final String FILTER_OPERATION_CONTAINS = "co"; public static final String SERVER_TRUSTSTORE_FILE = "Security.TrustStore.Location"; - + public static final String KEY_STORE_CONTEXT_SEPARATOR = "--"; /** * Enum for Keystore management service related errors. */ diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java index 1852608615aa..9e004f5b3949 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java @@ -1,9 +1,39 @@ +/* + * 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.security.keystore.service; import org.wso2.carbon.security.keystore.KeyStoreManagementException; +/** + * Interface for generating and managing context-specific tenant key stores. + */ public interface IdentityKeyStoreGenerator { - void generateContextKeyStore(String tenantDomain, String suffix) throws KeyStoreManagementException; - + /** + * Generates a context-specific KeyStore for a given tenant domain. + *

+ * This method creates a new KeyStore for the specified tenant domain and context if it does not already exist. + *

+ * + * @param tenantDomain the tenant domain for which the KeyStore is to be created. + * @param context the context for which the KeyStore is to be generated. + * @throws KeyStoreManagementException if an error occurs during KeyStore creation or initialization. + */ + void generateContextKeyStore(String tenantDomain, String context) throws KeyStoreManagementException; } diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java index abc998925cbd..6d059c8c6e75 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java @@ -36,12 +36,18 @@ import java.io.ByteArrayOutputStream; import java.math.BigInteger; -import java.security.*; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; +import static org.wso2.carbon.security.SecurityConstants.KeyStoreMgtConstants.KEY_STORE_CONTEXT_SEPARATOR; + /** * Implementation of the {@link IdentityKeyStoreGenerator} interface for generating and managing * context-specific tenant key stores. This class provides functionality to create, manage, and store @@ -94,11 +100,18 @@ public class IdentityKeyStoreGeneratorImpl implements IdentityKeyStoreGenerator{ private static final ConcurrentHashMap tenantLocks = new ConcurrentHashMap<>(); /** - * This method first generates the keystore, then persist it in the gov.registry of that tenant + * Generates a context-specific KeyStore for a given tenant domain. + *

+ * This method creates a new KeyStore for the specified tenant domain and context if it does not already exist. + * It ensures thread safety by using a lock mechanism to avoid concurrent modifications when creating the KeyStore. + * If the KeyStore for the given context already exists, the method exits without performing any operations. + *

* - * @throws KeyStoreManagementException Error when generating or storing the keystore + * @param tenantDomain the tenant domain for which the KeyStore is to be created. + * @param context the context for which the KeyStore is to be generated. + * @throws KeyStoreManagementException if an error occurs during KeyStore creation or initialization. */ - public void generateContextKeyStore(String tenantDomain, String suffix) throws KeyStoreManagementException { + public void generateContextKeyStore(String tenantDomain, String context) throws KeyStoreManagementException { int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); this.keyStoreManager = KeyStoreManager.getInstance(tenantId); @@ -108,21 +121,21 @@ public void generateContextKeyStore(String tenantDomain, String suffix) throws K try { IdentityTenantUtil.initializeRegistry(tenantId); - if (isSecondaryKeyStoreExists(suffix)) { + if (isContextKeyStoreExists(context)) { return; // KeyStore already exists, no need to create again } lock.lock(); // Acquire the lock lockAcquired = true; // Mark the lock as acquired - if (!isSecondaryKeyStoreExists(suffix)) { + if (!isContextKeyStoreExists(context)) { // Create the KeyStore password = generatePassword(); KeyStore keyStore = KeystoreUtils.getKeystoreInstance( KeystoreUtils.getKeyStoreFileType(tenantDomain)); keyStore.load(null, password.toCharArray()); - generateSecondaryKeyPair(keyStore, suffix); - persistSecondaryKeyStore(keyStore, suffix); + generateContextKeyPair(keyStore, context); + persistContextKeyStore(keyStore, context); } } catch (Exception e) { @@ -137,16 +150,16 @@ public void generateContextKeyStore(String tenantDomain, String suffix) throws K } } + private boolean isContextKeyStoreExists(String context) { - private boolean isSecondaryKeyStoreExists(String suffix) { - - String keyStoreName = KeystoreUtils.getKeyStoreFileLocation(tenantDomain + "_" + suffix); + String keyStoreName = KeystoreUtils.getKeyStoreFileLocation(tenantDomain + + KEY_STORE_CONTEXT_SEPARATOR + context); boolean isKeyStoreExists = false; try { keyStoreManager.getKeyStore(keyStoreName); isKeyStoreExists = true; } catch (Exception e) { - String msg = "Error while checking the existance of keystore. "; + String msg = "Error while checking the existence of keystore. "; LOG.error(msg + e.getMessage()); } return isKeyStoreExists; @@ -164,13 +177,50 @@ private String generatePassword() { return randString.substring(randString.length() - 10); } + /** + * Persist the keystore in the gov.registry + * + * @param keyStore created Keystore of the tenant + * @throws KeyStoreManagementException Exception when storing the keystore in the registry + */ + private void persistContextKeyStore(KeyStore keyStore, String context) + throws KeyStoreManagementException { + + String keyStoreName = generateContextKSNameFromDomainName(context); + try { + char[] passwordChar = password.toCharArray(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + keyStore.store(outputStream, passwordChar); + outputStream.flush(); + outputStream.close(); + + keyStoreManager.addKeyStore(outputStream.toByteArray(), keyStoreName, + passwordChar, " ", KeystoreUtils.getKeyStoreFileType(tenantDomain), passwordChar); + } catch (SecurityException e) { + if (e.getMessage() != null && e.getMessage().contains("Key store " + keyStoreName + " already available")) { + + LOG.warn("Key store " + keyStoreName + " is already available, ignoring."); + } else { + + String msg = "Error when adding a keyStore"; + LOG.error(msg, e); + throw new KeyStoreManagementException(msg, e); + } + } catch (Exception e) { + + String msg = "Error when processing keystore/pub. cert to be stored in registry"; + LOG.error(msg, e); + throw new KeyStoreManagementException(msg, e); + } + } + /** * This method generates the keypair and stores it in the keystore * * @param keyStore A keystore instance * @throws KeyStoreManagementException Error when generating key pair */ - private void generateSecondaryKeyPair(KeyStore keyStore, String suffix) + private void generateContextKeyPair(KeyStore keyStore, String context) throws KeyStoreManagementException { try { CryptoUtil.getDefaultCryptoUtil(); @@ -184,7 +234,8 @@ private void generateSecondaryKeyPair(KeyStore keyStore, String suffix) KeyPair keyPair = keyPairGenerator.generateKeyPair(); // Common Name and alias for the generated certificate - String commonName = "CN=" + tenantDomain +"_" + suffix + ", OU=None, O=None, L=None, C=None"; + String commonName = "CN=" + tenantDomain + + KEY_STORE_CONTEXT_SEPARATOR + context + ", OU=None, O=None, L=None, C=None"; //generate certificates X500Name distinguishedName = new X500Name(commonName); @@ -212,10 +263,11 @@ private void generateSecondaryKeyPair(KeyStore keyStore, String suffix) .getCertificate(certificateBuilder.build(signerBuilder.build(privateKey))); //add private key to KS - keyStore.setKeyEntry(tenantDomain +"_" + suffix, keyPair.getPrivate(), password.toCharArray(), + keyStore.setKeyEntry(tenantDomain +KEY_STORE_CONTEXT_SEPARATOR + context, + keyPair.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{x509Cert}); } catch (Exception ex) { - String msg = "Error while generating the secondary certificate for tenant :" + + String msg = "Error while generating the Context certificate for tenant :" + tenantDomain + "."; LOG.error(msg, ex); throw new KeyStoreManagementException(msg, ex); @@ -269,51 +321,14 @@ private static String getJCEProvider() { return ServerConstants.JCE_PROVIDER_BC; } - /** - * Persist the keystore in the gov.registry - * - * @param keyStore created Keystore of the tenant - * @throws KeyStoreManagementException Exception when storing the keystore in the registry - */ - private void persistSecondaryKeyStore(KeyStore keyStore, String suffix) - throws KeyStoreManagementException { - - String keyStoreName = generateSecondaryKSNameFromDomainName(suffix); - try { - char[] passwordChar = password.toCharArray(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - keyStore.store(outputStream, passwordChar); - outputStream.flush(); - outputStream.close(); - - keyStoreManager.addKeyStore(outputStream.toByteArray(), keyStoreName, - passwordChar, " ", KeystoreUtils.getKeyStoreFileType(tenantDomain), passwordChar); - } catch (SecurityException e) { - if (e.getMessage() != null && e.getMessage().contains("Key store " + keyStoreName + " already available")) { - - LOG.warn("Key store " + keyStoreName + " is already available, ignoring."); - } else { - - String msg = "Error when adding a keyStore"; - LOG.error(msg, e); - throw new KeyStoreManagementException(msg, e); - } - } catch (Exception e) { - - String msg = "Error when processing keystore/pub. cert to be stored in registry"; - LOG.error(msg, e); - throw new KeyStoreManagementException(msg, e); - } - } - /** * This method generates the key store file name from the Domain Name * @return keystore name. */ - private String generateSecondaryKSNameFromDomainName(String suffix){ + private String generateContextKSNameFromDomainName(String context){ String ksName = tenantDomain.trim().replace(".", "-"); - ksName = ksName + "_" + suffix; + ksName = ksName + KEY_STORE_CONTEXT_SEPARATOR + context; return (ksName + KeystoreUtils.getExtensionByFileType(KeystoreUtils.StoreFileType.defaultFileType())); } } From 6ca0b3d01b63c8f3f8ee122988af923b204f342b Mon Sep 17 00:00:00 2001 From: thumimku Date: Tue, 10 Dec 2024 18:54:44 +0530 Subject: [PATCH 3/7] address comments --- .../org.wso2.carbon.identity.core/pom.xml | 1 + .../core/IdentityKeyStoreResolver.java | 12 +- .../util/IdentityKeyStoreResolverUtil.java | 5 +- .../identity/core/util/IdentityUtil.java | 2 +- .../core/IdentityKeyStoreResolverTest.java | 7 + .../IdentityKeyStoreResolverUtilTest.java | 22 ++- .../identity/core/util/IdentityUtilTest.java | 5 + .../service/IdentityKeyStoreGenerator.java | 2 +- .../IdentityKeyStoreGeneratorImpl.java | 157 ++++++------------ 9 files changed, 98 insertions(+), 115 deletions(-) diff --git a/components/identity-core/org.wso2.carbon.identity.core/pom.xml b/components/identity-core/org.wso2.carbon.identity.core/pom.xml index 00237270da07..4f6279e5230e 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/pom.xml +++ b/components/identity-core/org.wso2.carbon.identity.core/pom.xml @@ -208,6 +208,7 @@ org.apache.commons.collections; version="${commons-collections.wso2.osgi.version.range}", org.apache.commons.collections4; version = "${commons-collections4.wso2.osgi.version.range}", ua_parser; version="${ua_parser.version.range}", + org.wso2.carbon.utils.security;version="${carbon.kernel.package.import.version.range}", !org.wso2.carbon.identity.core.internal, diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java index bd69a83123f4..50059ad95bac 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java @@ -268,10 +268,10 @@ private Certificate getCertificate(String tenantDomain, String context) throws I getCertificate(tenantDomain); } int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); - if (publicCerts.containsKey(String.valueOf(tenantId + - IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context))) { - return publicCerts.get(String.valueOf(tenantId + - IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context)); + if (publicCerts.containsKey(tenantId + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context)) { + return publicCerts.get(tenantId + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context); } KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); @@ -288,8 +288,8 @@ private Certificate getCertificate(String tenantDomain, String context) throws I tenantDomain), e); } - publicCerts.put(String.valueOf(tenantId + - IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context), publicCert); + publicCerts.put(tenantId + + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context, publicCert); return publicCert; } diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java index 8ea682561548..0aa40656f7c7 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java @@ -21,6 +21,7 @@ import org.apache.commons.lang.StringUtils; import org.wso2.carbon.core.RegistryResources; import org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverConstants.ErrorMessages; +import org.wso2.carbon.utils.security.KeystoreUtils; import javax.xml.namespace.QName; @@ -67,12 +68,12 @@ public static String buildTenantKeyStoreName(String tenantDomain, String context String ksName = tenantDomain.trim().replace(".", "-"); // Append context if provided - if (!StringUtils.isEmpty(context)) { + if (StringUtils.isNotBlank(context)) { ksName = ksName + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context.trim(); } // Add the keystore extension - return ksName + IdentityKeyStoreResolverConstants.KEY_STORE_EXTENSION; + return ksName + KeystoreUtils.getKeyStoreFileExtension(tenantDomain); } /** diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java index 0642ed81e1c9..9b44dc73a187 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java @@ -2001,7 +2001,7 @@ public static boolean validateSignatureFromTenant(String data, byte[] signature, } else { // Fetch certificate within the provided context publicKey = IdentityKeyStoreResolver.getInstance() - .getCertificate(tenantDomain, null, context) + .getCertificate(tenantDomain, null, context) .getPublicKey(); } diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolverTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolverTest.java index 52abb6a18591..4246e1eaaf14 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolverTest.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolverTest.java @@ -30,6 +30,7 @@ import org.wso2.carbon.identity.core.util.IdentityConfigParser; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; +import org.wso2.carbon.utils.security.KeystoreUtils; import java.io.FileInputStream; import java.lang.reflect.Field; @@ -84,6 +85,7 @@ public class IdentityKeyStoreResolverTest extends TestCase { private MockedStatic identityConfigParser; private MockedStatic identityTenantUtil; + private MockedStatic keystoreUtils; private IdentityKeyStoreResolver identityKeyStoreResolver; @@ -143,6 +145,7 @@ public void setUp() throws Exception { when(keyStoreManager.getCertificate("CUSTOM/" + CUSTOM_KEY_STORE, null)).thenReturn(customCertificate); identityKeyStoreResolver = IdentityKeyStoreResolver.getInstance(); + keystoreUtils = mockStatic(KeystoreUtils.class); } @AfterClass @@ -150,6 +153,7 @@ public void close() { identityConfigParser.close(); identityTenantUtil.close(); + keystoreUtils.close(); } @Test @@ -210,6 +214,7 @@ public Object[][] keyStoreDataProvider() { @Test(dataProvider = "KeyStoreDataProvider") public void testGetKeyStore(String tenantDomain, InboundProtocol inboundProtocol, KeyStore expectedKeyStore) throws Exception { + keystoreUtils.when(() -> KeystoreUtils.getKeyStoreFileExtension(tenantDomain)).thenReturn(".jks"); assertEquals(expectedKeyStore, identityKeyStoreResolver.getKeyStore(tenantDomain, inboundProtocol)); } @@ -229,6 +234,7 @@ public Object[][] privateKeyDataProvider() { @Test(dataProvider = "PrivateKeyDataProvider") public void testGetPrivateKey(String tenantDomain, InboundProtocol inboundProtocol, PrivateKey expectedKey) throws Exception { + keystoreUtils.when(() -> KeystoreUtils.getKeyStoreFileExtension(tenantDomain)).thenReturn(".jks"); assertEquals(expectedKey, identityKeyStoreResolver.getPrivateKey(tenantDomain, inboundProtocol)); } @@ -248,6 +254,7 @@ public Object[][] publicCertificateDataProvider() { @Test(dataProvider = "PublicCertificateDataProvider") public void testGetCertificate(String tenantDomain, InboundProtocol inboundProtocol, X509Certificate expectedCert) throws Exception { + keystoreUtils.when(() -> KeystoreUtils.getKeyStoreFileExtension(tenantDomain)).thenReturn(".jks"); assertEquals(expectedCert, identityKeyStoreResolver.getCertificate(tenantDomain, inboundProtocol)); } diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtilTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtilTest.java index 0a3d7eb3faf1..98d094f26871 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtilTest.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtilTest.java @@ -18,9 +18,14 @@ package org.wso2.carbon.identity.core.util; +import org.mockito.MockedStatic; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import org.wso2.carbon.utils.security.KeystoreUtils; +import static org.mockito.Mockito.mockStatic; import static org.testng.Assert.assertEquals; import static org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverUtil.buildCustomKeyStoreName; @@ -34,6 +39,14 @@ */ public class IdentityKeyStoreResolverUtilTest { + private MockedStatic keystoreUtils; + + @BeforeClass + public void setUp() throws Exception { + + keystoreUtils = mockStatic(KeystoreUtils.class); + } + @DataProvider(name = "CorrectTenantKeyStoreNameDataProvider") public Object[][] correctTenantKeyStoreNameDataProvider() { @@ -43,10 +56,17 @@ public Object[][] correctTenantKeyStoreNameDataProvider() { }; } + @AfterClass + public void close() { + + keystoreUtils.close(); + } + @Test(dataProvider = "CorrectTenantKeyStoreNameDataProvider") public void testCorrectBuildTenantKeyStoreName(String tenantDomain, String expectedResult) throws IdentityKeyStoreResolverException { - assertEquals(expectedResult, buildTenantKeyStoreName(tenantDomain)); + keystoreUtils.when(() -> KeystoreUtils.getKeyStoreFileExtension(tenantDomain)).thenReturn(".jks"); + assertEquals(buildTenantKeyStoreName(tenantDomain), expectedResult); } @DataProvider(name = "IncorrectTenantKeyStoreNameDataProvider") diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java index 4aeb8af2cf83..70c784bd0ba8 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java @@ -56,6 +56,7 @@ import org.wso2.carbon.utils.CarbonUtils; import org.wso2.carbon.utils.ConfigurationContextService; import org.wso2.carbon.utils.NetworkUtils; +import org.wso2.carbon.utils.security.KeystoreUtils; import java.io.FileInputStream; import java.io.IOException; @@ -150,6 +151,7 @@ public class IdentityUtilTest { MockedStatic signatureUtil; MockedStatic identityKeyStoreResolver; MockedStatic keyStoreManager; + private MockedStatic keystoreUtils; @BeforeMethod @@ -164,6 +166,7 @@ public void setUp() throws Exception { signatureUtil = mockStatic(SignatureUtil.class); identityKeyStoreResolver = mockStatic(IdentityKeyStoreResolver.class); keyStoreManager = mockStatic(KeyStoreManager.class); + keystoreUtils = mockStatic(KeystoreUtils.class); serverConfiguration.when(ServerConfiguration::getInstance).thenReturn(mockServerConfiguration); identityCoreServiceComponent.when( @@ -203,6 +206,7 @@ public void tearDown() throws Exception { signatureUtil.close(); identityKeyStoreResolver.close(); keyStoreManager.close(); + keystoreUtils.close(); } @Test(description = "Test converting a certificate to PEM format") @@ -1116,6 +1120,7 @@ public void testSignWithTenantKey() throws Exception { String data = "testData"; String superTenantDomain = "carbon.super"; keyStoreManager.when(() -> KeyStoreManager.getInstance(anyInt())).thenReturn(mockKeyStoreManager); + keystoreUtils.when(() -> KeystoreUtils.getKeyStoreFileExtension(superTenantDomain)).thenReturn(".jks"); when(mockKeyStoreManager.getDefaultPrivateKey()).thenReturn(mockPrivateKey); when(mockKeyStoreManager.getPrivateKey(anyString(), anyString())).thenReturn(mockPrivateKey); diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java index 9e004f5b3949..4cc380c1462e 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGenerator.java @@ -35,5 +35,5 @@ public interface IdentityKeyStoreGenerator { * @param context the context for which the KeyStore is to be generated. * @throws KeyStoreManagementException if an error occurs during KeyStore creation or initialization. */ - void generateContextKeyStore(String tenantDomain, String context) throws KeyStoreManagementException; + void generateKeyStore(String tenantDomain, String context) throws KeyStoreManagementException; } diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java index 6d059c8c6e75..24574d5a7118 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java @@ -43,8 +43,6 @@ import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.Date; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantLock; import static org.wso2.carbon.security.SecurityConstants.KeyStoreMgtConstants.KEY_STORE_CONTEXT_SEPARATOR; @@ -58,14 +56,9 @@ *
  • Generates keystores for tenants dynamically.
  • *
  • Supports various cryptographic algorithms and key generation techniques.
  • *
  • Handles secure persistence of keystores using {@link KeyStoreManager}.
  • - *
  • Ensures thread-safe operations with locking mechanisms for multi-tenant environments.
  • *
  • Provides explainable methods for certificate creation, storage, and retrieval.
  • * * - *

    Concurrency:

    - * This implementation uses {@link ReentrantLock} to handle concurrent access to keystores for - * specific tenants, ensuring thread safety and data integrity. - * *

    Usage:

    * This class is intended to be used in environments where context-specific cryptographic needs * must be met dynamically. @@ -74,83 +67,59 @@ * The methods in this class throw {@link KeyStoreManagementException} for errors encountered during * keystore creation, management, or persistence. */ -public class IdentityKeyStoreGeneratorImpl implements IdentityKeyStoreGenerator{ +public class IdentityKeyStoreGeneratorImpl implements IdentityKeyStoreGenerator { private static final Log LOG = LogFactory.getLog(IdentityKeyStoreGeneratorImpl.class); - private String tenantDomain; - private String password; - private KeyStoreManager keyStoreManager; private static final String SIGNING_ALG = "Tenant.SigningAlgorithm"; // Supported signature algorithms for public certificate generation. - private static final String DSA_SHA1 = "SHA1withDSA"; - private static final String ECDSA_SHA1 = "SHA1withECDSA"; - private static final String ECDSA_SHA256 = "SHA256withECDSA"; - private static final String ECDSA_SHA384 = "SHA384withECDSA"; - private static final String ECDSA_SHA512 = "SHA512withECDSA"; private static final String RSA_MD5 = "MD5withRSA"; private static final String RSA_SHA1 = "SHA1withRSA"; private static final String RSA_SHA256 = "SHA256withRSA"; private static final String RSA_SHA384 = "SHA384withRSA"; private static final String RSA_SHA512 = "SHA512withRSA"; private static final String[] signatureAlgorithms = new String[]{ - DSA_SHA1, ECDSA_SHA1, ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512, RSA_MD5, RSA_SHA1, RSA_SHA256, - RSA_SHA384, RSA_SHA512 + RSA_MD5, RSA_SHA1, RSA_SHA256, RSA_SHA384, RSA_SHA512 }; - private static final ConcurrentHashMap tenantLocks = new ConcurrentHashMap<>(); + private static final long CERT_NOT_BEFORE_TIME = 1000L * 60 * 60 * 24 * 30; // 30 days in milliseconds + private static final long CERT_NOT_AFTER_TIME = 1000L * 60 * 60 * 24 * 365 * 10; // 10 years in milliseconds /** - * Generates a context-specific KeyStore for a given tenant domain. + * Generates a context-specific KeyStore for a given tenant domain if it does not already exist. *

    - * This method creates a new KeyStore for the specified tenant domain and context if it does not already exist. - * It ensures thread safety by using a lock mechanism to avoid concurrent modifications when creating the KeyStore. - * If the KeyStore for the given context already exists, the method exits without performing any operations. + * This method checks whether a KeyStore exists for the specified tenant domain and context. + * If the KeyStore does not exist, it creates a new one, initializes it, generates the necessary + * key pairs, and persists it. *

    * - * @param tenantDomain the tenant domain for which the KeyStore is to be created. - * @param context the context for which the KeyStore is to be generated. - * @throws KeyStoreManagementException if an error occurs during KeyStore creation or initialization. + * @param tenantDomain the tenant domain for which the KeyStore is to be generated. + * @param context the specific context for which the KeyStore is to be generated. + * @throws KeyStoreManagementException if an error occurs during the KeyStore creation or initialization. */ - public void generateContextKeyStore(String tenantDomain, String context) throws KeyStoreManagementException { + public void generateKeyStore(String tenantDomain, String context) throws KeyStoreManagementException { int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); - this.keyStoreManager = KeyStoreManager.getInstance(tenantId); - this.tenantDomain = tenantDomain; - ReentrantLock lock = tenantLocks.computeIfAbsent(tenantId, id -> new ReentrantLock()); - boolean lockAcquired = false; // Track if the lock was acquired + KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); try { IdentityTenantUtil.initializeRegistry(tenantId); - if (isContextKeyStoreExists(context)) { + if (isContextKeyStoreExists(context, tenantDomain, keyStoreManager)) { return; // KeyStore already exists, no need to create again } - - lock.lock(); // Acquire the lock - lockAcquired = true; // Mark the lock as acquired - - if (!isContextKeyStoreExists(context)) { - // Create the KeyStore - password = generatePassword(); - KeyStore keyStore = KeystoreUtils.getKeystoreInstance( - KeystoreUtils.getKeyStoreFileType(tenantDomain)); - keyStore.load(null, password.toCharArray()); - generateContextKeyPair(keyStore, context); - persistContextKeyStore(keyStore, context); - } - + // Create the KeyStore + String password = generatePassword(); + KeyStore keyStore = KeystoreUtils.getKeystoreInstance( + KeystoreUtils.getKeyStoreFileType(tenantDomain)); + keyStore.load(null, password.toCharArray()); + generateContextKeyPair(keyStore, context, tenantDomain, password); + persistContextKeyStore(keyStore, context, tenantDomain, password, keyStoreManager); } catch (Exception e) { String msg = "Error while instantiating a keystore"; - LOG.error(msg, e); throw new KeyStoreManagementException(msg, e); - } finally { - if (lockAcquired) { // Only release the lock if it was acquired - lock.unlock(); - } - tenantLocks.remove(tenantId); // Clean up locks for this tenant if not needed anymore } } - private boolean isContextKeyStoreExists(String context) { + private boolean isContextKeyStoreExists(String context, String tenantDomain, KeyStoreManager keyStoreManager) { String keyStoreName = KeystoreUtils.getKeyStoreFileLocation(tenantDomain + KEY_STORE_CONTEXT_SEPARATOR + context); @@ -159,8 +128,8 @@ private boolean isContextKeyStoreExists(String context) { keyStoreManager.getKeyStore(keyStoreName); isKeyStoreExists = true; } catch (Exception e) { - String msg = "Error while checking the existence of keystore. "; - LOG.error(msg + e.getMessage()); + String msg = "Error while checking the existence of keystore."; + LOG.debug(msg + e.getMessage()); } return isKeyStoreExists; } @@ -178,15 +147,24 @@ private String generatePassword() { } /** - * Persist the keystore in the gov.registry + * Persists a context-specific KeyStore for a given tenant domain. + *

    + * This method stores the provided KeyStore in a persistent storage using the {@code KeyStoreManager}. + * It generates a KeyStore name based on the tenant domain and context, converts the KeyStore + * into a byte array, and saves it securely along with the provided password. + *

    * - * @param keyStore created Keystore of the tenant - * @throws KeyStoreManagementException Exception when storing the keystore in the registry + * @param keyStore the KeyStore to be persisted. + * @param context the specific context for which the KeyStore is being persisted. + * @param tenantDomain the tenant domain associated with the KeyStore. + * @param password the password used to protect the KeyStore. + * @param keyStoreManager the {@code KeyStoreManager} instance responsible for managing the persistence of the KeyStore. + * @throws KeyStoreManagementException if an error occurs while persisting the KeyStore or if security issues arise. */ - private void persistContextKeyStore(KeyStore keyStore, String context) - throws KeyStoreManagementException { + private void persistContextKeyStore(KeyStore keyStore, String context, String tenantDomain, String password, + KeyStoreManager keyStoreManager) throws KeyStoreManagementException { - String keyStoreName = generateContextKSNameFromDomainName(context); + String keyStoreName = generateContextKSNameFromDomainName(context, tenantDomain); try { char[] passwordChar = password.toCharArray(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); @@ -203,34 +181,32 @@ private void persistContextKeyStore(KeyStore keyStore, String context) } else { String msg = "Error when adding a keyStore"; - LOG.error(msg, e); throw new KeyStoreManagementException(msg, e); } } catch (Exception e) { String msg = "Error when processing keystore/pub. cert to be stored in registry"; - LOG.error(msg, e); throw new KeyStoreManagementException(msg, e); } } /** - * This method generates the keypair and stores it in the keystore + * This method generates the keypair and stores it in the keystore. * - * @param keyStore A keystore instance + * @param keyStore the KeyStore to be persisted. + * @param context the specific context for which the KeyStore is being persisted. + * @param tenantDomain the tenant domain associated with the KeyStore. + * @param password the password used to protect the KeyStore. * @throws KeyStoreManagementException Error when generating key pair */ - private void generateContextKeyPair(KeyStore keyStore, String context) + private void generateContextKeyPair(KeyStore keyStore, String context, String tenantDomain, String password) throws KeyStoreManagementException { + try { CryptoUtil.getDefaultCryptoUtil(); //generate key pair - String keyGenerationAlgorithm = getKeyGenerationAlgorithm(); - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyGenerationAlgorithm); - int keySize = getKeySize(keyGenerationAlgorithm); - if (keySize != 0) { - keyPairGenerator.initialize(keySize); - } + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // Common Name and alias for the generated certificate @@ -240,8 +216,8 @@ private void generateContextKeyPair(KeyStore keyStore, String context) //generate certificates X500Name distinguishedName = new X500Name(commonName); - Date notBefore = new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30); - Date notAfter = new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365 * 10)); + Date notBefore = new Date(System.currentTimeMillis() - CERT_NOT_BEFORE_TIME); + Date notAfter = new Date(System.currentTimeMillis() + CERT_NOT_AFTER_TIME); SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); BigInteger serialNumber = BigInteger.valueOf(new SecureRandom().nextInt()); @@ -263,31 +239,16 @@ private void generateContextKeyPair(KeyStore keyStore, String context) .getCertificate(certificateBuilder.build(signerBuilder.build(privateKey))); //add private key to KS - keyStore.setKeyEntry(tenantDomain +KEY_STORE_CONTEXT_SEPARATOR + context, + keyStore.setKeyEntry(tenantDomain + KEY_STORE_CONTEXT_SEPARATOR + context, keyPair.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{x509Cert}); } catch (Exception ex) { String msg = "Error while generating the Context certificate for tenant :" + tenantDomain + "."; - LOG.error(msg, ex); throw new KeyStoreManagementException(msg, ex); } } - private static String getKeyGenerationAlgorithm() { - - String signatureAlgorithm = getSignatureAlgorithm(); - // If the algorithm naming format is {digest}with{encryption}, we need to extract the encryption part. - int withIndex = signatureAlgorithm.indexOf("with"); - if (withIndex != -1 && withIndex + 4 < signatureAlgorithm.length()) { - return signatureAlgorithm.substring(withIndex + 4); - } else { - // The algorithm name is same as the encryption algorithm. - // This need to be updated if more algorithms are supported. - return signatureAlgorithm; - } - } - private static String getSignatureAlgorithm() { String algorithm = ServerConfiguration.getInstance().getFirstProperty(SIGNING_ALG); @@ -300,18 +261,6 @@ private static String getSignatureAlgorithm() { return RSA_MD5; } - private static int getKeySize(String algorithm) { - - // Initialize the key size according to the FIPS standard. - // This need to be updated if more algorithms are supported. - if ("ECDSA".equalsIgnoreCase(algorithm)) { - return 384; - } else if ("RSA".equalsIgnoreCase(algorithm) || "DSA".equalsIgnoreCase(algorithm)) { - return 2048; - } - return 0; - } - private static String getJCEProvider() { String provider = ServerConfiguration.getInstance().getFirstProperty(ServerConstants.JCE_PROVIDER); @@ -325,10 +274,10 @@ private static String getJCEProvider() { * This method generates the key store file name from the Domain Name * @return keystore name. */ - private String generateContextKSNameFromDomainName(String context){ + private String generateContextKSNameFromDomainName(String context, String tenantDomain){ String ksName = tenantDomain.trim().replace(".", "-"); ksName = ksName + KEY_STORE_CONTEXT_SEPARATOR + context; - return (ksName + KeystoreUtils.getExtensionByFileType(KeystoreUtils.StoreFileType.defaultFileType())); + return (ksName + KeystoreUtils.getKeyStoreFileExtension(tenantDomain)); } } From 8c49027b9037568618971d4b0bc32e06f0f15f3b Mon Sep 17 00:00:00 2001 From: thumimku Date: Wed, 11 Dec 2024 11:20:34 +0530 Subject: [PATCH 4/7] address comment and init unit tests --- .../core/IdentityKeyStoreResolver.java | 41 +++++++++++++++---- .../IdentityKeyStoreResolverConstants.java | 4 ++ .../util/IdentityKeyStoreResolverUtil.java | 14 ++++++- .../identity/core/util/IdentityUtil.java | 20 +++++++-- .../identity/core/util/IdentityUtilTest.java | 37 +++++++++++++++++ .../IdentityKeyStoreGeneratorImpl.java | 38 ++++++++++++----- 6 files changed, 130 insertions(+), 24 deletions(-) diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java index 50059ad95bac..a0829c01c927 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/IdentityKeyStoreResolver.java @@ -268,19 +268,33 @@ private Certificate getCertificate(String tenantDomain, String context) throws I getCertificate(tenantDomain); } int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); - if (publicCerts.containsKey(tenantId + - IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context)) { - return publicCerts.get(tenantId + - IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context); + + if (publicCerts.containsKey(buildDomainWithContext(tenantId, context))) { + return publicCerts.get(buildDomainWithContext(tenantId, context)); } KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); Certificate publicCert; + String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain, context); try { - String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain, context); publicCert = keyStoreManager.getCertificate(tenantKeyStoreName, tenantDomain + - IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR+ context); + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context); + + } catch (SecurityException e) { + if (e.getMessage() != null && e.getMessage().contains("Key Store with a name: " + tenantKeyStoreName + + " does not exist.")) { + throw new IdentityKeyStoreResolverException( + ErrorMessages.ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST.getCode(), + String.format( + ErrorMessages.ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST + .getDescription(), tenantDomain), e); + } else { + throw new IdentityKeyStoreResolverException( + ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getCode(), + String.format(ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getDescription(), + tenantDomain), e); + } } catch (Exception e) { throw new IdentityKeyStoreResolverException( ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getCode(), @@ -288,11 +302,22 @@ private Certificate getCertificate(String tenantDomain, String context) throws I tenantDomain), e); } - publicCerts.put(tenantId + - IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context, publicCert); + publicCerts.put(buildDomainWithContext(tenantId, context), publicCert); return publicCert; } + /** + * Concatenates tenantId and context with the separator. + * + * @param tenantId the key store name + * @param context the context + * @return a concatenated string in the format tenantDomain:context + */ + private String buildDomainWithContext(int tenantId, String context) { + + return tenantId + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context; + } + /** * Return Public Certificate of the Primary or tenant keystore according to given tenant domain. * diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java index 6dee9b4b17c1..795ed078dd6a 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverConstants.java @@ -120,6 +120,10 @@ public enum ErrorMessages { ERROR_CODE_ERROR_RETRIEVING_CUSTOM_KEYSTORE_CONFIGURATION( "IKSR-10009", "Error retrieving custom keystore configuration.", "Error occurred when retrieving custom keystore configuration for: %s."), + ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST( + "IKSR-10010", "Error retrieving context public certificate. Keystore doesn't exist.", + "Error occurred when retrieving context certificate for tenant: %s. " + + "Context Keystore doesn't exist."), // Errors occurred within the IdentityKeyStoreResolver ERROR_CODE_INVALID_ARGUMENT( diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java index 0aa40656f7c7..da0b99e69bfd 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityKeyStoreResolverUtil.java @@ -69,7 +69,7 @@ public static String buildTenantKeyStoreName(String tenantDomain, String context // Append context if provided if (StringUtils.isNotBlank(context)) { - ksName = ksName + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context.trim(); + ksName = buildDomainWithContext(ksName, context); } // Add the keystore extension @@ -103,4 +103,16 @@ public static QName getQNameWithIdentityNameSpace(String localPart) { return new QName(IdentityCoreConstants.IDENTITY_DEFAULT_NAMESPACE, localPart); } + + /** + * Concatenates tenantDomain and context with the separator. + * + * @param tenantDomain the key store name + * @param context the context + * @return a concatenated string in the format tenantDomain:context + */ + public static String buildDomainWithContext(String tenantDomain, String context) { + + return tenantDomain + IdentityKeyStoreResolverConstants.KEY_STORE_CONTEXT_SEPARATOR + context; + } } diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java index 9b44dc73a187..bf9203020df3 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/util/IdentityUtil.java @@ -114,6 +114,7 @@ import static org.wso2.carbon.identity.core.util.IdentityCoreConstants.ENCODED_ZERO; import static org.wso2.carbon.identity.core.util.IdentityCoreConstants.INDEXES; import static org.wso2.carbon.identity.core.util.IdentityCoreConstants.USERS_LIST_PER_ROLE_LOWER_BOUND; +import static org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverConstants.ErrorMessages.ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST; public class IdentityUtil { @@ -1999,10 +2000,21 @@ public static boolean validateSignatureFromTenant(String data, byte[] signature, .getCertificate(tenantDomain, null) .getPublicKey(); } else { - // Fetch certificate within the provided context - publicKey = IdentityKeyStoreResolver.getInstance() - .getCertificate(tenantDomain, null, context) - .getPublicKey(); + try { + // Fetch certificate within the provided context + Certificate certificate = IdentityKeyStoreResolver.getInstance() + .getCertificate(tenantDomain, null, context); + publicKey = certificate.getPublicKey(); + } catch (IdentityKeyStoreResolverException e) { + if (ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST.getCode() + .equals(e.getErrorCode())) { + // Context keystore not exits, hence return validation as false. + return false; + } else { + throw new SignatureException("Error while validating the signature for tenant: " + + tenantDomain, e); + } + } } // Validate the signature using the retrieved public key diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java index 70c784bd0ba8..e08b31d49668 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java @@ -97,6 +97,7 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import static org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverConstants.ErrorMessages.ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST; @Listeners(MockitoTestNGListener.class) public class IdentityUtilTest { @@ -1114,6 +1115,42 @@ public void testValidateSignatureFromTenant() throws Exception { assertTrue(result); } + @Test + public void testValidateSignatureFromContextKeystore() throws Exception { + + String data = "testData"; + byte[] signature = new byte[]{1, 2, 3}; + String tenantDomain = "carbon.super"; + String context = "cookie"; + + when(mockCertificate.getPublicKey()).thenReturn(mockPublicKey); + identityKeyStoreResolver.when(IdentityKeyStoreResolver::getInstance).thenReturn(mockIdentityKeyStoreResolver); + when(mockIdentityKeyStoreResolver.getCertificate(tenantDomain, null, context)).thenReturn(mockCertificate); + signatureUtil.when(() -> SignatureUtil.validateSignature(data, signature, mockPublicKey)).thenReturn(true); + + boolean result = IdentityUtil.validateSignatureFromTenant(data, signature, tenantDomain, context); + assertTrue(result); + } + + @Test + public void testValidateSignatureFromContextKeystoreIfNotExists() throws Exception { + + String data = "testData"; + byte[] signature = new byte[]{1, 2, 3}; + String tenantDomain = "carbon.super"; + String context = "cookie"; + + identityKeyStoreResolver.when(IdentityKeyStoreResolver::getInstance).thenReturn(mockIdentityKeyStoreResolver); + when(mockIdentityKeyStoreResolver.getCertificate(tenantDomain, null, context)) + .thenThrow(new IdentityKeyStoreResolverException + (ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST.getCode(), + ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST.getDescription())); + signatureUtil.when(() -> SignatureUtil.validateSignature(data, signature, mockPublicKey)).thenReturn(true); + + boolean result = IdentityUtil.validateSignatureFromTenant(data, signature, tenantDomain, context); + assertFalse(result); + } + @Test public void testSignWithTenantKey() throws Exception { diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java index 24574d5a7118..6b56e9290779 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java @@ -119,10 +119,10 @@ public void generateKeyStore(String tenantDomain, String context) throws KeyStor } } - private boolean isContextKeyStoreExists(String context, String tenantDomain, KeyStoreManager keyStoreManager) { + private boolean isContextKeyStoreExists(String context, String tenantDomain, KeyStoreManager keyStoreManager) + throws KeyStoreManagementException { - String keyStoreName = KeystoreUtils.getKeyStoreFileLocation(tenantDomain + - KEY_STORE_CONTEXT_SEPARATOR + context); + String keyStoreName = KeystoreUtils.getKeyStoreFileLocation(buildDomainWithContext(tenantDomain, context)); boolean isKeyStoreExists = false; try { keyStoreManager.getKeyStore(keyStoreName); @@ -204,16 +204,16 @@ private void generateContextKeyPair(KeyStore keyStore, String context, String te try { CryptoUtil.getDefaultCryptoUtil(); - //generate key pair + // Generate key pair KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // Common Name and alias for the generated certificate - String commonName = "CN=" + tenantDomain + - KEY_STORE_CONTEXT_SEPARATOR + context + ", OU=None, O=None, L=None, C=None"; + String commonName = "CN=" + buildDomainWithContext(tenantDomain, context) + + ", OU=None, O=None, L=None, C=None"; - //generate certificates + // Generate certificates X500Name distinguishedName = new X500Name(commonName); Date notBefore = new Date(System.currentTimeMillis() - CERT_NOT_BEFORE_TIME); @@ -238,8 +238,8 @@ private void generateContextKeyPair(KeyStore keyStore, String context, String te X509Certificate x509Cert = new JcaX509CertificateConverter().setProvider(getJCEProvider()) .getCertificate(certificateBuilder.build(signerBuilder.build(privateKey))); - //add private key to KS - keyStore.setKeyEntry(tenantDomain + KEY_STORE_CONTEXT_SEPARATOR + context, + // Add private key to KS + keyStore.setKeyEntry(buildDomainWithContext(tenantDomain, context), keyPair.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{x509Cert}); } catch (Exception ex) { @@ -274,10 +274,26 @@ private static String getJCEProvider() { * This method generates the key store file name from the Domain Name * @return keystore name. */ - private String generateContextKSNameFromDomainName(String context, String tenantDomain){ + private String generateContextKSNameFromDomainName(String context, String tenantDomain) + throws KeyStoreManagementException { String ksName = tenantDomain.trim().replace(".", "-"); - ksName = ksName + KEY_STORE_CONTEXT_SEPARATOR + context; + ksName = buildDomainWithContext(ksName, context); return (ksName + KeystoreUtils.getKeyStoreFileExtension(tenantDomain)); } + + /** + * Concatenates ksName and context with the separator. + * + * @param ksName the key store name + * @param context the context + * @return a concatenated string in the format ksName:context + */ + private String buildDomainWithContext(String ksName, String context) throws KeyStoreManagementException { + + if (ksName == null || context == null) { + throw new KeyStoreManagementException("ksName and context must not be null"); + } + return ksName + KEY_STORE_CONTEXT_SEPARATOR + context; + } } From 0477800848f16b5fc2a59cdd4696f788d7a4b225 Mon Sep 17 00:00:00 2001 From: thumimku Date: Wed, 11 Dec 2024 15:33:10 +0530 Subject: [PATCH 5/7] add unit tests --- .../identity/core/util/IdentityUtilTest.java | 23 +- .../IdentityKeyStoreGeneratorImpl.java | 29 +- .../IdentityKeyStoreGeneratorImplTest.java | 260 ++++++++++++++++++ .../security/carbon-super--cookie.jks | Bin 0 -> 37820 bytes .../src/test/resources/testng.xml | 1 + 5 files changed, 305 insertions(+), 8 deletions(-) create mode 100644 components/security-mgt/org.wso2.carbon.security.mgt/src/test/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImplTest.java create mode 100644 components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java index e08b31d49668..cd833299e972 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/util/IdentityUtilTest.java @@ -97,6 +97,7 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import static org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverConstants.ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE; import static org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverConstants.ErrorMessages.ERROR_RETRIEVING_TENANT_CONTEXT_PUBLIC_CERTIFICATE_KEYSTORE_NOT_EXIST; @Listeners(MockitoTestNGListener.class) @@ -1132,7 +1133,8 @@ public void testValidateSignatureFromContextKeystore() throws Exception { assertTrue(result); } - @Test + @Test(description = "Validate signature when the context keystore does not exist. " + + "Expect the method to return false without throwing an exception.") public void testValidateSignatureFromContextKeystoreIfNotExists() throws Exception { String data = "testData"; @@ -1151,6 +1153,25 @@ public void testValidateSignatureFromContextKeystoreIfNotExists() throws Excepti assertFalse(result); } + @Test(description = "Validate signature when an unexpected exception occurs while retrieving the " + + "tenant's public certificate. Expect a SignatureException to be thrown.", + expectedExceptions = SignatureException.class) + public void testValidateSignatureFromContextKeystoreNegative() throws Exception { + + String data = "testData"; + byte[] signature = new byte[]{1, 2, 3}; + String tenantDomain = "carbon.super"; + String context = "cookie"; + + identityKeyStoreResolver.when(IdentityKeyStoreResolver::getInstance).thenReturn(mockIdentityKeyStoreResolver); + when(mockIdentityKeyStoreResolver.getCertificate(tenantDomain, null, context)) + .thenThrow(new IdentityKeyStoreResolverException + (ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getCode(), + ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getDescription())); + + IdentityUtil.validateSignatureFromTenant(data, signature, tenantDomain, context); + } + @Test public void testSignWithTenantKey() throws Exception { diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java index 6b56e9290779..22f75c7e6905 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/main/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImpl.java @@ -29,6 +29,8 @@ import org.wso2.carbon.base.ServerConfiguration; import org.wso2.carbon.core.util.CryptoUtil; import org.wso2.carbon.core.util.KeyStoreManager; +import org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverConstants; +import org.wso2.carbon.identity.core.util.IdentityKeyStoreResolverException; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.security.keystore.KeyStoreManagementException; import org.wso2.carbon.utils.ServerConstants; @@ -127,9 +129,19 @@ private boolean isContextKeyStoreExists(String context, String tenantDomain, Key try { keyStoreManager.getKeyStore(keyStoreName); isKeyStoreExists = true; + } catch (SecurityException e) { + if (e.getMessage() != null && e.getMessage().contains("Key Store with a name: " + keyStoreName + + " does not exist.")) { + + String msg = "Key store not exits. Proceeding to create keystore : " + keyStoreName; + LOG.debug(msg + e.getMessage()); + } else { + String msg = "Error while checking the existence of keystore."; + throw new KeyStoreManagementException(msg, e); + } } catch (Exception e) { String msg = "Error while checking the existence of keystore."; - LOG.debug(msg + e.getMessage()); + throw new KeyStoreManagementException(msg, e); } return isKeyStoreExists; } @@ -165,12 +177,19 @@ private void persistContextKeyStore(KeyStore keyStore, String context, String te KeyStoreManager keyStoreManager) throws KeyStoreManagementException { String keyStoreName = generateContextKSNameFromDomainName(context, tenantDomain); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + char[] passwordChar = password.toCharArray(); try { - char[] passwordChar = password.toCharArray(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); keyStore.store(outputStream, passwordChar); outputStream.flush(); outputStream.close(); + } catch (Exception e) { + String msg = "Error occurred while storing the keystore or processing the public certificate for tenant: " + + tenantDomain + " and context: " + context + ". Ensure the keystore is valid and writable."; + throw new KeyStoreManagementException(msg, e); + } + + try { keyStoreManager.addKeyStore(outputStream.toByteArray(), keyStoreName, passwordChar, " ", KeystoreUtils.getKeyStoreFileType(tenantDomain), passwordChar); @@ -183,10 +202,6 @@ private void persistContextKeyStore(KeyStore keyStore, String context, String te String msg = "Error when adding a keyStore"; throw new KeyStoreManagementException(msg, e); } - } catch (Exception e) { - - String msg = "Error when processing keystore/pub. cert to be stored in registry"; - throw new KeyStoreManagementException(msg, e); } } diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImplTest.java b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImplTest.java new file mode 100644 index 000000000000..a8dfb803f402 --- /dev/null +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/java/org/wso2/carbon/security/keystore/service/IdentityKeyStoreGeneratorImplTest.java @@ -0,0 +1,260 @@ +/* + * 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.security.keystore.service; + +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.stubbing.Answer; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.*; +import org.wso2.carbon.base.CarbonBaseConstants; +import org.wso2.carbon.core.util.KeyStoreManager; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.testutil.IdentityBaseTest; +import org.wso2.carbon.security.keystore.KeyStoreManagementException; +import org.wso2.carbon.utils.security.KeystoreUtils; + +import java.io.FileInputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.Security; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@Listeners(MockitoTestNGListener.class) +public class IdentityKeyStoreGeneratorImplTest extends IdentityBaseTest { + + private static final String KEYSTORE_PASSWORD = "wso2carbon"; + + private IdentityKeyStoreGeneratorImpl identityKeyStoreGenerator; + + private MockedStatic identityTenantUtil; + @Mock + private KeyStoreManager keyStoreManager; + + @Mock + private KeyStore mockKeyStore; + + + @BeforeMethod + public void setUp() throws Exception { + + if (Security.getProvider("BC") == null) { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + System.setProperty( + CarbonBaseConstants.CARBON_HOME, + Paths.get(System.getProperty("user.dir"), "src", "test", "resources").toString() + ); + identityTenantUtil = mockStatic(IdentityTenantUtil.class); + } + + @AfterMethod + public void tearDown() throws Exception { + + identityKeyStoreGenerator = null; + identityTenantUtil.close(); + } + + @Test(description = "Test the generation of a keystore for a given tenant domain and context if exits.") + public void testGenerateKeystoreIfExists() throws Exception { + + try (MockedStatic keyStoreManager = mockStatic(KeyStoreManager.class); + MockedStatic keyStoreUtils = mockStatic(KeystoreUtils.class)) { + + keyStoreManager.when(() -> KeyStoreManager.getInstance(anyInt())).thenReturn(this.keyStoreManager); + identityTenantUtil.when(()->IdentityTenantUtil.getTenantId("carbon.super")) + .thenReturn(-1234); + identityTenantUtil.when(() -> IdentityTenantUtil.initializeRegistry(anyInt())) + .thenAnswer((Answer) invocation -> null); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileLocation("carbon.super--cookie")) + .thenReturn("carbon-super--cookie.jks"); + when(this.keyStoreManager.getKeyStore("carbon-super--cookie.jks")) + .thenReturn(getKeyStoreFromFile("carbon-super--cookie.jks", KEYSTORE_PASSWORD)); + identityKeyStoreGenerator = new IdentityKeyStoreGeneratorImpl(); + identityKeyStoreGenerator.generateKeyStore("carbon.super", "cookie"); + } + } + + /** + * Sets up the mock behavior for KeyStoreManager and KeystoreUtils. + * + * @param exceptionToThrow the exception to throw when `getKeyStore` is called. + * @throws Exception if any setup steps fail. + */ + private void setupKeyStoreMocksWithException(Exception exceptionToThrow) throws Exception { + try (MockedStatic keyStoreManager = mockStatic(KeyStoreManager.class); + MockedStatic keyStoreUtils = mockStatic(KeystoreUtils.class)) { + + keyStoreManager.when(() -> KeyStoreManager.getInstance(anyInt())).thenReturn(this.keyStoreManager); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId("carbon.super")).thenReturn(-1234); + identityTenantUtil.when(() -> IdentityTenantUtil.initializeRegistry(anyInt())) + .thenAnswer((Answer) invocation -> null); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileLocation("carbon.super--cookie")) + .thenReturn("wso2carbon--cookie.jks"); + + when(this.keyStoreManager.getKeyStore("wso2carbon--cookie.jks")).thenThrow(exceptionToThrow); + } + } + + @Test(description = "Test error creating a keystore for a given tenant domain and context with SecurityException.", + expectedExceptions = KeyStoreManagementException.class) + public void testGenerateKeystoreWithSecurityException() throws Exception { + + setupKeyStoreMocksWithException(new SecurityException("Error while creating keystore.")); + identityKeyStoreGenerator = new IdentityKeyStoreGeneratorImpl(); + identityKeyStoreGenerator.generateKeyStore("carbon.super", "cookie"); + } + + @Test(description = "Test error creating a keystore for a given tenant domain and context with generic Exception.", + expectedExceptions = KeyStoreManagementException.class) + public void testGenerateKeystoreWithGenericException() throws Exception { + + setupKeyStoreMocksWithException(new Exception("Error while creating keystore.")); + identityKeyStoreGenerator = new IdentityKeyStoreGeneratorImpl(); + identityKeyStoreGenerator.generateKeyStore("carbon.super", "cookie"); + } + + + @Test(description = "Test the generation of a keystore for a given tenant domain and context if not exits.") + public void testGenerateKeystoreIfNotExists() throws Exception { + + try (MockedStatic keyStoreManager = mockStatic(KeyStoreManager.class); + MockedStatic keyStoreUtils = mockStatic(KeystoreUtils.class)) { + + keyStoreManager.when(() -> KeyStoreManager.getInstance(anyInt())).thenReturn(this.keyStoreManager); + identityTenantUtil.when(()->IdentityTenantUtil.getTenantId("carbon.super")) + .thenReturn(-1234); + identityTenantUtil.when(() -> IdentityTenantUtil.initializeRegistry(anyInt())) + .thenAnswer((Answer) invocation -> null); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileLocation("carbon.super--cookie")) + .thenReturn("carbon-super--cookie.jks"); + when(this.keyStoreManager.getKeyStore("carbon-super--cookie.jks")) + .thenThrow(new SecurityException("Key Store with a name: carbon-super--cookie.jks" + + " does not exist.")); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileType("carbon.super")) + .thenReturn("JKS"); + keyStoreUtils.when(() -> KeystoreUtils.getKeystoreInstance("JKS")) + .thenReturn(this.mockKeyStore); + doNothing().when(this.mockKeyStore).setKeyEntry(anyString(), any(PrivateKey.class), any(), any()); + + identityKeyStoreGenerator = new IdentityKeyStoreGeneratorImpl(); + identityKeyStoreGenerator.generateKeyStore("carbon.super", "cookie"); + } + } + + @Test(description = "Test the generation of a keystore for a given tenant domain and context if not exits.") + public void testGenerateKeystoreAlreadyExists() throws Exception { + + try (MockedStatic keyStoreManager = mockStatic(KeyStoreManager.class); + MockedStatic keyStoreUtils = mockStatic(KeystoreUtils.class)) { + + keyStoreManager.when(() -> KeyStoreManager.getInstance(anyInt())).thenReturn(this.keyStoreManager); + identityTenantUtil.when(()->IdentityTenantUtil.getTenantId("carbon.super")) + .thenReturn(-1234); + identityTenantUtil.when(() -> IdentityTenantUtil.initializeRegistry(anyInt())) + .thenAnswer((Answer) invocation -> null); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileLocation("carbon.super--cookie")) + .thenReturn("carbon-super--cookie.jks"); + when(this.keyStoreManager.getKeyStore("carbon-super--cookie.jks")) + .thenThrow(new SecurityException("Key Store with a name: carbon-super--cookie.jks" + + " does not exist.")); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileType("carbon.super")) + .thenReturn("JKS"); + keyStoreUtils.when(() -> KeystoreUtils.getKeystoreInstance("JKS")) + .thenReturn(this.mockKeyStore); + doNothing().when(this.mockKeyStore).setKeyEntry(anyString(), any(PrivateKey.class), any(), any()); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileExtension("carbon.super")) + .thenReturn(".jks"); + doThrow(new SecurityException("Key store carbon-super--cookie.jks already available")) + .when(this.keyStoreManager) + .addKeyStore( + any(byte[].class), // Match any byte array + anyString(), // Match any String + any(char[].class), // Match any char array + anyString(), // Match any String + anyString(), // Match any String + any(char[].class) // Match any char array + ); + + identityKeyStoreGenerator = new IdentityKeyStoreGeneratorImpl(); + identityKeyStoreGenerator.generateKeyStore("carbon.super", "cookie"); + } + } + + @Test(description = "Test the generation of a keystore for a given tenant domain and context if not exits.", + expectedExceptions = KeyStoreManagementException.class) + public void testGenerateKeystoreIfNotExistsNegative() throws Exception { + + try (MockedStatic keyStoreManager = mockStatic(KeyStoreManager.class); + MockedStatic keyStoreUtils = mockStatic(KeystoreUtils.class)) { + + keyStoreManager.when(() -> KeyStoreManager.getInstance(anyInt())).thenReturn(this.keyStoreManager); + identityTenantUtil.when(()->IdentityTenantUtil.getTenantId("carbon.super")) + .thenReturn(-1234); + identityTenantUtil.when(() -> IdentityTenantUtil.initializeRegistry(anyInt())) + .thenAnswer((Answer) invocation -> null); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileLocation("carbon.super--cookie")) + .thenReturn("carbon-super--cookie.jks"); + when(this.keyStoreManager.getKeyStore("carbon-super--cookie.jks")) + .thenThrow(new SecurityException("Key Store with a name: carbon-super--cookie.jks" + + " does not exist.")); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileType("carbon.super")) + .thenReturn("JKS"); + keyStoreUtils.when(() -> KeystoreUtils.getKeystoreInstance("JKS")) + .thenReturn(this.mockKeyStore); + doNothing().when(this.mockKeyStore).setKeyEntry(anyString(), any(PrivateKey.class), any(), any()); + keyStoreUtils.when(() -> KeystoreUtils.getKeyStoreFileExtension("carbon.super")) + .thenReturn(".jks"); + doThrow(new SecurityException("Error while adding keystore")) + .when(this.keyStoreManager) + .addKeyStore( + any(byte[].class), // Match any byte array + anyString(), // Match any String + any(char[].class), // Match any char array + anyString(), // Match any String + anyString(), // Match any String + any(char[].class) // Match any char array + ); + + identityKeyStoreGenerator = new IdentityKeyStoreGeneratorImpl(); + identityKeyStoreGenerator.generateKeyStore("carbon.super", "cookie"); + } + } + + + private Path createPath(String keystoreName) { + + return Paths.get(System.getProperty(CarbonBaseConstants.CARBON_HOME), "repository", + "resources", "security", keystoreName); + } + + private KeyStore getKeyStoreFromFile(String keystoreName, String password) throws Exception { + + Path tenantKeystorePath = createPath(keystoreName); + FileInputStream file = new FileInputStream(tenantKeystorePath.toString()); + KeyStore keystore = KeyStore.getInstance("JKS"); + keystore.load(file, password.toCharArray()); + return keystore; + } +} diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks new file mode 100644 index 0000000000000000000000000000000000000000..d5af4f42973aa9826770673213d4b66fa211d1ed GIT binary patch literal 37820 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 literal 0 HcmV?d00001 diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml index ccc588beafc0..b74b69a95034 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml @@ -23,6 +23,7 @@ + From 2e3f8afe4a8781d9879fa7465c39a4c06a746ad2 Mon Sep 17 00:00:00 2001 From: thumimku Date: Wed, 11 Dec 2024 23:21:28 +0530 Subject: [PATCH 6/7] comment out unit test for PR analysis --- .../resources/security/carbon-super--cookie.jks | Bin 37820 -> 0 bytes .../src/test/resources/testng.xml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks deleted file mode 100644 index d5af4f42973aa9826770673213d4b66fa211d1ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37820 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 diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml index b74b69a95034..9dc060f8fe53 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml @@ -23,7 +23,7 @@ - + From d8e96599142e570587336e511acd02ee90ef6cc5 Mon Sep 17 00:00:00 2001 From: thumimku Date: Thu, 12 Dec 2024 10:30:47 +0530 Subject: [PATCH 7/7] enabled unit tests --- .../resources/security/carbon-super--cookie.jks | Bin 0 -> 37820 bytes .../src/test/resources/testng.xml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/repository/resources/security/carbon-super--cookie.jks new file mode 100644 index 0000000000000000000000000000000000000000..d5af4f42973aa9826770673213d4b66fa211d1ed GIT binary patch literal 37820 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 literal 0 HcmV?d00001 diff --git a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml index 9dc060f8fe53..b74b69a95034 100644 --- a/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml +++ b/components/security-mgt/org.wso2.carbon.security.mgt/src/test/resources/testng.xml @@ -23,7 +23,7 @@ - +