diff --git a/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java b/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java
index 820d39c..9c5c92d 100644
--- a/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java
+++ b/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java
@@ -1,6 +1,7 @@
package org.gcube.common.keycloak.model;
import java.security.KeyFactory;
+import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
@@ -16,7 +17,7 @@ import org.slf4j.LoggerFactory;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
-import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.JWTVerifier;
/**
@@ -49,13 +50,25 @@ public class ModelUtils {
}
/**
- * Creates and {@link RSAPublicKey} instance from its string PEM representation
+ * Creates a {@link RSAPublicKey} instance from its string PEM representation
*
* @param publicKeyPem the public key PEM string
* @return the RSA public key
- * @throws Exception if it's not possbile to create the RSA public key from the PEM string
+ * @throws Exception if it's not possible to create the RSA public key from the PEM string
*/
public static RSAPublicKey createRSAPublicKey(String publicKeyPem) throws Exception {
+ return (RSAPublicKey) createPublicKey(publicKeyPem, "RSA");
+ }
+
+ /**
+ * Creates a {@link PublicKey} instance from its string PEM representation
+ *
+ * @param publicKeyPem the public key PEM string
+ * @param algorithm the key type (e.g. RSA)
+ * @return the public key
+ * @throws Exception if it's not possible to create the public key from the PEM string
+ */
+ public static PublicKey createPublicKey(String publicKeyPem, String algorithm) throws Exception {
try {
String publicKey = publicKeyPem.replaceFirst("-----BEGIN (.*)-----\n", "");
publicKey = publicKey.replaceFirst("-----END (.*)-----", "");
@@ -63,10 +76,39 @@ public class ModelUtils {
publicKey = publicKey.replaceAll("\n", "");
byte[] encoded = Base64.getDecoder().decode(publicKey);
- KeyFactory kf = KeyFactory.getInstance("RSA");
- return (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
+ KeyFactory kf = KeyFactory.getInstance(algorithm);
+ return kf.generatePublic(new X509EncodedKeySpec(encoded));
} catch (Exception e) {
- throw new RuntimeException("Cant' create RSA public key from PEM string", e);
+ throw new RuntimeException("Cannot create public key from PEM string", e);
+ }
+ }
+
+ /**
+ * Verifies the token validity
+ *
+ * @param token the base64 JWT token string
+ * @param rsaPublicKey the realm's RSA public key on server
+ * @return true
if the token is valid, false
otherwise
+ * @throws RuntimeException if an error occurs constructing the verifier
+ */
+ public static boolean isValid(String token, RSAPublicKey rsaPublicKey) throws Exception {
+ return isValid(token, rsaPublicKey, true);
+ }
+
+ /**
+ * Verifies the token validity
+ *
+ * @param token the base64 JWT token string
+ * @param rsaPublicKey the realm's RSA public key on server
+ * @param checkExpiration if false
token expiration check is disabled
+ * @return true
if the token is valid, false
otherwise
+ * @throws RuntimeException if an error occurs constructing the verifier
+ */
+ public static boolean isValid(String token, RSAPublicKey rsaPublicKey, boolean checkExpiration) throws Exception {
+ try {
+ return isValid(token, Algorithm.RSA256(rsaPublicKey, null), checkExpiration);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot construct the JWT verifier", e);
}
}
@@ -75,23 +117,69 @@ public class ModelUtils {
*
* @param token the base64 JWT token string
* @param publicKey the realm's public key on server
+ * @param keyAlgorithm the public key algorithm
* @return true
if the token is valid, false
otherwise
- * @throws RuntimeException if an error occurs constructing the digital signature verifier
+ * @throws RuntimeException if an error occurs constructing the verifier
*/
- public static boolean isValid(String token, RSAPublicKey publicKey) throws RuntimeException {
- JWTVerifier verifier = null;
+ public static boolean isValid(String token, PublicKey publicKey, String keyAlgorithm) throws Exception {
+ return isValid(token, publicKey, keyAlgorithm, true);
+ }
+
+ /**
+ * Verifies the token validity
+ *
+ * @param token the base64 JWT token string
+ * @param publicKey the realm's public key on server
+ * @param keyAlgorithm the public key algorithm
+ * @param checkExpiration if false
token expiration check is disabled
+ * @return true
if the token is valid, false
otherwise
+ * @throws RuntimeException if an error occurs constructing the verifier
+ */
+ public static boolean isValid(String token, PublicKey publicKey, String keyAlgorithm, boolean checkExpiration) throws Exception {
try {
- Algorithm algorithm = Algorithm.RSA256(publicKey, null);
- verifier = JWT.require(algorithm).build();
+ Algorithm algorithm = null;
+ switch (keyAlgorithm) {
+ case "RS256":
+ algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
+ break;
+ case "RS384":
+ algorithm = Algorithm.RSA384((RSAPublicKey) publicKey, null);
+ break;
+ case "RS512":
+ algorithm = Algorithm.RSA512((RSAPublicKey) publicKey, null);
+ break;
+ default:
+ throw new RuntimeException("Unsupported key algorithm: " + algorithm);
+ }
+
+ return isValid(token, algorithm, checkExpiration);
} catch (Exception e) {
- throw new RuntimeException("Cannot construct the JWT digital signature verifier", e);
+ throw new RuntimeException("Cannot construct the JWT verifier", e);
}
+ }
+
+ /**
+ * Verifies the token validity
+ *
+ * @param token the base64 JWT token string
+ * @param algorithm the algorithm to use for verification
+ * @param checkExpiration if false
token expiration check is disabled
+ * @return true
if the token is valid, false
otherwise
+ */
+ public static boolean isValid(String token, Algorithm algorithm, boolean checkExpiration) throws Exception {
+ JWTVerifier verifier = JWT.require(algorithm).build();;
try {
verifier.verify(token);
return true;
- } catch (JWTVerificationException e) {
+ } catch (TokenExpiredException e) {
+ // This is OK because expiration check is after the signature validation in the implementation
if (logger.isDebugEnabled()) {
- logger.debug("JWT digital signature is not verified", e);
+ logger.debug("JWT is expired: {}", e.getMessage());
+ }
+ return !checkExpiration;
+ } catch (Exception e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("JWT is not verified: {}", e.getMessage());
}
return false;
}
diff --git a/src/test/java/org/gcube/common/keycloak/TestModelUtils.java b/src/test/java/org/gcube/common/keycloak/TestModelUtils.java
index 37f5bf0..f0398f5 100644
--- a/src/test/java/org/gcube/common/keycloak/TestModelUtils.java
+++ b/src/test/java/org/gcube/common/keycloak/TestModelUtils.java
@@ -3,6 +3,7 @@ package org.gcube.common.keycloak;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.security.interfaces.RSAPublicKey;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.common.keycloak.model.AccessToken;
@@ -33,16 +34,19 @@ public class TestModelUtils {
}
@Test
- public void testTokenDigitalSignature() throws Exception {
- logger.info("Start testing OIDC token response object binding...");
+ public void testTokenValidity() throws Exception {
+ logger.info("Start testing access token valdity...");
TokenResponse tr = new ObjectMapper().readValue(new File("src/test/resources/oidc-token-response.json"),
TokenResponse.class);
- // Valid signature
- Assert.assertFalse("Token signature is valid",
- ModelUtils.isValid(tr.getAccessToken(), ModelUtils.createRSAPublicKey(
- new String(Files.readAllBytes(Paths.get("src/test/resources/rsa-public-key.pem"))))));
+ RSAPublicKey publicKey = ModelUtils.createRSAPublicKey(
+ new String(Files.readAllBytes(Paths.get("src/test/resources/rsa-public-key.pem"))));
+ // Valid signature
+ Assert.assertFalse("Token is valid", ModelUtils.isValid(tr.getAccessToken(), publicKey));
+ Assert.assertTrue("Token is valid", ModelUtils.isValid(tr.getAccessToken(), publicKey, false));
+ Assert.assertFalse("Token signature is valid", ModelUtils.isValid(tr.getAccessToken().replace("ZV9hY2Nlc3", "ZV9hY2Nlcc"), publicKey));
+ Assert.assertFalse("Token is not expired", ModelUtils.isValid(tr.getAccessToken(), publicKey, true));
}
@Test