[java-idp-plugin-oidc-rp] branch main updated: Add sign request object
Phil Smart
philip.smart at jisc.ac.uk
Fri Jul 1 13:45:47 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=9e7d8cb828d00708058bf995f1a3199f2992b53e
The following commit(s) were added to refs/heads/main by this push:
new 9e7d8cb Add sign request object
9e7d8cb is described below
commit 9e7d8cb828d00708058bf995f1a3199f2992b53e
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jul 1 14:45:39 2022 +0100
Add sign request object
---
.../authn/oidc/rp/impl/SignRequestObject.java | 198 +++++++++++++++++++--
.../oidc-relying-party-authn-beans.xml | 10 +-
.../oidc-relying-party-authn-flow.xml | 7 +-
3 files changed, 188 insertions(+), 27 deletions(-)
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SignRequestObject.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SignRequestObject.java
index cc4d130..797821f 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SignRequestObject.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SignRequestObject.java
@@ -1,50 +1,216 @@
+/*
+ * 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.authn.oidc.rp.impl;
+import java.security.interfaces.ECPrivateKey;
+import java.text.ParseException;
+import java.util.function.Function;
+
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+import org.opensaml.xmlsec.context.SecurityParametersContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
-import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractSignJWTAction;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.SignIDToken;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.impl.CredentialConversionUtil;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+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 signs a request object and sets it to ???.
*/
-public class SignRequestObject extends AbstractSignJWTAction {
+public class SignRequestObject extends AbstractOIDCAuthenticationRequestAction {
/** Class logger. */
- @Nonnull
- private final Logger log = LoggerFactory.getLogger(SignRequestObject.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SignRequestObject.class);
+
+ /**
+ * Strategy used to locate the {@link SecurityParametersContext} to use for signing.
+ */
+ @Nonnull private Function<ProfileRequestContext, JWTSecurityParametersContext> securityParametersLookupStrategy;
+
+ /** The signature signing parameters. */
+ @Nullable private SignatureSigningParameters signatureSigningParameters;
+
+ /** resolved credential. */
+ @Nullable private Credential credential;
+
+ /** The claims to sign.*/
+ @Nullable private JWTClaimsSet jwtClaimSetToSign;
+
+ /** "typ" header to insert while signing. */
+ @Nullable @NotEmpty private String typeHeader;
+
+ /** Constructor.*/
+ public SignRequestObject() {
+ securityParametersLookupStrategy = new ChildContextLookup<>(JWTSecurityParametersContext.class)
+ .compose(new OutboundMessageContextLookup());
+ }
+
+ /**
+ * Sets the value to be inserted as a "typ" header for the JWS.
+ *
+ * @param type header value
+ */
+ public void setTypeHeader(@Nullable @NotEmpty final String type) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ typeHeader = StringSupport.trimOrNull(type);
+ }
+
+ /**
+ * Set the strategy used to locate the {@link SecurityParametersContext} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSecurityParametersLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, JWTSecurityParametersContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ securityParametersLookupStrategy =
+ Constraint.isNotNull(strategy, "SecurityParameterContext lookup strategy cannot be null");
+ }
/** {@inheritDoc} */
@Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
- if (!super.doPreExecute(profileRequestContext)) {
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
return false;
}
- if (getOidcResponseContext().getIDToken() == null) {
- log.error("{} No id token available", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ final JWTSecurityParametersContext secParamCtx = securityParametersLookupStrategy.apply(profileRequestContext);
+ if (secParamCtx == null) {
+ log.debug("{} no security parameters context are available", getLogPrefix());
return false;
}
+
+ signatureSigningParameters = secParamCtx.getSignatureSigningParameters();
+ if (signatureSigningParameters == null || signatureSigningParameters.getSigningCredential() == null) {
+ log.debug("{} no signature signing credentials available", getLogPrefix());
+ return false;
+ }
+ final JWT requestObject = getAuthenticationRequest().getRequestObject();
+ if (requestObject == null) {
+ log.debug("{} no JWT request object found, nothing to sign", getLogPrefix());
+ return false;
+ }
+
try {
- idTokenClaims = getOidcResponseContext().getIDToken().toJWTClaimsSet();
+ jwtClaimSetToSign = requestObject.getJWTClaimsSet();
} catch (final ParseException e) {
- log.error("{} id token parsing failed {}", getLogPrefix(), e.getMessage());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ log.debug("{} no JWT request object found, nothing to sign", getLogPrefix(), e);
return false;
}
+
+ credential = signatureSigningParameters.getSigningCredential();
+
return true;
}
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ SignedJWT jwt = null;
+ try {
+ final Algorithm jwsAlgorithm = resolveAlgorithm();
+ final JWSSigner signer = getSigner(jwsAlgorithm);
+ final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(new JWSAlgorithm(jwsAlgorithm.getName()))
+ .keyID(CredentialConversionUtil.resolveKid(credential));
+ if (typeHeader != null) {
+ headerBuilder.type(new JOSEObjectType(typeHeader));
+ }
+ jwt = new SignedJWT(headerBuilder.build(), jwtClaimSetToSign);
+ jwt.sign(signer);
+ } catch (final JOSEException e) {
+ log.error("{} Error signing claim set: {}", getLogPrefix(), e.getMessage());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_SIGN);
+ return;
+ }
+ // Add the signed JWT over the unsigned JWT
+ getAuthenticationRequest().setRequestObject(jwt);
+ }
+
+ /**
+ * Returns correct implementation of signer based on algorithm type.
+ *
+ * @param jwsAlgorithm JWS algorithm
+ * @return signer for algorithm and private key
+ * @throws JOSEException if algorithm cannot be supported
+ */
+ private JWSSigner getSigner(final Algorithm jwsAlgorithm) throws JOSEException {
+ if (JWSAlgorithm.Family.EC.contains(jwsAlgorithm)) {
+ return new ECDSASigner((ECPrivateKey) credential.getPrivateKey());
+ }
+ if (JWSAlgorithm.Family.RSA.contains(jwsAlgorithm)) {
+ return new RSASSASigner(credential.getPrivateKey());
+ }
+ if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
+ return new MACSigner(credential.getSecretKey());
+ }
+ throw new JOSEException("Unsupported algorithm " + jwsAlgorithm.getName());
+ }
+
+ /**
+ * Resolves JWS algorithm from signature signing parameters.
+ *
+ * @return JWS algorithm
+ */
+ protected JWSAlgorithm resolveAlgorithm() {
+
+ final JWSAlgorithm algorithm = new JWSAlgorithm(signatureSigningParameters.getSignatureAlgorithm());
+ if (credential instanceof JWKCredential) {
+ if (!algorithm.equals(((JWKCredential) credential).getAlgorithm())) {
+ log.debug("{} Signature signing algorithm {} differs from JWK algorithm {}", getLogPrefix(),
+ algorithm.getName(), ((JWKCredential) credential).getAlgorithm());
+ }
+ }
+ log.debug("{} Algorithm resolved {}", getLogPrefix(), algorithm.getName());
+ return algorithm;
+ }
}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 70919f0..e79adc0 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -162,11 +162,11 @@
scope="prototype"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
-
- <!-- Build the authentication request -->
- <bean id="AddOIDCAuthenticationRequest" scope="prototype"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AddOIDCAuthenticationRequest"
- p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+
+ <bean id="SignRequestObject" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.SignRequestObject" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
+
<!-- Message Encoder factory is a prototype to allow reuse of the encoders -->
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 543c056..b2c98a9 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -59,16 +59,11 @@
<action-state id="BuildRequestObject">
<evaluate expression="PopulateRequestObjectSignatureSigningParameters"/>
<evaluate expression="BuildRequestObject" />
+ <evaluate expression="SignRequestObject" />
<!-- <evaluate expression="'proceed'" />
<transition on="proceed" to="AuthRequest" /> -->
</action-state>
- <!-- <action-state id="FinaliseAuthenticationRequest">
- <evaluate expression="AddOIDCAuthenticationRequest" />
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="AuthRequest" />
- </action-state> -->
-
<view-state id="AuthRequest"
view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.OIDC.externalAuthnPath'), flowExecutionContext.getKey().toString())}">
<attribute name="csrf_excluded" value="true" type="boolean" />
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list