Skip to content

Commit

Permalink
Merge pull request #6189 from Thumimku/create-and-use-context-keystore
Browse files Browse the repository at this point in the history
Methods for create and call context specific tenanted keystores
  • Loading branch information
Thumimku authored Dec 12, 2024
2 parents e4fd720 + d8e9659 commit 969d45a
Show file tree
Hide file tree
Showing 17 changed files with 988 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
</Import-Package>
<Export-Package>
!org.wso2.carbon.identity.core.internal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,78 @@ public Key getPrivateKey(String tenantDomain, InboundProtocol inboundProtocol)
return getPrivateKey(tenantDomain);
}

/**
* Retrieves the public certificate for a given tenant domain and context.
* <p>
* 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.
* </p>
*
* @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(buildDomainWithContext(tenantId, context))) {
return publicCerts.get(buildDomainWithContext(tenantId, context));
}

KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId);
Certificate publicCert;
String tenantKeyStoreName = IdentityKeyStoreResolverUtil.buildTenantKeyStoreName(tenantDomain, context);
try {
publicCert = keyStoreManager.getCertificate(tenantKeyStoreName, tenantDomain +
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(),
String.format(ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_TENANT_PUBLIC_CERTIFICATE.getDescription(),
tenantDomain), e);
}

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.
*
Expand Down Expand Up @@ -285,17 +357,22 @@ 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)
public Certificate getCertificate(String tenantDomain, InboundProtocol inboundProtocol, String context)
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 (context != null) {
return getCertificate(tenantDomain, context);
}
if (inboundProtocol == null) {
return getCertificate(tenantDomain);
}
Expand Down Expand Up @@ -326,13 +403,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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -119,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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -38,13 +39,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(".", "-");
return ksName + IdentityKeyStoreResolverConstants.KEY_STORE_EXTENSION;

// Append context if provided
if (StringUtils.isNotBlank(context)) {
ksName = buildDomainWithContext(ksName, context);
}

// Add the keystore extension
return ksName + KeystoreUtils.getKeyStoreFileExtension(tenantDomain);
}

/**
Expand Down Expand Up @@ -74,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;
}
}
Loading

0 comments on commit 969d45a

Please sign in to comment.