[java-idp-oidc] 03/03: JOIDC-222 - Support for OpenID Federation

Henri Mikkonen henri.mikkonen at iki.fi
Fri Feb 21 14:24:18 UTC 2025


This is an automated email from the git hooks/post-receive script.

hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=822bb80a79bf71f4504009930101323a9faf491b

commit 822bb80a79bf71f4504009930101323a9faf491b
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Feb 21 16:23:55 2025 +0200

    JOIDC-222 - Support for OpenID Federation
    
    https://shibboleth.atlassian.net/browse/JOIDC-222
    
    Initial flow tests for automatic registration via PAR and authorize-flows
    - The global bean 'shibboleth.oidfed.HttpClient' is mocked so that metadata caches get populated correctly
---
 .../plugin/oidc/op/oidfed/TrustChainTestUtil.java  | 124 ++++++++++
 .../flow/oidfed/AbstractFederationFlowTest.java    | 268 +++++++++++++++++++++
 .../AuthorizeFlowAutomaticRegistrationTest.java    | 227 +++++++++++++++++
 ...shedAuthorizeFlowAutomaticRegistrationTest.java | 231 ++++++++++++++++++
 .../resources/credentials/fed-local-anchor.jwk     |  13 +
 .../net/shibboleth/idp/module/conf/global.xml      |   8 +
 .../net/shibboleth/idp/module/conf/oidc.properties |   5 +-
 .../idp/module/conf/oidfed-trust-anchors.json      |  13 +
 .../shibboleth/idp/module/conf/relying-party.xml   |  16 ++
 9 files changed, 904 insertions(+), 1 deletion(-)

diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
new file mode 100644
index 00000000..a76a63e3
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
@@ -0,0 +1,124 @@
+/*
+ * 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.oidc.op.oidfed;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
+import org.testng.Assert;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+/**
+ * Various utility methods for testing trust chains.
+ */
+public class TrustChainTestUtil {
+
+    public static List<EntityStatement> chainWithIntermediate(final EntityStatement leaf, final String anchorId,
+            final String intermediateId) {
+        try {
+            final RSAKey anchorKey = new RSAKeyGenerator(2048)
+                    .keyID("mockTrustAnchorKey")
+                    .keyUse(KeyUse.SIGNATURE)
+                    .generate();
+            final RSAKey intermediateKey = new RSAKeyGenerator(2048)
+                    .keyID("mockIntermediateKey")
+                    .keyUse(KeyUse.SIGNATURE)
+                    .generate();
+            final EntityStatement trustAnchor = trustAnchor(JWSAlgorithm.RS256, anchorKey, anchorId);
+            final EntityStatement intermediateStatement = entityStatement(JWSAlgorithm.RS256, anchorKey,
+                    new JWTClaimsSet.Builder()
+                    .subject(intermediateId)
+                    .issueTime(new Date())
+                    .issuer(anchorId)
+                    .build());
+            final EntityStatement leafSubordinateStatement = entityStatement(JWSAlgorithm.RS256, intermediateKey,
+                    new JWTClaimsSet.Builder(leaf.getClaimsSet().toJWTClaimsSet())
+                    .subject(leaf.getEntityID().getValue())
+                    .issueTime(new Date())
+                    .issuer(intermediateId)
+                    .build());
+            return List.of(leaf, leafSubordinateStatement, intermediateStatement, trustAnchor);
+        } catch (final ParseException | JOSEException e) {
+            Assert.fail("Could not construct trust chain", e);
+        }
+        return null;
+    }
+
+    public static EntityStatement trustAnchor(final JWSAlgorithm algorithm, final JWK jwk, final String entityId) {
+        return entityStatement(algorithm, jwk, 
+                new JWTClaimsSet.Builder()
+                .subject(entityId)
+                .issueTime(new Date())
+                .issuer(entityId)
+                .build());
+    }
+
+    public static EntityStatement entityStatement(final JWSAlgorithm algorithm, final JWK jwk,
+            final JWTClaimsSet claimsSet) {
+        try {
+            return EntityStatement.parse(signedJwt(algorithm, jwk, "entity-statement+jwt", claimsSet));
+        } catch (final ParseException e) {
+            Assert.fail("Could not construct entity configuration", e);
+        }
+        return null;
+    }
+
+    public static SignedJWT trustMark(final JWSAlgorithm algorithm, final JWK jwk, final String iss, final String sub,
+            final String id, final Instant exp) {
+        final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
+                .subject(sub)
+                .issuer(iss)
+                .claim("id", id)
+                .issueTime(new Date())
+                .expirationTime(exp == null ? null : Date.from(exp));
+        return signedJwt(algorithm, jwk, "trust-mark+jwt", builder.build());
+    }
+
+    public static SignedJWT signedJwt(final JWSAlgorithm algorithm, final JWK jwk, final String type,
+            final JWTClaimsSet claimsSet) {
+        final SignedJWT signedJwt = new SignedJWT(
+                new JWSHeader.Builder(algorithm)
+                    .type(new JOSEObjectType(type))
+                    .keyID(jwk.getKeyID())
+                    .build(),
+                claimsSet);
+        try {
+            if (JWSAlgorithm.Family.RSA.contains(algorithm)) {
+                signedJwt.sign(new RSASSASigner(jwk.toRSAKey()));
+            } else {
+                signedJwt.sign(new ECDSASigner(jwk.toECKey()));
+            }
+            return signedJwt;
+        } catch (final JOSEException e) {
+            Assert.fail("Could not construct signed JWT", e);
+        }
+        return null;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
new file mode 100644
index 00000000..ee5a9464
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.oidc.op.profile.flow.oidfed;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.time.Instant;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.Nonnull;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.mockito.ArgumentMatcher;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.TrustChainTestUtil;
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.AbstractOidcFlowTest;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Abstract unit test for the flows supporting OpenID federation.
+ */
+public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
+
+    final static AtomicInteger clientIndex = new AtomicInteger();
+    final String redirectUri = "https://rp.federation.local/cb";
+    final String clientIdPattern = "https://testrp%s.federation.local";
+    final String anchorId = "https://trust-anchor.federation.local";
+    final String anchorFetchEndpoint = anchorId + "/fetch";
+    String issuer = "https://op.example.org";
+
+    JWK rpKey;
+    JWK leafKey;
+    JWK anchorKey;
+    JWK trustedAnchorKey;
+
+    @Autowired
+    @Qualifier("shibboleth.oidfed.HttpClient")
+    HttpClient federationHttpClient;
+    
+    protected AbstractFederationFlowTest(final String flowId) {
+        super(flowId);
+    }
+
+    @BeforeClass
+    public void initKeys() throws NoSuchAlgorithmException {
+        rpKey = initializeNewJwk("RSA", 2048, "mockRpKey");
+        leafKey = initializeNewJwk("RSA", 2048, "mockLeafKey");
+        anchorKey = initializeNewJwk("RSA", 2048, "mockAnchorKey");
+        final BasicJWKCredential localAnchor = loadCredential("/credentials/fed-local-anchor.jwk");
+        trustedAnchorKey = new RSAKey.Builder((RSAPublicKey) localAnchor.getPublicKey())
+                .privateKey(localAnchor.getPrivateKey())
+                .keyID("locallyTrustedAnchorKey")
+                .build();
+    }
+
+    protected JWK initializeNewJwk(final String algorithm, final int size, final String kid)
+            throws NoSuchAlgorithmException {
+        if ("RSA".equals(algorithm)) {
+            final KeyPair keyPair = generateNewKeyPair(algorithm, size);
+            return new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())
+                    .privateKey(keyPair.getPrivate())
+                    .keyID(kid)
+                    .build();
+        }
+        throw new NoSuchAlgorithmException(algorithm);
+    }
+
+    protected JWT plainRequestObject(final Map<String,Object> claims) {
+        final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
+        for (final String claim : claims.keySet()) {
+            builder.claim(claim, claims.get(claim));
+        }
+        return new PlainJWT(builder.build());
+    }
+
+    protected JWT signedRequestObject(final Map<String,Object> claims) {
+        return signedRequestObject(claims, rpKey);
+    }
+
+    protected JWT signedRequestObject(final Map<String,Object> claims, final JWK jwk) {
+        final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
+        for (final String claim : claims.keySet()) {
+            builder.claim(claim, claims.get(claim));
+        }
+        try {
+            final SignedJWT jwt =
+                    new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(jwk.getKeyID()).build(),
+                            builder.build());
+            final RSASSASigner signer = new RSASSASigner(jwk.toRSAKey());
+            jwt.sign(signer);
+            return jwt;
+        } catch (JOSEException e) {
+            Assert.fail(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    protected String entityConfigurationUrl(final String entityId) {
+        return entityId + "/.well-known/openid-federation";
+    }
+
+    protected String subordinateStatementUrl(final String fetchEndpoint, final String issuer, final String subject) {
+        return fetchEndpoint + "?iss=" + issuer + "&sub=" + subject;
+    }
+
+    protected void mapResponse(final String requestUri, final ClassicHttpResponse classicResponse) throws IOException {
+        when(federationHttpClient.executeOpen(any(), argThat(new RequestUriMatcher(requestUri)), any()))
+            .thenReturn(classicResponse);
+    }
+
+    protected ClassicHttpResponse mockResponse(final String contents)
+            throws UnsupportedOperationException, IOException {
+        return mockResponse(200, "application/entity-statement+jwt", contents);
+    }
+
+    protected ClassicHttpResponse mockResponse(final int code, final String contentType, final String contents)
+            throws UnsupportedOperationException, IOException {
+        final ClassicHttpResponse classicResponse = mock(ClassicHttpResponse.class);
+        when(classicResponse.getCode()).thenReturn(code);
+        final HttpEntity responseEntity = mock(HttpEntity.class);
+        when(responseEntity.getContentType()).thenReturn(contentType);
+        when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(contents.getBytes()));
+        when(classicResponse.getEntity()).thenReturn(responseEntity);
+        return classicResponse;
+    }
+
+    protected String rpEntityConfiguration(final String clientId) throws URISyntaxException {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setResponseTypes(Set.of(ResponseType.CODE));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+        return rpEntityConfiguration(clientId, metadata);
+    }
+
+    protected String rpEntityConfiguration(final String clientId, final OIDCClientMetadata metadata)
+            throws URISyntaxException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(clientId).subject(clientId)
+                .issueTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+                .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+                .claim("metadata", Map.of("openid_relying_party", metadata.toJSONObject()))
+                .claim("authority_hints", new String[] { anchorId })
+                .build();
+        final EntityStatement rpConfiguration =
+                TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
+        return rpConfiguration.getSignedStatement().serialize();
+    }
+
+    protected String trustedAnchorConfiguration() {
+        final String anchorId = "https://trust-anchor.federation.local";
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(anchorId)
+                .issueTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+                .claim("jwks", new JWKSet(trustedAnchorKey).toJSONObject(true))
+                .claim("metadata", Map.of("federation_entity", Map.of("federation_fetch_endpoint",
+                        anchorFetchEndpoint)))
+                .build();
+        final EntityStatement anchorConfiguration =
+                TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
+        return anchorConfiguration.getSignedStatement().serialize();
+    }
+
+    protected String subordinateStatement(final String clientId) {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(clientId)
+                .issueTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+                .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+                .claim("metadata", Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+                .claim("authority_hints", new String[] { anchorId })
+                .build();
+        final EntityStatement rpConfiguration =
+                TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
+        return rpConfiguration.getSignedStatement().serialize();
+    }
+    
+    protected String uniqueClientId() {
+        return String.format(clientIdPattern, clientIndex.getAndIncrement());
+    }
+
+    protected void configureMockHttpClient(final String clientId) {
+        try {
+            mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId)));
+            mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+            mapResponse(subordinateStatementUrl(anchorFetchEndpoint, anchorId, clientId),
+                    mockResponse(subordinateStatement(clientId)));
+        } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+            Assert.fail("Could not initialize mock HTTP client", e);
+        }
+    }
+
+    protected void configureMockHttpClient(final String clientId, final OIDCClientMetadata metadata) {
+        try {
+            mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId, metadata)));
+            mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+            mapResponse(subordinateStatementUrl(anchorFetchEndpoint, anchorId, clientId),
+                    mockResponse(subordinateStatement(clientId)));
+        } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+            Assert.fail("Could not initialize mock HTTP client", e);
+        }
+    }
+
+    protected class RequestUriMatcher implements ArgumentMatcher<ClassicHttpRequest> {
+
+        @Nonnull private final String uri;
+
+        public RequestUriMatcher(final String value) {
+            uri = Constraint.isNotEmpty(value, "URI value cannot be null");
+        }
+
+        @Override
+        public boolean matches(final ClassicHttpRequest match) {
+            try {
+                return match != null && uri.equals(match.getUri().toString());
+            } catch (URISyntaxException e) {
+            }
+            return false;
+        }
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
new file mode 100644
index 00000000..4cdb852f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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.oidc.op.profile.flow.oidfed;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.AuthorizeFlowTest;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriSerializationFunction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.security.DataSealerException;
+
+public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
+
+    public AuthorizeFlowAutomaticRegistrationTest() {
+        super(AuthorizeFlowTest.FLOW_ID);
+    }
+
+    @Test
+    public void testWithValidTrustChain_noRequestObject()
+            throws IOException, UnsupportedOperationException, URISyntaxException {
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId);
+        final FlowExecutionResult result = launchAuthenticationRequest(clientId, "openid profile");
+        Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+    }
+
+    @Test
+    public void testWithValidTrustChain_plainRequestObject()
+            throws IOException, UnsupportedOperationException, URISyntaxException {
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId);
+        final FlowExecutionResult result =
+                launchAuthenticationRequest(clientId, "openid profile", plainRequestObject(Map.of(
+                        "iss", clientId,
+                        "aud", issuer,
+                        "response_type", "code",
+                        "scope", "openid profile",
+                        "redirect_uri", redirectUri)));
+        Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+    }
+
+    @Test
+    public void testWithValidTrustChain_signedRequestObject()
+            throws IOException, UnsupportedOperationException, URISyntaxException {
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId);
+        final FlowExecutionResult result =
+                launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+                        "iss", clientId,
+                        "aud", issuer,
+                        "response_type", "code",
+                        "scope", "openid profile",
+                        "redirect_uri", redirectUri)));
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertEquals(unwrapTrustChainFromAuthorizeCode(successResponse), List.of(clientId, anchorId));
+    }
+
+    @Test
+    public void testWithValidTrustChain_leafKeySignedRequestObject()
+            throws IOException, UnsupportedOperationException, URISyntaxException {
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId);
+        final FlowExecutionResult result =
+                launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+                        "iss", clientId,
+                        "aud", issuer,
+                        "response_type", "code",
+                        "scope", "openid profile",
+                        "redirect_uri", redirectUri), leafKey));
+        Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+    }
+
+    @Test
+    public void testWithPar_unresolvableTrustChain()
+            throws Exception {
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId);
+        final URI uri = createParGeneratedRequestUri(Map.of(
+                "client_id", clientId,
+                "response_type", "code",
+                "scope", "openid profile",
+                "redirect_uri", redirectUri), List.of(clientId, anchorId + "/notExisting"));
+        final FlowExecutionResult result = launchAuthenticationRequest(clientId, "openid profile", uri);
+        Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+    }
+
+    @Test
+    public void testWithPar_matchingAutoRegisteredTrustChain()
+            throws IOException, UnsupportedOperationException, URISyntaxException {
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId);
+        final FlowExecutionResult result =
+                launchAuthenticationRequest(clientId, "openid profile", createParGeneratedRequestUri(Map.of(
+                        "client_id", clientId,
+                        "response_type", "code",
+                        "scope", "openid profile",
+                        "redirect_uri", redirectUri), List.of(clientId, anchorId)));
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+        Assert.assertEquals(unwrapTrustChainFromAuthorizeCode(successResponse), List.of(clientId, anchorId));
+    }
+
+    protected URI createParGeneratedRequestUri(final Map<String, Object> parameters,
+            final List<String> trustChain) {
+        final DefaultPushedAuthorizationRequestUriSerializationFunction parGenerator =
+                new DefaultPushedAuthorizationRequestUriSerializationFunction();
+        parGenerator.setObjectMapper(new ObjectMapper());
+        parGenerator.setDataSealer(getDataSealer());
+        parGenerator.setIdentifierGeneratorLookupStrategy(prc -> idGenerator);
+        parGenerator.setId("mockPar");
+        try {
+            parGenerator.initialize();
+        } catch (ComponentInitializationException e) {
+            Assert.fail("Could not initialize PAR generator", e);
+        }
+        final Map<String, Object> map = new HashMap<>(parameters);
+        map.put(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChain);
+        return parGenerator.apply(null, map);
+    }
+    
+    protected FlowExecutionResult launchAuthenticationRequest(final String clientId, final String scope) {
+        return launchAuthenticationRequest(clientId, scope, (JWT) null);
+    }
+
+    protected FlowExecutionResult launchAuthenticationRequest(final String clientId, final String scope,
+            final JWT requestObject) {
+        setQueryParameters(request, List.of(new Pair<>("client_id", clientId),
+                new Pair<>("response_type", "code"),
+                new Pair<>("scope", scope),
+                new Pair<>("redirect_uri", redirectUri),
+                new Pair<>("request", requestObject == null ? "" : requestObject.serialize())));
+        request.setMethod("GET");
+
+        setBasicAuth("jdoe", "changeit");
+        initializeThreadLocals();
+        
+        return flowExecutor.launchExecution(AuthorizeFlowTest.FLOW_ID, null, externalContext);
+    }
+
+    protected FlowExecutionResult launchAuthenticationRequest(final String clientId, final String scope,
+            final URI requestUri) {
+        setQueryParameters(request, List.of(new Pair<>("client_id", clientId),
+                new Pair<>("response_type", "code"),
+                new Pair<>("scope", scope),
+                new Pair<>("request_uri", "" + requestUri)));
+        request.setMethod("GET");
+
+        setBasicAuth("jdoe", "changeit");
+        initializeThreadLocals();
+        
+        return flowExecutor.launchExecution(AuthorizeFlowTest.FLOW_ID, null, externalContext);
+    }
+
+    protected List<String> unwrapTrustChainFromAuthorizeCode(final AuthenticationSuccessResponse successResponse) {
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+        final String code = successResponse.getAuthorizationCode().getValue();
+        assert code != null;
+        final AuthorizeCodeClaimsSet claims;
+        try {
+            claims = AuthorizeCodeClaimsSet.parse(code, getDataSealer());
+            Assert.assertNotNull(claims.getAutomaticallyRegisteredTrustChain());
+            return claims.getAutomaticallyRegisteredTrustChain();
+        } catch (ParseException | DataSealerException e) {
+            return null;
+        }
+    }
+
+    protected static void setQueryParameters(final MockHttpServletRequest request,
+            final List<Pair<String, String>> pairs) {
+        final StringBuffer query = new StringBuffer();
+        request.removeAllParameters();
+        for (final Pair<String, String> pair : pairs) {
+            final String first = pair.getFirst();
+            assert first != null;
+            request.addParameter(first, pair.getSecond());
+            try {
+                query.append(pair.getFirst() + "=" + URLEncoder.encode(pair.getSecond(), "UTF-8") + "&");
+            } catch (UnsupportedEncodingException e) {
+                Assert.fail(e.getMessage());
+            }
+        }
+        request.setQueryString(query.toString());
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
new file mode 100644
index 00000000..b84a33f8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.oidc.op.profile.flow.oidfed;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.security.PublicKey;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.PushedAuthorizeFlowTest;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.security.DataSealerException;
+
+public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
+
+    public PushedAuthorizeFlowAutomaticRegistrationTest() {
+        super(PushedAuthorizeFlowTest.FLOW_ID);
+    }
+
+    @Test
+    public void testSuccess() throws Exception {
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId);
+        final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
+                rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+        final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+                ClientAuthenticationMethod.PRIVATE_KEY_JWT, rpKey.toRSAKey().toPublicKey());
+        assertSuccessResponse(result, clientId);
+        final PushedAuthorizationSuccessResponse response =
+                parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+        verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
+    }
+
+    @Test
+    public void testWithPublicClientWithoutRequestObject() throws Exception {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setResponseTypes(Set.of(ResponseType.CODE));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+        metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId, metadata);
+        setHttpFormRequest("POST", createRequestParameters(clientId));
+        final FlowExecutionResult result =
+                flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testWithPublicClientWithSignedRequestObject() throws Exception {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setResponseTypes(Set.of(ResponseType.CODE));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+        metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId, metadata);
+        setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+                "client_id", clientId,
+                "iss", clientId,
+                "aud", issuer,
+                "response_type", "code",
+                "scope", "openid",
+                "redirect_uri", redirectUri)).serialize()));
+        final FlowExecutionResult result =
+                flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+        assertSuccessResponse(result, clientId);
+        final PushedAuthorizationSuccessResponse response =
+                parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+        verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
+    }
+
+    @Test
+    public void testWithPublicClientWithPlainRequestObject() throws Exception {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setResponseTypes(Set.of(ResponseType.CODE));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+        metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+        final String clientId = uniqueClientId();
+        configureMockHttpClient(clientId, metadata);
+        setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", plainRequestObject(Map.of(
+                "client_id", clientId,
+                "iss", clientId,
+                "aud", issuer,
+                "response_type", "code",
+                "scope", "openid",
+                "redirect_uri", redirectUri)).serialize()));
+        final FlowExecutionResult result =
+                flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri) {
+        verifyAuthorizeEndpoint(clientId, requestUri, null);
+    }
+
+    protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri, final String jkt) {
+        initializeMocks();
+        initializeThreadLocals();
+        setBasicAuth("jdoe", "changeit");
+        request.setMethod("GET");
+
+        setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+                new Pair<>("request_uri", requestUri)));
+
+        final FlowExecutionResult result = flowExecutor.launchExecution("oidc/authorize", null, externalContext);
+        Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
+        if (jkt != null) {
+            final String url = response.getRedirectedUrl();
+            assert url != null;
+            final String code = url.substring(url.indexOf("code=") + 5);
+            assert code != null;
+            try {
+                final AuthorizeCodeClaimsSet claimsSet = AuthorizeCodeClaimsSet.parse(code, getDataSealer());
+                assert claimsSet != null;
+                Assert.assertEquals(claimsSet.getDpopProofJwkThumbprint(), jkt);
+            } catch (ParseException | DataSealerException e) {
+                Assert.fail(e.getMessage());
+            }
+        }
+    }
+    protected FlowExecutionResult launchWithJwtAuthentication(final JWT jwt, final JWSAlgorithm algorithm,
+            final ClientAuthenticationMethod method, final PublicKey publicKey) throws Exception {
+        // use 'iss' claim from JWT as clientId if set, 'sub' otherwise
+        final String iss =  jwt.getJWTClaimsSet().getStringClaim("iss");
+        final String clientId = iss == null ? jwt.getJWTClaimsSet().getStringClaim("sub") : iss;
+        final Map<String, String> requestParameters = createRequestParameters(clientId);
+        populateClientAssertionParams(requestParameters, jwt);
+        setHttpFormRequest("POST", requestParameters);
+        return flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+    }
+
+    protected void populateClientAssertionParams(final Map<String, String> requestParameters, 
+            final JWT jwt) {
+        requestParameters.put("client_assertion", jwt.serialize());
+        requestParameters.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
+    }
+
+    protected JWTClaimsSet validClaimsSet(final String clientId, final String audience) {
+        return new JWTClaimsSet.Builder()
+                .subject(clientId)
+                .issuer(clientId)
+                .audience(audience)
+                .expirationTime(Date.from(Instant.now().plusSeconds(600)))
+                .jwtID(idGenerator.generateIdentifier())
+                .build();
+    }
+
+    protected Map<String,String> createRequestParameters(final String id) {
+        return createRequestParameters(id, "openid", "code", null);
+    }
+
+    protected Map<String,String> createRequestParameters(final String id, final String scope,
+            final String responseType, final String requestObject) {
+        final Map<String,String> result = new HashMap<>();
+        result.put("client_id", id);
+        if (responseType != null) {
+            result.put("response_type", responseType);
+        }
+        if (scope != null) {
+            result.put("scope", scope);
+        }
+        result.put("redirect_uri", redirectUri);
+        if (requestObject != null) {
+            result.put("request", requestObject);
+        }
+        return result;
+    }
+
+    protected static void setRequestParameters(final MockHttpServletRequest request,
+            final List<Pair<String, String>> pairs) {
+        final StringBuffer query = new StringBuffer();
+        for (final Pair<String, String> pair : pairs) {
+            final String first = pair.getFirst();
+            assert first != null;
+            request.addParameter(first, pair.getSecond());
+            try {
+                query.append(pair.getFirst() + "=" + URLEncoder.encode(pair.getSecond(), "UTF-8") + "&");
+            } catch (UnsupportedEncodingException e) {
+                Assert.fail(e.getMessage());
+            }
+        }
+        request.setQueryString(query.toString());
+    }
+
+    protected void assertSuccessResponse(final FlowExecutionResult result, final String id) {
+        final PushedAuthorizationSuccessResponse resp =
+                parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+        Assert.assertNotNull(resp);
+        Assert.assertNotNull(resp.getRequestURI());
+        Assert.assertNotNull(resp.getLifetime());
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/credentials/fed-local-anchor.jwk b/idp-oidc-extension-impl/src/test/resources/credentials/fed-local-anchor.jwk
new file mode 100644
index 00000000..8601a78c
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/credentials/fed-local-anchor.jwk
@@ -0,0 +1,13 @@
+{
+    "alg": "RS256",
+    "kid": "locallyTrustedAnchorKey",
+    "kty": "RSA",
+    "p": "y_ul6_wF0dnPzfK_BW0q5dM2aKRFOekotB6mFmWXE4VY_EVRpPAcHQppBbOUOBMkg_NSmjRK3x8wZYhLeavqfsFNcpGMAMsL1LA_FtPFo1R2B8n-FpcA9uRimjDxlzIQKrbEYM1OGPA7jLCE57fNzta2YM4e1sjP3W7-TfOa7v0",
+    "q": "x--fmMMzN9CRakq-pi6I0ClQq4PXJnJROGzVSjMGKs-YbaFXaWPnF_rHqJunmFavTiiSwCv36kiseEAA8fTl-Ms75MXp7LGo30e0F7JJ3vXDD5MwvapswwoiWJWCgJxCOnWJdWvm_iZApK9xcaM_ngTWQuQEq1JhPq6rEB4wck0",
+    "d": "AnBEysAXveM6Lkt2U4RaRoq30upD1EwX7GBE0xCEyzXHESoZIhriDICL9DcCnH7wz0GHittysTrnzEcOWA37mMRKtMJ9ZX3P4QXhfcxGPVvJoDXuH3hR1rbXsu-b9-aYsGNeO6fZSafqhXBeXYU3q1CJFFjoe72drw3DJ3xwCRdYv3y94YBw09kAQYOA4zAtxrUF3ZhlLH9K4sfFLAEzztJX-OuY3a2UFeNC1FkpNDKovbXU0yrkP_y5jjSPrjI2lBQg4yFN5hM3YM7bvG001E796CEhqRYsnLazNDpRXAIQc_jbs_3YrdrlaWR-abMj1zr0zw44a8JZ5Tvd7imZaQ",
+    "e": "AQAB",
+    "qi": "OlB4Yfvc6MBNsFUPpFGXmuYjVDkgTtcAEG60n1TDsrh4C2ir0-ZyaQg1gV3D3EabD96lnp7IiO3uX6X6e6AJgC2AW052bc1EuRQkhyyXeEYN23u3elF1y2F4eQUbNgfc2ghZfUqx5xhZDWFXbrMC7NaOCn1UOIfBe2kuvqSL8Es",
+    "dp": "ebkddgjaYDOd8cPdgZt3cdXsLd15AenExFdVvR-6W4fDZibnZYly_VFtAl37IMsriyH0NNjnpOWzt6LxhxWzxRgM40U_SmngEXdq7nBJDAImvNcorMpHZQ08Wc7DG_pf811FKo7Y_8C7iGT9qljgk4FFK9dUR89lWzoUvueTmPE",
+    "dq": "UBHQ8pbJ_kJS2iSQ8XCVbff9zJKCKW2CxXwgdxS0FZUJ0G3a2eQeemX-a7HajpG4py5shvWU1YjBOW84ca3II7kQhXAVXKtRnAnVP-Aw4U-_DI-_51VHNVzroFpP5z2s8Eh-Aj5yRboADXQNlJryMVBylltG222kcDv3Wf8dG8k",
+    "n": "n0-NFV06ZDKLo1v8KrSJsQ8bbLEffVJw1F5jGXqrKh_4PpBt9FmyWY3gIA9aK1p1WneMaWRNlM1EObierCr0EdXCQbgpKorrPqxiwyl6cOMIH4fN_9uWGqD2HlyGcjcESrNjZz75tNr_9oegh6fWSMgrxyySpU38ALWUX1ZuNS8A4tj8XdJSbSHqftf7qOdgzuy0yaD5h7NwoBCRPOIY88vOLHkcQ4nYdkk8GLSIf5GgGb7JFiPuFHN7pK---LNnFBifag2wbEZ9nnAcAol4jc2gF7zq2mqhMSlbIVmTRj4Y9wxh3DPbmC8xZ-8nbhPmgi4vlij9JWJGEvfLuXaMGQ"
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
index 707f855f..54624fdf 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
@@ -106,4 +106,12 @@
 
     <bean id="alwaysFalsePolicyOperator" class="net.shibboleth.idp.plugin.oidc.op.profile.flow.AlwaysFalseCustomMetadataPolicyOperator" />
 
+    <bean id="MockitoMockFactory" class="org.mockito.Mockito" />
+
+    <bean id="shibboleth.oidfed.HttpClient"
+        factory-bean="MockitoMockFactory"
+        factory-method="mock">
+        <constructor-arg value="#{T(org.apache.hc.client5.http.classic.HttpClient)}" />
+    </bean>
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
index fca79757..dd370e35 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
@@ -20,4 +20,7 @@ idp.oauth2.defaultAllowedAudience = https://rp.example.org
 
 idp.oidc.discovery.resolver.values = CustomConfigurationValues
 
-idp.oidc.DefaultUnregisteredClientPolicyFile = src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
\ No newline at end of file
+idp.oidc.DefaultUnregisteredClientPolicyFile = src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
+
+idp.oidfed.authorize.automaticRegistrationCondition = shibboleth.Conditions.TRUE
+idp.oidfed.par.automaticRegistrationCondition = shibboleth.Conditions.TRUE
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidfed-trust-anchors.json b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidfed-trust-anchors.json
new file mode 100644
index 00000000..5464750f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidfed-trust-anchors.json
@@ -0,0 +1,13 @@
+{
+    "https://trust-anchor.federation.local": {
+        "keys": [
+            {
+                "alg": "RS256",
+                "kty":"RSA",
+                "e":"AQAB",
+                "kid":"locallyTrustedAnchorKey",
+                "n":"n0-NFV06ZDKLo1v8KrSJsQ8bbLEffVJw1F5jGXqrKh_4PpBt9FmyWY3gIA9aK1p1WneMaWRNlM1EObierCr0EdXCQbgpKorrPqxiwyl6cOMIH4fN_9uWGqD2HlyGcjcESrNjZz75tNr_9oegh6fWSMgrxyySpU38ALWUX1ZuNS8A4tj8XdJSbSHqftf7qOdgzuy0yaD5h7NwoBCRPOIY88vOLHkcQ4nYdkk8GLSIf5GgGb7JFiPuFHN7pK---LNnFBifag2wbEZ9nnAcAol4jc2gF7zq2mqhMSlbIVmTRj4Y9wxh3DPbmC8xZ-8nbhPmgi4vlij9JWJGEvfLuXaMGQ"
+            }
+        ]
+    }
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index e9db5719..83bc574e 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -77,6 +77,22 @@
     </bean>
 
     <util:list id="shibboleth.RelyingPartyOverrides">
+        <bean parent="RelyingPartyByName" c:relyingPartyIds="https://trust-anchor.federation.local">
+            <property name="profileConfigurations">
+                <list>
+                    <bean parent="OIDFED.AutomaticRegistration" p:mandatoryTrustMarks=""/>
+                </list>
+            </property>
+        </bean>
+        <bean parent="RelyingPartyByTrustAnchor" c:trustAnchorIds="https://trust-anchor.federation.local">
+            <property name="profileConfigurations">
+                <list>
+                    <ref bean="OIDC.SSO.MDDriven" />
+                    <bean parent="OAUTH2.Token.MDDriven" p:tokenEndpointAuthMethods="client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt,none"/>
+                    <bean parent="OAUTH2.PAR.MDDriven" p:tokenEndpointAuthMethods="client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt,none"/>
+                </list>
+            </property>
+        </bean>
         <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdFragmentResponseMode">
             <property name="profileConfigurations">
                  <list>

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


More information about the commits mailing list