[java-idp-oidc] branch main updated: JOIDC-179 - Add CSP protection to views

Henri Mikkonen henri.mikkonen at iki.fi
Fri Mar 15 13:27:11 UTC 2024


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=abd5718cfd053a230d023c4a67dd8b0c3cb2fccd

The following commit(s) were added to refs/heads/main by this push:
     new abd5718c JOIDC-179 - Add CSP protection to views
abd5718c is described below

commit abd5718cfd053a230d023c4a67dd8b0c3cb2fccd
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Mar 15 15:23:06 2024 +0200

    JOIDC-179 - Add CSP protection to views
    
    https://shibboleth.atlassian.net/browse/JOIDC-179
    
    - Defined OPCSPDigester and OPCSPNonce global beans
      - to be replaced with shibboleth.CSP -prefixed ones once we depend on 5.1+
    - Wired those to the flows having view-states or end-states with views
      - authorize -flow
      - end-session -flow
      - logoutprop -flow
    - Add policy to oidc-form-post.vm
---
 .../op/encoding/impl/NimbusResponseEncoder.java    | 38 ++++++++++++++++++++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     | 25 ++++++++++++++
 .../oidc/oidc-logout-propagation-flow.xml          |  2 ++
 .../flows/oidc/abstract/oidc-abstract-beans.xml    |  4 ++-
 .../idp/flows/oidc/authorize/authorize-flow.xml    |  8 +++++
 .../flows/oidc/end-session/end-session-flow.xml    | 14 ++++++++
 .../src/main/resources/templates/oidc-form-post.vm |  8 +++--
 7 files changed, 96 insertions(+), 3 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java
index ac255841..8ede8ee5 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java
@@ -41,7 +41,9 @@ import jakarta.servlet.http.HttpServletResponse;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.codec.HTMLEncoder;
+import net.shibboleth.shared.codec.StringDigester;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
 import net.shibboleth.shared.servlet.HttpServletSupport;
 
 /**
@@ -65,6 +67,12 @@ public class NimbusResponseEncoder extends AbstractHttpServletResponseMessageEnc
     /** Object mapper used for pretty-printing JSON response. */
     @NonnullAfterInit private ObjectMapper objectMapper;
 
+    /** CSP digester for generating CSP hashes. */
+    @Nullable private StringDigester cspDigester;
+    
+    /** CSP nonce generator. */
+    @Nullable private IdentifierGenerationStrategy cspNonceGenerator;
+
     /** Constructor. */
     public NimbusResponseEncoder() {
         super();
@@ -108,6 +116,30 @@ public class NimbusResponseEncoder extends AbstractHttpServletResponseMessageEnc
         objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
     }
 
+    /**
+     * Sets a {@link StringDigester} to use in computing CSP digests in views.
+     * 
+     * @param digester digester to set
+     * 
+     * @since 4.1.0
+     */
+    public void setCSPDigester(@Nullable final StringDigester digester) {
+        checkSetterPreconditions();
+        cspDigester = digester;
+    }
+    
+    /**
+     * Sets an {@link IdentifierGenerationStrategy} to use in computing CSP nonces in views.
+     * 
+     * @param strategy nonce strategy
+     * 
+     * @since 4.1.0
+     */
+    public void setCSPNonceGenerator(@Nullable final IdentifierGenerationStrategy strategy) {
+        checkSetterPreconditions();
+        cspNonceGenerator = strategy;
+    }
+
     /**
      * Whether we should use FORM POST response encoding.
      * 
@@ -131,6 +163,12 @@ public class NimbusResponseEncoder extends AbstractHttpServletResponseMessageEnc
             context.put(entry.getKey(), entry.getValue().get(0));
         }
         context.put("action", HTMLEncoder.encodeForHTMLAttribute(message.getRedirectionURI().toString()));
+        if (cspDigester != null) {
+            context.put("cspDigester", cspDigester);
+        }
+        if (cspNonceGenerator != null) {
+            context.put("cspNonce", cspNonceGenerator);
+        }
         return context;
     }
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 2f08bb74..8db4a7bd 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -782,4 +782,29 @@
         p:validator-ref="shibboleth.Conditions.TRUE"
         abstract="true" />
 
+    <!-- TODO: OPCSP-prefixed beans temporarily defined here and used in views to calculate CSP hashes and nonces.
+         Switch into shibboleth.CSP -prefixed ones once we depend on 5.1+ -->
+    
+    <bean id="OPCSPDigester" class="net.shibboleth.shared.codec.StringDigester"
+        c:algorithm="SHA256" c:format="BASE64" />
+
+    <bean id="OPCSPNonce" destroy-method=""
+            class="net.shibboleth.shared.security.IdentifierGenerationStrategy" factory-method="getInstance">
+        <constructor-arg>
+            <util:constant
+                static-field="net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType.SECURE" />
+        </constructor-arg>
+        <constructor-arg>
+            <bean class="net.shibboleth.shared.security.RandomIdentifierParameterSpec"
+                    c:identifierSize="16">
+                <constructor-arg name="source">
+                    <null/>
+                </constructor-arg>
+                <constructor-arg name="identifierEncoder">
+                    <null/>
+                </constructor-arg>
+            </bean>
+        </constructor-arg>
+    </bean>
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/logoutprop/oidc/oidc-logout-propagation-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/logoutprop/oidc/oidc-logout-propagation-flow.xml
index f5e50501..b3038cbf 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/logoutprop/oidc/oidc-logout-propagation-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/logoutprop/oidc/oidc-logout-propagation-flow.xml
@@ -65,6 +65,8 @@
             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.session.context.LogoutContext))" result="viewScope.logoutContext" />
             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.profile.context.MultiRelyingPartyContext))" result="viewScope.multiRPContext" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')" result="viewScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')" result="viewScope.cspNonce" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
             <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().getMessage().toHTTPResponse().getHeaderValue('Location')" result="viewScope.frontChannelLogoutLocation" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
index 8b55525c..b16e670f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
@@ -54,7 +54,9 @@
     <bean id="oidc.nimbusEncoder" class="net.shibboleth.idp.plugin.oidc.op.encoding.impl.NimbusResponseEncoder"
         scope="prototype" p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier" init-method=""
         p:velocityEngine-ref="shibboleth.VelocityEngine"
-        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"/>
+        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"
+        p:cSPDigester-ref="OPCSPDigester"
+        p:cSPNonceGenerator-ref="OPCSPNonce"/>
 
     <bean id="EncodeMessage" class="org.opensaml.profile.action.impl.EncodeMessage" scope="prototype"
         p:messageEncoderFactory-ref="oidc.messageEncoderFactory"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index aaee3297..7dab858e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -349,6 +349,10 @@
             <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)"
                 result="requestScope.encoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')"
+                result="requestScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')"
+                result="requestScope.cspNonce" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()"
                 result="requestScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()"
@@ -366,6 +370,10 @@
             <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)"
                 result="requestScope.encoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')"
+                result="requestScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')"
+                result="requestScope.cspNonce" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()"
                 result="requestScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml
index a9ad01c8..da2fc539 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml
@@ -145,6 +145,8 @@
             <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext))" result="viewScope.oidcRpInitiatedLogoutContext" />
             <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext)).getPostLogoutRedirectUri()" result="viewScope.postLogoutRedirectUri" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')" result="viewScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')" result="viewScope.cspNonce" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
             <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().containsBean('shibboleth.CustomViewContext') ? flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CustomViewContext') : null" result="viewScope.custom" />
@@ -177,6 +179,8 @@
             <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext)).getPostLogoutRedirectUri()" result="viewScope.postLogoutRedirectUri" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.htmlEncoder" />
             <evaluate expression="T(java.net.URLEncoder)" result="viewScope.urlEncoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')" result="viewScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')" result="viewScope.cspNonce" />
             <evaluate expression="T(org.cryptacular.util.CodecUtil)" result="viewScope.codecUtil" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
@@ -197,6 +201,8 @@
             <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext))" result="viewScope.oidcRpInitiatedLogoutContext" />
             <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext)).getPostLogoutRedirectUri()" result="viewScope.postLogoutRedirectUri" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')" result="viewScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')" result="viewScope.cspNonce" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
             <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().containsBean('shibboleth.CustomViewContext') ? flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CustomViewContext') : null" result="viewScope.custom" />
@@ -247,6 +253,10 @@
             <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)"
                 result="requestScope.encoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')"
+                result="requestScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')"
+                result="requestScope.cspNonce" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()"
                 result="requestScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()"
@@ -264,6 +274,10 @@
             <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
             <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)"
                 result="requestScope.encoder" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPDigester')"
+                result="requestScope.cspDigester" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('OPCSPNonce')"
+                result="requestScope.cspNonce" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()"
                 result="requestScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()"
diff --git a/idp-oidc-extension-impl/src/main/resources/templates/oidc-form-post.vm b/idp-oidc-extension-impl/src/main/resources/templates/oidc-form-post.vm
index 57a324cf..eb62b0d8 100644
--- a/idp-oidc-extension-impl/src/main/resources/templates/oidc-form-post.vm
+++ b/idp-oidc-extension-impl/src/main/resources/templates/oidc-form-post.vm
@@ -2,6 +2,10 @@
 ## Velocity Template for OIDC Form Post response mode.
 ##
 ##
+#set ($onLoad = "document.forms[0].submit()")
+#if($cspDigester)
+    $response.addHeader("Content-Security-Policy","object-src 'none'; script-src 'none'; script-src-attr 'unsafe-hashes' 'sha256-$cspDigester.apply($onLoad)'")
+#end
 <!DOCTYPE html>
 <html>
 
@@ -9,7 +13,7 @@
     <meta charset="utf-8" />
 </head>
 
-<body onload="document.forms[0].submit()">
+<body onload="$onLoad">
     <noscript>
         <p>
             <strong>Note:</strong> Since your browser does not support JavaScript, you must press the Continue button once to proceed.
@@ -45,4 +49,4 @@
     </form>
 </body>
 
-</html>
\ No newline at end of file
+</html>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list