[java-idp-oidc] branch main updated: JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
Henri Mikkonen
henri.mikkonen at iki.fi
Wed May 29 12:46:37 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=67f565e1d16dd07a2ca2cc3ee8afdcfa97c7af7a
The following commit(s) were added to refs/heads/main by this push:
new 67f565e1 JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
67f565e1 is described below
commit 67f565e1d16dd07a2ca2cc3ee8afdcfa97c7af7a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed May 29 15:46:12 2024 +0300
JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
https://shibboleth.atlassian.net/browse/JOIDC-201
- Switched 'iat' claim into mandatory in DPoP proofs
- Wired DPoP proof claim validators for MDDriven recovation and introspection
- Fine-tuned error event ID for DPoP proof signature validation
- Improved testing
---
.../dpop-proof-validation-beans.xml | 3 +-
.../idp/service/relying-party/postconfig.xml | 10 +
.../AbstractOidcClientAuthenticationFlowTest.java | 386 ++++++++++++++++++++-
.../oidc/op/profile/flow/AbstractOidcFlowTest.java | 42 +++
4 files changed, 438 insertions(+), 3 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml
index 387cddf9..0685e549 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml
@@ -75,7 +75,8 @@
<bean id="ValidateDPoPProofSignature" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
scope="prototype" c:executionDirection="INBOUND"
- p:activationCondition-ref="DPoPProofRequiredOrExists">
+ p:activationCondition-ref="DPoPProofRequiredOrExists"
+ p:errorEvent="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_DPOP_PROOF}">
<constructor-arg>
<bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
<property name="handlers">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 07b74c47..9b5d9e44 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -160,6 +160,11 @@
<property name="issuerLookupStrategy">
<bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="issuer" p:defaultValue-ref="shibboleth.oidc.issuer" />
</property>
+ <property name="dpopProofClaimsValidatorLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="dpopProofClaimsValidator"
+ p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
+ p:defaultValue-ref="DefaultDPoPWithAthProofClaimsValidator" />
+ </property>
</bean>
<bean id="AbstractMDDrivenOIDCFlowAwareProfile" parent="AbstractMDDrivenOAuthClientAuthenticatableProfile" abstract="true">
@@ -1037,6 +1042,11 @@
<util:list id="DPoPProofClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
<ref bean="ExpiryClaimsValidator" />
<ref bean="NotBeforeClaimsValidator" />
+ <bean id="DPoPProofIssuedAtClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.policy.messageLifetime:PT1M}"
+ p:requiredRule="true" />
<ref bean="IssuedAtClaimsValidator" />
<bean id="JWTIdentifierClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.JWTIdentifierClaimsValidator"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
index c0b90cef..96bf48ae 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
@@ -29,7 +29,9 @@ import org.springframework.webflow.executor.FlowExecutionResult;
import org.testng.annotations.Test;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jwt.JWT;
@@ -653,12 +655,392 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
}
}
+ @SuppressWarnings("null")
@Test
- public void testInvalidDPoPProof() throws Exception {
+ public void testInvalidDPoPProof_missingMendatoryNonce() throws Exception {
for (final String id : clientIds) {
+ request.removeHeader("DPoP");
final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
clientSecret, JWSAlgorithm.HS256);
- request.addHeader("DPoP", buildDPoPProof(super.defaultDPoPProofKey(), JWSAlgorithm.ES256, "POST", jwtAud,null, null));
+ request.addHeader("DPoP", buildDPoPProof(defaultDPoPProofKey(), JWSAlgorithm.ES256, "POST", jwtAud,
+ null, null).serialize());
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.USE_DPOP_NONCE_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_unsignedProof() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final PlainJWT dpopProof = buildUnsignedDPoPProof(jwk, "POST", jwtAud,
+ null, createValidDPoPNonce());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_wrongHeaderType() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(new Date())
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_missingIat() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_expiredIat() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now().minusSeconds(600)))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_futureIat() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now().plusSeconds(600)))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_twoHeaders() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_missingHtu() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_invalidHtu() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud + "2")
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_missingHtm() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_invalidHtm() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "GET")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_missingJti() throws Exception {
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_missingAth() throws Exception {
+ if ("oidc/token".equals(flowId) || "oauth2/pushed-authorization".equals(flowId)) {
+ return;
+ }
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_invalidAth() throws Exception {
+ if ("oidc/token".equals(flowId) || "oauth2/pushed-authorization".equals(flowId)) {
+ return;
+ }
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ request.removeHeader("Authorization");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = defaultDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .claim("ath", "invalidContents")
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("Authorization", "DPoP mockAccessToken");
+ request.addHeader("DPoP", headerValue);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+ }
+ }
+
+ @Test
+ public void testInvalidDPoPProof_excludedAlg() throws Exception {
+ if ("oidc/token".equals(flowId) || "oauth2/pushed-authorization".equals(flowId)) {
+ return;
+ }
+ for (final String id : clientIds) {
+ request.removeHeader("DPoP");
+ final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+ clientSecret, JWSAlgorithm.HS256);
+ final ECKey jwk = excludedDPoPProofKey();
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES384)
+ .jwk(jwk.toPublicJWK())
+ .type(new JOSEObjectType("dpop+jwt"))
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .jwtID(idGenerator.generateIdentifier())
+ .claim("htu", jwtAud)
+ .claim("htm", "POST")
+ .claim("nonce", createValidDPoPNonce())
+ .issueTime(Date.from(Instant.now()))
+ .build();
+ final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
+ final String headerValue = dpopProof.serialize(); assert headerValue != null;
+ request.addHeader("DPoP", headerValue);
final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
ClientAuthenticationMethod.CLIENT_SECRET_JWT);
assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
index ff431a5f..6d35d051 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
@@ -53,12 +53,14 @@ import org.testng.annotations.BeforeMethod;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.PlainHeader;
import com.nimbusds.jose.crypto.AESEncrypter;
import com.nimbusds.jose.crypto.ECDHEncrypter;
import com.nimbusds.jose.crypto.ECDSASigner;
@@ -73,6 +75,7 @@ import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.ErrorResponse;
import com.nimbusds.oauth2.sdk.GrantType;
@@ -636,7 +639,34 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
Assert.fail("Could not create DPoP proof", e);
}
return null;
+ }
+
+ protected static SignedJWT buildDPoPProof(final JWSHeader header, final JWTClaimsSet claimsSet,
+ final ECPrivateKey privateKey) {
+ final SignedJWT signedJWT = new SignedJWT(header, claimsSet);
+ try {
+ final ECDSASigner signer = new ECDSASigner(privateKey);
+ signedJWT.sign(signer);
+ } catch (JOSEException e) {
+ Assert.fail("Could not sign DPoP proof", e);
+ }
+ return signedJWT;
+ }
+ protected static PlainJWT buildUnsignedDPoPProof(final JWK jwk, final String method,
+ final String uri, final AccessToken accessToken, final String nonceValue) {
+ final PlainHeader header = new PlainHeader.Builder()
+ .type(new JOSEObjectType("dpop+jwt"))
+ .customParam("jwk", jwk.toJSONString())
+ .build();
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .claim("htu", uri.toString())
+ .claim("htm", method)
+ .claim("nonce", nonceValue)
+ .issueTime(new Date())
+ .build();
+ final PlainJWT jwt = new PlainJWT(header, claimsSet);
+ return jwt;
}
protected static ECKey defaultDPoPProofKey() {
@@ -649,4 +679,16 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
}
return null;
}
+
+ protected static ECKey excludedDPoPProofKey() {
+ try {
+ return new ECKeyGenerator(Curve.P_384)
+ .keyID("1")
+ .generate();
+ } catch (JOSEException e) {
+ Assert.fail("Could not initialize DPoP proof key", e);
+ }
+ return null;
+ }
+
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list