[java-idp-oidc] branch main updated: JOIDC-97 - support C_HASH in ID_Token also for Authorization Code Flow with PKCE
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Jun 10 13:34:41 UTC 2022
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=ef14718b716e9f0da6151a7d629628c8b8fb3941
The following commit(s) were added to refs/heads/main by this push:
new ef14718b JOIDC-97 - support C_HASH in ID_Token also for Authorization Code Flow with PKCE
ef14718b is described below
commit ef14718b716e9f0da6151a7d629628c8b8fb3941
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Jun 10 16:34:25 2022 +0300
JOIDC-97 - support C_HASH in ID_Token also for Authorization Code Flow with PKCE
https://shibboleth.atlassian.net/browse/JOIDC-97
Refactored AddAuthorizationCodeHashToIDToken to exploit newly added helper functions
when calculating the c_hash value. The helper functions may be used together with the
IDTokenManipulationStrategy (JOIDC-104) in order to include c_hash value to the id_token
also when it's not mandated by the spec.
---
...efaultComputeAuthorizationCodeHashFunction.java | 127 +++++++++++++++++++++
...onseContextAuthorizationCodeLookupFunction.java | 50 ++++++++
...okenRequestAuthorizationCodeLookupFunction.java | 44 +++++++
.../impl/AddAuthorizationCodeHashToIDToken.java | 54 ++++++---
.../idp/flows/oidc/authorize/authorize-beans.xml | 12 ++
.../AddAuthorizationCodeHashToIDTokenTest.java | 11 +-
6 files changed, 281 insertions(+), 17 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultComputeAuthorizationCodeHashFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultComputeAuthorizationCodeHashFunction.java
new file mode 100644
index 00000000..d8ed9175
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultComputeAuthorizationCodeHashFunction.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.context.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+import org.opensaml.xmlsec.context.SecurityParametersContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.openid.connect.sdk.claims.CodeHash;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * A function that computes a hash value for the authorization code via required configurable lookup function.
+ * The signature signing algorithm found in the {@link SecurityParametersContext} is used for calculating the hash.
+ * The lookup function for the security parameters context is required to be set.
+ *
+ * @since 3.2.0
+ */
+public class DefaultComputeAuthorizationCodeHashFunction extends AbstractIdentifiableInitializableComponent
+ implements ContextDataLookupFunction<ProfileRequestContext, String> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultComputeAuthorizationCodeHashFunction.class);
+
+ /**
+ * Strategy used to locate the {@link SecurityParametersContext} to use for calculating the code hash.
+ */
+ @NonnullAfterInit
+ private Function<ProfileRequestContext, SecurityParametersContext> securityParametersLookupStrategy;
+
+ /** Strategy used to locate the raw authorization code value (as {@link String}) to use for calculating the hash. */
+ @NonnullAfterInit private Function<ProfileRequestContext, String> authorizationCodeLookupStrategy;
+
+ /**
+ * Set the strategy used to locate the {@link SecurityParametersContext} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSecurityParametersLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, SecurityParametersContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ securityParametersLookupStrategy =
+ Constraint.isNotNull(strategy, "SecurityParameterContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the authorization code value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthorizationCodeLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ authorizationCodeLookupStrategy =
+ Constraint.isNotNull(strategy, "AuthorizationCode lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (securityParametersLookupStrategy == null) {
+ throw new ComponentInitializationException("SecurityParameterContext lookup strategy cannot be null");
+ }
+ if (authorizationCodeLookupStrategy == null) {
+ throw new ComponentInitializationException("AuthorizationCode lookup strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public String apply(@Nullable final ProfileRequestContext input) {
+ final String rawCode = authorizationCodeLookupStrategy.apply(input);
+ if (StringSupport.trimOrNull(rawCode) == null) {
+ log.error("Could not resolve a value for authorization code to calculate the hash value");
+ return null;
+ }
+ final AuthorizationCode code = new AuthorizationCode(rawCode);
+ final SecurityParametersContext securityParameters = securityParametersLookupStrategy.apply(input);
+ if (securityParameters == null || securityParameters.getSignatureSigningParameters() == null) {
+ log.error("Could not resolve security parameters for calculating the code hash value");
+ return null;
+ }
+ final SignatureSigningParameters signingParameters = securityParameters.getSignatureSigningParameters();
+ final CodeHash cHash = CodeHash.compute(code, new JWSAlgorithm(signingParameters.getSignatureAlgorithm()),
+ null);
+ if (cHash == null || cHash.getValue() == null) {
+ log.error("Not able to generate c_hash using algorithm {}", signingParameters.getSignatureAlgorithm());
+ return null;
+ }
+ return cHash.getValue();
+ }
+}
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ResponseContextAuthorizationCodeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ResponseContextAuthorizationCodeLookupFunction.java
new file mode 100644
index 00000000..ef7a6cd0
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ResponseContextAuthorizationCodeLookupFunction.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.context.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+
+/**
+ * A function that returns raw authorization code value from the {@link OIDCAuthenticationResponseContext}. Null is
+ * returned if the function is not able to locate a value.
+ *
+ * @since 3.2.0
+ */
+public class ResponseContextAuthorizationCodeLookupFunction implements
+ ContextDataLookupFunction<ProfileRequestContext, String> {
+
+ /** {@inheritDoc} */
+ @Nullable
+ public String apply(@Nullable final ProfileRequestContext input) {
+ if (input == null || input.getOutboundMessageContext() == null) {
+ return null;
+ }
+ final OIDCAuthenticationResponseContext ctx =
+ input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class);
+ if (ctx == null || ctx.getAuthorizationCode() == null) {
+ return null;
+ }
+ return ctx.getAuthorizationCode().getValue();
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestAuthorizationCodeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestAuthorizationCodeLookupFunction.java
new file mode 100644
index 00000000..3a7b4125
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestAuthorizationCodeLookupFunction.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.context.navigate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+
+/**
+ * A function that returns raw authorization code value via a lookup function. This lookup locates authorization code
+ * from token claims set for token request handling. If token claims are not available, null is returned.
+ *
+ * @since 3.2.0
+ */
+public class TokenRequestAuthorizationCodeLookupFunction extends AbstractTokenRequestLookupFunction<String> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable String doLookup(@Nonnull final TokenRequest req) {
+ if (GrantType.AUTHORIZATION_CODE.equals(req.getAuthorizationGrant().getType())) {
+ final AuthorizationCodeGrant grant = (AuthorizationCodeGrant) req.getAuthorizationGrant();
+ return grant.getAuthorizationCode().getValue();
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDToken.java
index 6a7760ec..7c81b325 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDToken.java
@@ -17,19 +17,25 @@
package net.shibboleth.idp.plugin.oidc.op.profile.impl;
+import java.util.function.Function;
+
import javax.annotation.Nonnull;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.openid.connect.sdk.claims.CodeHash;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
/**
- * Action that adds authorization code hash claim to a {@link IDTokenClaimsSet}. If there are no signing parameters
- * available, action fails without error event.
+ * Action that adds authorization code hash claim to a {@link IDTokenClaimsSet}. Configurable calculation strategy
+ * function is used for producing the hash.
*/
public class AddAuthorizationCodeHashToIDToken extends AbstractOIDCSigningResponseAction {
@@ -37,6 +43,31 @@ public class AddAuthorizationCodeHashToIDToken extends AbstractOIDCSigningRespon
@Nonnull
private Logger log = LoggerFactory.getLogger(AddAuthorizationCodeHashToIDToken.class);
+ /** The strategy used for calculating the authorization code hash value. */
+ @NonnullAfterInit private Function<ProfileRequestContext, String> codeHashCalculationStrategy;
+
+ /**
+ * Set the strategy used for calculating the authorization code hash value.
+ *
+ * @param strategy calculation strategy
+ */
+ public void setCodeHashCalculationStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ codeHashCalculationStrategy =
+ Constraint.isNotNull(strategy, "Authorization code hash calculation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (codeHashCalculationStrategy == null) {
+ throw new ComponentInitializationException("Authorization code hash calculation strategy cannot be null");
+ }
+ }
+
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -46,21 +77,14 @@ public class AddAuthorizationCodeHashToIDToken extends AbstractOIDCSigningRespon
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return;
}
- if (getOidcResponseContext().getAuthorizationCode() == null) {
- log.error("{} No authz code to calculate hash on", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
- return;
- }
- final CodeHash cHash = CodeHash.compute(getOidcResponseContext().getAuthorizationCode(),
- new JWSAlgorithm(getSignatureSigningParameters().getSignatureAlgorithm()));
- if (cHash == null || cHash.getValue() == null) {
- log.error("{} Not able to generate c_hash using algorithm {}", getLogPrefix(),
- getSignatureSigningParameters().getSignatureAlgorithm());
+ final String hashValue = codeHashCalculationStrategy.apply(profileRequestContext);
+ if (StringSupport.trimOrNull(hashValue) == null) {
+ log.error("{} Could not produce the authorization code hash value", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_SEC_CFG);
return;
}
log.debug("{} Setting authz code hash to id token", getLogPrefix());
- getOidcResponseContext().getIDToken().setClaim(IDTokenClaimsSet.C_HASH_CLAIM_NAME, cHash.getValue());
+ getOidcResponseContext().getIDToken().setClaim(IDTokenClaimsSet.C_HASH_CLAIM_NAME, hashValue);
log.debug("{} Updated token {}", getLogPrefix(),
getOidcResponseContext().getIDToken().toJSONObject().toJSONString());
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 787b1ad2..d2c929c0 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -531,6 +531,18 @@
c:g-ref="shibboleth.ChildLookup.SecurityParameters"
c:f-ref="shibboleth.ChildLookup.RelyingParty" />
</property>
+ <property name="codeHashCalculationStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultComputeAuthorizationCodeHashFunction">
+ <property name="securityParametersLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.SecurityParameters"
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+ </property>
+ <property name="authorizationCodeLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ResponseContextAuthorizationCodeLookupFunction" />
+ </property>
+ </bean>
+ </property>
</bean>
<bean id="ManipulateClaimsForIDToken"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDTokenTest.java
index c4981c6d..ca2b85ec 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAuthorizationCodeHashToIDTokenTest.java
@@ -20,6 +20,8 @@ package net.shibboleth.idp.plugin.oidc.op.profile.impl;
import java.net.URISyntaxException;
import java.time.Instant;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultComputeAuthorizationCodeHashFunction;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ResponseContextAuthorizationCodeLookupFunction;
import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -50,7 +52,12 @@ public class AddAuthorizationCodeHashToIDTokenTest extends BaseOIDCResponseActio
profileRequestCtx.addSubcontext(spCtx);
setIdTokenToResponseContext("iss", "sub", "aud", Instant.now(), Instant.now());
respCtx.setAuthorizationCode("authcode");
+ final DefaultComputeAuthorizationCodeHashFunction hashFunction =
+ new DefaultComputeAuthorizationCodeHashFunction();
+ hashFunction.setAuthorizationCodeLookupStrategy(new ResponseContextAuthorizationCodeLookupFunction());
+ hashFunction.setSecurityParametersLookupStrategy(prc -> spCtx);
action = new AddAuthorizationCodeHashToIDToken();
+ action.setCodeHashCalculationStrategy(hashFunction);
action.initialize();
}
@@ -77,11 +84,11 @@ public class AddAuthorizationCodeHashToIDTokenTest extends BaseOIDCResponseActio
* @throws URISyntaxException
*/
@Test
- public void testAuthorizationCode() throws ComponentInitializationException, ParseException, URISyntaxException {
+ public void testNoAuthorizationCode() throws ComponentInitializationException, ParseException, URISyntaxException {
init("RS256", credentialRSA);
respCtx.setAuthorizationCode(null);
final Event event = action.execute(requestCtx);
- ActionTestingSupport.assertEvent(event, EventIds.INVALID_MSG_CTX);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_SEC_CFG);
}
/**
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list