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

Add FAPI validations for DCR #2178

Merged
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fbfbc9b
add FAPI validations
SachiniSiriwardene Oct 2, 2023
27a8849
Add FAPI based dcr validations and PPID based validations
SachiniSiriwardene Oct 3, 2023
1974e9b
Remove changes
SachiniSiriwardene Oct 3, 2023
f83dc46
Remove changes
SachiniSiriwardene Oct 3, 2023
3912da3
remove * imports
SachiniSiriwardene Oct 3, 2023
435809a
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 3, 2023
2290f39
add changes for error handling
SachiniSiriwardene Oct 5, 2023
3e5943b
validate encryption algorithm and method
SachiniSiriwardene Oct 5, 2023
2224506
change config name
SachiniSiriwardene Oct 5, 2023
6ee33bb
fix pr comments
SachiniSiriwardene Oct 7, 2023
2190b37
change method signature for checking fapi compliant property
SachiniSiriwardene Oct 8, 2023
2e64b9e
add fapi compliant property to service provider
SachiniSiriwardene Oct 10, 2023
6dafaf8
add unit tests for signature validation
SachiniSiriwardene Oct 11, 2023
aacab81
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 11, 2023
6b66a41
fix pr comments
SachiniSiriwardene Oct 11, 2023
54a629e
refactor error handling
SachiniSiriwardene Oct 11, 2023
6a3a108
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 13, 2023
5f0f8d4
check FAPI validation enabled for DCR
SachiniSiriwardene Oct 13, 2023
1bf041a
refactor code
SachiniSiriwardene Oct 14, 2023
63f8978
refactor code
SachiniSiriwardene Oct 15, 2023
5bfc80f
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 17, 2023
df951e1
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 17, 2023
b7afd8d
add changes to send ssa in response
SachiniSiriwardene Oct 18, 2023
1bb49c5
remove unwanted imports
SachiniSiriwardene Oct 19, 2023
ed71ba9
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 19, 2023
b02c765
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 19, 2023
cdc11bc
add method to filter signature algorithms
SachiniSiriwardene Oct 19, 2023
54bde5b
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 19, 2023
ab47967
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 20, 2023
ce77254
add method to filter signature algorithms
SachiniSiriwardene Oct 20, 2023
45be6e1
modify to send correct error code
SachiniSiriwardene Oct 20, 2023
1dd335c
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 20, 2023
4df315c
refactor code
SachiniSiriwardene Oct 21, 2023
d030cee
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 22, 2023
a418767
address pr comments
SachiniSiriwardene Oct 23, 2023
feb4731
address pr comments
SachiniSiriwardene Oct 23, 2023
f6b06f0
change variable name
SachiniSiriwardene Oct 24, 2023
fda251e
add pr review changes
SachiniSiriwardene Oct 25, 2023
ec5f3e4
refactor code
SachiniSiriwardene Oct 25, 2023
12f4e99
address pr comments
SachiniSiriwardene Oct 25, 2023
e4cfcc7
add default subject type if empty
SachiniSiriwardene Oct 25, 2023
ed3a3d5
remove null check
SachiniSiriwardene Oct 25, 2023
c7a9144
Merge branch 'master' of https://github.com/wso2-extensions/identity-…
SachiniSiriwardene Oct 25, 2023
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 @@ -48,6 +48,7 @@ public class ApplicationDTO {
private String subjectType = null;
private String requestObjectEncryptionAlgorithm = null;
private String requestObjectEncryptionMethod = null;
private String softwareStatement = null;


/**
Expand Down Expand Up @@ -264,6 +265,16 @@ public void setRequestObjectEncryptionMethod(String requestObjectEncryptionMetho
this.requestObjectEncryptionMethod = requestObjectEncryptionMethod;
}

@ApiModelProperty(value = "")
@JsonProperty("software_statement")
public String getSoftwareStatement() {
return softwareStatement;
}

public void setSoftwareStatement(String softwareStatement) {
this.softwareStatement = softwareStatement;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ public static void handleErrorResponse(DCRMException dcrmException, Log log) thr
status = Response.Status.UNAUTHORIZED;
} else if (errorCode.startsWith(FORBIDDEN_STATUS)) {
status = Response.Status.FORBIDDEN;
} else if (errorCode.startsWith(DCRMConstants.ErrorCodes.INVALID_CLIENT_METADATA) ||
errorCode.startsWith(DCRMConstants.ErrorCodes.INVALID_SOFTWARE_STATEMENT)) {
status = Response.Status.BAD_REQUEST;
isStatusOnly = false;
}
}
throw buildDCRMEndpointException(status, errorCode, dcrmException.getMessage(), isStatusOnly);
Expand Down Expand Up @@ -229,6 +233,7 @@ public static ApplicationDTO getApplicationDTOFromApplication(Application applic
applicationDTO.setRequestObjectEncryptionMethod(application.getRequestObjectEncryptionMethod());
applicationDTO.setRequirePushAuthorizationRequest(application.isRequirePushedAuthorizationRequests());
applicationDTO.setTlsClientCertificateBoundAccessToken(application.isTlsClientCertificateBoundAccessTokens());
applicationDTO.setSoftwareStatement(application.getSoftwareStatement());
return applicationDTO;
}

Expand Down Expand Up @@ -265,6 +270,9 @@ private static DCRMEndpointException buildDCRMEndpointException(Response.Status
if (code.equals(DCRMConstants.ErrorMessages.BAD_REQUEST_INVALID_REDIRECT_URI.toString())) {
error = DCRMConstants.ErrorCodes.INVALID_REDIRECT_URI;
}
if (code.equals(DCRMConstants.ErrorCodes.INVALID_SOFTWARE_STATEMENT)) {
error = DCRMConstants.ErrorCodes.INVALID_SOFTWARE_STATEMENT;
}

ErrorDTO errorDTO = new ErrorDTO();
errorDTO.setError(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,32 @@ public static SubjectType fromValue(String text) {
public static final String ORG_ID = "org_id";
public static final String IS_FAPI_CONFORMANT_APP = "isFAPIConformant";
public static final String ENABLE_FAPI = "OAuth.OpenIDConnect.FAPI.EnableFAPIValidation";
public static final String IS_THIRD_PARTY_APP = "isThirdPartyApp";
public static final String ENABLE_DCR_FAPI_ENFORCEMENT = "OAuth.DCRM.EnableFAPIEnforcement";
public static final String FAPI_CLIENT_AUTH_METHOD_CONFIGURATION = "OAuth.OpenIDConnect.FAPI." +
"AllowedClientAuthenticationMethods.AllowedClientAuthenticationMethod";
public static final String FAPI_SIGNATURE_ALGORITHM_CONFIGURATION = "OAuth.OpenIDConnect.FAPI." +
"AllowedSignatureAlgorithms.AllowedSignatureAlgorithm";
public static final String VALIDATE_SECTOR_IDENTIFIER = "OAuth.DCRM.EnableSectorIdentifierURIValidation";
public static final String TOKEN_EP_SIGNATURE_ALG_CONFIGURATION = "OAuth.OpenIDConnect" +
".SupportedTokenEndpointSigningAlgorithms.SupportedTokenEndpointSigningAlgorithm";
public static final String ID_TOKEN_SIGNATURE_ALG_CONFIGURATION = "OAuth.OpenIDConnect" +
".SupportedIDTokenSigningAlgorithms.SupportedIDTokenSigningAlgorithm";
public static final String REQUEST_OBJECT_SIGNATURE_ALG_CONFIGURATION = "OAuth.OpenIDConnect" +
".SupportedRequestObjectSigningAlgorithms.SupportedRequestObjectSigningAlgorithm";
public static final String ID_TOKEN_ENCRYPTION_ALGORITHM = "OAuth.OpenIDConnect." +
"SupportedIDTokenEncryptionAlgorithms.SupportedIDTokenEncryptionAlgorithm";
public static final String REQUEST_OBJECT_ENCRYPTION_ALGORITHM = "OAuth.OpenIDConnect." +
"SupportedRequestObjectEncryptionAlgorithms.SupportedRequestObjectEncryptionAlgorithm";
public static final String ID_TOKEN_ENCRYPTION_METHOD = "OAuth.OpenIDConnect.SupportedIDTokenEncryptionMethods." +
"SupportedIDTokenEncryptionMethod";
public static final String REQUEST_OBJECT_ENCRYPTION_METHOD = "OAuth.OpenIDConnect." +
"SupportedRequestObjectEncryptionMethods.SupportedRequestObjectEncryptionMethod";


public static final String IS_THIRD_PARTY_APP = "isThirdPartyApp";
public static final String PRIVATE_KEY_JWT = "private_key_jwt";
public static final String TLS_CLIENT_AUTH = "tls_client_auth";
public static final String RESTRICTED_ENCRYPTION_ALGORITHM = "RSA1_5";
janakamarasena marked this conversation as resolved.
Show resolved Hide resolved

private OAuthConstants() {

Expand Down Expand Up @@ -600,6 +624,8 @@ public static class SignatureAlgorithms {
public static final String SHA1 = "SHA-1";
public static final String KID_HASHING_ALGORITHM = SHA256;
public static final String PREVIOUS_KID_HASHING_ALGORITHM = SHA1;
public static final String PS256 = "PS256";
public static final String ES256 = "ES256";

private SignatureAlgorithms() {

Expand Down
3 changes: 3 additions & 0 deletions components/org.wso2.carbon.identity.oauth.dcr/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@
org.wso2.carbon.user.api; version="${carbon.user.api.imp.pkg.version.range}",
org.wso2.carbon.identity.oauth.*;version="${identity.inbound.auth.oauth.imp.pkg.version.range}",
org.wso2.carbon.identity.oauth2.*;version="${identity.inbound.auth.oauth.imp.pkg.version.range}",

com.nimbusds.jose.*; version="${nimbusds.osgi.version.range}",
com.nimbusds.jwt; version="${nimbusds.osgi.version.range}",
</Import-Package>
<Export-Package>
!org.wso2.carbon.identity.oauth.dcr.internal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public enum ErrorMessages {
ERROR_CODE_UNEXPECTED("Unexpected error"),
TENANT_DOMAIN_MISMATCH("NOT_FOUND_60001", "Tenant domain in request does not match with the application " +
"tenant domain for consumer key: %s"),
FAILED_TO_VALIDATE_TENANT_DOMAIN("Error occurred during validating tenant domain for consumer key: %s");
FAILED_TO_VALIDATE_TENANT_DOMAIN("Error occurred during validating tenant domain for consumer key: %s"),
SIGNATURE_VALIDATION_FAILED("Signature validation failed for the software statement");

private final String message;
private final String errorCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ public class Application implements Serializable {
private boolean isRequestObjectSignatureValidationEnabled;
private String idTokenEncryptionAlgorithm = null;
private String idTokenEncryptionMethod = null;
private String softwareStatement = null;

public String getSoftwareStatement() {

return softwareStatement;
}

public void setSoftwareStatement(String softwareStatement) {

this.softwareStatement = softwareStatement;
}

public String getClientName() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.wso2.carbon.identity.oauth.dcr.service;

import com.google.gson.Gson;
import com.nimbusds.jwt.SignedJWT;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
Expand All @@ -34,7 +35,9 @@
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.application.mgt.ApplicationMgtUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.oauth.IdentityOAuthAdminException;
import org.wso2.carbon.identity.oauth.IdentityOAuthClientException;
import org.wso2.carbon.identity.oauth.OAuthAdminService;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException;
Expand All @@ -52,11 +55,15 @@
import org.wso2.carbon.identity.oauth.dcr.util.ErrorCodes;
import org.wso2.carbon.identity.oauth.dto.OAuthConsumerAppDTO;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.util.JWTSignatureValidationUtils;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import static org.wso2.carbon.identity.oauth.Error.INVALID_OAUTH_CLIENT;
Expand All @@ -74,6 +81,8 @@ public class DCRMService {
private static final String GRANT_TYPE_SEPARATOR = " ";
private static final String APP_DISPLAY_NAME = "DisplayName";
private static Pattern clientIdRegexPattern = null;
private static final String SSA_VALIDATION_JWKS = "OAuth.DCRM.SoftwareStatementJWKS";


/**
* Get OAuth2/OIDC application information with client_id.
Expand Down Expand Up @@ -292,8 +301,7 @@ public Application updateApplication(ApplicationUpdateRequest updateRequest, Str
appDTO.setIdTokenEncryptionMethod(updateRequest.getIdTokenEncryptionMethod());
}
if (updateRequest.getRequestObjectSignatureAlgorithm() != null) {
appDTO.setRequestObjectSignatureValidationEnabled
janakamarasena marked this conversation as resolved.
Show resolved Hide resolved
(updateRequest.isRequireSignedRequestObject());
appDTO.setRequestObjectSignatureAlgorithm(updateRequest.getRequestObjectSignatureAlgorithm());
}
if (updateRequest.getTlsClientAuthSubjectDN() != null) {
appDTO.setTlsClientAuthSubjectDN(updateRequest.getTlsClientAuthSubjectDN());
Expand All @@ -315,14 +323,18 @@ public Application updateApplication(ApplicationUpdateRequest updateRequest, Str
appDTO.setPkceSupportPlain(updateRequest.isExtPkceSupportPlain());
appDTO.setBypassClientCredentials(updateRequest.isExtPublicClient());
oAuthAdminService.updateConsumerApplication(appDTO);
} catch (IdentityOAuthClientException e) {
throw new DCRMClientException(DCRMConstants.ErrorCodes.INVALID_CLIENT_METADATA, e.getMessage(), e);
} catch (IdentityOAuthAdminException e) {
throw DCRMUtils.generateServerException(
DCRMConstants.ErrorMessages.FAILED_TO_UPDATE_APPLICATION, clientId, e);
}
OAuthConsumerAppDTO oAuthConsumerAppDTO = getApplicationById(clientId);
// Setting the jwksURI to be sent in the response.
oAuthConsumerAppDTO.setJwksURI(updateRequest.getJwksURI());
return buildResponse(oAuthConsumerAppDTO);
Application application = buildResponse(oAuthConsumerAppDTO);
application.setSoftwareStatement(updateRequest.getSoftwareStatement());
return application;
}

/**
Expand Down Expand Up @@ -414,6 +426,15 @@ private Application createOAuthApplication(ApplicationRegistrationRequest regist
throw DCRMUtils.generateClientException(DCRMConstants.ErrorMessages.CONFLICT_EXISTING_CLIENT_ID,
registrationRequest.getConsumerKey());
}
// Validate software statement assertion signature.
if (StringUtils.isNotEmpty(registrationRequest.getSoftwareStatement())) {
try {
validateSSASignature(registrationRequest.getSoftwareStatement());
} catch (IdentityOAuth2Exception e) {
throw new DCRMClientException(DCRMConstants.ErrorCodes.INVALID_SOFTWARE_STATEMENT,
janakamarasena marked this conversation as resolved.
Show resolved Hide resolved
DCRMConstants.ErrorMessages.SIGNATURE_VALIDATION_FAILED.getMessage(), e);
}
}

// Create a service provider.
ServiceProvider serviceProvider = createServiceProvider(applicationOwner, tenantDomain, spName, templateName,
Expand Down Expand Up @@ -448,7 +469,9 @@ private Application createOAuthApplication(ApplicationRegistrationRequest regist
deleteApplication(createdApp.getOauthConsumerKey());
throw ex;
}
return buildResponse(createdApp);
Application application = buildResponse(createdApp);
application.setSoftwareStatement(registrationRequest.getSoftwareStatement());
return application;
}

private Application buildResponse(OAuthConsumerAppDTO createdApp) {
Expand Down Expand Up @@ -603,6 +626,8 @@ private OAuthConsumerAppDTO createOAuthApp(ApplicationRegistrationRequest regist
OAuthConsumerAppDTO createdApp;
try {
createdApp = oAuthAdminService.registerAndRetrieveOAuthApplicationData(oAuthConsumerApp);
} catch (IdentityOAuthClientException e) {
throw new DCRMClientException(DCRMConstants.ErrorCodes.INVALID_CLIENT_METADATA, e.getMessage(), e);
} catch (IdentityOAuthAdminException e) {
throw DCRMUtils.generateServerException(
DCRMConstants.ErrorMessages.FAILED_TO_REGISTER_APPLICATION, spName, e);
Expand Down Expand Up @@ -630,6 +655,19 @@ private ServiceProvider createServiceProvider(String applicationOwner, String te
sp.setDescription("Service Provider for application " + spName);
sp.setManagementApp(isManagementApp);

Map<String, Object> spProperties = new HashMap<>();
boolean enableFAPI = Boolean.parseBoolean(IdentityUtil.getProperty(OAuthConstants.ENABLE_FAPI));
if (enableFAPI) {
boolean enableFAPIDCR = Boolean.parseBoolean(IdentityUtil.getProperty(
OAuthConstants.ENABLE_DCR_FAPI_ENFORCEMENT));
if (enableFAPIDCR) {
// Add FAPI conformant application nad isThirdParty property to the service provider.
spProperties.put(OAuthConstants.IS_FAPI_CONFORMANT_APP, true);
}
}
spProperties.put(OAuthConstants.IS_THIRD_PARTY_APP, true);
addSPProperties(spProperties, sp);

createServiceProvider(sp, tenantDomain, applicationOwner, templateName);

// Get created service provider.
Expand Down Expand Up @@ -925,4 +963,52 @@ private ServiceProvider cloneServiceProvider(ServiceProvider serviceProvider) {
ServiceProvider clonedServiceProvider = gson.fromJson(gson.toJson(serviceProvider), ServiceProvider.class);
return clonedServiceProvider;
}

/**
* Validate SSA signature using jwks_uri.
* @param softwareStatement Software Statement
* @throws DCRMClientException
* @throws IdentityOAuth2Exception
*/
private void validateSSASignature(String softwareStatement) throws DCRMClientException, IdentityOAuth2Exception {

String jwksURL = IdentityUtil.getProperty(SSA_VALIDATION_JWKS);
janakamarasena marked this conversation as resolved.
Show resolved Hide resolved
if (StringUtils.isNotEmpty(jwksURL)) {
try {
SignedJWT signedJWT = SignedJWT.parse(softwareStatement);
if (!JWTSignatureValidationUtils.validateUsingJWKSUri(signedJWT, jwksURL)) {
throw new DCRMClientException(DCRMConstants.ErrorCodes.INVALID_SOFTWARE_STATEMENT,
DCRMConstants.ErrorMessages.SIGNATURE_VALIDATION_FAILED.getMessage());
}
} catch (ParseException e) {
throw new DCRMClientException(DCRMConstants.ErrorCodes.INVALID_SOFTWARE_STATEMENT,
DCRMConstants.ErrorMessages.SIGNATURE_VALIDATION_FAILED.getMessage(), e);
}

} else {
log.debug("Skipping Software Statement signature validation as jwks_uri is not configured.");
}
}

/**
* Add the properties to the service provider.
* @param spProperties Map of property name and values to be added.
* @param serviceProvider ServiceProvider object.
*/
private void addSPProperties(Map<String, Object> spProperties, ServiceProvider serviceProvider) {

ServiceProviderProperty[] serviceProviderProperties = serviceProvider.getSpProperties();
for (Map.Entry<String, Object> entry : spProperties.entrySet()) {
boolean propertyExists = Arrays.stream(serviceProviderProperties)
.anyMatch(property -> property.getName().equals(entry.getKey()));
if (!propertyExists) {
ServiceProviderProperty serviceProviderProperty = new ServiceProviderProperty();
serviceProviderProperty.setName(entry.getKey());
serviceProviderProperty.setValue(entry.getValue().toString());
serviceProviderProperties = (ServiceProviderProperty[]) ArrayUtils.add(serviceProviderProperties,
serviceProviderProperty);
}
}
serviceProvider.setSpProperties(serviceProviderProperties);
}
}
Loading
Loading