Skip to content

Commit d2ebbe9

Browse files
fix(pop-spring): bind request content, single-source htu authority
- PopAuthenticationFilter hands CallerVerifier a bounded ContentSource, so the request body is read once, on the verifier's demand after the SCITT binding (ANS-6 §7.4 step 12), and is then replayed to the handler through a request wrapper. Content above the bound (Builder withMaxContentBytes, default 1 MiB) is rejected with 413 before it is hashed. Previously the filter never supplied content, so every proof that carried ans_content_digest was rejected and body-bearing requests were accepted with the body unbound. - The trusted-authority check reads the same URL the proof's htu is compared against instead of the raw Host header (§7.7), so forwarded header processing can no longer make the two diverge. The withTrustedHosts Javadoc now says that behind a TLS-terminating proxy the container's URL is the proxy hop, and withExternalUrl or trusted forwarded-header handling is required. - Builder gains withVerifier(CallerVerifier), mutually exclusive with withPoPSkew, and withRootKeyRefresher(RootKeyRefresher). The example wires the refresher to TransparencyClient.refreshRootKeysIfNeeded. Addresses the review on PR #108. Assisted-by: Claude Code (claude-fable-5-1) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: kperry <kperry@godaddy.com>
1 parent 4fded3e commit d2ebbe9

3 files changed

Lines changed: 503 additions & 33 deletions

File tree

‎ans-sdk-pop-spring/examples/dpop-scitt-auth/src/main/java/com/godaddy/ans/examples/dpopscittauth/PopSecurityConfig.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import com.godaddy.ans.sdk.pop.ReplayCache;
55
import com.godaddy.ans.sdk.pop.spring.PopAuthenticationFilter;
66
import com.godaddy.ans.sdk.transparency.TransparencyClient;
7+
import com.godaddy.ans.sdk.transparency.scitt.RefreshDecision;
78
import org.springframework.beans.factory.annotation.Value;
89
import org.springframework.boot.ApplicationRunner;
910
import org.springframework.boot.web.servlet.FilterRegistrationBean;
1011
import org.springframework.context.annotation.Bean;
1112
import org.springframework.context.annotation.Configuration;
1213

1314
import java.security.PublicKey;
15+
import java.time.Instant;
1416
import java.util.Map;
17+
import java.util.Optional;
1518
import java.util.concurrent.TimeUnit;
1619
import java.util.function.Supplier;
1720

@@ -49,10 +52,19 @@ public FilterRegistrationBean<PopAuthenticationFilter> popAuthenticationFilter(
4952
PopAuthenticationFilter filter = PopAuthenticationFilter
5053
.builder(expectedIssuer, rootKeys, replayCache)
5154
.withTrustedHosts(trustedHost)
55+
.withRootKeyRefresher(artifactIssuedAt -> refreshRootKeys(transparencyClient, artifactIssuedAt))
5256
.build();
5357

5458
FilterRegistrationBean<PopAuthenticationFilter> registration = new FilterRegistrationBean<>(filter);
5559
registration.addUrlPatterns("/*");
5660
return registration;
5761
}
62+
63+
private static Optional<Map<String, PublicKey>> refreshRootKeys(TransparencyClient client,
64+
Instant artifactIssuedAt) {
65+
RefreshDecision decision = client.refreshRootKeysIfNeeded(artifactIssuedAt)
66+
.orTimeout(2, TimeUnit.SECONDS)
67+
.join();
68+
return decision.isRefreshed() ? Optional.of(decision.keys()) : Optional.empty();
69+
}
5870
}

0 commit comments

Comments
 (0)