Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Methods for create and call context specific tenanted keystores #6189

Merged
merged 7 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
hwupathum marked this conversation as resolved.
Show resolved Hide resolved

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) {
Copy link
Contributor

@hwupathum hwupathum Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we rename the method to avoid confusion with buildDomainWithContext(int tenantId, String context) from IdentityKeyStoreResolverUtil?


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)
Thumimku marked this conversation as resolved.
Show resolved Hide resolved
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
Loading