Downgrade Java for client libraries to 8

Closes #33051

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-09-19 10:49:42 +02:00 committed by Marek Posolda
parent 71bc313297
commit c532751ff4
19 changed files with 408 additions and 240 deletions

View file

@ -85,7 +85,7 @@ jobs:
SEP=","
done
./mvnw test -pl "$PROJECTS" -am
./mvnw install -pl "$PROJECTS" -am
- name: Upload JVM Heapdumps
if: always()

View file

@ -18,6 +18,12 @@
<description>Keycloak Authz: Client API. This module is supposed to be used just in the Keycloak repository for the testsuite. It is NOT supposed to be used by the 3rd party applications.
For the use by 3rd party applications, please use `org.keycloak:keycloak-authz-client` module.</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.release>8</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>

View file

@ -32,8 +32,9 @@
<description>Common library and dependencies shared with server and all adapters</description>
<properties>
<!-- We still need to support EAP 8, set the Java version to 11. -->
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.release>8</maven.compiler.release>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
</properties>

View file

@ -988,7 +988,7 @@ public class Reflections {
* @throws InstantiationException
* @deprecated for removal in Keycloak 27
*/
@Deprecated(forRemoval = true)
@Deprecated
public static <T> T newInstance(final Class<T> fromClass) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return newInstance(fromClass, fromClass.getName());
}
@ -1008,7 +1008,7 @@ public class Reflections {
* @throws InstantiationException
* @deprecated for removal in Keycloak 27
*/
@Deprecated(forRemoval = true)
@Deprecated
public static <T> T newInstance(final Class<?> type, final String fullQualifiedName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return (T) classForName(fullQualifiedName, type.getClassLoader()).newInstance();
}

View file

@ -24,7 +24,7 @@ import java.security.PrivilegedAction;
* A {@link java.security.PrivilegedAction} that calls {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)}
* @deprecated for removal in Keycloak 27
*/
@Deprecated(forRemoval = true)
@Deprecated
public class SetAccessiblePrivilegedAction implements PrivilegedAction<Void> {
private final AccessibleObject member;

View file

@ -24,7 +24,7 @@ import java.security.PrivilegedAction;
* A {@link PrivilegedAction} that calls {@link AccessibleObject#setAccessible(boolean)}
* @deprecated for removal in Keycloak 27
*/
@Deprecated(forRemoval = true)
@Deprecated
public class UnSetAccessiblePrivilegedAction implements PrivilegedAction<Void> {
private final AccessibleObject member;

View file

@ -32,6 +32,9 @@
<description/>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.release>8</maven.compiler.release>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
</properties>
@ -72,6 +75,38 @@
</dependency>
</dependencies>
<profiles>
<profile>
<id>jdk-16</id>
<activation>
<jdk>[16,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile-java16</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>16</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java16</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<resources>
<resource>

View file

@ -17,19 +17,10 @@
package org.keycloak.jose.jwk;
import java.math.BigInteger;
import java.security.Key;
import java.security.interfaces.EdECPublicKey;
import java.security.spec.EdECPoint;
import java.util.Arrays;
import java.util.Optional;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import java.security.Key;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@ -54,55 +45,13 @@ public class JWKBuilder extends AbstractJWKBuilder {
@Override
public JWK okp(Key key) {
return okp(key, DEFAULT_PUBLIC_KEY_USE);
// not supported if jdk vesion < 17
throw new UnsupportedOperationException("EdDSA algorithms not supported in this JDK version");
}
@Override
public JWK okp(Key key, KeyUse keyUse) {
EdECPublicKey eddsaPublicKey = (EdECPublicKey) key;
OKPPublicJWK k = new OKPPublicJWK();
String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(KeyType.OKP);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(keyUse == null ? DEFAULT_PUBLIC_KEY_USE.getSpecName() : keyUse.getSpecName());
k.setCrv(eddsaPublicKey.getParams().getName());
Optional<String> x = edPublicKeyInJwkRepresentation(eddsaPublicKey);
k.setX(x.orElse(""));
return k;
// not supported if jdk version < 17
throw new UnsupportedOperationException("EdDSA algorithms not supported in this JDK version");
}
private Optional<String> edPublicKeyInJwkRepresentation(EdECPublicKey eddsaPublicKey) {
EdECPoint edEcPoint = eddsaPublicKey.getPoint();
BigInteger yCoordinate = edEcPoint.getY();
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 57;
} else {
return Optional.ofNullable(null);
}
// consider the case where yCoordinate.toByteArray() is less than bytesLength due to relatively small value of y-coordinate.
byte[] yCoordinateLittleEndianBytes = new byte[bytesLength];
// convert big endian representation of BigInteger to little endian representation of JWK representation (RFC 8032,8027)
yCoordinateLittleEndianBytes = Arrays.copyOf(reverseBytes(yCoordinate.toByteArray()), bytesLength);
// set a parity of x-coordinate to the most significant bit of the last octet (RFC 8032, 8037)
if (edEcPoint.isXOdd()) {
yCoordinateLittleEndianBytes[yCoordinateLittleEndianBytes.length - 1] |= -128; // 0b10000000
}
return Optional.ofNullable(Base64Url.encode(yCoordinateLittleEndianBytes));
}
}

View file

@ -17,18 +17,6 @@
package org.keycloak.jose.jwk;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.util.JsonSerialization;
/**
@ -60,68 +48,4 @@ public class JWKParser extends AbstractJWKParser {
}
}
@Override
public PublicKey toPublicKey() {
if (jwk == null) {
throw new IllegalStateException("Not possible to convert to the publicKey. The jwk is not set");
}
String keyType = jwk.getKeyType();
if (KeyType.RSA.equals(keyType)) {
return createRSAPublicKey();
} else if (KeyType.EC.equals(keyType)) {
return createECPublicKey();
} else if (KeyType.OKP.equals(keyType)) {
return createOKPPublicKey();
} else {
throw new RuntimeException("Unsupported keyType " + keyType);
}
}
private PublicKey createOKPPublicKey() {
String x = (String) jwk.getOtherClaims().get(OKPPublicJWK.X);
String crv = (String) jwk.getOtherClaims().get(OKPPublicJWK.CRV);
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(crv)) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(crv)) {
bytesLength = 57;
} else {
throw new RuntimeException("Invalid JWK representation of OKP type algorithm");
}
byte[] decodedX = Base64Url.decode(x);
if (decodedX.length != bytesLength) {
throw new RuntimeException("Invalid JWK representation of OKP type public key");
}
// x-coordinate's parity check shown by MSB(bit) of MSB(byte) of decoded "x": 1 is odd, 0 is even
boolean isOddX = false;
if ((decodedX[decodedX.length - 1] & -128) != 0) { // 0b10000000
isOddX = true;
}
// MSB(bit) of MSB(byte) showing x-coodinate's parity is set to 0
decodedX[decodedX.length - 1] &= 127; // 0b01111111
// both x and y-coordinate in twisted Edwards curve are always 0 or natural number
BigInteger y = new BigInteger(1, JWKBuilder.reverseBytes(decodedX));
NamedParameterSpec spec = new NamedParameterSpec(crv);
EdECPoint ep = new EdECPoint(isOddX, y);
EdECPublicKeySpec keySpec = new EdECPublicKeySpec(spec, ep);
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance(crv).generatePublic(keySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return publicKey;
}
@Override
public boolean isKeyTypeSupported(String keyType) {
return (RSAPublicJWK.RSA.equals(keyType) || ECPublicJWK.EC.equals(keyType) || OKPPublicJWK.OKP.equals(keyType));
}
}

View file

@ -16,7 +16,9 @@
*/
package org.keycloak.sdjwt;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -140,7 +142,7 @@ public class IssuerSignedJWT extends SdJws {
* Returns `cnf` claim (establishing key binding)
*/
public Optional<JsonNode> getCnfClaim() {
var cnf = getPayload().get("cnf");
JsonNode cnf = getPayload().get("cnf");
return Optional.ofNullable(cnf);
}
@ -148,7 +150,7 @@ public class IssuerSignedJWT extends SdJws {
* Returns declared hash algorithm from SD hash claim.
*/
public String getSdHashAlg() {
var hashAlgNode = getPayload().get(CLAIM_NAME_SD_HASH_ALGORITHM);
JsonNode hashAlgNode = getPayload().get(CLAIM_NAME_SD_HASH_ALGORITHM);
return hashAlgNode == null ? "sha-256" : hashAlgNode.asText();
}
@ -159,10 +161,10 @@ public class IssuerSignedJWT extends SdJws {
*/
public void verifySdHashAlgorithm() throws VerificationException {
// Known secure algorithms
final Set<String> secureAlgorithms = Set.of(
final Set<String> secureAlgorithms = new HashSet<>(Arrays.asList(
"sha-256", "sha-384", "sha-512",
"sha3-256", "sha3-384", "sha3-512"
);
));
// Read SD hash claim
String hashAlg = getSdHashAlg();

View file

@ -83,7 +83,7 @@ public class SdJwtUtils {
JsonNode jsonNode;
// Decode Base64URL-encoded disclosure
var decoded = new String(decodeNoPad(disclosure));
String decoded = new String(decodeNoPad(disclosure));
// Parse the disclosure string into a JSON array
try {

View file

@ -32,7 +32,9 @@ import org.keycloak.sdjwt.vp.KeyBindingJwtVerificationOpts;
import org.keycloak.util.JWKSUtils;
import java.time.Instant;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -75,9 +77,9 @@ public class SdJwtVerificationContext {
private Map<String, String> computeDigestDisclosureMap(List<String> disclosureStrings) {
return disclosureStrings.stream()
.map(disclosureString -> {
var digest = SdJwtUtils.hashAndBase64EncodeNoPad(
String digest = SdJwtUtils.hashAndBase64EncodeNoPad(
disclosureString.getBytes(), issuerSignedJwt.getSdHashAlg());
return Map.entry(digest, disclosureString);
return new AbstractMap.SimpleEntry<String,String>(digest, disclosureString);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@ -103,7 +105,7 @@ public class SdJwtVerificationContext {
validateIssuerSignedJwt(issuerSignedJwtVerificationOpts.getVerifier());
// Validate disclosures.
var disclosedPayload = validateDisclosuresDigests();
JsonNode disclosedPayload = validateDisclosuresDigests();
// Validate time claims.
// Issuers will typically include claims controlling the validity of the SD-JWT in plaintext in the
@ -182,13 +184,13 @@ public class SdJwtVerificationContext {
validateKeyBindingJwtTyp();
// Determine the public key for the Holder from the SD-JWT
var cnf = issuerSignedJwt.getCnfClaim().orElseThrow(
JsonNode cnf = issuerSignedJwt.getCnfClaim().orElseThrow(
() -> new VerificationException("No cnf claim in Issuer-signed JWT for key binding")
);
// Ensure that a signing algorithm was used that was deemed secure for the application.
// The none algorithm MUST NOT be accepted.
var holderVerifier = buildHolderVerifier(cnf);
SignatureVerifierContext holderVerifier = buildHolderVerifier(cnf);
// Validate the signature over the Key Binding JWT
try {
@ -219,7 +221,7 @@ public class SdJwtVerificationContext {
* @throws VerificationException if verification failed
*/
private void validateKeyBindingJwtTyp() throws VerificationException {
var typ = keyBindingJwt.getHeader().getType();
String typ = keyBindingJwt.getHeader().getType();
if (!typ.equals(KeyBindingJWT.TYP)) {
throw new VerificationException("Key Binding JWT is not of declared typ " + KeyBindingJWT.TYP);
}
@ -234,7 +236,7 @@ public class SdJwtVerificationContext {
Objects.requireNonNull(cnf);
// Read JWK
var cnfJwk = cnf.get("jwk");
JsonNode cnfJwk = cnf.get("jwk");
if (cnfJwk == null) {
throw new UnsupportedOperationException("Only cnf/jwk claim supported");
}
@ -394,7 +396,7 @@ public class SdJwtVerificationContext {
Set<String> visitedSalts = new HashSet<>();
Set<String> visitedDigests = new HashSet<>();
Set<String> visitedDisclosureStrings = new HashSet<>();
var disclosedPayload = validateViaRecursiveDisclosing(
JsonNode disclosedPayload = validateViaRecursiveDisclosing(
SdJwtUtils.deepClone(issuerSignedJwt.getPayload()),
visitedSalts, visitedDigests, visitedDisclosureStrings);
@ -427,11 +429,11 @@ public class SdJwtVerificationContext {
// Find all objects having an _sd key that refers to an array of strings.
if (currentNode.isObject()) {
var currentObjectNode = ((ObjectNode) currentNode);
ObjectNode currentObjectNode = ((ObjectNode) currentNode);
var sdArray = currentObjectNode.get(IssuerSignedJWT.CLAIM_NAME_SELECTIVE_DISCLOSURE);
JsonNode sdArray = currentObjectNode.get(IssuerSignedJWT.CLAIM_NAME_SELECTIVE_DISCLOSURE);
if (sdArray != null && sdArray.isArray()) {
for (var el : sdArray) {
for (JsonNode el : sdArray) {
if (!el.isTextual()) {
throw new VerificationException(
"Unexpected non-string element inside _sd array: " + el
@ -441,16 +443,16 @@ public class SdJwtVerificationContext {
// Compare the value with the digests calculated previously and find the matching Disclosure.
// If no such Disclosure can be found, the digest MUST be ignored.
var digest = el.asText();
String digest = el.asText();
markDigestAsVisited(digest, visitedDigests);
var disclosure = disclosures.get(digest);
String disclosure = disclosures.get(digest);
if (disclosure != null) {
// Mark disclosure as visited
visitedDisclosureStrings.add(disclosure);
// Validate disclosure format
var decodedDisclosure = validateSdArrayDigestDisclosureFormat(disclosure);
DisclosureFields decodedDisclosure = validateSdArrayDigestDisclosureFormat(disclosure);
// Mark salt as visited
markSaltAsVisited(decodedDisclosure.getSaltValue(), visitedSalts);
@ -475,29 +477,29 @@ public class SdJwtVerificationContext {
// Find all array elements that are objects with one key, that key being ... and referring to a string
if (currentNode.isArray()) {
var currentArrayNode = ((ArrayNode) currentNode);
var indexesToRemove = new ArrayList<Integer>();
ArrayNode currentArrayNode = ((ArrayNode) currentNode);
ArrayList<Integer> indexesToRemove = new ArrayList<>();
for (int i = 0; i < currentArrayNode.size(); ++i) {
var itemNode = currentArrayNode.get(i);
JsonNode itemNode = currentArrayNode.get(i);
if (itemNode.isObject() && itemNode.size() == 1) {
// Check single "..." field
var field = itemNode.fields().next();
Map.Entry<String, JsonNode> field = itemNode.fields().next();
if (field.getKey().equals(UndisclosedArrayElement.SD_CLAIM_NAME)
&& field.getValue().isTextual()) {
// Compare the value with the digests calculated previously and find the matching Disclosure.
// If no such Disclosure can be found, the digest MUST be ignored.
var digest = field.getValue().asText();
String digest = field.getValue().asText();
markDigestAsVisited(digest, visitedDigests);
var disclosure = disclosures.get(digest);
String disclosure = disclosures.get(digest);
if (disclosure != null) {
// Mark disclosure as visited
visitedDisclosureStrings.add(disclosure);
// Validate disclosure format
var decodedDisclosure = validateArrayElementDigestDisclosureFormat(disclosure);
DisclosureFields decodedDisclosure = validateArrayElementDigestDisclosureFormat(disclosure);
// Mark salt as visited
markSaltAsVisited(decodedDisclosure.getSaltValue(), visitedSalts);
@ -584,10 +586,10 @@ public class SdJwtVerificationContext {
// If the claim name is _sd or ..., the SD-JWT MUST be rejected.
var denylist = List.of(
List<String> denylist = Arrays.asList(new String[]{
IssuerSignedJWT.CLAIM_NAME_SELECTIVE_DISCLOSURE,
UndisclosedArrayElement.SD_CLAIM_NAME
);
});
String claimName = arrayNode.get(1).asText();
if (denylist.contains(claimName)) {

View file

@ -0,0 +1,108 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.jose.jwk;
import java.math.BigInteger;
import java.security.Key;
import java.security.interfaces.EdECPublicKey;
import java.security.spec.EdECPoint;
import java.util.Arrays;
import java.util.Optional;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKBuilder extends AbstractJWKBuilder {
private JWKBuilder() {
}
public static JWKBuilder create() {
return new JWKBuilder();
}
public JWKBuilder kid(String kid) {
this.kid = kid;
return this;
}
public JWKBuilder algorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
@Override
public JWK okp(Key key) {
return okp(key, DEFAULT_PUBLIC_KEY_USE);
}
@Override
public JWK okp(Key key, KeyUse keyUse) {
EdECPublicKey eddsaPublicKey = (EdECPublicKey) key;
OKPPublicJWK k = new OKPPublicJWK();
String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(KeyType.OKP);
k.setAlgorithm(algorithm);
k.setPublicKeyUse(keyUse == null ? DEFAULT_PUBLIC_KEY_USE.getSpecName() : keyUse.getSpecName());
k.setCrv(eddsaPublicKey.getParams().getName());
Optional<String> x = edPublicKeyInJwkRepresentation(eddsaPublicKey);
k.setX(x.orElse(""));
return k;
}
private Optional<String> edPublicKeyInJwkRepresentation(EdECPublicKey eddsaPublicKey) {
EdECPoint edEcPoint = eddsaPublicKey.getPoint();
BigInteger yCoordinate = edEcPoint.getY();
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(eddsaPublicKey.getParams().getName())) {
bytesLength = 57;
} else {
return Optional.ofNullable(null);
}
// consider the case where yCoordinate.toByteArray() is less than bytesLength due to relatively small value of y-coordinate.
byte[] yCoordinateLittleEndianBytes = new byte[bytesLength];
// convert big endian representation of BigInteger to little endian representation of JWK representation (RFC 8032,8027)
yCoordinateLittleEndianBytes = Arrays.copyOf(reverseBytes(yCoordinate.toByteArray()), bytesLength);
// set a parity of x-coordinate to the most significant bit of the last octet (RFC 8032, 8037)
if (edEcPoint.isXOdd()) {
yCoordinateLittleEndianBytes[yCoordinateLittleEndianBytes.length - 1] |= -128; // 0b10000000
}
return Optional.ofNullable(Base64Url.encode(yCoordinateLittleEndianBytes));
}
}

View file

@ -0,0 +1,127 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.jose.jwk;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKParser extends AbstractJWKParser {
private JWKParser() {
}
public static JWKParser create() {
return new JWKParser();
}
public JWKParser(JWK jwk) {
this.jwk = jwk;
}
public static JWKParser create(JWK jwk) {
return new JWKParser(jwk);
}
public JWKParser parse(String jwk) {
try {
this.jwk = JsonSerialization.mapper.readValue(jwk, JWK.class);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public PublicKey toPublicKey() {
if (jwk == null) {
throw new IllegalStateException("Not possible to convert to the publicKey. The jwk is not set");
}
String keyType = jwk.getKeyType();
if (KeyType.RSA.equals(keyType)) {
return createRSAPublicKey();
} else if (KeyType.EC.equals(keyType)) {
return createECPublicKey();
} else if (KeyType.OKP.equals(keyType)) {
return createOKPPublicKey();
} else {
throw new RuntimeException("Unsupported keyType " + keyType);
}
}
private PublicKey createOKPPublicKey() {
String x = (String) jwk.getOtherClaims().get(OKPPublicJWK.X);
String crv = (String) jwk.getOtherClaims().get(OKPPublicJWK.CRV);
// JWK representation "x" of a public key
int bytesLength = 0;
if (Algorithm.Ed25519.equals(crv)) {
bytesLength = 32;
} else if (Algorithm.Ed448.equals(crv)) {
bytesLength = 57;
} else {
throw new RuntimeException("Invalid JWK representation of OKP type algorithm");
}
byte[] decodedX = Base64Url.decode(x);
if (decodedX.length != bytesLength) {
throw new RuntimeException("Invalid JWK representation of OKP type public key");
}
// x-coordinate's parity check shown by MSB(bit) of MSB(byte) of decoded "x": 1 is odd, 0 is even
boolean isOddX = false;
if ((decodedX[decodedX.length - 1] & -128) != 0) { // 0b10000000
isOddX = true;
}
// MSB(bit) of MSB(byte) showing x-coodinate's parity is set to 0
decodedX[decodedX.length - 1] &= 127; // 0b01111111
// both x and y-coordinate in twisted Edwards curve are always 0 or natural number
BigInteger y = new BigInteger(1, JWKBuilder.reverseBytes(decodedX));
NamedParameterSpec spec = new NamedParameterSpec(crv);
EdECPoint ep = new EdECPoint(isOddX, y);
EdECPublicKeySpec keySpec = new EdECPublicKeySpec(spec, ep);
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance(crv).generatePublic(keySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return publicKey;
}
@Override
public boolean isKeyTypeSupported(String keyType) {
return (RSAPublicJWK.RSA.equals(keyType) || ECPublicJWK.EC.equals(keyType) || OKPPublicJWK.OKP.equals(keyType));
}
}

View file

@ -38,6 +38,7 @@ import org.keycloak.util.TokenUtil;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ -96,7 +97,7 @@ public abstract class RSAVerifierTest {
String encoded = new JWSBuilder()
.jwk(jwk)
.x5c(List.of(idpCertificate, caCertificate))
.x5c(Arrays.asList(new X509Certificate[]{idpCertificate, caCertificate}))
.jsonContent(token)
.rsa256(idpPair.getPrivate());
TokenVerifier tokenVerifier = TokenVerifier.create(encoded, JsonWebToken.class);

View file

@ -26,6 +26,9 @@ import org.keycloak.common.VerificationException;
import org.keycloak.rule.CryptoInitRule;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -44,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, TimeUnit.HOURS.toChronoUnit()).getEpochSecond());
node.put("exp", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
node.put("name", "Test User");
return node;
}
@ -66,7 +69,7 @@ public abstract class SdJwsTest {
@Test
public void testVerifyExpClaim_ExpiredJWT() {
JsonNode payload = createPayload();
((ObjectNode) payload).put("exp", Instant.now().minus(1, TimeUnit.HOURS.toChronoUnit()).getEpochSecond());
((ObjectNode) payload).put("exp", Instant.now().minus(1, ChronoUnit.HOURS).getEpochSecond());
SdJws sdJws = new SdJws(payload) {
};
assertThrows(VerificationException.class, sdJws::verifyExpClaim);
@ -75,7 +78,7 @@ public abstract class SdJwsTest {
@Test
public void testVerifyExpClaim_Positive() throws Exception {
JsonNode payload = createPayload();
((ObjectNode) payload).put("exp", Instant.now().plus(1, TimeUnit.HOURS.toChronoUnit()).getEpochSecond());
((ObjectNode) payload).put("exp", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
SdJws sdJws = new SdJws(payload) {
};
sdJws.verifyExpClaim();
@ -84,7 +87,7 @@ public abstract class SdJwsTest {
@Test
public void testVerifyNotBeforeClaim_Negative() {
JsonNode payload = createPayload();
((ObjectNode) payload).put("nbf", Instant.now().plus(1, TimeUnit.HOURS.toChronoUnit()).getEpochSecond());
((ObjectNode) payload).put("nbf", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
SdJws sdJws = new SdJws(payload) {
};
assertThrows(VerificationException.class, sdJws::verifyNotBeforeClaim);
@ -93,7 +96,7 @@ public abstract class SdJwsTest {
@Test
public void testVerifyNotBeforeClaim_Positive() throws Exception {
JsonNode payload = createPayload();
((ObjectNode) payload).put("nbf", Instant.now().minus(1, TimeUnit.HOURS.toChronoUnit()).getEpochSecond());
((ObjectNode) payload).put("nbf", Instant.now().minus(1, ChronoUnit.HOURS).getEpochSecond());
SdJws sdJws = new SdJws(payload) {
};
sdJws.verifyNotBeforeClaim();
@ -124,17 +127,17 @@ public abstract class SdJwsTest {
@Test
public void testVerifyIssClaim_Negative() {
List<String> allowedIssuers = List.of("issuer1@sdjwt.com", "issuer2@sdjwt.com");
List<String> allowedIssuers = Arrays.asList(new String[]{"issuer1@sdjwt.com", "issuer2@sdjwt.com"});
JsonNode payload = createPayload();
((ObjectNode) payload).put("iss", "unknown-issuer@sdjwt.com");
SdJws sdJws = new SdJws(payload) {};
var exception = assertThrows(VerificationException.class, () -> sdJws.verifyIssClaim(allowedIssuers));
VerificationException exception = assertThrows(VerificationException.class, () -> sdJws.verifyIssClaim(allowedIssuers));
assertEquals("Unknown 'iss' claim value: unknown-issuer@sdjwt.com", exception.getMessage());
}
@Test
public void testVerifyIssClaim_Positive() throws VerificationException {
List<String> allowedIssuers = List.of("issuer1@sdjwt.com", "issuer2@sdjwt.com");
List<String> allowedIssuers = Arrays.asList(new String[]{"issuer1@sdjwt.com", "issuer2@sdjwt.com"});
JsonNode payload = createPayload();
((ObjectNode) payload).put("iss", "issuer1@sdjwt.com");
SdJws sdJws = new SdJws(payload) {};
@ -146,7 +149,7 @@ public abstract class SdJwsTest {
JsonNode payload = createPayload();
((ObjectNode) payload).put("vct", "IdentityCredential");
SdJws sdJws = new SdJws(payload) {};
var exception = assertThrows(VerificationException.class, () -> sdJws.verifyVctClaim(List.of("PassportCredential")));
VerificationException exception = assertThrows(VerificationException.class, () -> sdJws.verifyVctClaim(Collections.singletonList("PassportCredential")));
assertEquals("Unknown 'vct' claim value: IdentityCredential", exception.getMessage());
}
@ -155,26 +158,26 @@ public abstract class SdJwsTest {
JsonNode payload = createPayload();
((ObjectNode) payload).put("vct", "IdentityCredential");
SdJws sdJws = new SdJws(payload) {};
sdJws.verifyVctClaim(List.of("IdentityCredential"));
sdJws.verifyVctClaim(Collections.singletonList("IdentityCredential"));
}
@Test
public void shouldValidateAgeSinceIssued() throws VerificationException {
long now = Instant.now().getEpochSecond();
var sdJws = exampleSdJws(now);
SdJws sdJws = exampleSdJws(now);
sdJws.verifyAge(180);
}
@Test
public void shouldValidateAgeSinceIssued_IfJwtIsTooOld() {
long now = Instant.now().getEpochSecond();
var sdJws = exampleSdJws(now - 1000); // that will be too old
var exception = assertThrows(VerificationException.class, () -> sdJws.verifyAge(180));
SdJws sdJws = exampleSdJws(now - 1000); // that will be too old
VerificationException exception = assertThrows(VerificationException.class, () -> sdJws.verifyAge(180));
assertEquals("jwt is too old", exception.getMessage());
}
private SdJws exampleSdJws(long iat) {
var payload = SdJwtUtils.mapper.createObjectNode();
ObjectNode payload = SdJwtUtils.mapper.createObjectNode();
payload.set("iat", SdJwtUtils.mapper.valueToTree(iat));
return new SdJws(payload) {

View file

@ -26,8 +26,9 @@ import org.keycloak.common.VerificationException;
import org.keycloak.rule.CryptoInitRule;
import java.time.Instant;
import java.util.List;
import java.util.Set;
import java.util.Arrays;
import java.util.Collections;
import org.keycloak.crypto.SignatureSignerContext;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.is;
@ -50,14 +51,14 @@ public abstract class SdJwtVerificationTest {
@Test
public void settingsTest() {
var issuerSignerContext = testSettings.issuerSigContext;
SignatureSignerContext issuerSignerContext = testSettings.issuerSigContext;
assertNotNull(issuerSignerContext);
}
@Test
public void testSdJwtVerification_FlatSdJwt() throws VerificationException {
for (String hashAlg : List.of("sha-256", "sha-384", "sha-512")) {
var sdJwt = exampleFlatSdJwtV1()
for (String hashAlg : Arrays.asList(new String[]{"sha-256", "sha-384", "sha-512"})) {
SdJwt sdJwt = exampleFlatSdJwtV1()
.withHashAlgorithm(hashAlg)
.build();
@ -67,36 +68,36 @@ public abstract class SdJwtVerificationTest {
@Test
public void testSdJwtVerification_EnforceIdempotence() throws VerificationException {
var sdJwt = exampleFlatSdJwtV1().build();
SdJwt sdJwt = exampleFlatSdJwtV1().build();
sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build());
sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build());
}
@Test
public void testSdJwtVerification_SdJwtWithUndisclosedNestedFields() throws VerificationException {
var sdJwt = exampleSdJwtWithUndisclosedNestedFieldsV1().build();
SdJwt sdJwt = exampleSdJwtWithUndisclosedNestedFieldsV1().build();
sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build());
}
@Test
public void testSdJwtVerification_SdJwtWithUndisclosedArrayElements() throws Exception {
var sdJwt = exampleSdJwtWithUndisclosedArrayElementsV1().build();
SdJwt sdJwt = exampleSdJwtWithUndisclosedArrayElementsV1().build();
sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build());
}
@Test
public void testSdJwtVerification_RecursiveSdJwt() throws Exception {
var sdJwt = exampleRecursiveSdJwtV1().build();
SdJwt sdJwt = exampleRecursiveSdJwtV1().build();
sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build());
}
@Test
public void sdJwtVerificationShouldFail_OnInsecureHashAlg() {
var sdJwt = exampleFlatSdJwtV1()
SdJwt sdJwt = exampleFlatSdJwtV1()
.withHashAlgorithm("sha-224") // not deemed secure
.build();
var exception = assertThrows(
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build())
);
@ -106,8 +107,8 @@ public abstract class SdJwtVerificationTest {
@Test
public void sdJwtVerificationShouldFail_WithWrongVerifier() {
var sdJwt = exampleFlatSdJwtV1().build();
var exception = assertThrows(
SdJwt sdJwt = exampleFlatSdJwtV1().build();
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts()
.withVerifier(testSettings.holderVerifierContext) // wrong verifier
@ -127,15 +128,15 @@ public abstract class SdJwtVerificationTest {
claimSet.put("exp", now - 1000); // expired 1000 seconds ago
// Exp claim is plain
var sdJwtV1 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
SdJwt sdJwtV1 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
// Exp claim is undisclosed
var sdJwtV2 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withRedListedClaimNames(DisclosureRedList.of(Set.of()))
SdJwt sdJwtV2 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withRedListedClaimNames(DisclosureRedList.of(Collections.emptySet()))
.withUndisclosedClaim("exp", "eluV5Og3gSNII8EYnsxA_A")
.build()).build();
for (SdJwt sdJwt : List.of(sdJwtV1, sdJwtV2)) {
var exception = assertThrows(
for (SdJwt sdJwt : Arrays.asList(new SdJwt[]{sdJwtV1, sdJwtV2})) {
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts()
.withValidateExpirationClaim(true)
@ -162,11 +163,11 @@ public abstract class SdJwtVerificationTest {
.withUndisclosedClaim("given_name", "eluV5Og3gSNII8EYnsxA_A")
.build();
var sdJwtV1 = exampleFlatSdJwtV2(claimSet1, disclosureSpec).build();
var sdJwtV2 = exampleFlatSdJwtV2(claimSet2, disclosureSpec).build();
SdJwt sdJwtV1 = exampleFlatSdJwtV2(claimSet1, disclosureSpec).build();
SdJwt sdJwtV2 = exampleFlatSdJwtV2(claimSet2, disclosureSpec).build();
for (SdJwt sdJwt : List.of(sdJwtV1, sdJwtV2)) {
var exception = assertThrows(
for (SdJwt sdJwt : Arrays.asList(new SdJwt[]{sdJwtV1, sdJwtV2})) {
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts()
.withValidateExpirationClaim(true)
@ -187,15 +188,15 @@ public abstract class SdJwtVerificationTest {
claimSet.put("iat", now + 1000); // issued in the future
// Exp claim is plain
var sdJwtV1 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
SdJwt sdJwtV1 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
// Exp claim is undisclosed
var sdJwtV2 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withRedListedClaimNames(DisclosureRedList.of(Set.of()))
SdJwt sdJwtV2 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withRedListedClaimNames(DisclosureRedList.of(Collections.emptySet()))
.withUndisclosedClaim("iat", "eluV5Og3gSNII8EYnsxA_A")
.build()).build();
for (SdJwt sdJwt : List.of(sdJwtV1, sdJwtV2)) {
var exception = assertThrows(
for (SdJwt sdJwt : Arrays.asList(new SdJwt[]{sdJwtV1, sdJwtV2})) {
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts()
.withValidateIssuedAtClaim(true)
@ -216,15 +217,15 @@ public abstract class SdJwtVerificationTest {
claimSet.put("nbf", now + 1000); // now will be too soon to accept the jwt
// Exp claim is plain
var sdJwtV1 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
SdJwt sdJwtV1 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
// Exp claim is undisclosed
var sdJwtV2 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withRedListedClaimNames(DisclosureRedList.of(Set.of()))
SdJwt sdJwtV2 = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withRedListedClaimNames(DisclosureRedList.of(Collections.emptySet()))
.withUndisclosedClaim("iat", "eluV5Og3gSNII8EYnsxA_A")
.build()).build();
for (SdJwt sdJwt : List.of(sdJwtV1, sdJwtV2)) {
var exception = assertThrows(
for (SdJwt sdJwt : Arrays.asList(new SdJwt[]{sdJwtV1, sdJwtV2})) {
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts()
.withValidateNotBeforeClaim(true)
@ -242,9 +243,9 @@ public abstract class SdJwtVerificationTest {
claimSet.put("given_name", "John");
claimSet.set("_sd", mapper.readTree("[123]"));
var sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
SdJwt sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder().build()).build();
var exception = assertThrows(
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts()
.build())
@ -255,15 +256,15 @@ public abstract class SdJwtVerificationTest {
@Test
public void sdJwtVerificationShouldFail_IfForbiddenClaimNames() {
for (String forbiddenClaimName : List.of("_sd", "...")) {
for (String forbiddenClaimName : Arrays.asList(new String[]{"_sd", "..."})) {
ObjectNode claimSet = mapper.createObjectNode();
claimSet.put(forbiddenClaimName, "Value");
var sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
SdJwt sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withUndisclosedClaim(forbiddenClaimName, "eluV5Og3gSNII8EYnsxA_A")
.build()).build();
var exception = assertThrows(
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build())
);
@ -277,13 +278,13 @@ public abstract class SdJwtVerificationTest {
ObjectNode claimSet = mapper.createObjectNode();
claimSet.put("given_name", "John"); // this same field will also be nested
var sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
SdJwt sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withUndisclosedClaim("given_name", "eluV5Og3gSNII8EYnsxA_A")
.withDecoyClaim("G02NSrQfjFXQ7Io09syajA")
.withDecoyClaim("G02NSrQfjFXQ7Io09syajA")
.build()).build();
var exception = assertThrows(
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build())
);
@ -297,14 +298,14 @@ public abstract class SdJwtVerificationTest {
claimSet.put("given_name", "John");
claimSet.put("family_name", "Doe");
var salt = "eluV5Og3gSNII8EYnsxA_A";
var sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
String salt = "eluV5Og3gSNII8EYnsxA_A";
SdJwt sdJwt = exampleFlatSdJwtV2(claimSet, DisclosureSpec.builder()
.withUndisclosedClaim("given_name", salt)
// We are reusing the same salt value, and that is the problem
.withUndisclosedClaim("family_name", salt)
.build()).build();
var exception = assertThrows(
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwt.verify(defaultIssuerSignedJwtVerificationOpts().build())
);

View file

@ -33,6 +33,7 @@ import org.keycloak.sdjwt.vp.KeyBindingJwtVerificationOpts;
import org.keycloak.sdjwt.vp.SdJwtVP;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.CoreMatchers.containsString;
@ -69,9 +70,11 @@ public abstract class SdJwtVPVerificationTest {
@Test
public void testVerif_s20_8_sdjwt_with_kb__AltCnfCurves() throws VerificationException {
var entries = List.of("sdjwt/s20.8-sdjwt+kb--es384.txt", "sdjwt/s20.8-sdjwt+kb--es512.txt");
List<String> entries = Arrays.asList(new String[]{
"sdjwt/s20.8-sdjwt+kb--es384.txt", "sdjwt/s20.8-sdjwt+kb--es512.txt"
});
for (var entry : entries) {
for (String entry : entries) {
String sdJwtVPString = TestUtils.readFileAsString(getClass(), entry);
SdJwtVP sdJwtVP = SdJwtVP.of(sdJwtVPString);
@ -84,14 +87,14 @@ public abstract class SdJwtVPVerificationTest {
@Test
public void testVerif_s20_8_sdjwt_with_kb__CnfRSA() throws VerificationException {
var entries = List.of(
List<String> entries = Arrays.asList(new String[]{
"sdjwt/s20.8-sdjwt+kb--cnf-rsa-rs256.txt",
"sdjwt/s20.8-sdjwt+kb--cnf-rsa-ps256.txt",
"sdjwt/s20.8-sdjwt+kb--cnf-rsa-ps384.txt",
"sdjwt/s20.8-sdjwt+kb--cnf-rsa-ps512.txt"
);
});
for (var entry : entries) {
for (String entry : entries) {
String sdJwtVPString = TestUtils.readFileAsString(getClass(), entry);
SdJwtVP sdJwtVP = SdJwtVP.of(sdJwtVPString);
@ -220,7 +223,7 @@ public abstract class SdJwtVPVerificationTest {
@Test
public void testShouldFail_IfKbSdHashWrongFormat() {
var kbPayload = exampleKbPayload();
ObjectNode kbPayload = exampleKbPayload();
// This hash is not a string
kbPayload.set("sd_hash", mapper.valueToTree(1234));
@ -235,7 +238,7 @@ public abstract class SdJwtVPVerificationTest {
@Test
public void testShouldFail_IfKbSdHashInvalid() {
var kbPayload = exampleKbPayload();
ObjectNode kbPayload = exampleKbPayload();
// This hash makes no sense
kbPayload.put("sd_hash", "c3FmZHFmZGZlZXNkZmZi");
@ -252,7 +255,7 @@ public abstract class SdJwtVPVerificationTest {
public void testShouldFail_IfKbIssuedInFuture() {
long now = Instant.now().getEpochSecond();
var kbPayload = exampleKbPayload();
ObjectNode kbPayload = exampleKbPayload();
kbPayload.set("iat", mapper.valueToTree(now + 1000));
testShouldFailGeneric2(
@ -267,7 +270,7 @@ public abstract class SdJwtVPVerificationTest {
public void testShouldFail_IfKbTooOld() {
long issuerSignedJwtIat = 1683000000; // same value in test vector
var kbPayload = exampleKbPayload();
ObjectNode kbPayload = exampleKbPayload();
// This KB-JWT is then issued more than 60s ago
kbPayload.set("iat", mapper.valueToTree(issuerSignedJwtIat - 120));
@ -285,7 +288,7 @@ public abstract class SdJwtVPVerificationTest {
public void testShouldFail_IfKbExpired() {
long now = Instant.now().getEpochSecond();
var kbPayload = exampleKbPayload();
ObjectNode kbPayload = exampleKbPayload();
kbPayload.set("exp", mapper.valueToTree(now - 1000));
testShouldFailGeneric2(
@ -302,7 +305,7 @@ public abstract class SdJwtVPVerificationTest {
public void testShouldFail_IfKbNotBeforeTimeYet() {
long now = Instant.now().getEpochSecond();
var kbPayload = exampleKbPayload();
ObjectNode kbPayload = exampleKbPayload();
kbPayload.set("nbf", mapper.valueToTree(now + 1000));
testShouldFailGeneric2(
@ -321,7 +324,7 @@ public abstract class SdJwtVPVerificationTest {
String sdJwtVPString = TestUtils.readFileAsString(getClass(), "sdjwt/s20.8-sdjwt+kb--cnf-is-not-jwk.txt");
SdJwtVP sdJwtVP = SdJwtVP.of(sdJwtVPString);
var exception = assertThrows(
UnsupportedOperationException exception = assertThrows(
UnsupportedOperationException.class,
() -> sdJwtVP.verify(
defaultIssuerSignedJwtVerificationOpts().build(),
@ -363,7 +366,7 @@ public abstract class SdJwtVPVerificationTest {
String sdJwtVPString = TestUtils.readFileAsString(getClass(), testFilePath);
SdJwtVP sdJwtVP = SdJwtVP.of(sdJwtVPString);
var exception = assertThrows(
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwtVP.verify(
defaultIssuerSignedJwtVerificationOpts().build(),
@ -399,7 +402,7 @@ public abstract class SdJwtVPVerificationTest {
+ keyBindingJWT.toJws()
);
var exception = assertThrows(
VerificationException exception = assertThrows(
VerificationException.class,
() -> sdJwtVP.verify(
defaultIssuerSignedJwtVerificationOpts().build(),
@ -431,7 +434,7 @@ public abstract class SdJwtVPVerificationTest {
}
private ObjectNode exampleKbPayload() {
var payload = mapper.createObjectNode();
ObjectNode payload = mapper.createObjectNode();
payload.put("nonce", "1234567890");
payload.put("aud", "https://verifier.example.org");
payload.put("sd_hash", "X9RrrfWt_70gHzOcovGSIt4Fms9Tf2g2hjlWVI_cxZg");

View file

@ -32,6 +32,12 @@
For the use by 3rd party applications, please use `org.keycloak:keycloak-admin-client` module.
</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.release>8</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>