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

[ELY-2584] Add the ability to specify that the OIDC Authentication Request should include request and request_uri parameters #1984

Open
wants to merge 2 commits into
base: 2.x
Choose a base branch
from

Conversation

Copy link
Contributor

@Skyllarr Skyllarr left a comment

Choose a reason for hiding this comment

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

@PrarthonaPaul Looks good! I've just had a quick look at it and added some minor comments, I'll look at it more and its affiliated wildfly PR later this week

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>3.1.0.Final</version>
Copy link
Contributor

Choose a reason for hiding this comment

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

@PrarthonaPaul Just a minor, please put a version as a property to the properties tag in the parent pom

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @Skyllarr
I am not sure how to do this. Do you have an example of this being done for another dependency?
Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

@PrarthonaPaul In the parent pom.xml we have properties tag where you can find versions of libraries, see here. Place your keycloak-services dependency there. Then, as with other libraries in that parent pom.xml, place your dependency to the dependencyManagement tag the same way as other deps are. Then you can just use this dependency in child modules without having to specify a version. So in http/oidc/pom.xml you will then be able to place it without a version tag

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you!
I have updated the pom files to reflect this.

@@ -156,7 +156,7 @@ protected JwtClaims createRequestToken(String clientId, String tokenUrl) {
return jwtClaims;
}

private static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
public static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@PrarthonaPaul Just a minor, can this method be protected instead of public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I have changed it to protected.

@@ -41,12 +41,13 @@
"expose-token", "bearer-only", "autodetect-bearer-only",
"connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password",
"client-keystore-file", "client-keystore-password", "client-key-password", "client-key-alias", "client-keystore-type",
Copy link
Contributor

Choose a reason for hiding this comment

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

@PrarthonaPaul Just a minor, should the "client-keystore" stay here as possible configuration property because of backwards compatibility? Users could have been using it before?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh! Thanks for noticing it! I have fixed it! Also changed the class to add a new variable called clientKeyStoreFile that's different from clientKeystore along with a getter and setter for that.

deployment.setClientKeyStore(deployment.getClientKeyStore().replace(PROTOCOL_CLASSPATH, Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath()));
}
if (!deployment.getRequestSignatureAlgorithm().equals(NONE) && deployment.getClientKeyStore() == null){
throw new RuntimeException("Invalid keystore configuration for signing Request Objects.");
Copy link
Contributor

Choose a reason for hiding this comment

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

@PrarthonaPaul Just a minor, please put this exception into the ElytronMessages class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added! Thanks

}

private static KeyStore createEmptyKeyStore() throws IOException, GeneralSecurityException {
KeyStore keyStore = KeyStore.getInstance("JKS");
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a minor, let's not forget to change this from JKS to PKCS12

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!
Thanks for going though the code!

Comment on lines 224 to 240
} else {
// send request as usual
redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri)
.addParameter(STATE, state)
.addParameters(forwardedQueryParams);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently if request or request_uri is not supported, then the authentication is sent as usual.
However, we can change it so that if it is not supported, then we throw a runtime error.
Another thing we can do, is keep it as is, but add a log message saying "Request/request_uri is not supported by the OpenID provider. Request is sent using the Oauth2 format"

Copy link
Contributor

@Skyllarr Skyllarr Oct 6, 2023

Choose a reason for hiding this comment

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

@PrarthonaPaul Thinking.. this should be discussed in the proposal, I see you asked me this there as well. If the authentication is sent as oauth despite a different configuration it has to be logged as a minimum.

I think we should log this and then document that the request and the request_uri methods are used only if available with the OpenID provider, if not available a warning message will be logged. I will write this on the proposal as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have fixed the elytron code and the docs to reflect it.
Will change it in the proposal as well. Thanks!

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>3.1.0.Final</version>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @Skyllarr
I am not sure how to do this. Do you have an example of this being done for another dependency?
Thanks!

@@ -156,7 +156,7 @@ protected JwtClaims createRequestToken(String clientId, String tokenUrl) {
return jwtClaims;
}

private static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
public static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I have changed it to protected.

@@ -41,12 +41,13 @@
"expose-token", "bearer-only", "autodetect-bearer-only",
"connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password",
"client-keystore-file", "client-keystore-password", "client-key-password", "client-key-alias", "client-keystore-type",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh! Thanks for noticing it! I have fixed it! Also changed the class to add a new variable called clientKeyStoreFile that's different from clientKeystore along with a getter and setter for that.

deployment.setClientKeyStore(deployment.getClientKeyStore().replace(PROTOCOL_CLASSPATH, Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath()));
}
if (!deployment.getRequestSignatureAlgorithm().equals(NONE) && deployment.getClientKeyStore() == null){
throw new RuntimeException("Invalid keystore configuration for signing Request Objects.");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added! Thanks

}

private static KeyStore createEmptyKeyStore() throws IOException, GeneralSecurityException {
KeyStore keyStore = KeyStore.getInstance("JKS");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!
Thanks for going though the code!

Comment on lines 531 to 536
} else if (deployment.getRequestSignatureAlgorithm().contains(HS_256)
|| deployment.getRequestSignatureAlgorithm().contains(HS_384)
|| deployment.getRequestSignatureAlgorithm().contains(HS_512)) { //signed with symmetric key
jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestSignatureAlgorithm());
Key key = new HmacKey(deployment.getResourceCredentials().get("secret").toString().getBytes(StandardCharsets.UTF_8)); //the client secret is a shared secret between the server and the client
jsonWebSignature.setDoKeyValidation(false);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For now, when the user wants to sign using symmetric keys, we are signing using the client secret. However, I am not sure what to do when they use JWT as client credentials instead of client secret.

Copy link
Contributor

Choose a reason for hiding this comment

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

@PrarthonaPaul Can they use JWT as a client credential? Can you please describe the background for the situation you are asking about?

in the proposal you mentioned:

In order to sign the jwt using an algorithm other than none, the user must specify the KeyPair used.

So I thought the keypair can be used in all situations for signing but you might be asking about something else

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, sorry.
According to this doc: https://www.keycloak.org/docs/latest/securing_apps/#_client_authentication_adapter
we can either set up the client credentials to be just the client secret, which is a just a hash/SHA (like 19666a4f-32dd-4049-b082-684c74115f28).
But you can also configure it using a JWT. But it is not related to the request RFE.
This is what the options are for specifying the client credentials:
image

Now the reason I am using the client secret to sign a JWT for my RFE is because as we found out HS keys are symmetric. And from what I have seen instead of creating a certificate, we create a secret that is shared between the client. For Keycloak, the client secret is something that is shared between the client and the server.

But what I am wondering is what happens when the user configures the client credentials as a JWT or X509 certificate?

@@ -156,7 +156,7 @@ protected JwtClaims createRequestToken(String clientId, String tokenUrl) {
return jwtClaims;
}

private static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
protected static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is a generic helper method that will also be used in OidcRequestAuthenticator, it would be better to move this method to either an existing utility class or introduce a new utility class and include this method there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good.
Does this class: https://github.com/wildfly-security/wildfly-elytron/blob/2.x/http/oidc/src/main/java/org/wildfly/security/http/oidc/ClientCredentialsProviderUtils.java look like a good one to move this function to?
If not, then we can create another utility class called JWTClientCredentialsProviderUtils.java or something.

Copy link
Contributor

Choose a reason for hiding this comment

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

Since the method will also be used for signing the authentication request in addition to being used for client credentials handling, I think we could create another utility class but try to give it a more generic name (e.g., JWTSigningUtils or something along those lines).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay! Sounds good! Thanks :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is done!

@@ -52,10 +52,14 @@ public class Oidc {
public static final String TEXT_CONTENT_TYPE = "text/*";
public static final String DISCOVERY_PATH = ".well-known/openid-configuration";
public static final String KEYCLOAK_REALMS_PATH = "realms/";
public static final String KEYSTORE_PASS = "password";
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this something that's used for tests? It shouldn't be added to the constants class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved relevant constants to KeycloakConfiguration class.

@@ -52,10 +52,14 @@ public class Oidc {
public static final String TEXT_CONTENT_TYPE = "text/*";
public static final String DISCOVERY_PATH = ".well-known/openid-configuration";
public static final String KEYCLOAK_REALMS_PATH = "realms/";
public static final String KEYSTORE_PASS = "password";
public static final String PKCS12_KEYSTORE_TYPE = "PKCS12";
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, this looks like something that's only needed for tests, it shouldn't be in the Oidc constants class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved to keycloakConfiguration class

public static final String JSON_CONFIG_CONTEXT_PARAM = "org.wildfly.security.http.oidc.json.config";
static final String ACCOUNT_PATH = "account";
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "clients-managements/register-node";
public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "clients-managements/unregister-node";
public static final String ADMIN_CONSOLE_PATH = "admin/master/console/#";
Copy link
Contributor

Choose a reason for hiding this comment

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

Please check all the new constants introduced in this class, it looks like these were meant for tests purposes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

@@ -116,6 +125,35 @@ public class Oidc {
public static final String X_REQUESTED_WITH = "X-Requested-With";
public static final String XML_HTTP_REQUEST = "XMLHttpRequest";

/* Accepted Request Object Signing Algorithms for KeyCloak*/
public static final String NONE = "none";
public static final String RS_256 = "RS256";
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that this class already contains some constants with similar names and also makes use of the AlgorithmIdentifiers class.

These newly added constants appear to be specific to the Keycloak OpenID provider.

Are these really needed?

Can we get the allowed values from discovery?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!
I am using AlgorithmIdentifiers class and throwing an exception if the algorithm is not supported.

public static final String PS_512 = "PS512";

/* Accepted Request Object Encrypting Algorithms for KeyCloak*/
public static final String RSA_OAEP = "RSA-OAEP";
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as above here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

public static final String RSA1_5 = "RSA1_5";

/* Accepted Request Object Encryption Methods for KeyCloak*/
public static final String A256GCM = "A256GCM";
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, can we get these accepted values through discovery instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like most of these, I use for testing, and some of them I don't use at all.
So, I can remove/move those to a test Class, like KeycloakConfiguration. For some of the others, I am using them in the actual implementation. So, I can replace them using AlgorithmIdentifiers.
I can get them using Discovery and I think I do get them. But I don't think I use them yet.

@@ -223,6 +249,15 @@ protected void resolveUrls() {
tokenUrl = config.getTokenEndpoint();
logoutUrl = config.getLogoutEndpoint();
jwksUrl = config.getJwksUri();
authorizationEndpoint = config.getAuthorizationEndpoint();
Copy link
Contributor

Choose a reason for hiding this comment

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

Which endpoint is this?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see something that corresponds to this added in OidcProviderMetadata, is this necessary? Or is it just the pushedAuthorizationRequestEndpoint that's needed?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually I do see authorization_endpoint already referenced in the existing OidcProviderMetadata code. But just to understand better, is the authorizationEndpoint variable that's being introduced here used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It has this endpoint: http://localhost:8080/realms/myrealm/protocol/openid-connect/auth/device
I don't think I use it tho. So I can get rid of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

requestObjectEncryptionEncValuesSupported = config.getRequestObjectEncryptionEncValuesSupported();
requestObjectEncryptionAlgValuesSupported = config.getRequestObjectEncryptionAlgValuesSupported();
requestUriParameterSupported = config.getRequestUriParameterSupported();
realmKeySet = createrealmKeySet();
Copy link
Contributor

@fjuma fjuma Oct 30, 2023

Choose a reason for hiding this comment

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

I don't think we need to create the realm key set here. It would be better to do this when we know we actually need to make use of it (similar to JWKPublicKeyLocator where we use the jwks url when we know we need to).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved to OidcPublicKeyExtractor

@@ -237,6 +272,26 @@ protected void resolveUrls() {
}
}

private JsonWebKeySet createrealmKeySet() throws Exception{
Copy link
Contributor

Choose a reason for hiding this comment

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

"realm" is terminology specific to Keycloak so I don't think we should use it in the method name here.

This method is meant to obtain the encryption keys from the OpenID provider, right? We should rename this method to reflect that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed the function from this class and moved it to a utility class. New function is called extractPublicKeySet

@@ -237,6 +272,26 @@ protected void resolveUrls() {
}
}

private JsonWebKeySet createrealmKeySet() throws Exception{
HttpGet request = new HttpGet(jwksUrl);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make use of the Oidc.sendJsonHttpRequest method here to simplify the logic below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried doing this and kept getting a class mismatch for the JsonSerialization.readValue() function.
I tried this for the JsonWebKeySet, String, and StringBuilder classes.
When I was initially writing this function, I was also trying to use JsonSerialization.readValue() and did not succeed. Which is why I had to do this workaround.

Copy link
Contributor

Choose a reason for hiding this comment

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

There should be an example in JWKPublicKeyLocator where we call sendJsonHttpRequest and store the result in a JsonWebKeySet. Did you take a look there to see how that differs from what you were trying to do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see what the difference is.
The JWKPublicKeyLocator class uses org.wildfly.security.jose.jwk.JsonWebKeySet and I am using the Jose4j one, which is why the parsing failed before.
I have switched it to the wildfly.security one now and it works.
Thank you!

return authenticationRequestFormat;
}

public void setAuthenticationRequestFormat(String requestObjectType ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

s/requestObjectType/authenticationRequestFormat ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed!

return requestSignatureAlgorithm;
}

public void setRequestSignatureAlgorithm(String algorithm) {
Copy link
Contributor

Choose a reason for hiding this comment

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

s/algorithm/requestSignatureAlgorithm

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

return requestEncryptAlgorithm;
}

public void setRequestEncryptAlgorithm(String algorithm) {
Copy link
Contributor

Choose a reason for hiding this comment

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

s/algorithm/requestEncryptionAlgorithm

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed!

return requestEncryptEncValue;
}

public void setRequestEncryptEncValue (String enc) {
Copy link
Contributor

Choose a reason for hiding this comment

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

extra white space before bracket

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

this.requestSignatureAlgorithm = algorithm;
}

public String getRequestEncryptAlgorithm() {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be consistent with the naming pattern for the signing case, there we used getRequestObjectSigningAlgValuesSupported so we should follow a similar pattern here and below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed!

this.requestEncryptEncValue = enc;
}

public String getRealmKey () {
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't use "realm" here. We should rename this to better explain what this returns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't using these functions, so I removed anything related to realmKey that I added.

this.clientKeyAlias = alias;
}

public JsonWebKeySet getrealmKeySet() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that method names should use camel case, i.e., style wise, getrealm should be getRealm.

However, we should rename this to not mention realm as described in comments above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved to utility class and renamed.

redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri)
.addParameter(STATE, state)
.addParameters(forwardedQueryParams);
log.info("The OpenID provider does not support the request parameter. Sending the request using OAuth2 format.");
Copy link
Contributor

Choose a reason for hiding this comment

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

This should make use of ElytronMessages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

@Message(id = 23057, value = "Invalid keystore configuration for signing Request Objects.")
IOException invalidKeyStoreConfiguration();

@Message(id = 23058, value = "The signature algorithm specified is not supported by the OpenID Provider.")
Copy link
Contributor

Choose a reason for hiding this comment

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

s/The signature algorithm/request object signature algorithm

@Message(id = 23058, value = "The signature algorithm specified is not supported by the OpenID Provider.")
IOException invalidRequestObjectSignatureAlgorithm();

@Message(id = 23059, value = "The encryption algorithm specified is not supported by the OpenID Provider.")
Copy link
Contributor

Choose a reason for hiding this comment

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

s/The encryption algorithm/request object encryption algorithm

@Message(id = 23059, value = "The encryption algorithm specified is not supported by the OpenID Provider.")
IOException invalidRequestObjectEncryptionAlgorithm();

@Message(id = 23060, value = "The content encryption algorithm specified is not supported by the OpenID Provider.")
Copy link
Contributor

Choose a reason for hiding this comment

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

s/The content encryption algorithm/The request object content encryption algorithm

@Message(id = 23060, value = "The content encryption algorithm specified is not supported by the OpenID Provider.")
IOException invalidRequestObjectContentEncryptionAlgorithm();

@LogMessage(level = INFO)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should use WARN here instead of INFO.

*
* @author <a href="mailto:prpaul@redhat.com">Prarthona Paul</a>
*/
public interface OidcPublicKeyExtractor {
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks similar to the PublicKeyLocator interface. Can we make use of the existing interface instead?

* @author <a href="mailto:prpaul@redhat.com">Prarthona Paul</a>
*/

public class JWTSigningUtils implements JWTSigning{
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the JWTSignging interface is needed. We can just use the utility class.

/**
* Get the string value for this referral mode.
*
* @return the string value for this referral mode
Copy link
Contributor

Choose a reason for hiding this comment

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

Likely just a copy/paste issue but "referral mode" needs to be updated.

} else {
oidcClientConfiguration.setRequestSignatureAlgorithm(NONE);
}
if (oidcJsonConfiguration.getRequestEncryptAlgorithm() != null && oidcJsonConfiguration.getRequestContentEncryptionMethod() != null) { //both are required to encrypt the request object
Copy link
Contributor

@fjuma fjuma Mar 20, 2024

Choose a reason for hiding this comment

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

Might be good to throw an IllegalArgumentException if only one is specified if that's invalid configuration.

Copy link
Contributor

Choose a reason for hiding this comment

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

Since we know that we'll need to encrypt the request object, maybe we can set the public key locator for that here on the oidcClientConfiguration (e.g., oidcClientConfiguration.setEncPublicKeyLocator or something like that) similar to the way we set the public key locator for the SIG case.

this.requestObjectSigningKeystoreFile = keyStoreFile;
}

public String getRequestObjectSigningKeyStorePassword() {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be consistent with Keystore or KeyStore in the method names in this file.

}

@Override
public String getRequestObjectSigningKeyStorePassword() {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be consistent with Keystore vs KeyStore in the method names in this file.

*
* @author <a href="mailto:prpaul@redhat.com">Prarthona Paul</a>
* */
public class JWKPublicKeySetExtractor implements OidcPublicKeyExtractor {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this could be a non-public class that implements PublicKeyLocator, similar to JWKPublicKeyLocator but it would be used to obtain the public key for JWK.Use.ENC instead of JWK.Use.SIG. This should also cache the key like JWKPublicKeyLocator does.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have followed the PublicKeyLocator class to implement caching.
Please let me know if it makes sense.

* @author <a href="mailto:prpaul@redhat.com">Prarthona Paul</a>
*/

public interface JWTSigning {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need this interface.

String[] unsupportedAttributes = unsupportedAttributesParameter.split(" ");
for (String attributeName : unsupportedAttributes) {
switch(attributeName) {
case AUTHENTICATION_REQUEST_FORMAT:
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the scope RFE, we shouldn't need to hardcode information about the unsupported attributes for WildFly. We'll need to add the handling for unsupported attributes in the elytron-oidc-client subsystem.

protected String authenticationRequestFormat;
protected String requestSignatureAlgorithm;
protected String requestEncryptAlgorithm;
protected String requestContentEncryptionMethod;
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be consistent with the use of request vs requestObject. I think using requestObject makes sense since that matches the actual attribute names too.

protected String authenticationRequestFormat;

@JsonProperty("request-object-signing-algorithm")
protected String requestSignatureAlgorithm;
Copy link
Contributor

Choose a reason for hiding this comment

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

requestObjectSignatureAlgorithm

protected String requestSignatureAlgorithm;

@JsonProperty("request-object-encryption-algorithm")
protected String requestEncryptAlgorithm;
Copy link
Contributor

Choose a reason for hiding this comment

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

requestObjectEncryptionAlgorithm

protected String requestEncryptAlgorithm;

@JsonProperty("request-object-content-encryption-algorithm")
protected String requestContentEncryptionMethod;
Copy link
Contributor

@fjuma fjuma Mar 21, 2024

Choose a reason for hiding this comment

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

requestObjectContentEncryptionAlgorithm

@@ -62,8 +63,16 @@ public class OidcJsonConfiguration {
protected String clientKeystore;
@JsonProperty("client-keystore-password")
protected String clientKeystorePassword;
@JsonProperty("client-key-password")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this removed?

"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password",
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like client-key-password was removed?

return pushedAuthorizationRequestEndpoint;
}

public void setPushedAuthorizationRequestEndpoint (String url) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just minor, extra space before the (

@@ -66,7 +67,7 @@ public void contextInitialized(ServletContextEvent sce) {
if (is == null) {
oidcClientConfiguration = new OidcClientConfiguration();
} else {
oidcClientConfiguration = OidcClientConfigurationBuilder.build(is);
oidcClientConfiguration = OidcClientConfigurationBuilder.buildWithoutUnsupportedAttributes(is, servletContext.getInitParameter(JSON_CONFIG_UNSUPPORTED_ATTRIBUTE_PARAM));
Copy link
Contributor

Choose a reason for hiding this comment

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

}
}

public KeyPair getkeyPair() throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be private static. The deployment can be passed as a parameter.

jsonEncryption.setAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, deployment.getRequestEncryptAlgorithm(), deployment.getRequestContentEncryptionMethod()));
jsonEncryption.setAlgorithmHeaderValue(deployment.getRequestEncryptAlgorithm());
jsonEncryption.setEncryptionMethodHeaderParameter(deployment.getRequestContentEncryptionMethod());
JWK[] jwkList = new JWKPublicKeySetExtractor().extractPublicKeySet(deployment).getKeys();
Copy link
Contributor

@fjuma fjuma Mar 21, 2024

Choose a reason for hiding this comment

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

Instead of needing to use new JWKPublicKeySetExtractor(), we can get the public key locator to use from the oidcClientConfiguration (e.g., oidcClientConfiguration.getEncPublicKeyLocator) and then make use of that to be obtain the public key.

jsonEncryption.setEncryptionMethodHeaderParameter(deployment.getRequestContentEncryptionMethod());
JWK[] jwkList = new JWKPublicKeySetExtractor().extractPublicKeySet(deployment).getKeys();
for (JWK jwk : jwkList) {
if (jwk.getPublicKeyUse().equals("enc")) { //JWTs are to be encrypted with public keys shared by the OpenID provider and decrypted by the private key they hold
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the logic of finding the "enc" public key would be part of the locator class.

return redirectUriBuilder.build().toString();
} catch (URISyntaxException e) {
throw log.unableToCreateRedirectResponse(e);
} catch (IOException | JoseException | InvalidJwtException e) {
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should add a new message to ElytronMessages for this (there should be other examples there for RuntimeExceptions that wrap another exception).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have changed this to be a nested try-catch, where the inner try catch surrounds createRequestWithRequestParameter(REQUEST, redirectUriBuilder, redirectUri, state, forwardedQueryParams);
However, log.unableToCreateRequestWithRequestParameter() throws a runtime exception, since it can either be an IO exception, or a joseException or InvalidJwtException.
Alternatively, I could throw these exceptions separately, but earlier in the stack of function calls.

@@ -0,0 +1,44 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2020 Red Hat, Inc., and individual contributors
Copy link
Contributor

Choose a reason for hiding this comment

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

2024?

@PrarthonaPaul PrarthonaPaul force-pushed the ELY-2584 branch 3 times, most recently from df298ec to 6ad38e0 Compare April 4, 2024 17:01
@PrarthonaPaul
Copy link
Contributor Author

Thanks, @fjuma and @darranl for your review.
I have addressed your comments on this PR.

…quest should include request and request_uri parameters
@PrarthonaPaul PrarthonaPaul force-pushed the ELY-2584 branch 2 times, most recently from a764c3f to ab5f2a1 Compare April 12, 2024 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants