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
base: 2.x
Are you sure you want to change the base?
Conversation
93dc491
to
3ce624a
Compare
There was a problem hiding this 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
http/oidc/pom.xml
Outdated
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-services</artifactId> | ||
<version>3.1.0.Final</version> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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."); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
3ce624a
to
aa4ac31
Compare
} else { | ||
// send request as usual | ||
redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri) | ||
.addParameter(STATE, state) | ||
.addParameters(forwardedQueryParams); | ||
} |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
http/oidc/pom.xml
Outdated
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-services</artifactId> | ||
<version>3.1.0.Final</version> |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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."); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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!
} 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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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:
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?
aa4ac31
to
58fd4a6
Compare
58fd4a6
to
0bc8938
Compare
@@ -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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay! Sounds good! Thanks :)
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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/#"; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which endpoint is this?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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{ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/requestObjectType/authenticationRequestFormat ?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/algorithm/requestSignatureAlgorithm
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/algorithm/requestEncryptionAlgorithm
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 () { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java
Show resolved
Hide resolved
this.clientKeyAlias = alias; | ||
} | ||
|
||
public JsonWebKeySet getrealmKeySet() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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."); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!
@@ -0,0 +1,44 @@ | |||
/* | |||
* JBoss, Home of Professional Open Source. | |||
* Copyright 2020 Red Hat, Inc., and individual contributors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2024?
df298ec
to
6ad38e0
Compare
ab5f2a1
to
ec566f3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks very much for the updates @PrarthonaPaul! I've added some comments.
* | ||
* @author <a href="mailto:prpaul@redhat.com">Prarthona Paul</a> | ||
* */ | ||
class JWKPublicKeySetExtractor implements PublicKeyLocator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wondering if we could rename this so the difference between this one and the existing JWKPublicKeyLocator class is more obvious. Maybe JWKEncPublicKeyLocator? WDYT?
We should also update the javadoc accordingly to indicate that this locator dynamically obtains the public key to use for encryption.
private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>(); | ||
|
||
private volatile int lastRequestTime = 0; | ||
public JWKPublicKeySetExtractor() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be removed (since it's just the default no-arg constructor).
} | ||
|
||
private void sendRequest(String kid, OidcClientConfiguration config) { | ||
HttpGet request = new HttpGet(config.getJwksUrl()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should add a trace log message at the beginning like the one here:
wildfly-elytron/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKPublicKeyLocator.java
Line 90 in 0dba5eb
if (log.isTraceEnabled()) { |
try { | ||
JWK[] jwkList = Oidc.sendJsonHttpRequest(config, request, JsonWebKeySet.class).getKeys(); | ||
for (JWK jwk : jwkList) { | ||
if (jwk.getPublicKeyUse().equals(kid)) { //JWTs are to be encrypted with public keys shared by the OpenID provider and decrypted by the private key they hold |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the kid that will end up being passed in is "enc" but am wondering if that's something that we can rely on. Shouldn't we be filtering for encryption keys using something like JsonWebKeySetUtil.getKeysForUse(jwks, JWK.Use.ENC)
? The kid
can be any value right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this function is only called within the elytron Oidc code, and it is not dependent on any user input, could there be cases where kid would not be JWK.Use.ENC
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just realized I misread this a bit. The kid
stands for key identifier and this is something that would be specified by the OpenID provider in their JWKS, we don't have control over this. So when I saw the kid
being set to enc
in the other file, I assumed that we were going to search for a key identifier with value enc
. But here, I see that the enc
value is actually being used to check the getPublicKeyUse()
value which is what the method I suggested to use makes use of. Because this public key locator class is going to be used specifically for locating the encryption key, it doesn't need to be a parameter that's passed in. Take a look at the existing locator implementation to see how we make use of the JsonWebKeySetUtil.getKeysForUse method there, we can do something similar here. The main difference is just the JWK.Use value that is used. Here we'll use JWK.Use.ENC.
* @author <a href="mailto:prpaul@redhat.com">Prarthona Paul</a> | ||
*/ | ||
|
||
public class JWTSigningUtils { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be public?
return authenticationRequestFormat; | ||
} | ||
|
||
public void setAuthenticationRequestFormat(String authenticationRequestFormat ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor, extra space before the closing bracket.
this.requestObjectSignatureAlgorithm = requestObjectSignatureAlgorithm; | ||
} | ||
|
||
public String getRequestObjectEncryptAlgorithm() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Encrypt/Encryption here and below?
http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java
Show resolved
Hide resolved
http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java
Show resolved
Hide resolved
try { | ||
response = deployment.getClient().execute(parRequest); | ||
} catch (Exception e) { | ||
throw log.failedToConnectToOidcProvider(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this log message more specific? e.g., it would be good to indicate there was an issue with sending the request to the OpenID provider's pushed authorization request endpoint.
return requestObjectSigningKeyStorePass; | ||
} | ||
|
||
public void setRequestObjectSigningKeyStorePassword(String pass) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/pass/requestObjectSigningKeyStorePass
return requestObjectSigningKeyPass; | ||
} | ||
|
||
public void setRequestObjectSigningKeyPassword(String pass) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/pass/requestObjectSigningKeyPassword
s/requestObjectSigningKeyPass/requestObjectSigningKeyPassword
return requestObjectSigningKeyStoreType; | ||
} | ||
|
||
public void setRequestObjectSigningKeyStoreType(String type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/type/requestObjectSigningKeyStoreType
return requestObjectSigningKeyAlias; | ||
} | ||
|
||
public void setRequestObjectSigningKeyAlias(String alias) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/alias/requestObjectSigningKeyAlias
return pushedAuthorizationRequestEndpoint; | ||
} | ||
|
||
public void setPushedAuthorizationRequestEndpoint(String url) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/url/pushedAuthorizationRequestEndpoint
@@ -126,6 +132,17 @@ public enum RelativeUrlsUsed { | |||
protected boolean verifyTokenAudience = false; | |||
|
|||
protected String tokenSignatureAlgorithm = DEFAULT_TOKEN_SIGNATURE_ALGORITHM; | |||
protected String authenticationRequestFormat; | |||
protected String requestObjectSignatureAlgorithm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good to be consistent with the JSON config option that will be used to configure this. It looks like we use signing there as opposed to signature, right? If so, we should update this.
…quest should include request and request_uri parameters
this.requestObjectSignatureAlgorithm = requestObjectSignatureAlgorithm; | ||
} | ||
|
||
public String getRequestEncryptAlgorithm() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be getRequestObjectEncryptionAlgorithm?
} | ||
|
||
@Override | ||
public String getRequestObjectSignatureAlgorithm() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Signing?
} | ||
|
||
@Override | ||
public void setRequestObjectSignatureAlgorithm(String requestSignature) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Signing?
protected String authenticationRequestFormat; | ||
|
||
@JsonProperty("request-object-signing-algorithm") | ||
protected String requestObjectSignatureAlgorithm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Signing?
protected String requestObjectSignatureAlgorithm; | ||
|
||
@JsonProperty("request-object-encryption-algorithm") | ||
protected String requestObjectEncryptAlgorithm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Encryption?
protected String requestObjectEncryptAlgorithm; | ||
|
||
@JsonProperty("request-object-content-encryption-algorithm") | ||
protected String requestObjectContentEncryptionMethod; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/requestObjectContentEncryptionMethod/requestObjectContentEncryptionAlgorithm?
It's good for the variable name to be consistent with the configuration option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just looking closer in the OIDC spec and I don't see references to "request object content encryption methods". Am just wondering if from a usability perspective, it will be confusing to determine what values should be specified for request-object-encryption-algorithm
vs. request-object-content-encryption-algorithm
. Do you recall the rationale for these names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was just reviewing the analysis doc and see that we picked these names to match the Keycloak options in their console. However, it doesn't look like the OIDC spec uses the same terminology.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could use something like request-object-encryption-enc-value
and request-object-encryption-alg-value
to more closely align with the spec? WDYT?
return requestObjectSigningKeyStoreType; | ||
} | ||
|
||
public void setRequestObjectSigningKeyStoreType(String type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/type/requestObjectSigningKeyStoreType
return requestObjectSigningKeyAlias; | ||
} | ||
|
||
public void setRequestObjectSigningKeyAlias(String alias) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
return requestObjectEncryptAlgorithm; | ||
} | ||
|
||
public void setRequestObjectEncryptAlgorithm(String requestObjectSignatureAlgorithm) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Encryption?
this.requestObjectEncryptAlgorithm = requestObjectSignatureAlgorithm; | ||
} | ||
|
||
public String getRequestObjectContentEncryptionMethod() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be getRequestObjectContentEncryptionAlgorithm?
return requestObjectContentEncryptionMethod; | ||
} | ||
|
||
public void setRequestObjectContentEncryptionMethod (String requestObjectContentEncryptionMethod) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
@@ -461,4 +544,124 @@ private void addScopes(String scopes, Set<String> allScopes) { | |||
allScopes.addAll(Arrays.asList(scopes.split("\\s+"))); | |||
} | |||
} | |||
|
|||
private String convertToRequestParameter(URIBuilder redirectUriBuilder, String redirectUri, String state, List<NameValuePair> forwardedQueryParams) throws JoseException, IOException { | |||
redirectUriBuilder.addParameter(SCOPE, OIDC_SCOPE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if additional scopes have been configured?
|
||
JwtClaims jwtClaims = new JwtClaims(); | ||
jwtClaims.setIssuer(deployment.getIssuerUrl()); | ||
jwtClaims.setAudience(deployment.getProviderUrl()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the spec, "the aud value SHOULD be or include the OP's Issuer Identifier URL". So getIssuerUrl should be used here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the iss and aud claims should be included if the request object will be signed.
redirectUriBuilder.addParameter(SCOPE, OIDC_SCOPE); | ||
|
||
JwtClaims jwtClaims = new JwtClaims(); | ||
jwtClaims.setIssuer(deployment.getIssuerUrl()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the spec, "the iss value SHOULD be the Client ID of the RP, unless it was signed by a different party than the RP". So here, we can use getResourceName.
JsonWebSignature signedRequest = signRequest(jwtClaims); | ||
|
||
// Encrypting optional | ||
if (deployment.getRequestObjectEncryptionAlgorithm() != null && !deployment.getRequestObjectEncryptionAlgorithm().isEmpty() && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be the empty string? Is that a valid value?
https://issues.redhat.com/browse/ELY-2584
Analysis doc: wildfly/wildfly-proposals#532
Relevant discussion page: