[java-idp-plugin-oidc-rp] branch main updated: JOIDCRP-30 - Support PCKE (RFC7636)
Phil Smart
philip.smart at jisc.ac.uk
Fri Sep 29 16:04:26 UTC 2023
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=65171b311cc3faf45b0644fbb20d481f72633101
The following commit(s) were added to refs/heads/main by this push:
new 65171b3 JOIDCRP-30 - Support PCKE (RFC7636)
65171b3 is described below
commit 65171b311cc3faf45b0644fbb20d481f72633101
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Aug 28 21:47:19 2023 +0100
JOIDCRP-30 - Support PCKE (RFC7636)
https://shibboleth.atlassian.net/browse/JOIDCRP-30
---
.../impl/DefaultAuthCodeTokenRequestEncoder.java | 6 +-
.../impl/AddPCKECodeVerifierAndChallenge.java | 128 +++++++++++++++++++++
.../oidc-relying-party-authn-beans.xml | 2 +
.../authn/oidc/rp/impl/AbstractOIDCTest.java | 6 +-
.../impl/AddPCKECodeVerifierAndChallengeTest.java | 81 +++++++++++++
5 files changed, 220 insertions(+), 3 deletions(-)
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
index 4c53b01..7747762 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
@@ -28,6 +28,7 @@ import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
import com.nimbusds.oauth2.sdk.AuthorizationGrant;
import com.nimbusds.oauth2.sdk.TokenRequest;
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
@@ -65,8 +66,11 @@ public class DefaultAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFu
return null;
}
+ // If PKCE was set in the request (is not null) use it, else set it to null
final AuthorizationGrant codeGrant =
- new AuthorizationCodeGrant(authnResponse.getAuthorizationCode(), authnRequest.getRedirectURI());
+ new AuthorizationCodeGrant(authnResponse.getAuthorizationCode(), authnRequest.getRedirectURI(),
+ authnRequest.getCodeVerifier() != null ? new CodeVerifier(authnRequest.getCodeVerifier())
+ : null);
final TokenRequest tokenRequest = new TokenRequest(providerMetadata.getTokenEndpointURI(),
authnContext.getClientAuthentication(), codeGrant);
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/AddPCKECodeVerifierAndChallenge.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/AddPCKECodeVerifierAndChallenge.java
new file mode 100644
index 0000000..dab0177
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/AddPCKECodeVerifierAndChallenge.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed 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.messaging.impl;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.profile.core.OAuthAuthorizationRequest.CodeChallengeMethod;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Create an OAuth 2.0 PCKE code_verifier to use in the token request, and derives a code_challenge for immediate use in
+ * the authorization request.
+ */
+public class AddPCKECodeVerifierAndChallenge extends AbstractOIDCAuthenticationRequestActionMessageHandler {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AddPCKECodeVerifierAndChallenge.class);
+
+ @Override
+ protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
+
+ if (getProfileConfiguration().isForcePKCE(lookupProfileRequestContext(messageContext))) {
+
+ log.trace("{} PKCE enabled, adding code_challenge to request", getLogPrefix());
+
+ final CodeChallengeMethod method =
+ getProfileConfiguration().isAllowPKCEPlain(lookupProfileRequestContext(messageContext))
+ ? CodeChallengeMethod.PLAIN : CodeChallengeMethod.S256;
+
+ final String codeVerifier = generateCodeVerifier(32);
+ final String challenge = computeCodeChallenge(codeVerifier, method);
+
+ if (log.isTraceEnabled()) {
+ log.trace("{} Created code verifier '...{}'", getLogPrefix(),
+ codeVerifier.substring(challenge.length()-3));
+ log.trace("{} Derived code challenge '...{}'", getLogPrefix(),
+ challenge.substring(challenge.length()-3));
+ }
+ getAuthenticationRequest().setCodeVerifier(codeVerifier);
+ getAuthenticationRequest().setCodeChallenge(challenge);
+ getAuthenticationRequest().setCodeChallengeMethod(method);
+
+ } else {
+ log.trace("{} PKCE not enabled", getLogPrefix());
+ }
+ }
+
+ /**
+ * Generates a code_verifier for use during Proof Key for Code Exchange. The generated bytes are base64 URL
+ * encoded before they are returned.
+ *
+ * @param length the byte length of the code_verifier. Must be at least 32 bytes long (RFC7636 section 7.1).
+ *
+ * @return the base64 URL encoded coder_verifier value.
+ *
+ * @throws MessageHandlerException if there is an error generating the verifier.
+ */
+ @Nonnull private static String generateCodeVerifier(@Nonnull final Integer length) throws MessageHandlerException {
+ if (length < 32) {
+ throw new MessageHandlerException("PKCE coder_verifier must be at least 32 bytes long");
+ }
+ try {
+ final SecureRandom secureRandom = new SecureRandom();
+ final byte[] verifierInBytes = new byte[length];
+ secureRandom.nextBytes(verifierInBytes);
+ return Base64Support.encodeURLSafe(verifierInBytes);
+ } catch (final Exception e) {
+ throw new MessageHandlerException(e);
+ }
+ }
+
+ /**
+ * Compute the code_challenge from the code_verifier. If the {@link CodeChallengeMethod#PLAIN} method is used, the
+ * codeVerifier is returned directly. If the {@link CodeChallengeMethod#S256} method is used, the bytes of the
+ * codeVerifier are SHA-256 hashed and base 64 URL encoded before being returned.
+ *
+ * @param codeVerifier the code_verifier to compute the code_challenge from
+ * @param method the code_challenge_method
+ *
+ * @return the computed code_challenge
+ *
+ * @throws MessageHandlerException on error computing the code_challenge
+ */
+ @Nonnull @NotEmpty private String computeCodeChallenge(@Nonnull @NotEmpty final String codeVerifier,
+ @Nonnull final CodeChallengeMethod method) throws MessageHandlerException {
+
+ if (method == CodeChallengeMethod.PLAIN) {
+ return codeVerifier;
+ }
+ try {
+ final MessageDigest md = MessageDigest.getInstance("SHA-256");
+ final byte[] hash = md.digest(codeVerifier.getBytes());
+ assert hash != null;
+ return Base64Support.encodeURLSafe(hash);
+
+ } catch (final NoSuchAlgorithmException | EncodingException e) {
+ throw new MessageHandlerException("Unable to compute code_challenge", e);
+ }
+
+
+
+ }
+
+}
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 ace2305..0b2c168 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
@@ -117,6 +117,8 @@
<bean id="AddRequestedClaims" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.AddRequestedClaimsHandler"
p:requestedClaimsHook="#{getObject('shibboleth.authn.oidc.rp.RequestedClaimsHook')}" />
+ <bean id="AddPCKECodeVerifierAndChallenge" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.AddPCKECodeVerifierAndChallenge"/>
<bean id="AddRedirectURI" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.AddRedirectURIHandler"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
index f7f8328..8e8dfcc 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -326,7 +326,8 @@ public abstract class AbstractOIDCTest {
* @param prc the prc
* @return the relying party configuration
*/
- protected DefaultOIDCAuthorizationConfiguration getRelyingPartyProfileConfig(@Nonnull final ProfileRequestContext prc) {
+ @Nullable protected DefaultOIDCAuthorizationConfiguration getRelyingPartyProfileConfig(
+ @Nonnull final ProfileRequestContext prc) {
final var rpc = prc.getSubcontext(RelyingPartyContext.class);
assert rpc != null;
return (DefaultOIDCAuthorizationConfiguration) rpc.getProfileConfig();
@@ -340,7 +341,8 @@ public abstract class AbstractOIDCTest {
*
* @return the message context. Never null at that point.
*/
- @Nonnull protected MessageContext getOutboundMessageContextFailIfNull(@Nullable final ProfileRequestContext prc){
+ @Nonnull protected MessageContext getOutboundMessageContextFailIfNull(
+ @Nullable final ProfileRequestContext prc){
assertNotNull(prc);
assert prc != null;
final var outboundMsgCtx = prc.getOutboundMessageContext();
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/AddPCKECodeVerifierAndChallengeTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/AddPCKECodeVerifierAndChallengeTest.java
new file mode 100644
index 0000000..8d63700
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/AddPCKECodeVerifierAndChallengeTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.messaging.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+
+/**
+ * Tests for {@link AddPCKECodeVerifierAndChallenge}.
+ */
+public class AddPCKECodeVerifierAndChallengeTest extends AbstractOIDCTest {
+
+ /** The action to test.*/
+ private AddPCKECodeVerifierAndChallenge handler;
+
+ @Override
+ @BeforeMethod
+ public void setup() throws Exception {
+ super.setup();
+ handler = new AddPCKECodeVerifierAndChallenge();
+ }
+
+ @Test
+ public void testSuccessS256() throws Exception {
+ partyConfig.setForcePKCE(true);
+ partyConfig.setAllowPKCEPlain(false);
+ handler.initialize();
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ handler.invoke(outboundMsgCtx);
+
+ assertNotNull(authnRequest.getCodeVerifier());
+ assertNotNull(authnRequest.getCodeChallenge());
+ }
+
+ @Test
+ public void testSuccessDisabled() throws Exception {
+ partyConfig.setForcePKCE(false);
+ partyConfig.setAllowPKCEPlain(false);
+ handler.initialize();
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ handler.invoke(outboundMsgCtx);
+
+ assertNull(authnRequest.getCodeVerifier());
+ assertNull(authnRequest.getCodeChallenge());
+ }
+
+ @Test
+ public void testSuccessPlain() throws Exception {
+ partyConfig.setForcePKCE(true);
+ partyConfig.setAllowPKCEPlain(true);
+ handler.initialize();
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ handler.invoke(outboundMsgCtx);
+
+ assertNotNull(authnRequest.getCodeVerifier());
+ assertNotNull(authnRequest.getCodeChallenge());
+ assertEquals(authnRequest.getCodeVerifier(), authnRequest.getCodeChallenge());
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list