CVE-2026-40942
Description
The Data Sharing Framework (DSF) implements a distributed process engine based on the BPMN 2.0 and FHIR R4 standards. Prior to 2.1.0, The OIDC JWKS and Metadata Document caches used an inverted time comparison (isBefore instead of isAfter), causing the cache to never return cached values. Every incoming request triggered a fresh HTTP fetch of the OIDC Metadata Document and JWKS keys from the OIDC provider. The OIDC token cache for the FHIR client connections used an inverted time comparison (isBefore instead of isAfter), causing the cache to never invalidate. Every incoming request returned the same OIDC token even if expired. This vulnerability is fixed in 2.1.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
dev.dsf:dsf-bpe-process-api-v2Maven | >= 0 | — |
dev.dsf:dsf-bpe-serverMaven | >= 0 | — |
Affected products
1Patches
2d3ca59b4daccfixed inverted token cache timeout logic
1 file changed · +1 −1
dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientWithCache.java+1 −1 modified@@ -115,7 +115,7 @@ public DecodedJWT getAccessTokenDecoded() throws OidcClientException @Override public DecodedJWT getAccessTokenDecoded(Configuration configuration, Jwks jwks) throws OidcClientException { - if (accessTokenCache != null && accessTokenCache.timeout.isBefore(ZonedDateTime.now())) + if (accessTokenCache != null && accessTokenCache.timeout.isAfter(ZonedDateTime.now())) return accessTokenCache.resource; else {
31c2e974dfd4refactored code, improved oidc / jwks handling (use='sig', min RSA)
8 files changed · +198 −102
dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientJersey.java+7 −22 modified@@ -169,22 +169,6 @@ public DecodedJWT getAccessTokenDecoded(OidcConfiguration configuration, Jwks jw } } - /** - * Does not verify if the access token is expired. Supported algorithms: RS256, RS384, RS512, ES256, ES384 and - * ES512. - * - * @param accessToken - * not <code>null</code> - * @param jwks - * not <code>null</code> - * @return decoded access token - * @throws OidcClientException - * if verification fails, the public key to verify is unknown or a unsupported signature algorithm was - * used. - * - * @see DecodedJWT#getExpiresAt() - * @see DecodedJWT#getExpiresAtAsInstant() - */ private DecodedJWT verifyAndDecodeAccessToken(String accessToken, Jwks jwks) throws OidcClientException { try @@ -196,14 +180,15 @@ private DecodedJWT verifyAndDecodeAccessToken(String accessToken, Jwks jwks) thr throw new OidcClientException("Access token has no kid property"); Optional<JwksKey> key = jwks.getKey(keyId); - if (key.isEmpty()) - throw new OidcClientException("Access token key with kid '" + keyId + "' not in JWKS"); + if (key.isEmpty() || !key.get().use().equals("sig")) + throw new OidcClientException("Access token key with kid '" + keyId + "' and use 'sig' not in JWKS"); - Optional<Algorithm> algorithm = key.map(JwksKey::toAlgorithm); + Optional<Algorithm> algorithm = key.flatMap(JwksKey::toAlgorithm); if (key.isEmpty()) + { throw new OidcClientException("Access token key with kid '" + keyId - + "' has unsupported type (kty) / algorithm (alg) in JWKS '" + key.get().kty() + "' / '" - + key.get().alg() + "'"); + + "' has unsupported type (kty) / algorithm (alg) / key-size in JWKS"); + } try { @@ -231,7 +216,7 @@ else if (requiredAudiences.size() > 1) } catch (TokenExpiredException e) { - throw new OidcClientException("JWT verification failed: claim missing", e); + throw new OidcClientException("JWT verification failed: token expired", e); } catch (IncorrectClaimException e) {
dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientWithCache.java+10 −10 modified@@ -32,7 +32,7 @@ private static final record CacheEntry<T>(ZonedDateTime timeout, T resource) { } - private final Duration cacheTimeoutconfigurationResource; + private final Duration cacheTimeoutConfigurationResource; private final Duration cacheTimeoutJwksResource; private final Duration cacheTimeoutAccessTokenBeforeExpiration; private final OidcClientWithDecodedJwt delegate; @@ -42,7 +42,7 @@ private static final record CacheEntry<T>(ZonedDateTime timeout, T resource) private CacheEntry<DecodedJWT> accessTokenCache; /** - * @param cacheTimeoutconfigurationResource + * @param cacheTimeoutConfigurationResource * not <code>null</code>, not negative * @param cacheTimeoutJwksResource * not <code>null</code>, not negative @@ -51,13 +51,13 @@ private static final record CacheEntry<T>(ZonedDateTime timeout, T resource) * @param delegate * not <code>null</code> */ - public OidcClientWithCache(Duration cacheTimeoutconfigurationResource, Duration cacheTimeoutJwksResource, + public OidcClientWithCache(Duration cacheTimeoutConfigurationResource, Duration cacheTimeoutJwksResource, Duration cacheTimeoutAccessTokenBeforeExpiration, OidcClientWithDecodedJwt delegate) { - this.cacheTimeoutconfigurationResource = Objects.requireNonNull(cacheTimeoutconfigurationResource, - "cacheTimeoutconfigurationResource"); - if (cacheTimeoutconfigurationResource.isNegative()) - throw new IllegalArgumentException("cacheTimeoutconfigurationResource negative"); + this.cacheTimeoutConfigurationResource = Objects.requireNonNull(cacheTimeoutConfigurationResource, + "cacheTimeoutConfigurationResource"); + if (cacheTimeoutConfigurationResource.isNegative()) + throw new IllegalArgumentException("cacheTimeoutConfigurationResource negative"); this.cacheTimeoutJwksResource = Objects.requireNonNull(cacheTimeoutJwksResource, "cacheTimeoutJwksResource"); if (cacheTimeoutJwksResource.isNegative()) @@ -73,13 +73,13 @@ public OidcClientWithCache(Duration cacheTimeoutconfigurationResource, Duration @Override public Configuration getConfiguration() throws OidcClientException { - if (configurationCache != null && configurationCache.timeout.isBefore(ZonedDateTime.now())) + if (configurationCache != null && configurationCache.timeout.isAfter(ZonedDateTime.now())) return configurationCache.resource; else { Configuration configuration = delegate.getConfiguration(); configurationCache = new CacheEntry<Configuration>( - ZonedDateTime.now().plus(cacheTimeoutconfigurationResource), configuration); + ZonedDateTime.now().plus(cacheTimeoutConfigurationResource), configuration); return configuration; } } @@ -89,7 +89,7 @@ public Jwks getJwks() throws OidcClientException { Configuration configuration = getConfiguration(); - if (jwksCache != null && jwksCache.timeout.isBefore(ZonedDateTime.now())) + if (jwksCache != null && jwksCache.timeout.isAfter(ZonedDateTime.now())) return jwksCache.resource; else {
dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/auth/DsfOpenIdLoginService.java+0 −13 modified@@ -33,14 +33,12 @@ public class DsfOpenIdLoginService extends OpenIdLoginService { private static final Logger logger = LoggerFactory.getLogger(DsfOpenIdLoginService.class); - private final OpenIdConfiguration configuration; private final LoginService loginService; public DsfOpenIdLoginService(OpenIdConfiguration configuration, LoginService loginService) { super(configuration, loginService); - this.configuration = configuration; this.loginService = loginService; } @@ -49,17 +47,6 @@ public UserIdentity login(String identifier, Object credentials, Request request Function<Boolean, Session> getOrCreateSession) { OpenIdCredentials openIdCredentials = (OpenIdCredentials) credentials; - try - { - openIdCredentials.redeemAuthCode(configuration); - } - catch (Exception e) - { - logger.debug("Unable to redeem auth code", e); - logger.warn("Unable to redeem auth code: {} - {}", e.getClass().getName(), e.getMessage()); - - return null; - } return loginService.login(openIdCredentials.getUserId(), credentials, request, getOrCreateSession); }
dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java+34 −6 modified@@ -162,6 +162,14 @@ public abstract class AbstractJettyConfig extends AbstractCertificateConfig @Value("${dev.dsf.server.auth.oidc.provider.discovery.path:/.well-known/openid-configuration}") private String oidcProviderDiscoveryPath; + @Documentation(description = "OIDC provider client cache timeout of the 'openid-configuration' discovery resource") + @Value("${dev.dsf.server.auth.oidc.provider.client.cache.timeout.configuration.resource:PT1H}") + private String oidcProviderClientCacheConfigurationResourceTimeout; + + @Documentation(description = "OIDC provider client cache timeout of the jwks resource") + @Value("${dev.dsf.server.auth.oidc.provider.client.cache.timeout.jwks.resource:PT1H}") + private String oidcProviderClientCacheJwksResourceTimeout; + @Documentation(description = "OIDC provider client connect timeout") @Value("${dev.dsf.server.auth.oidc.provider.client.timeout.connect:PT5S}") private String oidcProviderClientTimeoutConnect; @@ -407,24 +415,44 @@ public BaseOidcClient baseOidcClient() char[] keyStorePassword = UUID.randomUUID().toString().toCharArray(); KeyStore keyStore = oidcProviderClientKeyStore(keyStorePassword); - return new BaseOidcClientWithCache(new BaseOidcClientJersey(oidcProviderRealmBaseUrl, oidcProviderDiscoveryPath, - trustStore, keyStore, keyStore == null ? null : keyStorePassword, proxyUrl, proxyUsername, - proxyPassword, buildInfoReader().getUserAgentValue(), oidcProviderClientTimeoutConnect(), - oidcProviderClientTimeoutRead(), false)); + return new BaseOidcClientWithCache(getOidcProviderClientCacheConfigurationResourceTimeout(), + getOidcProviderClientCacheJwksResourceTimeout(), + new BaseOidcClientJersey(oidcProviderRealmBaseUrl, oidcProviderDiscoveryPath, trustStore, keyStore, + keyStore == null ? null : keyStorePassword, proxyUrl, proxyUsername, proxyPassword, + buildInfoReader().getUserAgentValue(), oidcProviderClientTimeoutConnect(), + oidcProviderClientTimeoutRead(), false)); + } + + private Duration getOidcProviderClientCacheConfigurationResourceTimeout() + { + return assertPositive(Duration.parse(oidcProviderClientCacheConfigurationResourceTimeout)); + } + + private Duration getOidcProviderClientCacheJwksResourceTimeout() + { + return assertPositive(Duration.parse(oidcProviderClientCacheJwksResourceTimeout)); } @Bean @Lazy public Duration oidcProviderClientTimeoutRead() { - return Duration.parse(oidcProviderClientTimeoutRead); + return assertPositive(Duration.parse(oidcProviderClientTimeoutRead)); } @Bean @Lazy public Duration oidcProviderClientTimeoutConnect() { - return Duration.parse(oidcProviderClientTimeoutConnect); + return assertPositive(Duration.parse(oidcProviderClientTimeoutConnect)); + } + + private Duration assertPositive(Duration duration) + { + if (duration != null && duration.isNegative()) + throw new IllegalArgumentException("configured duration is negative"); + else + return duration; } private Proxy oidcClientProxy()
dsf-common/dsf-common-oidc/src/main/java/dev/dsf/common/oidc/BaseOidcClientWithCache.java+30 −16 modified@@ -15,51 +15,65 @@ */ package dev.dsf.common.oidc; +import java.time.Duration; +import java.time.ZonedDateTime; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; public class BaseOidcClientWithCache implements BaseOidcClient { - private final BaseOidcClient delegate; + private static final record CacheEntry<T>(ZonedDateTime timeout, T resource) + { + } + + private final Duration cacheTimeoutConfigurationResource; + private final Duration cacheTimeoutJwksResource; + + private final AtomicReference<CacheEntry<OidcConfiguration>> configurationCache = new AtomicReference<>(); + private final AtomicReference<CacheEntry<Jwks>> jwksCache = new AtomicReference<>(); - private final AtomicReference<OidcConfiguration> oidcConfiguration = new AtomicReference<>(); - private final AtomicReference<Jwks> jwks = new AtomicReference<>(); + private final BaseOidcClient delegate; /** + * @param cacheTimeoutconfigurationResource + * not <code>null</code> + * @param cacheTimeoutJwksResource + * not <code>null</code> * @param delegate * not <code>null</code> */ - public BaseOidcClientWithCache(BaseOidcClient delegate) + public BaseOidcClientWithCache(Duration cacheTimeoutconfigurationResource, Duration cacheTimeoutJwksResource, + BaseOidcClient delegate) { + this.cacheTimeoutConfigurationResource = Objects.requireNonNull(cacheTimeoutconfigurationResource, + "cacheTimeoutconfigurationResource"); + this.cacheTimeoutJwksResource = Objects.requireNonNull(cacheTimeoutJwksResource, "cacheTimeoutJwksResource"); this.delegate = Objects.requireNonNull(delegate, "delegate"); } @Override public OidcConfiguration getConfiguration() throws OidcClientException { - return getOrSet(oidcConfiguration, delegate::getConfiguration); + return getOrSet(configurationCache, cacheTimeoutConfigurationResource, delegate::getConfiguration); } - private <T> T getOrSet(AtomicReference<T> cache, Supplier<T> supplier) + private <T> T getOrSet(AtomicReference<CacheEntry<T>> cache, Duration timeout, Supplier<T> supplier) { - T cached = cache.get(); - if (cached == null) + CacheEntry<T> cached = cache.get(); + if (cached != null && cached.timeout.isAfter(ZonedDateTime.now())) + return cached.resource; + else { - T value = supplier.get(); - if (cache.compareAndSet(cached, value)) - return value; - else - return cache.get(); + cache.compareAndSet(cached, new CacheEntry<>(ZonedDateTime.now().plus(timeout), supplier.get())); + return cache.get().resource; } - else - return cached; } @Override public Jwks getJwks() throws OidcClientException { - return getOrSet(jwks, () -> delegate.getJwks(getConfiguration())); + return getOrSet(jwksCache, cacheTimeoutJwksResource, () -> delegate.getJwks(getConfiguration())); } @Override
dsf-common/dsf-common-oidc/src/main/java/dev/dsf/common/oidc/Jwks.java+55 −16 modified@@ -39,6 +39,9 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; @@ -49,6 +52,10 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class Jwks { + private static final Logger logger = LoggerFactory.getLogger(Jwks.class); + + private static final int RSA_MIN_KEY_LENGTH = 2048; + @JsonIgnoreProperties(ignoreUnknown = true) public static record JwksKey(@JsonProperty("kid") String kid, @JsonProperty("kty") String kty, @JsonProperty("alg") String alg, @JsonProperty("crv") String crv, @JsonProperty("use") String use, @@ -77,20 +84,27 @@ public JwksKey(@JsonProperty("kid") String kid, @JsonProperty("kty") String kty, * @throws JwksException * if {@link Algorithm} can't be created or is not supported for the enclosed key material */ - public Algorithm toAlgorithm() throws JwksException + public Optional<Algorithm> toAlgorithm() throws JwksException { - return switch (kty) + return Optional.ofNullable(switch (kty) { case "RSA" -> toRsaAlgorithm(); case "EC" -> toEcdsaAlgorithm(); - default -> throw new JwksException("JWKS kty property value '" + kty + "' not one of 'RSA' or 'EC'"); - }; + default -> { + logger.warn("JWKS kty property value '{}' not one of 'RSA' or 'EC'", kty); + yield null; + } + }); } private Algorithm toRsaAlgorithm() { RSAPublicKey key = toRsaPublicKey(n, e); + + if (key == null) + return null; + RSAKeyProvider keyProvider = toRsaKeyProvider(key, kid); return switch (alg) @@ -99,14 +113,20 @@ private Algorithm toRsaAlgorithm() case "RS384" -> Algorithm.RSA384(keyProvider); case "RS512" -> Algorithm.RSA512(keyProvider); - default -> throw new JwksException( - "JWKS alg property value '" + alg + "' not one of 'RSA256', 'RSA384' or 'RSA512'"); + default -> { + logger.warn("JWKS alg property value '{}' not one of 'RS256', 'RS384' or 'RS512'", alg); + yield null; + } }; } private Algorithm toEcdsaAlgorithm() { ECPublicKey key = toEcPublicKey(x, y, crv); + + if (key == null) + return null; + ECDSAKeyProvider keyProvider = toEcKeyProvider(key, kid); return switch (alg) @@ -115,8 +135,10 @@ private Algorithm toEcdsaAlgorithm() case "ES384" -> Algorithm.ECDSA384(keyProvider); case "ES512" -> Algorithm.ECDSA512(keyProvider); - default -> throw new JwksException( - "JWKS crv property value '" + alg + "' not one of 'ES256', 'ES384' or 'ES512'"); + default -> { + logger.warn("JWKS crv property value '{}' not one of 'ES256', 'ES384' or 'ES512'", alg); + yield null; + } }; } @@ -152,6 +174,12 @@ private RSAPublicKey toRsaPublicKey(String n, String e) BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(n)); BigInteger exponent = new BigInteger(1, Base64.getUrlDecoder().decode(e)); + if (modulus.bitLength() < RSA_MIN_KEY_LENGTH) + { + logger.warn("JWKS RSA key (kid: '{}') length {} <{} bit", kid, modulus.bitLength(), RSA_MIN_KEY_LENGTH); + return null; + } + try { RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent); @@ -161,7 +189,10 @@ private RSAPublicKey toRsaPublicKey(String n, String e) } catch (InvalidKeySpecException | NoSuchAlgorithmException ex) { - throw new JwksException("Unable to create RSA public key", ex); + logger.debug("Unable to create RSA public key: {} - {}", ex.getClass().getName(), ex.getMessage()); + logger.warn("Unable to create RSA public key", ex); + + return null; } } @@ -197,16 +228,21 @@ private ECPublicKey toEcPublicKey(String x, String y, String crv) BigInteger xCoordinate = new BigInteger(1, Base64.getUrlDecoder().decode(x)); BigInteger yCoordinate = new BigInteger(1, Base64.getUrlDecoder().decode(y)); - ECGenParameterSpec curve = switch (crv) + return switch (crv) { - case "P-256" -> new ECGenParameterSpec("secp256r1"); - case "P-384" -> new ECGenParameterSpec("secp384r1"); - case "P-521" -> new ECGenParameterSpec("secp521r1"); + case "P-256" -> toEcPublicKey(xCoordinate, yCoordinate, new ECGenParameterSpec("secp256r1")); + case "P-384" -> toEcPublicKey(xCoordinate, yCoordinate, new ECGenParameterSpec("secp384r1")); + case "P-521" -> toEcPublicKey(xCoordinate, yCoordinate, new ECGenParameterSpec("secp521r1")); - default -> throw new JwksException( - "JWKS crv property value '" + crv + "' not one of 'P-256', 'P-384' or 'P-521'"); + default -> { + logger.warn("JWKS crv property value '{}' not one of 'P-256', 'P-384' or 'P-521'", crv); + yield null; + } }; + } + private ECPublicKey toEcPublicKey(BigInteger xCoordinate, BigInteger yCoordinate, ECGenParameterSpec curve) + { try { AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC"); @@ -219,7 +255,10 @@ private ECPublicKey toEcPublicKey(String x, String y, String crv) } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException ex) { - throw new JwksException("Unable to create EC public key", ex); + logger.debug("Unable to create EC public key", ex); + logger.warn("Unable to create EC public key: {} - {}", ex.getClass().getName(), ex.getMessage()); + + return null; } } }
dsf-common/dsf-common-oidc/src/main/java/dev/dsf/common/oidc/JwtVerifierImpl.java+27 −13 modified@@ -16,6 +16,7 @@ package dev.dsf.common.oidc; import java.util.Objects; +import java.util.Optional; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; @@ -57,14 +58,21 @@ public DecodedJWT verifyBackchannelLogout(String token) throws JWTVerificationEx { final String keyId = JWT.decode(token).getKeyId(); - JWTVerifier verifier = oidcClient.getJwks().getKey(keyId).map(JwksKey::toAlgorithm).map(algorithm -> + Optional<JwksKey> key = oidcClient.getJwks().getKey(keyId); + if (key.isEmpty() || !key.get().use().equals("sig")) + throw new OidcClientException("Logout token key with kid '" + keyId + "' and use 'sig' not in JWKS"); + + Optional<Algorithm> algorithm = key.flatMap(JwksKey::toAlgorithm); + if (key.isEmpty()) { - return createVerification(algorithm).withAudience(clientId).withClaim("events", - (claim, _) -> claim.asMap().containsKey("http://schemas.openid.net/event/backchannel-logout")) - .build(); + throw new OidcClientException("Logout token key with kid '" + keyId + + "' has unsupported type (kty) / algorithm (alg) / key-size in JWKS"); + } - }).orElseThrow(() -> new OidcClientException( - "Key with id " + keyId + " not found in JWKS resource from OIDC provider")); + JWTVerifier verifier = createVerification(algorithm.get()).withAudience(clientId) + .withClaim("events", + (claim, _) -> claim.asMap().containsKey("http://schemas.openid.net/event/backchannel-logout")) + .build(); return verifier.verify(token); } @@ -79,17 +87,23 @@ public DecodedJWT verifyBearerToken(String token) throws JWTVerificationExceptio { final String keyId = JWT.decode(token).getKeyId(); - JWTVerifier verifier = oidcClient.getJwks().getKey(keyId).map(JwksKey::toAlgorithm).map(algorithm -> + Optional<JwksKey> key = oidcClient.getJwks().getKey(keyId); + if (key.isEmpty() || !key.get().use().equals("sig")) + throw new OidcClientException("Bearer token key with kid '" + keyId + "' and use 'sig' not in JWKS"); + + Optional<Algorithm> algorithm = key.flatMap(JwksKey::toAlgorithm); + if (key.isEmpty()) { - Verification verification = createVerification(algorithm).acceptLeeway(1); + throw new OidcClientException("Bearer token key with kid '" + keyId + + "' has unsupported type (kty) / algorithm (alg) / key-size in JWKS"); + } - if (!bearerTokenAudience.isBlank()) - verification.withAnyOfAudience(bearerTokenAudience); + Verification verification = createVerification(algorithm.get()); - return verification.build(); + if (!bearerTokenAudience.isBlank()) + verification.withAnyOfAudience(bearerTokenAudience); - }).orElseThrow(() -> new OidcClientException( - "Key with id " + keyId + " not found in JWKS resource from OIDC provider")); + JWTVerifier verifier = verification.build(); return verifier.verify(token); }
dsf-common/dsf-common-oidc/src/test/java/dev/dsf/common/oidc/JwksTest.java+35 −6 modified@@ -16,14 +16,15 @@ package dev.dsf.common.oidc; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import java.util.Optional; import org.junit.Test; +import com.auth0.jwt.algorithms.Algorithm; import com.fasterxml.jackson.databind.ObjectMapper; import dev.dsf.common.oidc.Jwks.JwksKey; @@ -58,7 +59,21 @@ public class JwksTest ], "x5t": "dQL-LEROCVCUfvs0W_5ayioFWjA", "x5t#S256": "yi-b9TklWk5X5d_Pr_moQVmdkdVa4wZTuYnDxWXrXag" - } + }, + { + "kid": "BMvf48wBJERBDMGInNfOsSiTWAnNiWGinVPnjSCeWcg", + "kty": "EC", + "alg": "ES384", + "use": "sig", + "x5c": [ + "MIIBTTCB1AIGAZ0bZiPeMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMMB2VjX3Rlc3QwHhcNMjYwMzIzMTU1MTExWhcNMzYwMzIzMTU1MjUxWjASMRAwDgYDVQQDDAdlY190ZXN0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3Nb3kbLLkQ6y/E8yDKrB3LZVo1aXQV12OR3yWFl9D/Xc4RZ874NngV9FVccqVS41WZp5HbiH/OhIgvKvyRE97BRZu/i+UhZz59l74fogV4sNGLtbnbzA62eQbIAu/c/kMAoGCCqGSM49BAMCA2gAMGUCMFI+m0Z21NfMGjlPpr4v64DOEzjoP4Y9fS4SJK3YIEfHvkVidDgm2A1lZD0ZRKXb7AIxAPi1ScvwtrIl0oadty4Qjg1+lWFAp943fHdFEuNE6GeagkB/dm9Nuo1wqy6hKm5O9Q==" + ], + "x5t": "3luSUTuX5v_MVEGVP_WDwy1e9GI", + "x5t#S256": "ra4lrnu_zhxYpamDEvtcxEvAFenH9CAuS5OvEiDtTho", + "crv": "P-384", + "x": "3Nb3kbLLkQ6y_E8yDKrB3LZVo1aXQV12OR3yWFl9D_Xc4RZ874NngV9FVccqVS41", + "y": "WZp5HbiH_OhIgvKvyRE97BRZu_i-UhZz59l74fogV4sNGLtbnbzA62eQbIAu_c_k" + } ] }"""; @@ -70,27 +85,41 @@ public void testDecodeJwks() throws Exception assertNotNull(jwks); assertNotNull(jwks.getKeys()); - assertEquals(2, jwks.getKeys().size()); + assertEquals(3, jwks.getKeys().size()); Optional<JwksKey> jwk0o = jwks.getKey("kncc6492FTtclCO8qJvhS2PvYap_VabfAPOLhK3mkfA"); Optional<JwksKey> jwk1o = jwks.getKey("Zp7ockRwsxqM6FrZlDJUOVwAxPICO2jBW0Rbk25oYGk"); + Optional<JwksKey> jwk2o = jwks.getKey("BMvf48wBJERBDMGInNfOsSiTWAnNiWGinVPnjSCeWcg"); assertTrue(jwk0o.isPresent()); assertTrue(jwk1o.isPresent()); + assertTrue(jwk2o.isPresent()); assertTrue(jwks.getKey(null).isEmpty()); assertTrue(jwks.getKey("not existing").isEmpty()); JwksKey jwk0 = jwk0o.get(); JwksKey jwk1 = jwk1o.get(); + JwksKey jwk2 = jwk2o.get(); assertNotNull(jwk0.kid()); assertEquals("kncc6492FTtclCO8qJvhS2PvYap_VabfAPOLhK3mkfA", jwk0.kid()); assertNotNull(jwk1.kid()); assertEquals("Zp7ockRwsxqM6FrZlDJUOVwAxPICO2jBW0Rbk25oYGk", jwk1.kid()); + assertNotNull(jwk2.kid()); + assertEquals("BMvf48wBJERBDMGInNfOsSiTWAnNiWGinVPnjSCeWcg", jwk2.kid()); + + Optional<Algorithm> jwk0a = jwk0.toAlgorithm(); + assertNotNull(jwk0a); + assertTrue(jwk0a.isPresent()); + assertEquals("RS256", jwk0.toAlgorithm().get().getName()); - assertNotNull(jwk0.toAlgorithm()); - assertEquals("RS256", jwk0.toAlgorithm().getName()); + Optional<Algorithm> jwk1a = jwk1.toAlgorithm(); + assertNotNull(jwk1a); + assertFalse(jwk1a.isPresent()); - assertThrows(JwksException.class, jwk1::toAlgorithm); + Optional<Algorithm> jwk2a = jwk2.toAlgorithm(); + assertNotNull(jwk2a); + assertTrue(jwk2a.isPresent()); + assertEquals("ES384", jwk2.toAlgorithm().get().getName()); } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-xmj9-7625-f634ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-40942ghsaADVISORY
- github.com/datasharingframework/dsf/commit/31c2e974dfd4351756104ee8c53dbcd666192fefnvdWEB
- github.com/datasharingframework/dsf/commit/d3ca59b4daccde16a006fedeccce28fd1f826908nvdWEB
- github.com/datasharingframework/dsf/security/advisories/GHSA-xmj9-7625-f634nvdWEB
News mentions
0No linked articles in our index yet.