mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
[OID4VCI]: Use Keycloak time utility for OID4VC related timestamps (#44871)
Closes: #44235 Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
This commit is contained in:
parent
3218cd1847
commit
ca617d9711
@ -21,6 +21,7 @@ import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.rule.CryptoInitRule;
|
||||
|
||||
@ -46,7 +47,7 @@ public abstract class SdJwsTest {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ObjectNode node = mapper.createObjectNode();
|
||||
node.put("sub", "test");
|
||||
node.put("exp", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
node.put("exp", Instant.ofEpochSecond(Time.currentTime()).plus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
node.put("name", "Test User");
|
||||
return node;
|
||||
}
|
||||
@ -72,7 +73,7 @@ public abstract class SdJwsTest {
|
||||
@Test
|
||||
public void testVerifyExpClaim_ExpiredJWT() {
|
||||
ObjectNode payload = createPayload();
|
||||
payload.put("exp", Instant.now().minus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
payload.put("exp", Instant.ofEpochSecond(Time.currentTime()).minus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
assertThrows(VerificationException.class, () -> {
|
||||
new ClaimVerifier.ExpCheck(0, false).test(payload);
|
||||
});
|
||||
@ -81,7 +82,7 @@ public abstract class SdJwsTest {
|
||||
@Test
|
||||
public void testVerifyExpClaim_Positive() throws Exception {
|
||||
ObjectNode payload = createPayload();
|
||||
payload.put("exp", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
payload.put("exp", Instant.ofEpochSecond(Time.currentTime()).plus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
|
||||
new ClaimVerifier.ExpCheck(0, false).test(payload);
|
||||
}
|
||||
@ -89,7 +90,7 @@ public abstract class SdJwsTest {
|
||||
@Test
|
||||
public void testVerifyNotBeforeClaim_Negative() {
|
||||
ObjectNode payload = createPayload();
|
||||
payload.put("nbf", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
payload.put("nbf", Instant.ofEpochSecond(Time.currentTime()).plus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
assertThrows(VerificationException.class, () -> {
|
||||
new ClaimVerifier.NbfCheck(0, false).test(payload);
|
||||
});
|
||||
@ -98,7 +99,7 @@ public abstract class SdJwsTest {
|
||||
@Test
|
||||
public void testVerifyNotBeforeClaim_Positive() throws Exception {
|
||||
ObjectNode payload = createPayload();
|
||||
payload.put("nbf", Instant.now().minus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
payload.put("nbf", Instant.ofEpochSecond(Time.currentTime()).minus(1, ChronoUnit.HOURS).getEpochSecond());
|
||||
|
||||
new ClaimVerifier.NbfCheck(0, false).test(payload);
|
||||
}
|
||||
@ -179,7 +180,7 @@ public abstract class SdJwsTest {
|
||||
|
||||
@Test
|
||||
public void shouldValidateAgeSinceIssued() throws VerificationException {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
JwsToken sdJws = exampleSdJws(now);
|
||||
|
||||
new ClaimVerifier.IatLifetimeCheck(0, 180).test(sdJws.getPayload());
|
||||
@ -187,7 +188,7 @@ public abstract class SdJwsTest {
|
||||
|
||||
@Test
|
||||
public void shouldValidateAgeSinceIssued_IfJwtIsTooOld() {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
long iat = now - 1000;
|
||||
long maxLifetime = 180;
|
||||
JwsToken sdJws = exampleSdJws(iat); // that will be too old
|
||||
|
||||
@ -30,6 +30,7 @@ import java.util.stream.Collectors;
|
||||
import org.keycloak.OID4VCConstants;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.ECDSASignatureSignerContext;
|
||||
import org.keycloak.crypto.ECDSASignatureVerifierContext;
|
||||
@ -66,9 +67,10 @@ public abstract class SdJwtCreationAndSigningTest {
|
||||
@Test
|
||||
public void testCreateSdJwtWithoutKeybindingAndNoSignature() throws Exception {
|
||||
|
||||
final long iat = Instant.now().minus(10, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long nbf = Instant.now().minus(5, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long exp = Instant.now().plus(60, ChronoUnit.SECONDS).getEpochSecond();
|
||||
Instant now = Instant.ofEpochSecond(Time.currentTime());
|
||||
final long iat = now.minus(10, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long nbf = now.minus(5, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long exp = now.plus(60, ChronoUnit.SECONDS).getEpochSecond();
|
||||
|
||||
String disclosurePayload = "{\n" +
|
||||
" \"given_name\": \"Carlos\",\n" +
|
||||
@ -193,9 +195,10 @@ public abstract class SdJwtCreationAndSigningTest {
|
||||
SignatureSignerContext issuerSignerContext = new ECDSASignatureSignerContext(issuerKeyPair);
|
||||
SignatureSignerContext holderSignerContext = new ECDSASignatureSignerContext(holderKeyPair);
|
||||
|
||||
final long iat = Instant.now().minus(10, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long nbf = Instant.now().minus(5, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long exp = Instant.now().plus(60, ChronoUnit.SECONDS).getEpochSecond();
|
||||
Instant now = Instant.ofEpochSecond(Time.currentTime());
|
||||
final long iat = now.minus(10, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long nbf = now.minus(5, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long exp = now.plus(60, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final String nonce = "123456789";
|
||||
final String audience = String.format("x509_san_dns:%s", authorizationServerUrl);
|
||||
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
|
||||
package org.keycloak.sdjwt;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -25,6 +24,7 @@ import java.util.function.Function;
|
||||
|
||||
import org.keycloak.OID4VCConstants;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.crypto.SignatureSignerContext;
|
||||
import org.keycloak.crypto.SignatureVerifierContext;
|
||||
import org.keycloak.rule.CryptoInitRule;
|
||||
@ -169,7 +169,7 @@ public abstract class SdJwtVerificationTest {
|
||||
|
||||
@Test
|
||||
public void sdJwtVerificationShouldFail_IfExpired() {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode claimSet = mapper.createObjectNode();
|
||||
claimSet.put("given_name", "John");
|
||||
@ -220,7 +220,7 @@ public abstract class SdJwtVerificationTest {
|
||||
// exp: null
|
||||
ObjectNode claimSet1 = mapper.createObjectNode();
|
||||
claimSet1.put("given_name", "John");
|
||||
claimSet1.put("exp", Instant.now().getEpochSecond() - (31536000));
|
||||
claimSet1.put("exp", Time.currentTime() - (31536000));
|
||||
|
||||
// exp: invalid
|
||||
ObjectNode claimSet2 = mapper.createObjectNode();
|
||||
@ -268,7 +268,7 @@ public abstract class SdJwtVerificationTest {
|
||||
|
||||
@Test
|
||||
public void sdJwtVerificationShouldFail_IfIssuedInTheFuture() {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode claimSet = mapper.createObjectNode();
|
||||
claimSet.put("given_name", "John");
|
||||
@ -317,7 +317,7 @@ public abstract class SdJwtVerificationTest {
|
||||
|
||||
@Test
|
||||
public void sdJwtVerificationShouldFail_IfNbfInvalid() {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode claimSet = mapper.createObjectNode();
|
||||
claimSet.put("given_name", "John");
|
||||
|
||||
@ -17,13 +17,13 @@
|
||||
|
||||
package org.keycloak.sdjwt.sdjwtvp;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.OID4VCConstants;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.crypto.SignatureVerifierContext;
|
||||
import org.keycloak.rule.CryptoInitRule;
|
||||
import org.keycloak.sdjwt.IssuerSignedJwtVerificationOpts;
|
||||
@ -265,7 +265,7 @@ public abstract class SdJwtVPVerificationTest {
|
||||
|
||||
@Test
|
||||
public void testShouldFail_IfKbIssuedInFuture() {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode kbPayload = exampleKbPayload();
|
||||
kbPayload.set(OID4VCConstants.CLAIM_NAME_IAT, mapper.valueToTree(now + 1000));
|
||||
@ -280,7 +280,7 @@ public abstract class SdJwtVPVerificationTest {
|
||||
|
||||
@Test
|
||||
public void testShouldTolerateKbIssuedInTheFutureWithinClockSkew() throws VerificationException {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode kbPayload = exampleKbPayload();
|
||||
// Issued just 5 seconds in the future. Should pass with a clock skew of 10 seconds.
|
||||
@ -317,7 +317,7 @@ public abstract class SdJwtVPVerificationTest {
|
||||
|
||||
@Test
|
||||
public void testShouldFail_IfKbExpired() {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode kbPayload = exampleKbPayload();
|
||||
kbPayload.set(OID4VCConstants.CLAIM_NAME_EXP, mapper.valueToTree(now - 1000));
|
||||
@ -332,7 +332,7 @@ public abstract class SdJwtVPVerificationTest {
|
||||
|
||||
@Test
|
||||
public void testShouldTolerateExpiredKbWithinClockSkew() throws VerificationException {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode kbPayload = exampleKbPayload();
|
||||
// Expires just 5 seconds ago. Should pass with a clock skew of 10 seconds.
|
||||
@ -351,7 +351,7 @@ public abstract class SdJwtVPVerificationTest {
|
||||
|
||||
@Test
|
||||
public void testShouldFail_IfKbNotBeforeTimeYet() {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
|
||||
ObjectNode kbPayload = exampleKbPayload();
|
||||
kbPayload.set(OID4VCConstants.CLAIM_NAME_NBF, mapper.valueToTree(now + 1000));
|
||||
|
||||
@ -18,8 +18,6 @@
|
||||
|
||||
package org.keycloak.protocol.oid4vc.issuance.keybinding;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
@ -34,6 +32,7 @@ import jakarta.annotation.Nullable;
|
||||
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.constants.OID4VCIConstants;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
@ -80,10 +79,10 @@ public class JwtCNonceHandler implements CNonceHandler {
|
||||
RealmModel realm = keycloakSession.getContext().getRealm();
|
||||
final String issuer = OID4VCIssuerWellKnownProvider.getIssuer(keycloakSession.getContext());
|
||||
// TODO discussion about the attribute name to use
|
||||
final Integer nonceLifetimeMillis = realm.getAttribute(OID4VCIConstants.C_NONCE_LIFETIME_IN_SECONDS, 60);
|
||||
final Integer nonceLifetimeSeconds = realm.getAttribute(OID4VCIConstants.C_NONCE_LIFETIME_IN_SECONDS, 60);
|
||||
audiences = Optional.ofNullable(audiences).orElseGet(Collections::emptyList);
|
||||
final Instant now = Instant.now();
|
||||
final long expiresAt = now.plus(nonceLifetimeMillis, ChronoUnit.SECONDS).getEpochSecond();
|
||||
final long nowSeconds = Time.currentTime();
|
||||
final long expiresAt = nowSeconds + nonceLifetimeSeconds;
|
||||
final int nonceLength = NONCE_DEFAULT_LENGTH + new Random().nextInt(NONCE_LENGTH_RANDOM_OFFSET);
|
||||
// this generated value itself is basically just a salt-value for the generated token, which itself is the nonce.
|
||||
final String strongSalt = Base64.getEncoder().encodeToString(RandomSecret.createRandomSecret(nonceLength));
|
||||
@ -144,7 +143,7 @@ public class JwtCNonceHandler implements CNonceHandler {
|
||||
if (exp == null) {
|
||||
throw new VerificationException("c_nonce has no expiration time");
|
||||
}
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long now = Time.currentTime();
|
||||
if (exp < now) {
|
||||
String message = String.format(
|
||||
"c_nonce not valid: %s(exp) < %s(now)",
|
||||
|
||||
@ -24,6 +24,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.oid4vci.CredentialScopeModel;
|
||||
@ -121,9 +122,9 @@ public class OID4VCIssuedAtTimeClaimMapper extends OID4VCMapper {
|
||||
Instant iat = Optional.ofNullable(mapperModel.getConfig())
|
||||
.flatMap(config -> Optional.ofNullable(config.get(VALUE_SOURCE)))
|
||||
.filter(valueSource -> Objects.equals(valueSource, "COMPUTE"))
|
||||
.map(valueSource -> Instant.now())
|
||||
.map(valueSource -> Instant.ofEpochSecond(Time.currentTime()))
|
||||
.orElseGet(() -> Optional.ofNullable(verifiableCredential.getIssuanceDate())
|
||||
.orElse(Instant.now()));
|
||||
.orElse(Instant.ofEpochSecond(Time.currentTime())));
|
||||
|
||||
Instant normalizedIat = new TimeClaimNormalizer(userSessionModel.getRealm())
|
||||
.normalize(iat);
|
||||
|
||||
@ -55,6 +55,7 @@ import org.keycloak.common.util.CertificateUtils;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.constants.OID4VCIConstants;
|
||||
import org.keycloak.crypto.ECDSASignatureSignerContext;
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
@ -119,7 +120,9 @@ public abstract class OID4VCTest extends AbstractTestRealmKeycloakTest {
|
||||
protected static final String CONTEXT_URL = "https://www.w3.org/2018/credentials/v1";
|
||||
protected static final URI TEST_DID = URI.create("did:web:test.org");
|
||||
protected static final List<String> TEST_TYPES = List.of("VerifiableCredential");
|
||||
protected static final Instant TEST_EXPIRATION_DATE = Instant.now().plus(365, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS);
|
||||
protected static final Instant TEST_EXPIRATION_DATE = Instant.ofEpochMilli(Time.currentTimeMillis())
|
||||
.plus(365, ChronoUnit.DAYS)
|
||||
.truncatedTo(ChronoUnit.SECONDS);
|
||||
protected static final Instant TEST_ISSUANCE_DATE = Instant.ofEpochSecond(1000);
|
||||
|
||||
protected static final KeyWrapper RSA_KEY = getRsaKey();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user