Skip to content

Commit ca617d9

Browse files
[OID4VCI]: Use Keycloak time utility for OID4VC related timestamps (#44871)
Closes: #44235 Signed-off-by: forkimenjeckayang <[email protected]>
1 parent 3218cd1 commit ca617d9

File tree

7 files changed

+40
-33
lines changed

7 files changed

+40
-33
lines changed

core/src/test/java/org/keycloak/sdjwt/SdJwsTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.time.temporal.ChronoUnit;
2222

2323
import org.keycloak.common.VerificationException;
24+
import org.keycloak.common.util.Time;
2425
import org.keycloak.jose.jws.JWSHeader;
2526
import org.keycloak.rule.CryptoInitRule;
2627

@@ -46,7 +47,7 @@ private ObjectNode createPayload() {
4647
ObjectMapper mapper = new ObjectMapper();
4748
ObjectNode node = mapper.createObjectNode();
4849
node.put("sub", "test");
49-
node.put("exp", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
50+
node.put("exp", Instant.ofEpochSecond(Time.currentTime()).plus(1, ChronoUnit.HOURS).getEpochSecond());
5051
node.put("name", "Test User");
5152
return node;
5253
}
@@ -72,7 +73,7 @@ public void testVerifySignature_WrongPublicKey() {
7273
@Test
7374
public void testVerifyExpClaim_ExpiredJWT() {
7475
ObjectNode payload = createPayload();
75-
payload.put("exp", Instant.now().minus(1, ChronoUnit.HOURS).getEpochSecond());
76+
payload.put("exp", Instant.ofEpochSecond(Time.currentTime()).minus(1, ChronoUnit.HOURS).getEpochSecond());
7677
assertThrows(VerificationException.class, () -> {
7778
new ClaimVerifier.ExpCheck(0, false).test(payload);
7879
});
@@ -81,15 +82,15 @@ public void testVerifyExpClaim_ExpiredJWT() {
8182
@Test
8283
public void testVerifyExpClaim_Positive() throws Exception {
8384
ObjectNode payload = createPayload();
84-
payload.put("exp", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
85+
payload.put("exp", Instant.ofEpochSecond(Time.currentTime()).plus(1, ChronoUnit.HOURS).getEpochSecond());
8586

8687
new ClaimVerifier.ExpCheck(0, false).test(payload);
8788
}
8889

8990
@Test
9091
public void testVerifyNotBeforeClaim_Negative() {
9192
ObjectNode payload = createPayload();
92-
payload.put("nbf", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
93+
payload.put("nbf", Instant.ofEpochSecond(Time.currentTime()).plus(1, ChronoUnit.HOURS).getEpochSecond());
9394
assertThrows(VerificationException.class, () -> {
9495
new ClaimVerifier.NbfCheck(0, false).test(payload);
9596
});
@@ -98,7 +99,7 @@ public void testVerifyNotBeforeClaim_Negative() {
9899
@Test
99100
public void testVerifyNotBeforeClaim_Positive() throws Exception {
100101
ObjectNode payload = createPayload();
101-
payload.put("nbf", Instant.now().minus(1, ChronoUnit.HOURS).getEpochSecond());
102+
payload.put("nbf", Instant.ofEpochSecond(Time.currentTime()).minus(1, ChronoUnit.HOURS).getEpochSecond());
102103

103104
new ClaimVerifier.NbfCheck(0, false).test(payload);
104105
}
@@ -179,15 +180,15 @@ public void testVerifyVctClaim_Positive() throws VerificationException {
179180

180181
@Test
181182
public void shouldValidateAgeSinceIssued() throws VerificationException {
182-
long now = Instant.now().getEpochSecond();
183+
long now = Time.currentTime();
183184
JwsToken sdJws = exampleSdJws(now);
184185

185186
new ClaimVerifier.IatLifetimeCheck(0, 180).test(sdJws.getPayload());
186187
}
187188

188189
@Test
189190
public void shouldValidateAgeSinceIssued_IfJwtIsTooOld() {
190-
long now = Instant.now().getEpochSecond();
191+
long now = Time.currentTime();
191192
long iat = now - 1000;
192193
long maxLifetime = 180;
193194
JwsToken sdJws = exampleSdJws(iat); // that will be too old

core/src/test/java/org/keycloak/sdjwt/SdJwtCreationAndSigningTest.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.keycloak.OID4VCConstants;
3131
import org.keycloak.common.VerificationException;
3232
import org.keycloak.common.util.KeyUtils;
33+
import org.keycloak.common.util.Time;
3334
import org.keycloak.crypto.Algorithm;
3435
import org.keycloak.crypto.ECDSASignatureSignerContext;
3536
import org.keycloak.crypto.ECDSASignatureVerifierContext;
@@ -66,9 +67,10 @@ public abstract class SdJwtCreationAndSigningTest {
6667
@Test
6768
public void testCreateSdJwtWithoutKeybindingAndNoSignature() throws Exception {
6869

69-
final long iat = Instant.now().minus(10, ChronoUnit.SECONDS).getEpochSecond();
70-
final long nbf = Instant.now().minus(5, ChronoUnit.SECONDS).getEpochSecond();
71-
final long exp = Instant.now().plus(60, ChronoUnit.SECONDS).getEpochSecond();
70+
Instant now = Instant.ofEpochSecond(Time.currentTime());
71+
final long iat = now.minus(10, ChronoUnit.SECONDS).getEpochSecond();
72+
final long nbf = now.minus(5, ChronoUnit.SECONDS).getEpochSecond();
73+
final long exp = now.plus(60, ChronoUnit.SECONDS).getEpochSecond();
7274

7375
String disclosurePayload = "{\n" +
7476
" \"given_name\": \"Carlos\",\n" +
@@ -193,9 +195,10 @@ public void testCreateSdJwtWithKeybindingJwt() throws Exception {
193195
SignatureSignerContext issuerSignerContext = new ECDSASignatureSignerContext(issuerKeyPair);
194196
SignatureSignerContext holderSignerContext = new ECDSASignatureSignerContext(holderKeyPair);
195197

196-
final long iat = Instant.now().minus(10, ChronoUnit.SECONDS).getEpochSecond();
197-
final long nbf = Instant.now().minus(5, ChronoUnit.SECONDS).getEpochSecond();
198-
final long exp = Instant.now().plus(60, ChronoUnit.SECONDS).getEpochSecond();
198+
Instant now = Instant.ofEpochSecond(Time.currentTime());
199+
final long iat = now.minus(10, ChronoUnit.SECONDS).getEpochSecond();
200+
final long nbf = now.minus(5, ChronoUnit.SECONDS).getEpochSecond();
201+
final long exp = now.plus(60, ChronoUnit.SECONDS).getEpochSecond();
199202
final String nonce = "123456789";
200203
final String audience = String.format("x509_san_dns:%s", authorizationServerUrl);
201204

core/src/test/java/org/keycloak/sdjwt/SdJwtVerificationTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717

1818
package org.keycloak.sdjwt;
1919

20-
import java.time.Instant;
2120
import java.util.Arrays;
2221
import java.util.Collections;
2322
import java.util.List;
2423
import java.util.function.Function;
2524

2625
import org.keycloak.OID4VCConstants;
2726
import org.keycloak.common.VerificationException;
27+
import org.keycloak.common.util.Time;
2828
import org.keycloak.crypto.SignatureSignerContext;
2929
import org.keycloak.crypto.SignatureVerifierContext;
3030
import org.keycloak.rule.CryptoInitRule;
@@ -169,7 +169,7 @@ public void sdJwtVerificationShouldFail_WithWrongVerifier() {
169169

170170
@Test
171171
public void sdJwtVerificationShouldFail_IfExpired() {
172-
long now = Instant.now().getEpochSecond();
172+
long now = Time.currentTime();
173173

174174
ObjectNode claimSet = mapper.createObjectNode();
175175
claimSet.put("given_name", "John");
@@ -220,7 +220,7 @@ public void sdJwtVerificationShouldFail_IfExpired_CaseExpInvalid() {
220220
// exp: null
221221
ObjectNode claimSet1 = mapper.createObjectNode();
222222
claimSet1.put("given_name", "John");
223-
claimSet1.put("exp", Instant.now().getEpochSecond() - (31536000));
223+
claimSet1.put("exp", Time.currentTime() - (31536000));
224224

225225
// exp: invalid
226226
ObjectNode claimSet2 = mapper.createObjectNode();
@@ -268,7 +268,7 @@ public void sdJwtVerificationShouldFail_IfExpired_CaseExpInvalid() {
268268

269269
@Test
270270
public void sdJwtVerificationShouldFail_IfIssuedInTheFuture() {
271-
long now = Instant.now().getEpochSecond();
271+
long now = Time.currentTime();
272272

273273
ObjectNode claimSet = mapper.createObjectNode();
274274
claimSet.put("given_name", "John");
@@ -317,7 +317,7 @@ public void sdJwtVerificationShouldFail_IfIssuedInTheFuture() {
317317

318318
@Test
319319
public void sdJwtVerificationShouldFail_IfNbfInvalid() {
320-
long now = Instant.now().getEpochSecond();
320+
long now = Time.currentTime();
321321

322322
ObjectNode claimSet = mapper.createObjectNode();
323323
claimSet.put("given_name", "John");

core/src/test/java/org/keycloak/sdjwt/sdjwtvp/SdJwtVPVerificationTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717

1818
package org.keycloak.sdjwt.sdjwtvp;
1919

20-
import java.time.Instant;
2120
import java.util.Arrays;
2221
import java.util.Collections;
2322
import java.util.List;
2423

2524
import org.keycloak.OID4VCConstants;
2625
import org.keycloak.common.VerificationException;
26+
import org.keycloak.common.util.Time;
2727
import org.keycloak.crypto.SignatureVerifierContext;
2828
import org.keycloak.rule.CryptoInitRule;
2929
import org.keycloak.sdjwt.IssuerSignedJwtVerificationOpts;
@@ -265,7 +265,7 @@ public void testShouldFail_IfKbSdHashInvalid() {
265265

266266
@Test
267267
public void testShouldFail_IfKbIssuedInFuture() {
268-
long now = Instant.now().getEpochSecond();
268+
long now = Time.currentTime();
269269

270270
ObjectNode kbPayload = exampleKbPayload();
271271
kbPayload.set(OID4VCConstants.CLAIM_NAME_IAT, mapper.valueToTree(now + 1000));
@@ -280,7 +280,7 @@ public void testShouldFail_IfKbIssuedInFuture() {
280280

281281
@Test
282282
public void testShouldTolerateKbIssuedInTheFutureWithinClockSkew() throws VerificationException {
283-
long now = Instant.now().getEpochSecond();
283+
long now = Time.currentTime();
284284

285285
ObjectNode kbPayload = exampleKbPayload();
286286
// Issued just 5 seconds in the future. Should pass with a clock skew of 10 seconds.
@@ -317,7 +317,7 @@ public void testShouldFail_IfKbTooOld() {
317317

318318
@Test
319319
public void testShouldFail_IfKbExpired() {
320-
long now = Instant.now().getEpochSecond();
320+
long now = Time.currentTime();
321321

322322
ObjectNode kbPayload = exampleKbPayload();
323323
kbPayload.set(OID4VCConstants.CLAIM_NAME_EXP, mapper.valueToTree(now - 1000));
@@ -332,7 +332,7 @@ public void testShouldFail_IfKbExpired() {
332332

333333
@Test
334334
public void testShouldTolerateExpiredKbWithinClockSkew() throws VerificationException {
335-
long now = Instant.now().getEpochSecond();
335+
long now = Time.currentTime();
336336

337337
ObjectNode kbPayload = exampleKbPayload();
338338
// Expires just 5 seconds ago. Should pass with a clock skew of 10 seconds.
@@ -351,7 +351,7 @@ public void testShouldTolerateExpiredKbWithinClockSkew() throws VerificationExce
351351

352352
@Test
353353
public void testShouldFail_IfKbNotBeforeTimeYet() {
354-
long now = Instant.now().getEpochSecond();
354+
long now = Time.currentTime();
355355

356356
ObjectNode kbPayload = exampleKbPayload();
357357
kbPayload.set(OID4VCConstants.CLAIM_NAME_NBF, mapper.valueToTree(now + 1000));

services/src/main/java/org/keycloak/protocol/oid4vc/issuance/keybinding/JwtCNonceHandler.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
package org.keycloak.protocol.oid4vc.issuance.keybinding;
2020

21-
import java.time.Instant;
22-
import java.time.temporal.ChronoUnit;
2321
import java.util.ArrayList;
2422
import java.util.Arrays;
2523
import java.util.Base64;
@@ -34,6 +32,7 @@
3432

3533
import org.keycloak.TokenVerifier;
3634
import org.keycloak.common.VerificationException;
35+
import org.keycloak.common.util.Time;
3736
import org.keycloak.constants.OID4VCIConstants;
3837
import org.keycloak.crypto.Algorithm;
3938
import org.keycloak.crypto.KeyUse;
@@ -80,10 +79,10 @@ public String buildCNonce(List<String> audiences, Map<String, Object> additional
8079
RealmModel realm = keycloakSession.getContext().getRealm();
8180
final String issuer = OID4VCIssuerWellKnownProvider.getIssuer(keycloakSession.getContext());
8281
// TODO discussion about the attribute name to use
83-
final Integer nonceLifetimeMillis = realm.getAttribute(OID4VCIConstants.C_NONCE_LIFETIME_IN_SECONDS, 60);
82+
final Integer nonceLifetimeSeconds = realm.getAttribute(OID4VCIConstants.C_NONCE_LIFETIME_IN_SECONDS, 60);
8483
audiences = Optional.ofNullable(audiences).orElseGet(Collections::emptyList);
85-
final Instant now = Instant.now();
86-
final long expiresAt = now.plus(nonceLifetimeMillis, ChronoUnit.SECONDS).getEpochSecond();
84+
final long nowSeconds = Time.currentTime();
85+
final long expiresAt = nowSeconds + nonceLifetimeSeconds;
8786
final int nonceLength = NONCE_DEFAULT_LENGTH + new Random().nextInt(NONCE_LENGTH_RANDOM_OFFSET);
8887
// this generated value itself is basically just a salt-value for the generated token, which itself is the nonce.
8988
final String strongSalt = Base64.getEncoder().encodeToString(RandomSecret.createRandomSecret(nonceLength));
@@ -144,7 +143,7 @@ public void verifyCNonce(String cNonce, List<String> audiences, @Nullable Map<St
144143
if (exp == null) {
145144
throw new VerificationException("c_nonce has no expiration time");
146145
}
147-
long now = Instant.now().getEpochSecond();
146+
long now = Time.currentTime();
148147
if (exp < now) {
149148
String message = String.format(
150149
"c_nonce not valid: %s(exp) < %s(now)",

services/src/main/java/org/keycloak/protocol/oid4vc/issuance/mappers/OID4VCIssuedAtTimeClaimMapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Objects;
2525
import java.util.Optional;
2626

27+
import org.keycloak.common.util.Time;
2728
import org.keycloak.models.KeycloakSession;
2829
import org.keycloak.models.UserSessionModel;
2930
import org.keycloak.models.oid4vci.CredentialScopeModel;
@@ -121,9 +122,9 @@ public void setClaimsForCredential(VerifiableCredential verifiableCredential,
121122
Instant iat = Optional.ofNullable(mapperModel.getConfig())
122123
.flatMap(config -> Optional.ofNullable(config.get(VALUE_SOURCE)))
123124
.filter(valueSource -> Objects.equals(valueSource, "COMPUTE"))
124-
.map(valueSource -> Instant.now())
125+
.map(valueSource -> Instant.ofEpochSecond(Time.currentTime()))
125126
.orElseGet(() -> Optional.ofNullable(verifiableCredential.getIssuanceDate())
126-
.orElse(Instant.now()));
127+
.orElse(Instant.ofEpochSecond(Time.currentTime())));
127128

128129
Instant normalizedIat = new TimeClaimNormalizer(userSessionModel.getRealm())
129130
.normalize(iat);

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.keycloak.common.util.KeyUtils;
5656
import org.keycloak.common.util.MultivaluedHashMap;
5757
import org.keycloak.common.util.PemUtils;
58+
import org.keycloak.common.util.Time;
5859
import org.keycloak.constants.OID4VCIConstants;
5960
import org.keycloak.crypto.ECDSASignatureSignerContext;
6061
import org.keycloak.crypto.KeyUse;
@@ -119,7 +120,9 @@ public abstract class OID4VCTest extends AbstractTestRealmKeycloakTest {
119120
protected static final String CONTEXT_URL = "https://www.w3.org/2018/credentials/v1";
120121
protected static final URI TEST_DID = URI.create("did:web:test.org");
121122
protected static final List<String> TEST_TYPES = List.of("VerifiableCredential");
122-
protected static final Instant TEST_EXPIRATION_DATE = Instant.now().plus(365, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS);
123+
protected static final Instant TEST_EXPIRATION_DATE = Instant.ofEpochMilli(Time.currentTimeMillis())
124+
.plus(365, ChronoUnit.DAYS)
125+
.truncatedTo(ChronoUnit.SECONDS);
123126
protected static final Instant TEST_ISSUANCE_DATE = Instant.ofEpochSecond(1000);
124127

125128
protected static final KeyWrapper RSA_KEY = getRsaKey();

0 commit comments

Comments
 (0)