[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