[java-idp-plugin-vci] 03/07: Rounding issuance to improve unlinkability for batch issuance
Codeberg
noreply at shibboleth.net
Thu Sep 24 13:16:28 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-idp-plugin-vci.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/4b8a237b33d6495edd10add6ef276c3a6c24c9c9
commit 4b8a237b33d6495edd10add6ef276c3a6c24c9c9
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Thu Sep 24 16:10:46 2026 +0300
Rounding issuance to improve unlinkability for batch issuance
---
.../profile/impl/ResolveCredentialLifetime.java | 42 +++++++++++++++++++---
.../impl/ResolveCredentialLifetimeTest.java | 25 +++++++++++++
2 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetime.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetime.java
index b6671da..883d8a3 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetime.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetime.java
@@ -72,6 +72,10 @@ public class ResolveCredentialLifetime extends AbstractProfileAction {
@Nullable
private CredentialIssuanceConfiguration settings;
+ /** Granularity the instant of issuance is rounded down to. */
+ @Nonnull
+ private Duration timestampPrecision = Duration.ofHours(1);
+
/** Credentials context. */
@Nullable
private CredentialsContext ctx;
@@ -96,6 +100,22 @@ public class ResolveCredentialLifetime extends AbstractProfileAction {
"RelyingPartyContext lookup strategy cannot be null");
}
+ /**
+ * Set the granularity the instant of issuance is rounded down to.
+ *
+ * @param precision granularity the instant of issuance is rounded down to,
+ * at least a second and shorter than the lifetime of any
+ * credential configuration
+ */
+ public void setTimestampPrecision(@Nonnull final Duration precision) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ Constraint.isNotNull(precision, "Timestamp precision cannot be null");
+ Constraint.isFalse(precision.getSeconds() < 1, "Timestamp precision cannot be shorter than a second");
+
+ timestampPrecision = precision;
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -148,17 +168,19 @@ public class ResolveCredentialLifetime extends AbstractProfileAction {
assert ctx != null && lifetime != null;
- final Instant issuedAt = Instant.now().truncatedTo(ChronoUnit.SECONDS);
+ final Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
+ final Instant issuedAt = roundDown(now);
final Instant notBefore = (settings == null ? issuedAt : settings.resolveNotBefore(issuedAt))
.truncatedTo(ChronoUnit.SECONDS);
final Instant expiresAt = notBefore.plus(lifetime);
- if (!expiresAt.isAfter(issuedAt)) {
+ if (!expiresAt.isAfter(now)) {
// TODO: A closed window deserves an error the wallet is able to act on.
log.error(
"{} Validity window of the requested credential closed at {}, there is nothing to issue."
- + " Move the 'not_before' or the 'lifetime' of its credential configuration.",
- getLogPrefix(), expiresAt);
+ + " Move the 'not_before' or the 'lifetime' of its credential configuration, or"
+ + " shorten the timestamp precision {}.",
+ getLogPrefix(), expiresAt, timestampPrecision);
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
return;
}
@@ -175,4 +197,16 @@ public class ResolveCredentialLifetime extends AbstractProfileAction {
ctx.getExpiresAt());
}
+ /**
+ * Round an instant down to a multiple of {@link #timestampPrecision}.
+ *
+ * @param instant instant to round down
+ * @return the rounded instant
+ */
+ @Nonnull
+ private Instant roundDown(@Nonnull final Instant instant) {
+ final long precision = timestampPrecision.getSeconds();
+
+ return Instant.ofEpochSecond(Math.floorDiv(instant.getEpochSecond(), precision) * precision);
+ }
}
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetimeTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetimeTest.java
index 2c90557..4171294 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetimeTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ResolveCredentialLifetimeTest.java
@@ -18,6 +18,7 @@ package org.geant.shibboleth.plugin.openidvci.profile.impl;
import java.time.Duration;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import org.geant.shibboleth.plugin.openidvci.credential.CredentialConfiguration;
import org.geant.shibboleth.plugin.openidvci.messaging.context.CredentialsContext;
@@ -85,6 +86,30 @@ public class ResolveCredentialLifetimeTest {
Assert.assertEquals(ctx.getExpiresAt(), ctx.getIssuedAt().plus(PROFILE_LIFETIME));
}
+ @Test
+ public void testIssuanceIsRoundedDownToPrecision() throws Exception {
+ credentialWith(null);
+
+ final Instant before = Instant.now();
+ ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
+
+ final CredentialsContext ctx = resolved();
+ Assert.assertEquals(ctx.getIssuedAt(), ctx.getIssuedAt().truncatedTo(ChronoUnit.HOURS));
+ Assert.assertFalse(ctx.getIssuedAt().isAfter(before));
+ Assert.assertTrue(Duration.between(ctx.getIssuedAt(), before).compareTo(Duration.ofHours(1)) < 0);
+ }
+
+ @Test
+ public void testPrecisionCoarserThanTheLifetimeIssuesNothing() throws Exception {
+ action = new ResolveCredentialLifetime();
+ action.setTimestampPrecision(Duration.ofDays(365));
+ action.initialize();
+ credentialWith("{\"lifetime\":\"PT1M\"}");
+
+ ActionTestingSupport.assertEvent(action.execute(requestCtx), IdPEventIds.INVALID_PROFILE_CONFIG);
+ Assert.assertNull(resolved().getExpiresAt());
+ }
+
@Test
public void testCredentialLifetimeOverridesTheProfileConfiguration() throws Exception {
credentialWith("{\"lifetime\":\"P7D\"}");
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list