diff --git a/README.md b/README.md
index 42ff396be..f2e9cfe60 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ These are the source of the GeneXus Standard Classes for Java, valid since GeneX
| gxexternalproviders | Implements service provider for IBM Cloud, Google, Azure, Amazon
| gxgeospatial | Geography data type implementation
| gxodata | OData access
+| gamutils | GAM external object with utilities
The dependencies between the projects are specified in each pom.xml within their directory.
diff --git a/gamutils/pom.xml b/gamutils/pom.xml
new file mode 100644
index 000000000..90e75af79
--- /dev/null
+++ b/gamutils/pom.xml
@@ -0,0 +1,72 @@
+
+
+ 4.0.0
+
+
+ com.genexus
+ parent
+ ${revision}${changelist}
+
+
+ gamutils
+ GAM Utils EO
+
+
+ UTF-8
+
+
+
+ com.nimbusds
+ nimbus-jose-jwt
+ 9.37.3
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+ 1.78.1
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+ 1.78.1
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ commons-io
+ commons-io
+ 2.11.0
+
+
+ ${project.groupId}
+ gxcommon
+ ${project.version}
+
+
+ commons-codec
+ commons-codec
+ 1.15
+ test
+
+
+
+
+ gamutils
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+ 1.8
+ 1.8
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gamutils/src/main/java/com/genexus/gam/GamUtilsEO.java b/gamutils/src/main/java/com/genexus/gam/GamUtilsEO.java
new file mode 100644
index 000000000..bbe199f09
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/GamUtilsEO.java
@@ -0,0 +1,106 @@
+package com.genexus.gam;
+
+import com.genexus.gam.utils.Encoding;
+import com.genexus.gam.utils.Pkce;
+import com.genexus.gam.utils.Random;
+import com.genexus.gam.utils.cryptography.Encryption;
+import com.genexus.gam.utils.cryptography.Hash;
+import com.genexus.gam.utils.json.Jwk;
+import com.genexus.gam.utils.json.Jwt;
+import com.genexus.gam.utils.json.UnixTimestamp;
+
+import java.util.Date;
+
+public class GamUtilsEO {
+
+ /********EXTERNAL OBJECT PUBLIC METHODS - BEGIN ********/
+
+ //**HASH**//
+ public static String sha512(String plainText) {
+ return Hash.hash(plainText, Hash.SHA512);
+ }
+
+ public static String sha256(String plainText) {
+ return Hash.hash(plainText, Hash.SHA256);
+ }
+
+ //**ENCRYPTION**//
+
+ public static String AesGcm(String input, String key, String nonce, int macSize, boolean toEncrypt) {
+ return Encryption.AesGcm(input, key, nonce, macSize, toEncrypt);
+ }
+
+ //**RANDOM**//
+ public static String randomAlphanumeric(int length) {
+ return Random.alphanumeric(length);
+ }
+
+ public static String randomNumeric(int length) {
+ return Random.numeric(length);
+ }
+
+ public static String randomHexaBits(int bits) {
+ return Random.hexaBits(bits);
+ }
+
+ //**JWK**//
+
+ public static String generateKeyPair() {
+ return Jwk.generateKeyPair();
+ }
+
+ public static String getPublicJwk(String jwkString) {
+ return Jwk.getPublic(jwkString);
+ }
+
+ public static String getJwkAlgorithm(String jwkString) {
+ return Jwk.getAlgorithm(jwkString);
+ }
+
+ //**JWT**//
+ public static boolean verifyJwt(String path, String alias, String password, String token) {
+ return Jwt.verify(path, alias, password, token);
+ }
+
+ public static String createJwt(String path, String alias, String password, String payload, String header) {
+ return Jwt.create(path, alias, password, payload, header);
+ }
+
+ public static boolean verifyAlgorithm(String expectedAlgorithm, String token)
+ {
+ return Jwt.verifyAlgorithm(expectedAlgorithm, token);
+ }
+
+ public static long createUnixTimestamp(Date date) {
+ return UnixTimestamp.create(date);
+ }
+
+ public static String getJwtHeader(String token) {
+ return Jwt.getHeader(token);
+ }
+
+ public static String getJwtPayload(String token) {
+ return Jwt.getPayload(token);
+ }
+
+ //**ENCODING**//
+ public static String base64ToBase64Url(String base64) {
+ return Encoding.b64ToB64Url(base64);
+ }
+
+ public static String hexaToBase64(String hexa) { return Encoding.hexaToBase64(hexa); }
+
+ public static String toBase64Url(String input) { return Encoding.toBase64Url(input); }
+
+ public static String fromBase64Url(String base64) { return Encoding.fromBase64Url(base64); }
+
+ public static String base64ToHexa(String base64) { return Encoding.base64ToHexa(base64); }
+
+ //**PKCE**//
+
+ public static String pkce_create(int len, String option) { return Pkce.create(len, option); }
+
+ public static boolean pkce_verify(String code_verifier, String code_challenge, String option) { return Pkce.verify(code_verifier, code_challenge, option); }
+
+ /********EXTERNAL OBJECT PUBLIC METHODS - END ********/
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/DynamicCall.java b/gamutils/src/main/java/com/genexus/gam/utils/DynamicCall.java
new file mode 100644
index 000000000..45b09a73c
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/DynamicCall.java
@@ -0,0 +1,100 @@
+package com.genexus.gam.utils;
+
+import com.genexus.ModelContext;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+@SuppressWarnings("unused")
+public class DynamicCall {
+ private final ModelContext mContext;
+ private final Integer mRemoteHandle;
+ private static final Logger logger = LogManager.getLogger(DynamicCall.class);
+
+ /********EXTERNAL OBJECT PUBLIC METHODS - BEGIN ********/
+
+ @SuppressWarnings("unused")
+ public DynamicCall(ModelContext context, Integer remoteHandle) {
+ mContext = context;
+ mRemoteHandle = remoteHandle;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean execute(String assembly, String typeName, boolean useContext, String method, String jsonParms, String[] jsonOutput) {
+ logger.debug("execute");
+ return doCall(assembly, typeName, useContext, method, false, "", jsonParms, jsonOutput);
+ }
+
+ @SuppressWarnings("unused")
+ public boolean executeEventHandler(String assembly, String typeName, boolean useContext, String method, String eventType, String jsonInput, String[] jsonOutput) {
+ logger.debug("executeEventHandler");
+ return doCall(assembly, typeName, useContext, method, true, eventType, jsonInput, jsonOutput);
+ }
+
+ /********EXTERNAL OBJECT PUBLIC METHODS - END ********/
+
+
+ private boolean doCall(String assembly, String typeName, boolean useContext, String method, boolean isEventHandler, String parm1, String parm2, String[] jsonOutput) {
+ logger.debug("doCall");
+ Object[] parms;
+ Class>[] parmTypes;
+ if (isEventHandler) {
+ parms = new Object[]{parm1, parm2, new String[]{jsonOutput[0]}};
+ parmTypes = new Class[]{String.class, String.class, String[].class};
+ } else {
+ parms = new Object[]{parm2, new String[]{jsonOutput[0]}};
+ parmTypes = new Class[]{String.class, String[].class};
+ }
+
+ try {
+ Class> myClass = Class.forName(typeName);
+ Class>[] constructorParms;
+ Constructor> constructor;
+ Object instance = null;
+
+ if (useContext && (mContext != null)) {
+ try {
+ constructorParms = new Class[]{int.class, ModelContext.class};
+ constructor = myClass.getConstructor(constructorParms);
+ instance = constructor.newInstance(new Object[]{mRemoteHandle, mContext});
+ } catch (NoSuchMethodException e) {
+ logger.error("doCall", e);
+ }
+ }
+
+ if (instance == null) {
+ constructorParms = new Class[]{int.class};
+ constructor = myClass.getConstructor(constructorParms);
+ instance = constructor.newInstance(new Object[]{-2});
+ }
+
+ myClass.getMethod(method, parmTypes).invoke(instance, parms);
+ } catch (ClassNotFoundException e) {
+ logger.error("doCall", e);
+ jsonOutput[0] = "{\"error\":\"" + " class " + typeName + " not found" + "\"}";
+ return false;
+ } catch (NoSuchMethodException e) {
+ logger.error("doCall", e);
+ jsonOutput[0] = "{\"error\":\"" + " method " + method + " not found" + "\"}";
+ return false;
+ } catch (InstantiationException e) {
+ logger.error("doCall", e);
+ jsonOutput[0] = "{\"error\":\"" + " cannot instantiate type " + typeName + "\"}";
+ return false;
+ } catch (IllegalAccessException e) {
+ logger.error("doCall", e);
+ jsonOutput[0] = "{\"error\":\"" + " cannot access method " + method + "\"}";
+ return false;
+ } catch (InvocationTargetException e) {
+ logger.error("doCall", e);
+ jsonOutput[0] = "{\"error\":\"" + " InvocationTargetException in class " + typeName + "\"}";
+ return false;
+ }
+ String[] result = (String[]) parms[parms.length - 1];
+ jsonOutput[0] = result[0];
+ logger.debug("doCall result {}", result[0]);
+ return true;
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/Encoding.java b/gamutils/src/main/java/com/genexus/gam/utils/Encoding.java
new file mode 100644
index 000000000..7b25fd0e3
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/Encoding.java
@@ -0,0 +1,74 @@
+package com.genexus.gam.utils;
+
+
+import com.nimbusds.jose.util.Base64URL;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.encoders.UrlBase64;
+
+import java.nio.charset.StandardCharsets;
+
+public class Encoding {
+
+ private static final Logger logger = LogManager.getLogger(Encoding.class);
+
+ public static String b64ToB64Url(String input) {
+ logger.debug("b64ToB64Url");
+ try {
+ return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(Base64.decode(input));
+ } catch (Exception e) {
+ logger.error("b64ToB64Url", e);
+ return "";
+ }
+ }
+
+ public static String hexaToBase64(String hexa)
+ {
+ logger.debug("hexaToBase64");
+ try{
+ return Base64.toBase64String(Hex.decode(hexa));
+ }catch (Exception e)
+ {
+ logger.error("hexaToBase64", e);
+ return "";
+ }
+ }
+
+ public static String toBase64Url(String input)
+ {
+ logger.debug("UTF8toBase64Url");
+ try{
+ return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(input.getBytes(StandardCharsets.UTF_8));
+ }catch (Exception e)
+ {
+ logger.error("UTF8toBase64Url", e);
+ return "";
+ }
+ }
+
+ public static String fromBase64Url(String base64Url)
+ {
+ logger.debug("fromBase64Url");
+ try{
+ return new String(java.util.Base64.getUrlDecoder().decode(base64Url), StandardCharsets.ISO_8859_1);
+ }catch (Exception e)
+ {
+ logger.error("fromBase64Url", e);
+ return "";
+ }
+ }
+
+ public static String base64ToHexa(String base64)
+ {
+ logger.debug("base64ToHexa");
+ try{
+ return Hex.toHexString(Base64.decode(base64));
+ }catch (Exception e)
+ {
+ logger.error("base64ToHexa", e);
+ return "";
+ }
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/Pkce.java b/gamutils/src/main/java/com/genexus/gam/utils/Pkce.java
new file mode 100644
index 000000000..d72da2c91
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/Pkce.java
@@ -0,0 +1,64 @@
+package com.genexus.gam.utils;
+
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+import java.text.MessageFormat;
+import java.util.Base64;
+
+@SuppressWarnings("LoggingSimilarMessage")
+public class Pkce {
+
+ private static final Logger logger = LogManager.getLogger(Pkce.class);
+
+ public static String create(int len, String option) {
+ logger.trace("create");
+ byte[] code_verifier_bytes = getRandomBytes(len);
+ String code_verifier = Base64.getUrlEncoder().withoutPadding().encodeToString(code_verifier_bytes);
+ switch (option.toUpperCase().trim()) {
+ case "S256":
+ byte[] digest = hash(new SHA256Digest(), code_verifier.getBytes(StandardCharsets.US_ASCII));
+ return MessageFormat.format("{0},{1}", code_verifier, Base64.getUrlEncoder().withoutPadding().encodeToString(digest));
+ case "PLAIN":
+ return MessageFormat.format("{0},{1}", code_verifier, code_verifier);
+ default:
+ logger.error("Unknown PKCE option");
+ return "";
+ }
+
+ }
+
+ public static boolean verify(String code_verifier, String code_challenge, String option) {
+ logger.trace("verify");
+ switch (option.toUpperCase().trim()) {
+ case "S256":
+ byte[] digest = hash(new SHA256Digest(), code_verifier.trim().getBytes(StandardCharsets.US_ASCII));
+ return Base64.getUrlEncoder().withoutPadding().encodeToString(digest).equals(code_challenge.trim());
+ case "PLAIN":
+ return code_challenge.trim().equals(code_verifier.trim());
+ default:
+ logger.error("Unknown PKCE option");
+ return false;
+ }
+ }
+
+ private static byte[] hash(Digest digest, byte[] inputBytes) {
+ byte[] retValue = new byte[digest.getDigestSize()];
+ digest.update(inputBytes, 0, inputBytes.length);
+ digest.doFinal(retValue, 0);
+ return retValue;
+ }
+
+ private static byte[] getRandomBytes(int len) {
+ logger.trace("getRandomBytes");
+ SecureRandom secureRandom = new SecureRandom();
+ byte[] bytes = new byte[len];
+ secureRandom.nextBytes(bytes);
+ return bytes;
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/Random.java b/gamutils/src/main/java/com/genexus/gam/utils/Random.java
new file mode 100644
index 000000000..4586973c6
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/Random.java
@@ -0,0 +1,63 @@
+package com.genexus.gam.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+
+public class Random {
+
+ private static Logger logger = LogManager.getLogger(Random.class);
+
+ private static SecureRandom instanceRandom() {
+ try {
+ return new SecureRandom();
+ } catch (Exception e) {
+ logger.error("instanceRandom", e);
+ return null;
+ }
+ }
+
+ public static String alphanumeric(int length) {
+ SecureRandom random = instanceRandom();
+ if (random == null) {
+ logger.error("randomAlphanumeric SecureRandom is null");
+ return "";
+ }
+ String characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ StringBuilder sb = new StringBuilder(length);
+ for (int i = 0; i < length; i++)
+ sb.append(characters.charAt(random.nextInt(characters.length())));
+ return sb.toString();
+ }
+
+ public static String numeric(int length) {
+ SecureRandom random = instanceRandom();
+ if (random == null) {
+ logger.error("randomNumeric SecureRandom is null");
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ sb.append(random.nextInt(10));
+ }
+ return sb.toString();
+ }
+
+ public static String hexaBits(int bits)
+ {
+ SecureRandom random = instanceRandom();
+ if (random == null) {
+ logger.error("randomNumeric SecureRandom is null");
+ return "";
+ }
+ byte[] values = new byte[bits / 8];
+ random.nextBytes(values);
+ StringBuilder sb = new StringBuilder();
+ for (byte b : values) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString().replaceAll("\\s", "");
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/cryptography/Encryption.java b/gamutils/src/main/java/com/genexus/gam/utils/cryptography/Encryption.java
new file mode 100644
index 000000000..2ce4e0350
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/cryptography/Encryption.java
@@ -0,0 +1,38 @@
+package com.genexus.gam.utils.cryptography;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+
+import java.nio.charset.StandardCharsets;
+
+public class Encryption {
+
+ private static Logger logger = LogManager.getLogger(Encryption.class);
+
+ public static String AesGcm(String input, String key, String nonce, int macSize, boolean toEncrypt) {
+ return toEncrypt ? Base64.toBase64String(internal_AesGcm(input.getBytes(StandardCharsets.UTF_8), key, nonce, macSize, toEncrypt)) : new String(internal_AesGcm(Base64.decode(input), key, nonce, macSize, toEncrypt), StandardCharsets.UTF_8);
+ }
+
+ public static byte[] internal_AesGcm(byte[] inputBytes, String key, String nonce, int macSize, boolean toEncrypt) {
+ logger.debug("internal_AesGcm");
+ AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
+ AEADParameters AEADparams = new AEADParameters(new KeyParameter(Hex.decode(key)), macSize, Hex.decode(nonce));
+ try {
+ cipher.init(toEncrypt, AEADparams);
+ byte[] outputBytes = new byte[cipher.getOutputSize(inputBytes.length)];
+ int length = cipher.processBytes(inputBytes, 0, inputBytes.length, outputBytes, 0);
+ cipher.doFinal(outputBytes, length);
+ return outputBytes;
+ } catch (Exception e) {
+ logger.error("Aes_gcm", e);
+ return null;
+ }
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/cryptography/Hash.java b/gamutils/src/main/java/com/genexus/gam/utils/cryptography/Hash.java
new file mode 100644
index 000000000..f483cc3ec
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/cryptography/Hash.java
@@ -0,0 +1,45 @@
+package com.genexus.gam.utils.cryptography;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.util.encoders.Base64;
+
+import java.nio.charset.StandardCharsets;
+
+public enum Hash {
+
+ SHA256, SHA512;
+
+ private static final Logger logger = LogManager.getLogger(Hash.class);
+
+ public static String hash(String plainText, Hash hash)
+ {
+ switch (hash)
+ {
+ case SHA256:
+ return internalHash(new SHA256Digest(), plainText);
+ case SHA512:
+ return internalHash(new SHA512Digest(), plainText);
+ default:
+ logger.error("unrecognized hash");
+ return "";
+ }
+ }
+
+ private static String internalHash(Digest digest, String plainText)
+ {
+ logger.debug("internalHash");
+ if (plainText.isEmpty()) {
+ logger.error("hash plainText is empty");
+ return "";
+ }
+ byte[] inputBytes = plainText.getBytes(StandardCharsets.UTF_8);
+ byte[] retValue = new byte[digest.getDigestSize()];
+ digest.update(inputBytes, 0, inputBytes.length);
+ digest.doFinal(retValue, 0);
+ return Base64.toBase64String(retValue);
+ }
+}
\ No newline at end of file
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/json/JWTAlgorithm.java b/gamutils/src/main/java/com/genexus/gam/utils/json/JWTAlgorithm.java
new file mode 100644
index 000000000..56ae300ab
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/json/JWTAlgorithm.java
@@ -0,0 +1,72 @@
+package com.genexus.gam.utils.json;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public enum JWTAlgorithm {
+
+ HS256, HS512, HS384, RS256, RS512;
+
+ private static final Logger logger = LogManager.getLogger(JWTAlgorithm.class);
+
+ public static JWSAlgorithm getJWSAlgorithm(JWTAlgorithm alg)
+ {
+ logger.debug("getJWSAlgorithm");
+ switch (alg)
+ {
+ case HS256:
+ return JWSAlgorithm.HS256;
+ case HS512:
+ return JWSAlgorithm.HS512;
+ case HS384:
+ return JWSAlgorithm.HS384;
+ case RS256:
+ return JWSAlgorithm.RS256;
+ case RS512:
+ return JWSAlgorithm.RS512;
+ default:
+ logger.error("getJWSAlgorithm - not implemented algorithm");
+ return null;
+ }
+ }
+
+ public static JWTAlgorithm getJWTAlgoritm(String alg)
+ {
+ logger.debug("getJWTAlgoritm");
+ switch (alg.trim().toUpperCase())
+ {
+ case "HS256":
+ return JWTAlgorithm.HS256;
+ case "HS512":
+ return JWTAlgorithm.HS512;
+ case "HS384":
+ return JWTAlgorithm.HS384;
+ case "RS256":
+ return JWTAlgorithm.RS256;
+ case "RS512":
+ return JWTAlgorithm.RS512;
+ default:
+ logger.error("getJWTAlgoritm- not implemented algorithm");
+ return null;
+ }
+ }
+
+ public static boolean isSymmetric(JWTAlgorithm alg)
+ {
+ logger.debug("isSymmetric");
+ switch (alg)
+ {
+ case HS256:
+ case HS384:
+ case HS512:
+ return true;
+ case RS256:
+ case RS512:
+ return false;
+ default:
+ logger.error("isSymmetric - not implemented algorithm");
+ return false;
+ }
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/json/Jwk.java b/gamutils/src/main/java/com/genexus/gam/utils/json/Jwk.java
new file mode 100644
index 000000000..c4342a299
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/json/Jwk.java
@@ -0,0 +1,101 @@
+package com.genexus.gam.utils.json;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.UUID;
+
+public class Jwk {
+
+ private static Logger logger = LogManager.getLogger(Jwk.class);
+
+ public static String generateKeyPair() {
+ try {
+ String kid = UUID.randomUUID().toString();
+ RSAKey key = new RSAKeyGenerator(2048)
+ .keyUse(KeyUse.SIGNATURE)
+ .keyID(kid)
+ .algorithm(Algorithm.parse("RS256"))
+ .generate();
+ return key.toString();
+
+ } catch (Exception e) {
+ logger.error("generateKeyPair", e);
+ return "";
+ }
+ }
+
+ public static String getPublic(String jwkString) {
+ if (jwkString.isEmpty()) {
+ logger.error("getPublic jwkString parameter is empty");
+ return "";
+ }
+ try {
+ JWK jwk = JWK.parse(jwkString);
+ return jwk.toPublicJWK().toString();
+ } catch (Exception e) {
+ logger.error("getPublic", e);
+ return "";
+ }
+ }
+
+ public static String getAlgorithm(String jwkString) {
+ if (jwkString.isEmpty()) {
+ logger.error("getAlgorithm jwkString parameter is empty");
+ return "";
+ }
+ try {
+ return JWK.parse(jwkString).getAlgorithm().toString();
+ } catch (Exception e) {
+ logger.error("getPublic", e);
+ return "";
+ }
+ }
+
+
+ /*public static boolean verifyJWT(String jwkString, String token) {
+ if (jwkString.isEmpty()) {
+ logger.error("verifyJWT jwkString parameter is empty");
+ return false;
+ }
+ if (token.isEmpty()) {
+ logger.error("verifyJWT token parameter is empty");
+ return false;
+ }
+ try {
+ JWK jwk = JWK.parse(jwkString);
+ return Jwt.verify(jwk.toRSAKey().toRSAPublicKey(), token);
+ } catch (Exception e) {
+ logger.error("verifyJWT", e);
+ return false;
+ }
+
+ }
+
+ public static String createJwt(String jwkString, String payload, String header) {
+ if (jwkString.isEmpty()) {
+ logger.error("createJwt jwkString parameter is empty");
+ return "";
+ }
+ if (payload.isEmpty()) {
+ logger.error("createJwt payload parameter is empty");
+ return "";
+ }
+ if (header.isEmpty()) {
+ logger.error("createJwt header parameter is empty");
+ return "";
+ }
+ try {
+ JWK jwk = JWK.parse(jwkString);
+ return Jwt.create(jwk.toRSAKey().toRSAPrivateKey(), payload, header);
+ } catch (Exception e) {
+ logger.error("createJwt", e);
+ return "";
+ }
+ }*/
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/json/Jwt.java b/gamutils/src/main/java/com/genexus/gam/utils/json/Jwt.java
new file mode 100644
index 000000000..64709fe5b
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/json/Jwt.java
@@ -0,0 +1,102 @@
+package com.genexus.gam.utils.json;
+
+import com.genexus.gam.utils.keys.PrivateKeyUtil;
+import com.genexus.gam.utils.keys.PublicKeyUtil;
+import com.nimbusds.jose.*;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.MACVerifier;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.Objects;
+
+public class Jwt {
+
+ private static final Logger logger = LogManager.getLogger(Jwt.class);
+
+ /******** EXTERNAL OBJECT PUBLIC METHODS - BEGIN ********/
+
+ public static boolean verify(String path, String alias, String password, String token) {
+ logger.debug("verify");
+ try {
+ return verify_internal(path, alias, password, token);
+ } catch (Exception e) {
+ logger.error("verify", e);
+ return false;
+ }
+ }
+
+ public static String create(String path, String alias, String password, String payload, String header) {
+ logger.debug("create");
+ try {
+ return create_internal(path, alias, password, payload, header);
+ }catch (Exception e)
+ {
+ logger.error("create", e);
+ return "";
+ }
+ }
+
+ public static String getHeader(String token) {
+ logger.debug("getHeader");
+ try {
+ return SignedJWT.parse(token).getHeader().toString();
+ } catch (Exception e) {
+ logger.error("getHeader", e);
+ return "";
+ }
+ }
+
+ public static String getPayload(String token) {
+ logger.debug("getPayload");
+ try {
+ return SignedJWT.parse(token).getPayload().toString();
+ } catch (Exception e) {
+ logger.error("getPayload", e);
+ return "";
+ }
+ }
+
+ public static boolean verifyAlgorithm(String algorithm, String token)
+ {
+ logger.debug("verifyAlgorithm");
+ try{
+ return SignedJWT.parse(token).getHeader().getAlgorithm().equals(JWSAlgorithm.parse(algorithm));
+ }catch (Exception e)
+ {
+ logger.error("verifyAlgorithm", e);
+ return false;
+ }
+ }
+
+ /******** EXTERNAL OBJECT PUBLIC METHODS - END ********/
+
+ private static boolean verify_internal(String path, String alias, String password, String token) throws JOSEException, ParseException {
+ logger.debug("verify_internal");
+ JWTAlgorithm algorithm = JWTAlgorithm.getJWTAlgoritm(JWSHeader.parse(getHeader(token)).getAlgorithm().getName());
+ assert algorithm != null;
+ boolean isSymmetric = JWTAlgorithm.isSymmetric(algorithm);
+ SignedJWT signedJWT = SignedJWT.parse(token);
+ JWSVerifier verifier = isSymmetric ? new MACVerifier(password):new RSASSAVerifier(Objects.requireNonNull(PublicKeyUtil.getPublicKey(path, alias, password, token)));
+ return signedJWT.verify(verifier);
+ }
+
+ private static String create_internal(String path, String alias, String password, String payload, String header) throws Exception {
+ logger.debug("create_internal");
+ JWSHeader parsedHeader = JWSHeader.parse(header);
+ JWTAlgorithm algorithm = JWTAlgorithm.getJWTAlgoritm(parsedHeader.getAlgorithm().getName());
+ assert algorithm != null;
+ boolean isSymmetric = JWTAlgorithm.isSymmetric(algorithm);
+ SignedJWT signedJWT = new SignedJWT(parsedHeader, JWTClaimsSet.parse(payload));
+ JWSSigner signer = isSymmetric ? new MACSigner(password): new RSASSASigner(Objects.requireNonNull(PrivateKeyUtil.getPrivateKey(path, alias, password)));
+ signedJWT.sign(signer);
+ return signedJWT.serialize();
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/json/UnixTimestamp.java b/gamutils/src/main/java/com/genexus/gam/utils/json/UnixTimestamp.java
new file mode 100644
index 000000000..e3b1044a4
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/json/UnixTimestamp.java
@@ -0,0 +1,11 @@
+package com.genexus.gam.utils.json;
+
+import java.util.Date;
+
+public class UnixTimestamp {
+
+ public static long create(Date gxdate) {
+ return gxdate.toInstant().getEpochSecond();
+ }
+
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/keys/PrivateKeyUtil.java b/gamutils/src/main/java/com/genexus/gam/utils/keys/PrivateKeyUtil.java
new file mode 100644
index 000000000..964a43ce9
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/keys/PrivateKeyUtil.java
@@ -0,0 +1,181 @@
+package com.genexus.gam.utils.keys;
+
+import com.nimbusds.jose.jwk.JWK;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+
+import javax.crypto.EncryptedPrivateKeyInfo;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.Security;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Objects;
+
+public enum PrivateKeyUtil {
+
+ pfx, jks, pkcs12, p12, pem, key, b64, json;
+
+ private static Logger logger = LogManager.getLogger(PrivateKeyUtil.class);
+
+ public static PrivateKeyUtil value(String ext) {
+ switch (ext.toLowerCase().trim()) {
+ case "pfx":
+ return pfx;
+ case "jks":
+ return jks;
+ case "pkcs12":
+ return pkcs12;
+ case "p12":
+ return p12;
+ case "pem":
+ return pem;
+ case "key":
+ return key;
+ case "b64":
+ return b64;
+ case "json":
+ return json;
+ default:
+ logger.error("Invalid private key file extension");
+ return null;
+ }
+ }
+
+ public static RSAPrivateKey getPrivateKey(String path, String alias, String password) throws Exception {
+ PrivateKeyUtil ext = PrivateKeyUtil.value(fixType(path));
+ switch (Objects.requireNonNull(ext)) {
+ case pfx:
+ case jks:
+ case pkcs12:
+ case p12:
+ return loadFromPkcs12(path, alias, password);
+ case pem:
+ case key:
+ return loadFromPkcs8(path, password);
+ case b64:
+ return loadFromBase64(path);
+ case json:
+ return loadFromJson(path);
+ default:
+ logger.error("Invalid private key file extension");
+ return null;
+ }
+ }
+
+ private static RSAPrivateKey loadFromJson(String json) {
+ logger.debug("loadFromJson");
+ try {
+ JWK jwk = JWK.parse(json);
+ return jwk.toRSAKey().toRSAPrivateKey();
+ } catch (Exception e) {
+ logger.error("loadFromJson", e);
+ return null;
+ }
+ }
+
+ private static String fixType(String input) {
+ logger.debug("fixType");
+ try {
+ String extension = FilenameUtils.getExtension(input);
+ if (extension.isEmpty()) {
+ try {
+ Base64.decode(input);
+ logger.debug("b64");
+ return "b64";
+ } catch (Exception e) {
+ logger.debug("json");
+ return "json";
+ }
+ } else {
+ return extension;
+ }
+ } catch (IllegalArgumentException e) {
+ logger.debug("json");
+ return "json";
+ }
+ }
+
+ private static RSAPrivateKey loadFromBase64(String base64) {
+ logger.debug("loadFromBase64");
+ try (ASN1InputStream stream = new ASN1InputStream(Base64.decode(base64))) {
+ ASN1Sequence seq = (ASN1Sequence) stream.readObject();
+ return castPrivateKeyInfo(PrivateKeyInfo.getInstance(seq));
+ } catch (Exception e) {
+ logger.error("loadFromBase64", e);
+ return null;
+ }
+ }
+
+ private static RSAPrivateKey loadFromPkcs8(String path, String password) {
+ logger.debug("loadFromPkcs8");
+ try (FileReader privateKeyReader = new FileReader(path)) {
+ try (PEMParser parser = new PEMParser(privateKeyReader)) {
+ Object obj;
+ obj = parser.readObject();
+ if (obj instanceof PrivateKeyInfo) {
+ return castPrivateKeyInfo((PrivateKeyInfo) obj);
+ } else if (obj instanceof PEMKeyPair) {
+ PEMKeyPair pemKeyPair = (PEMKeyPair) obj;
+ return castPrivateKeyInfo(pemKeyPair.getPrivateKeyInfo());
+ } else if (obj instanceof EncryptedPrivateKeyInfo || obj instanceof PKCS8EncryptedPrivateKeyInfo) {
+ logger.debug("loadFromPkcs8 encrypted private key");
+ Security.addProvider(new BouncyCastleProvider());
+ PKCS8EncryptedPrivateKeyInfo encPrivKeyInfo = (PKCS8EncryptedPrivateKeyInfo) obj;
+ InputDecryptorProvider pkcs8Prov = new JcePKCSPBEInputDecryptorProviderBuilder().setProvider("BC")
+ .build(password.toCharArray());
+ return castPrivateKeyInfo(encPrivKeyInfo.decryptPrivateKeyInfo(pkcs8Prov));
+ } else {
+ logger.error("loadFromPkcs8: Could not load private key");
+ return null;
+ }
+ }
+ } catch (Exception e) {
+ logger.error("loadFromPkcs8", e);
+ return null;
+ }
+ }
+
+ private static RSAPrivateKey castPrivateKeyInfo(PrivateKeyInfo privateKeyInfo) {
+ logger.debug("castPrivateKeyInfo");
+ try {
+ KeyFactory kf = KeyFactory.getInstance(privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm().getId());
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded());
+ return (RSAPrivateKey) kf.generatePrivate(keySpec);
+ } catch (Exception e) {
+ logger.error("castPrivateKeyInfo", e);
+ return null;
+ }
+ }
+
+ private static RSAPrivateKey loadFromPkcs12(String path, String alias, String password) {
+ logger.debug("loadFromPkcs12");
+ try (InputStream targetStream = Files.newInputStream(Paths.get(path))) {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(targetStream, password.toCharArray());
+ if (alias.isEmpty()) {
+ return (RSAPrivateKey) ks.getKey(ks.aliases().nextElement(), password.toCharArray());
+ } else {
+ return (RSAPrivateKey) ks.getKey(alias, password.toCharArray());
+ }
+ } catch (Exception e) {
+ logger.error("loadFromPkcs12", e);
+ return null;
+ }
+ }
+}
diff --git a/gamutils/src/main/java/com/genexus/gam/utils/keys/PublicKeyUtil.java b/gamutils/src/main/java/com/genexus/gam/utils/keys/PublicKeyUtil.java
new file mode 100644
index 000000000..02e6ce314
--- /dev/null
+++ b/gamutils/src/main/java/com/genexus/gam/utils/keys/PublicKeyUtil.java
@@ -0,0 +1,190 @@
+package com.genexus.gam.utils.keys;
+
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.SignedJWT;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.util.encoders.Base64;
+
+import java.io.*;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.Objects;
+
+public enum PublicKeyUtil {
+
+ crt, cer, pfx, jks, pkcs12, p12, pem, key, b64, json;
+
+ private static final Logger logger = LogManager.getLogger(PublicKeyUtil.class);
+
+
+ public static RSAPublicKey getPublicKey(String path, String alias, String password, String token) throws NullPointerException {
+ logger.debug("getPublicKey");
+ PublicKeyUtil ext = PublicKeyUtil.value(fixType(path));
+ switch (Objects.requireNonNull(ext)) {
+ case crt:
+ case cer:
+ return (RSAPublicKey) Objects.requireNonNull(loadFromDer(path)).getPublicKey();
+ case pfx:
+ case jks:
+ case pkcs12:
+ case p12:
+ return (RSAPublicKey) Objects.requireNonNull(loadFromPkcs12(path, alias, password)).getPublicKey();
+ case pem:
+ case key:
+ return (RSAPublicKey) Objects.requireNonNull(loadFromPkcs8(path)).getPublicKey();
+ case b64:
+ return (RSAPublicKey) Objects.requireNonNull(loadFromBase64(path)).getPublicKey();
+ case json:
+ return loadFromJson(path, token);
+ default:
+ logger.error("Invalid public key file extension");
+ return null;
+ }
+ }
+
+ private static PublicKeyUtil value(String ext) {
+ switch (ext.toLowerCase().trim()) {
+ case "crt":
+ return crt;
+ case "cer":
+ return cer;
+ case "pfx":
+ return pfx;
+ case "jks":
+ return jks;
+ case "pkcs12":
+ return pkcs12;
+ case "p12":
+ return p12;
+ case "pem":
+ return pem;
+ case "key":
+ return key;
+ case "b64":
+ return b64;
+ case "json":
+ return json;
+ default:
+ logger.error("Invalid certificate file extension");
+ return null;
+ }
+ }
+
+ private static RSAPublicKey loadFromJson(String json, String token) {
+ logger.debug("loadFromJson");
+ try {
+ JWK jwk = JWK.parse(json);
+ return (RSAPublicKey) jwk.toRSAKey().toPublicKey();
+ } catch (ParseException e) {
+ return loadFromJwks(json, token);
+ } catch (Exception e) {
+ logger.error("loadFromJson", e);
+ return null;
+ }
+ }
+
+ private static RSAPublicKey loadFromJwks(String json, String token) {
+ logger.debug("loadFromJwks");
+ try {
+ com.nimbusds.jose.JWSHeader header = SignedJWT.parse(token).getHeader();
+ JWKSet set = JWKSet.parse(json);
+ JWK jwk = set.getKeyByKeyId(header.getKeyID());
+ return (RSAPublicKey) jwk.toRSAKey().toPublicKey();
+ } catch (Exception e) {
+ logger.error("loadFromJwks", e);
+ return null;
+ }
+ }
+
+ private static String fixType(String input) {
+ logger.debug("fixType");
+ try {
+ String extension = FilenameUtils.getExtension(input);
+ if (extension.isEmpty() || extension.contains("}")) {
+ try {
+ Base64.decode(input);
+ logger.debug("b64");
+ return "b64";
+ } catch (Exception e) {
+ logger.debug("json");
+ return "json";
+ }
+ } else {
+ return extension;
+ }
+ } catch (IllegalArgumentException e) {
+ logger.debug("json");
+ return "json";
+ }
+ }
+
+
+ private static X509Certificate loadFromBase64(String base64) {
+ logger.debug("loadBase64");
+ try {
+ ByteArrayInputStream byteArray = new ByteArrayInputStream(Base64.decode(base64));
+ CertificateFactory factory = new CertificateFactory();
+ return (X509Certificate) factory.engineGenerateCertificate(byteArray);
+ } catch (Exception e) {
+ logger.error("loadBase64", e);
+ return null;
+ }
+ }
+
+ private static X509Certificate loadFromPkcs8(String path) {
+ logger.debug("loadFromPkcs8");
+ try (FileReader privateKeyReader = new FileReader(new File(path))) {
+ try (PEMParser parser = new PEMParser(privateKeyReader)) {
+ Object obj = parser.readObject();
+ if (obj instanceof X509CertificateHolder) {
+ X509CertificateHolder x509 = (X509CertificateHolder) obj;
+ try (InputStream in = new ByteArrayInputStream(x509.getEncoded())) {
+ CertificateFactory certFactory = new CertificateFactory();
+ return (X509Certificate) certFactory.engineGenerateCertificate(in);
+ }
+ } else {
+ logger.error("Error reading certificate");
+ return null;
+ }
+ }
+ } catch (Exception e) {
+ logger.error("loadFromPem", e);
+ return null;
+ }
+ }
+
+ private static X509Certificate loadFromPkcs12(String path, String alias, String password) {
+ logger.debug("loadFromPkcs12");
+ try (FileInputStream inStream = new FileInputStream(path)) {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(inStream, password.toCharArray());
+ if (alias.isEmpty()) {
+ return (X509Certificate) ks.getCertificate(ks.aliases().nextElement());
+ } else {
+ return (X509Certificate) ks.getCertificate(alias);
+ }
+ } catch (Exception e) {
+ logger.error("loadFromPkcs12", e);
+ return null;
+ }
+ }
+
+ private static X509Certificate loadFromDer(String path) {
+ logger.debug("loadFromDer");
+ try (FileInputStream inStream = new FileInputStream(path)) {
+ CertificateFactory cf = new CertificateFactory();
+ return (X509Certificate) cf.engineGenerateCertificate(inStream);
+ } catch (Exception e) {
+ logger.error("loadFromDer", e);
+ return null;
+ }
+ }
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/EncodingTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/EncodingTest.java
new file mode 100644
index 000000000..1aaed898c
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/EncodingTest.java
@@ -0,0 +1,80 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import com.genexus.gam.utils.Encoding;
+import com.genexus.gam.utils.Random;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+
+public class EncodingTest {
+
+ @Test
+ public void testB64ToB64Url() {
+ int i = 0;
+ do {
+ String randomString = GamUtilsEO.randomAlphanumeric(128);
+ String testing = GamUtilsEO.base64ToBase64Url(Base64.toBase64String(randomString.getBytes(StandardCharsets.UTF_8)));
+ Assert.assertEquals("testB64ToB64Url", randomString, b64UrlToUtf8(testing));
+ i++;
+ } while (i < 50);
+ }
+
+ private static String b64UrlToUtf8(String base64Url) {
+ try {
+ return new String(java.util.Base64.getUrlDecoder().decode(base64Url), StandardCharsets.ISO_8859_1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ @Test
+ public void testBase64Url() {
+ String[] utf8 = new String[]{"GQTuYnnS9AbcKXndwxiZbxk4Q60nhuEd", "rf7tZx8aWO28YOKLISDWY33HuarNHkIZ", "sF7Ic0iuZxE50nz3W5Jnj7R0nQlRD0b1", "GGKmW2ubkhnA9ASaVlVAKM6FQdPCQ1pj", "LMW0GSCVyeGiGzf84eIwuX6OHAfur9fp", "zq9Kni7W1r0UIzG9hjYeiqJhSYlWVZSa", "WcyhGLQNyQkP2YmOjVtIilpqcHgYCzjq", "DuhO4PBiXRDDj50RBRo8wNUU8R3UXbp0", "pkPfYXOyoLUsEwm4HjjDB6E2c3aUjYNh", "fgbrZoKKMym9HN5zlKj0a8ohgQlJm3PM", "owGXQ7p6BeFeK1KFVOsdbSRd0sMwgFRU"};
+ String[] b64 = new String[]{"R1FUdVlublM5QWJjS1huZHd4aVpieGs0UTYwbmh1RWQ", "cmY3dFp4OGFXTzI4WU9LTElTRFdZMzNIdWFyTkhrSVo", "c0Y3SWMwaXVaeEU1MG56M1c1Sm5qN1IwblFsUkQwYjE", "R0dLbVcydWJraG5BOUFTYVZsVkFLTTZGUWRQQ1ExcGo", "TE1XMEdTQ1Z5ZUdpR3pmODRlSXd1WDZPSEFmdXI5ZnA", "enE5S25pN1cxcjBVSXpHOWhqWWVpcUpoU1lsV1ZaU2E", "V2N5aEdMUU55UWtQMlltT2pWdElpbHBxY0hnWUN6anE", "RHVoTzRQQmlYUkREajUwUkJSbzh3TlVVOFIzVVhicDA", "cGtQZllYT3lvTFVzRXdtNEhqakRCNkUyYzNhVWpZTmg", "ZmdiclpvS0tNeW05SE41emxLajBhOG9oZ1FsSm0zUE0", "b3dHWFE3cDZCZUZlSzFLRlZPc2RiU1JkMHNNd2dGUlU"};
+ for (int i = 0; i < utf8.length; i++) {
+ Assert.assertEquals(MessageFormat.format("testBase64Url toBase64Url fail index: {0}", i), b64[i], Encoding.toBase64Url(utf8[i]));
+ Assert.assertEquals(MessageFormat.format("testBase64Url fromBase64Url fail index: {0}", i), utf8[i], Encoding.fromBase64Url(b64[i]));
+ }
+ }
+
+ @Test
+ public void testToBase64Url() {
+ int i = 0;
+ do {
+ String randomString = GamUtilsEO.randomAlphanumeric(128);
+ String testing = GamUtilsEO.toBase64Url(randomString);
+ Assert.assertEquals("testB64ToB64Url", randomString, GamUtilsEO.fromBase64Url(testing));
+ i++;
+ } while (i < 50);
+ }
+
+ @Test
+ public void testHexaToBase64()
+ {
+ int i = 0;
+ do {
+ String randomHexa = Random.hexaBits(128);
+ String testing = b64ToHexa(Encoding.hexaToBase64(randomHexa));
+ Assert.assertEquals("testB64ToB64Url", randomHexa, testing);
+ i++;
+ } while (i < 50);
+ }
+
+ private static String b64ToHexa(String base64) {
+ try {
+ byte[] bytes = Base64.decode(base64);
+ return Hex.toHexString(bytes);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/EncryptionTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/EncryptionTest.java
new file mode 100644
index 000000000..f5b616cb4
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/EncryptionTest.java
@@ -0,0 +1,20 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class EncryptionTest {
+
+ @Test
+ public void testAesGcm() {
+ String key = GamUtilsEO.randomHexaBits(256);
+ String nonce = GamUtilsEO.randomHexaBits(128);
+ String txt = "hello world";
+ int macSize = 64;
+ String encrypted = GamUtilsEO.AesGcm(txt, key, nonce, macSize, true);
+ Assert.assertFalse("testAesGcm encrypt", encrypted.isEmpty());
+ String decrypted = GamUtilsEO.AesGcm(encrypted, key, nonce, macSize, false);
+ Assert.assertEquals("testAesGcm decrypt", txt, decrypted);
+ }
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/HashTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/HashTest.java
new file mode 100644
index 000000000..ad81f8b3a
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/HashTest.java
@@ -0,0 +1,57 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import com.genexus.gam.utils.Random;
+import com.genexus.gam.utils.test.resources.CryptographicHash;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class HashTest {
+
+ private static String one;
+ private static String two;
+ private static String three;
+ private static String four;
+ private static String five;
+
+ private static CryptographicHash cryptographicHash;
+
+ @BeforeClass
+ public static void setUp() {
+ one = "one";
+ two = "two";
+ three = "three";
+ four = "four";
+ five = "five";
+ cryptographicHash = new CryptographicHash("SHA-512");
+ }
+
+ @Test
+ public void testSha512() {
+ Assert.assertEquals("one: ", cryptographicHash.ComputeHash(one), GamUtilsEO.sha512(one));
+ Assert.assertEquals("two: ", cryptographicHash.ComputeHash(two), GamUtilsEO.sha512(two));
+ Assert.assertEquals("three: ", cryptographicHash.ComputeHash(three), GamUtilsEO.sha512(three));
+ Assert.assertEquals("four: ", cryptographicHash.ComputeHash(four), GamUtilsEO.sha512(four));
+ Assert.assertEquals("five: ", cryptographicHash.ComputeHash(five), GamUtilsEO.sha512(five));
+ }
+
+ @Test
+ public void testSha512Random() {
+ for (int i = 0; i < 100; i++) {
+ String value = Random.alphanumeric(15);
+ Assert.assertEquals("random sha512 ", cryptographicHash.ComputeHash(value), GamUtilsEO.sha512(value));
+ }
+ }
+ @Test
+ public void testSha256()
+ {
+ String[] arrayInputs = new String[] {one, two, three, four, five};
+ String[] arrayRes= new String[] {"dpLDrTVAu4A8Ags67mbNiIcSMjTqDG5xQ8Ct1z/0Me0=", "P8TM/nRYcOLA2Z9x8w/wZWyN7dQcwdfT03aw2+aF4vM=", "i1udsME9skJWyCmqNkqpDG0uujGLkjKkq5MTuVTTVV8=", "BO+vCA9aPnThwp0cpqSFaTgsu80yTo1Z0rg+8hwDnwA=", "IisL1R/O9+ZcLmLbLtZUVwE7q1a+b6/rGe4R1FMVPIA="};
+ for(int i = 0; i < arrayInputs.length; i++)
+ {
+ Assert.assertEquals("testSha256 error", GamUtilsEO.sha256(arrayInputs[i]), arrayRes[i]);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/JwkTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/JwkTest.java
new file mode 100644
index 000000000..3c6a40d6c
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/JwkTest.java
@@ -0,0 +1,38 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import com.nimbusds.jose.jwk.JWKSet;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.text.ParseException;
+
+public class JwkTest {
+
+ @Test
+ public void testGenerateKeyPair() {
+ String jwk = GamUtilsEO.generateKeyPair();
+ Assert.assertFalse("Generate key pair jwk", jwk.isEmpty());
+ }
+
+ @Test
+ public void testPublicJwk() {
+ String jwk = GamUtilsEO.generateKeyPair();
+ String public_jwk = GamUtilsEO.getPublicJwk(jwk);
+ String public_jwks = "{\"keys\": [" + public_jwk + "]}";
+ try {
+ JWKSet jwks = JWKSet.parse(public_jwks).toPublicJWKSet();
+ Assert.assertNotNull("To public JWK fail", jwks);
+ } catch (ParseException e) {
+ Assert.fail("Exception on testPublicJwk" + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetAlgorithm()
+ {
+ String jwk = GamUtilsEO.generateKeyPair();
+ String algorithm = GamUtilsEO.getJwkAlgorithm(jwk);
+ Assert.assertEquals("testGetAlgorithm", algorithm, "RS256");
+ }
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/JwtTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/JwtTest.java
new file mode 100644
index 000000000..45025a4ae
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/JwtTest.java
@@ -0,0 +1,221 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import com.nimbusds.jose.jwk.JWK;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.UUID;
+
+public class JwtTest {
+
+ private static String headerRsa;
+ private static String payload;
+ private static String path_RSA_sha256_2048;
+ private static String alias;
+ private static String password;
+
+ @BeforeClass
+ public static void setUp() {
+
+ String resources = System.getProperty("user.dir").concat("/src/test/resources");
+ String kid = UUID.randomUUID().toString();
+ headerRsa = "{\n" +
+ " \"alg\": \"RS256\",\n" +
+ " \"kid\": \"" + kid + "\",\n" +
+ " \"typ\": \"JWT\"\n" +
+ "}";
+ payload = "{\n" +
+ " \"sub\": \"1234567890\",\n" +
+ " \"name\": \"John Doe\",\n" +
+ " \"iat\": 1516239022\n" +
+ "}";
+ path_RSA_sha256_2048 = resources.concat("/dummycerts/RSA_sha256_2048/");
+ alias = "1";
+ password = "dummy";
+ }
+
+ @Test
+ public void test_pkcs8_pem() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256d_key.pem", "", "", payload, headerRsa);
+ Assert.assertFalse("test_pkcs8 create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.cer", "", "", token);
+ Assert.assertTrue("test_pkcs8 verify cer", result);
+ }
+
+ @Test
+ public void test_get() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256d_key.pem", "", "", payload, headerRsa);
+ Assert.assertFalse("test_get create", token.isEmpty());
+ String header_get = GamUtilsEO.getJwtHeader(token);
+ Assert.assertFalse("test_get getHeader", header_get.isEmpty());
+ String payload_get = GamUtilsEO.getJwtPayload(token);
+ Assert.assertFalse("test_get getPayload", payload_get.isEmpty());
+ }
+
+ @Test
+ public void test_pkcs8_key() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256d_key.key", "", "", payload, headerRsa);
+ Assert.assertFalse("test_pkcs8 create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.crt", "", "", token);
+ Assert.assertTrue("test_pkcs8 verify crt", result);
+ }
+
+ @Test
+ public void test_pkcs8_encrypted() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256_key.pem", "", password, payload, headerRsa);
+ Assert.assertFalse("test_pkcs8_encrypted", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.crt", "", "", token);
+ Assert.assertTrue("test_pkcs8_encrypted verify crt", result);
+ }
+
+ @Test
+ public void test_pkcs12_p12() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256_cert.p12", alias, password, payload, headerRsa);
+ Assert.assertFalse("test_pkcs12_p12 create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.p12", alias, password, token);
+ Assert.assertTrue("test_pkcs12_p12 verify", result);
+ }
+
+ @Test
+ public void test_pkcs12_pkcs12() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256_cert.pkcs12", alias, password, payload, headerRsa);
+ Assert.assertFalse("test_pkcs12_pkcs12 create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.pkcs12", alias, password, token);
+ Assert.assertTrue("test_pkcs12_pkcs12 verify", result);
+ }
+
+ @Test
+ public void test_pkcs12_jks() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256_cert.jks", alias, password, payload, headerRsa);
+ Assert.assertFalse("test_pkcs12_jks create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.jks", alias, password, token);
+ Assert.assertTrue("test_pkcs12_jks verify", result);
+ }
+
+ @Test
+ public void test_pkcs12_pfx() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256_cert.pfx", alias, password, payload, headerRsa);
+ Assert.assertFalse("test_pkcs12_pfx create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.pfx", alias, password, token);
+ Assert.assertTrue("test_pkcs12_pfx verify", result);
+ }
+
+ @Test
+ public void test_pkcs12_noalias() {
+ String token = GamUtilsEO.createJwt(path_RSA_sha256_2048 + "sha256_cert.jks", "", password, payload, headerRsa);
+ Assert.assertFalse("test_pkcs12_noalias jks create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(path_RSA_sha256_2048 + "sha256_cert.jks", "", password, token);
+ Assert.assertTrue("test_pkcs12_noalias jks verify", result);
+ }
+
+ @Test
+ public void test_b64() {
+ String publicKey = "MIIEATCCAumgAwIBAgIJAIAqvKHZ+gFhMA0GCSqGSIb3DQEBCwUAMIGWMQswCQYDVQQGEwJVWTETMBEGA1UECAwKTW9udGV2aWRlbzETMBEGA1UEBwwKTW9udGV2aWRlbzEQMA4GA1UECgwHR2VuZVh1czERMA8GA1UECwwIU2VjdXJpdHkxEjAQBgNVBAMMCXNncmFtcG9uZTEkMCIGCSqGSIb3DQEJARYVc2dyYW1wb25lQGdlbmV4dXMuY29tMB4XDTIwMDcwODE4NTcxN1oXDTI1MDcwNzE4NTcxN1owgZYxCzAJBgNVBAYTAlVZMRMwEQYDVQQIDApNb250ZXZpZGVvMRMwEQYDVQQHDApNb250ZXZpZGVvMRAwDgYDVQQKDAdHZW5lWHVzMREwDwYDVQQLDAhTZWN1cml0eTESMBAGA1UEAwwJc2dyYW1wb25lMSQwIgYJKoZIhvcNAQkBFhVzZ3JhbXBvbmVAZ2VuZXh1cy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1zgaU+Wh63p9DNWoAy64252EvZjN49AY3x0QCnAa8JO9Pk7znQwrxEFUKgZzv0GHEYW7+X+uyJr7BW4TA6fuJJ8agE/bmZRZyjdJjoue0FML6fbmCZ9Tsxpxe4pzispyWQ8jYT4Kl4I3fdZNUSn4XSidnDKBISeC05mrcchDKhInpiYDJ481lsB4JTEti3S4Xy/ToKwY4t6attw6z5QDhBc+Yro+YUqruliOAKqcfybe9k07jwMCvFVM1hrYYJ7hwHDSFo3MKwZ0y2gw0w6SgVBxLFo+KYP3q63b5wVhD8lzaSh+8UcyiHM2/yjEej7EnRFzdclTSNXRFNaiLnEVdAgMBAAGjUDBOMB0GA1UdDgQWBBQtQAWJRWNr/OswPSAdwCQh0Eei/DAfBgNVHSMEGDAWgBQtQAWJRWNr/OswPSAdwCQh0Eei/DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCjHe3JbNKv0Ywc1zlLacUNWcjLbmzvnjs8Wq5oxtf5wG5PUlhLSYZ9MPhuf95PlibnrO/xVY292P5lo4NKhS7VOonpbPQ/PrCMO84Pz1LGfM/wCWQIowh6VHq18PiZka9zbwl6So0tgClKkFSRk4wpKrWX3+M3+Y+D0brd8sEtA6dXeYHDtqV0YgjKdZIIOx0vDT4alCoVQrQ1yAIq5INT3cSLgJezIhEadDv3Tc7bMxMFeL+81qHm9Z/9/KE6Z+JB0ZEOkF/2NSQJd+Z7MBR8CxOdTQis3ltMoXDatNkjZ2Env40sw4NICB8YYhsWMIarew5uNT+RS28YHNlbmogh";
+ String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1zgaU+Wh63p9DNWoAy64252EvZjN49AY3x0QCnAa8JO9Pk7znQwrxEFUKgZzv0GHEYW7+X+uyJr7BW4TA6fuJJ8agE/bmZRZyjdJjoue0FML6fbmCZ9Tsxpxe4pzispyWQ8jYT4Kl4I3fdZNUSn4XSidnDKBISeC05mrcchDKhInpiYDJ481lsB4JTEti3S4Xy/ToKwY4t6attw6z5QDhBc+Yro+YUqruliOAKqcfybe9k07jwMCvFVM1hrYYJ7hwHDSFo3MKwZ0y2gw0w6SgVBxLFo+KYP3q63b5wVhD8lzaSh+8UcyiHM2/yjEej7EnRFzdclTSNXRFNaiLnEVdAgMBAAECggEBAJP8ajslcThisjzg47JWGS8z1FXi2Q8hg1Yv61o8avcHEY0y8tdEKUnkQ3TT4E0M0CgsL078ATz4cNmvhzYIv+j66aEv3w/XRRhl/NWBqx1YsQV5BWHy5sz9Nhe+WnnlbbSa5Ie+4NfpG1LDv/Mi19RZVg15p5ZwHGrkDCP47VYKgFXw51ZPxq/l3IIeq4PyueC/EPSAp4e9qei7p85k3i2yiWsHgZaHwHgDTx2Hgq1y/+/E5+HNxL2OlPr5lzlN2uIPZ9Rix2LDh0FriuCEjrXFsTJHw4LTK04rkeGledMtw6/bOTxibFbgeuQtY1XzG/M0+xlP2niBbAEA4Z6vTsECgYEA6k7LSsh6azsk0W9+dE6pbc3HisOoKI76rXi38gEQdCuF04OKt46WttQh4r1+dseO4OgjXtRMS0+5Hmx2jFXjPJexMgLftvrbwaVqg9WHenKL/qj5imCn4kVaa4Jo1VHFaIY+1b+iv+6WY/leFxGntAki9u4PRogRrUrWLRH9keUCgYEAxqLisgMHQGcpJDHJtI2N+HUgLDN065PtKlEP9o6WBwAb86/InVaTo2gmEvmslNQDYH16zdTczWMHnnBx1B012KpUD+t5CWIvMZdsTnMRDjWhdgm5ylN9NT89t5X8GPvo36WjuXAKWWjcRodzRgo57z9achCyMKhGU5yDOxh8jhkCgYAx6rtwoSlDcwQzAjfEe4Wo+PAL5gcLLPrGvjMiAYwJ08Pc/ectl9kP9j2J2qj4kSclTw9KApyGZuOfUagn2Zxhqkd7yhTzHJp4tM7uay1DrueYR1NyYYkisXfD87J1z8forsDwNLVtglzTy6p56674sgGa7bifZBmv+4OJco286QKBgQC4dGXDHGDNg36G590A1zpw8ILxyM7YPEPOOfxy3rGeypEqV6AZy13KLlq84DFM+xwvrBYvsW1hJIbcsFpjuMRZ8MGjDu0Us6JTkOO4bc32vgKzlBB9O85XdeSf6J1zrenwVOaWut5BbMiwjfOTpMdrzg71QV/XI0w7NGoApJp1cQKBgERfI6AfJTaKtEpfX3udR1B3zra1Y42ppU2TvGI5J2/cItENoyRmtyKYDp2I036/Pe63nuIzs31i6q/hCr9Tv3AGoSVKuPLpCWv5xVO/BPhGs5dwx81nUo0/P+H2X8dx7g57PQY4uf4F9+EIXeAdbPqfB8GBW7RX3FDx5NpB+Hh/";
+ String token = GamUtilsEO.createJwt(privateKey, "", "", payload, headerRsa);
+ Assert.assertFalse("test_b64 create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt(publicKey, "", "", token);
+ Assert.assertTrue("test_b64 verify", result);
+ }
+
+ @Test
+ public void test_json_jwk() {
+ String keyPair = GamUtilsEO.generateKeyPair();
+ String token = GamUtilsEO.createJwt(keyPair, "", "", payload, headerRsa);
+ Assert.assertFalse("test_json_jwk create", token.isEmpty());
+ String publicJwk = GamUtilsEO.getPublicJwk(keyPair);
+ boolean result = GamUtilsEO.verifyJwt(publicJwk, "", "", token);
+ Assert.assertTrue("test_json_jwk verify", result);
+ }
+
+ @Test
+ public void test_json_jwks() {
+ String keyPair = GamUtilsEO.generateKeyPair();
+ String publicJwk = GamUtilsEO.getPublicJwk(keyPair);
+ String header_jwks = makeHeader(publicJwk);
+ String token = GamUtilsEO.createJwt(keyPair, "", "", payload, header_jwks);
+ Assert.assertFalse("test_json_jwks create", token.isEmpty());
+ String publicJwks = "{\"keys\": [" + publicJwk + "]}";
+ boolean result = GamUtilsEO.verifyJwt(publicJwks, "", "", token);
+ Assert.assertTrue("test_json_jwks verify", result);
+ }
+
+ @Test
+ public void test_json_Sha256()
+ {
+ String header = "{\n" +
+ " \"alg\": \"HS256\",\n" +
+ " \"typ\": \"JWT\"\n" +
+ "}";
+ int[] lengths = new int[]{32, 64, 128};
+ for (int n : lengths) {
+ String secret = GamUtilsEO.randomAlphanumeric(n);
+ String token = GamUtilsEO.createJwt("", "", secret, payload, header);
+ Assert.assertFalse("test_json_Sha256 create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt("", "", secret, token);
+ Assert.assertTrue("test_json_Sha256 verify", result);
+ }
+ }
+
+ @Test
+ public void test_json_Sha512()
+ {
+ String header = "{\n" +
+ " \"alg\": \"HS512\",\n" +
+ " \"typ\": \"JWT\"\n" +
+ "}";
+ int[] lengths = new int[]{64, 128};
+ for (int n : lengths) {
+ String secret = GamUtilsEO.randomAlphanumeric(n);
+ String token = GamUtilsEO.createJwt("", "", secret, payload, header);
+ Assert.assertFalse("test_json_Sha512 create", token.isEmpty());
+ boolean result = GamUtilsEO.verifyJwt("", "", secret, token);
+ Assert.assertTrue("test_json_Sha512 verify", result);
+ }
+ }
+
+ @Test
+ public void test_VerifyAlgorithm_True()
+ {
+ String header = "{\n" +
+ " \"alg\": \"HS512\",\n" +
+ " \"typ\": \"JWT\"\n" +
+ "}";
+ String secret = GamUtilsEO.randomAlphanumeric(64);
+ String token = GamUtilsEO.createJwt("", "", secret, payload, header);
+ boolean resultSha512 = GamUtilsEO.verifyAlgorithm("HS512", token);
+ Assert.assertTrue("test_VerifyAlgorithm_True", resultSha512);
+ }
+
+ @Test
+ public void test_VerifyAlgorithm_False()
+ {
+ String header = "{\n" +
+ " \"alg\": \"HS512\",\n" +
+ " \"typ\": \"JWT\"\n" +
+ "}";
+ String secret = GamUtilsEO.randomAlphanumeric(64);
+ String token = GamUtilsEO.createJwt("", "", secret, payload, header);
+ boolean resultSha512 = GamUtilsEO.verifyAlgorithm("RS256", token);
+ Assert.assertFalse("test_VerifyAlgorithm_False", resultSha512);
+ }
+
+ private static String makeHeader(String publicJwk) {
+ try {
+ JWK jwk = JWK.parse(publicJwk);
+ return "{\n" +
+ " \"alg\": \"RS256\",\n" +
+ " \"kid\": \"" + jwk.getKeyID() + "\",\n" +
+ " \"typ\": \"JWT\"\n" +
+ "}";
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/PkceTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/PkceTest.java
new file mode 100644
index 000000000..eb588e7db
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/PkceTest.java
@@ -0,0 +1,42 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import com.genexus.gam.utils.Pkce;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.text.MessageFormat;
+
+public class PkceTest {
+
+ @Test
+ public void testPkceS256()
+ {
+ int i = 0;
+ while (i<50)
+ {
+ String[] s256_true = GamUtilsEO.pkce_create(20, "S256").split(",");
+ Assert.assertTrue("testPkceS256 true", GamUtilsEO.pkce_verify(s256_true[0], s256_true[1], "S256"));
+
+ String[] s256_false = GamUtilsEO.pkce_create(20, "S256").split(",");
+ Assert.assertFalse("testPkceS256 false", GamUtilsEO.pkce_verify(MessageFormat.format("{0}tralala",s256_false[0]), s256_false[1], "S256"));
+ i++;
+ }
+ }
+
+ @Test
+ public void testPkcePlain()
+ {
+ int i = 0;
+ while (i<50)
+ {
+ String[] plain_true = GamUtilsEO.pkce_create(20, "PLAIN").split(",");
+ Assert.assertTrue("testPkceS256", GamUtilsEO.pkce_verify(plain_true[0], plain_true[1], "PLAIN"));
+
+ String[] plain_false = GamUtilsEO.pkce_create(20, "PLAIN").split(",");
+ Assert.assertFalse("testPkceS256 false", GamUtilsEO.pkce_verify(MessageFormat.format("{0}tralala",plain_false[0]), plain_false[1], "PLAIN"));
+ i++;
+ }
+ }
+
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/RandomTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/RandomTest.java
new file mode 100644
index 000000000..792db9832
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/RandomTest.java
@@ -0,0 +1,78 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+public class RandomTest {
+
+ private static int l128;
+ private static int l256;
+
+ private static int l5;
+
+ private static int l10;
+
+ @BeforeClass
+ public static void setUp() {
+ l128 = 128;
+ l256 = 256;
+ l5 = 5;
+ l10 = 10;
+ }
+
+ @Test
+ public void testRandomNumeric() {
+ String l5_string = GamUtilsEO.randomNumeric(l5);
+ Assert.assertEquals("l5 numeric: ", l5, l5_string.length());
+
+ String l10_string = GamUtilsEO.randomNumeric(l10);
+ Assert.assertEquals("l10 numeric: ", l10, l10_string.length());
+
+ String l128_string = GamUtilsEO.randomNumeric(l128);
+ Assert.assertEquals("l128 numeric: ", l128, l128_string.length());
+
+ String l256_string = GamUtilsEO.randomNumeric(l256);
+ Assert.assertEquals("l256 numeric: ", l256, l256_string.length());
+
+ }
+
+ @Test
+ public void testRandomAlphanumeric() {
+ String l5_string = GamUtilsEO.randomAlphanumeric(l5);
+ Assert.assertEquals("l5 alphanumeric: ", l5, l5_string.length());
+
+ String l10_string = GamUtilsEO.randomAlphanumeric(l10);
+ Assert.assertEquals("l10 alphanumeric: ", l10, l10_string.length());
+
+ String l128_string = GamUtilsEO.randomAlphanumeric(l128);
+ Assert.assertEquals("l128 alphanumeric: ", l128, l128_string.length());
+
+ String l256_string = GamUtilsEO.randomAlphanumeric(l256);
+ Assert.assertEquals("l256 alphanumeric: ", l256, l256_string.length());
+ }
+
+ @Test
+ public void testHexaBits() {
+ int[] lengths = new int[]{32, 64, 128, 256, 512, 1024};
+ for (int n : lengths) {
+ String hexa = GamUtilsEO.randomHexaBits(n);
+ Assert.assertFalse("TestHexaBits", hexa.isEmpty());
+ try
+ {
+ byte[] decoded = Hex.decode(hexa);
+ if(decoded.length*8 != n)
+ {
+ Assert.fail("testHexaBits wrong hexa length");
+ }
+ }catch(Exception e)
+ {
+ Assert.fail("testHexaBits not hexa characters" + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/UnixTimestampTest.java b/gamutils/src/test/java/com/genexus/gam/utils/test/UnixTimestampTest.java
new file mode 100644
index 000000000..43ebf395b
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/UnixTimestampTest.java
@@ -0,0 +1,44 @@
+package com.genexus.gam.utils.test;
+
+import com.genexus.gam.GamUtilsEO;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class UnixTimestampTest {
+
+ @Test
+ public void testCreate() {
+ Date one = createDate("2024/02/02 02:02:02"); //1706839322
+ Date two = createDate("2023/03/03 03:03:03"); //1677812583
+ Date three = createDate("2022/04/04 04:04:04"); //1649045044
+ Date four = createDate("2020/02/02 02:22:22"); //1580610142
+ Date five = createDate("2010/05/05 05:05:05"); //1273035905
+ Date six = createDate("2000/05/05 05:05:05"); //957503105
+
+ Date[] arrayDates = new Date[]{one, two, three, four, five, six};
+ long[] arrayStamps = new long[]{1706839322L, 1677812583L, 1649045044L, 1580610142L, 1273035905L, 957503105L};
+
+ for (int i = 0; i < arrayDates.length; i++) {
+ Assert.assertEquals("testCreate", GamUtilsEO.createUnixTimestamp(arrayDates[i]), arrayStamps[i]);
+ }
+
+ }
+
+ private static Date createDate(String date) {
+
+ DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ try {
+ return dateFormat.parse(date);
+ } catch (Exception e) {
+
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/gamutils/src/test/java/com/genexus/gam/utils/test/resources/CryptographicHash.java b/gamutils/src/test/java/com/genexus/gam/utils/test/resources/CryptographicHash.java
new file mode 100644
index 000000000..006097d1c
--- /dev/null
+++ b/gamutils/src/test/java/com/genexus/gam/utils/test/resources/CryptographicHash.java
@@ -0,0 +1,38 @@
+package com.genexus.gam.utils.test.resources;
+
+import org.apache.commons.codec.CharEncoding;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class CryptographicHash {
+ private MessageDigest alg = null;
+
+ // private HashAlgorithm alg;
+ public CryptographicHash(String algorithm) {
+ if (algorithm.equals("SHA512"))
+ algorithm = "SHA-512";
+ // Supports algorithm = {MD2, MD5, SHA-1, SHA-256, SHA-384, SHA-512}
+ try {
+ alg = MessageDigest.getInstance(algorithm); // step 2
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public String ComputeHash(String data) {
+ try {
+ if (alg == null) alg = MessageDigest.getInstance("SHA-512");
+ alg.update(data.getBytes(CharEncoding.UTF_8));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+
+ byte[] bin = alg.digest();
+ org.apache.commons.codec.binary.Base64 base = new org.apache.commons.codec.binary.Base64(100000);
+ return base.encodeToString(bin).trim();
+ }
+}
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.cer b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.cer
new file mode 100644
index 000000000..bd788b5de
Binary files /dev/null and b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.cer differ
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.crt b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.crt
new file mode 100644
index 000000000..bd788b5de
Binary files /dev/null and b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.crt differ
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.jks b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.jks
new file mode 100644
index 000000000..54cc6fb26
Binary files /dev/null and b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.jks differ
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.key b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.key
new file mode 100644
index 000000000..d9abc2f04
--- /dev/null
+++ b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.key
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEATCCAumgAwIBAgIJAIAqvKHZ+gFhMA0GCSqGSIb3DQEBCwUAMIGWMQswCQYD
+VQQGEwJVWTETMBEGA1UECAwKTW9udGV2aWRlbzETMBEGA1UEBwwKTW9udGV2aWRl
+bzEQMA4GA1UECgwHR2VuZVh1czERMA8GA1UECwwIU2VjdXJpdHkxEjAQBgNVBAMM
+CXNncmFtcG9uZTEkMCIGCSqGSIb3DQEJARYVc2dyYW1wb25lQGdlbmV4dXMuY29t
+MB4XDTIwMDcwODE4NTcxN1oXDTI1MDcwNzE4NTcxN1owgZYxCzAJBgNVBAYTAlVZ
+MRMwEQYDVQQIDApNb250ZXZpZGVvMRMwEQYDVQQHDApNb250ZXZpZGVvMRAwDgYD
+VQQKDAdHZW5lWHVzMREwDwYDVQQLDAhTZWN1cml0eTESMBAGA1UEAwwJc2dyYW1w
+b25lMSQwIgYJKoZIhvcNAQkBFhVzZ3JhbXBvbmVAZ2VuZXh1cy5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1zgaU+Wh63p9DNWoAy64252EvZjN4
+9AY3x0QCnAa8JO9Pk7znQwrxEFUKgZzv0GHEYW7+X+uyJr7BW4TA6fuJJ8agE/bm
+ZRZyjdJjoue0FML6fbmCZ9Tsxpxe4pzispyWQ8jYT4Kl4I3fdZNUSn4XSidnDKBI
+SeC05mrcchDKhInpiYDJ481lsB4JTEti3S4Xy/ToKwY4t6attw6z5QDhBc+Yro+Y
+UqruliOAKqcfybe9k07jwMCvFVM1hrYYJ7hwHDSFo3MKwZ0y2gw0w6SgVBxLFo+K
+YP3q63b5wVhD8lzaSh+8UcyiHM2/yjEej7EnRFzdclTSNXRFNaiLnEVdAgMBAAGj
+UDBOMB0GA1UdDgQWBBQtQAWJRWNr/OswPSAdwCQh0Eei/DAfBgNVHSMEGDAWgBQt
+QAWJRWNr/OswPSAdwCQh0Eei/DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
+A4IBAQCjHe3JbNKv0Ywc1zlLacUNWcjLbmzvnjs8Wq5oxtf5wG5PUlhLSYZ9MPhu
+f95PlibnrO/xVY292P5lo4NKhS7VOonpbPQ/PrCMO84Pz1LGfM/wCWQIowh6VHq1
+8PiZka9zbwl6So0tgClKkFSRk4wpKrWX3+M3+Y+D0brd8sEtA6dXeYHDtqV0YgjK
+dZIIOx0vDT4alCoVQrQ1yAIq5INT3cSLgJezIhEadDv3Tc7bMxMFeL+81qHm9Z/9
+/KE6Z+JB0ZEOkF/2NSQJd+Z7MBR8CxOdTQis3ltMoXDatNkjZ2Env40sw4NICB8Y
+YhsWMIarew5uNT+RS28YHNlbmogh
+-----END CERTIFICATE-----
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.p12 b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.p12
new file mode 100644
index 000000000..54cc6fb26
Binary files /dev/null and b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.p12 differ
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pem b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pem
new file mode 100644
index 000000000..d9abc2f04
--- /dev/null
+++ b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEATCCAumgAwIBAgIJAIAqvKHZ+gFhMA0GCSqGSIb3DQEBCwUAMIGWMQswCQYD
+VQQGEwJVWTETMBEGA1UECAwKTW9udGV2aWRlbzETMBEGA1UEBwwKTW9udGV2aWRl
+bzEQMA4GA1UECgwHR2VuZVh1czERMA8GA1UECwwIU2VjdXJpdHkxEjAQBgNVBAMM
+CXNncmFtcG9uZTEkMCIGCSqGSIb3DQEJARYVc2dyYW1wb25lQGdlbmV4dXMuY29t
+MB4XDTIwMDcwODE4NTcxN1oXDTI1MDcwNzE4NTcxN1owgZYxCzAJBgNVBAYTAlVZ
+MRMwEQYDVQQIDApNb250ZXZpZGVvMRMwEQYDVQQHDApNb250ZXZpZGVvMRAwDgYD
+VQQKDAdHZW5lWHVzMREwDwYDVQQLDAhTZWN1cml0eTESMBAGA1UEAwwJc2dyYW1w
+b25lMSQwIgYJKoZIhvcNAQkBFhVzZ3JhbXBvbmVAZ2VuZXh1cy5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1zgaU+Wh63p9DNWoAy64252EvZjN4
+9AY3x0QCnAa8JO9Pk7znQwrxEFUKgZzv0GHEYW7+X+uyJr7BW4TA6fuJJ8agE/bm
+ZRZyjdJjoue0FML6fbmCZ9Tsxpxe4pzispyWQ8jYT4Kl4I3fdZNUSn4XSidnDKBI
+SeC05mrcchDKhInpiYDJ481lsB4JTEti3S4Xy/ToKwY4t6attw6z5QDhBc+Yro+Y
+UqruliOAKqcfybe9k07jwMCvFVM1hrYYJ7hwHDSFo3MKwZ0y2gw0w6SgVBxLFo+K
+YP3q63b5wVhD8lzaSh+8UcyiHM2/yjEej7EnRFzdclTSNXRFNaiLnEVdAgMBAAGj
+UDBOMB0GA1UdDgQWBBQtQAWJRWNr/OswPSAdwCQh0Eei/DAfBgNVHSMEGDAWgBQt
+QAWJRWNr/OswPSAdwCQh0Eei/DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
+A4IBAQCjHe3JbNKv0Ywc1zlLacUNWcjLbmzvnjs8Wq5oxtf5wG5PUlhLSYZ9MPhu
+f95PlibnrO/xVY292P5lo4NKhS7VOonpbPQ/PrCMO84Pz1LGfM/wCWQIowh6VHq1
+8PiZka9zbwl6So0tgClKkFSRk4wpKrWX3+M3+Y+D0brd8sEtA6dXeYHDtqV0YgjK
+dZIIOx0vDT4alCoVQrQ1yAIq5INT3cSLgJezIhEadDv3Tc7bMxMFeL+81qHm9Z/9
+/KE6Z+JB0ZEOkF/2NSQJd+Z7MBR8CxOdTQis3ltMoXDatNkjZ2Env40sw4NICB8Y
+YhsWMIarew5uNT+RS28YHNlbmogh
+-----END CERTIFICATE-----
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pfx b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pfx
new file mode 100644
index 000000000..54cc6fb26
Binary files /dev/null and b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pfx differ
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pkcs12 b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pkcs12
new file mode 100644
index 000000000..54cc6fb26
Binary files /dev/null and b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_cert.pkcs12 differ
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_key.pem b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_key.pem
new file mode 100644
index 000000000..35a78f26f
--- /dev/null
+++ b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256_key.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI5Xxbb/ckzykCAggA
+MBQGCCqGSIb3DQMHBAiLR0U2DKvbeQSCBMg5y4x5chPFsx7N081HcFhNVV2xwvIx
+vbRcBeBUhiVaJUuJfRSXzwm16oqV7bZj91IkbePXGwkhHPeBoyy0yrNgsfFJ+t05
+lQU7vKxSFCiTdijGVbp2nxntOb1yRPkc2pA0iSSED00QNHpjDgcpywAKPDmRJSyM
+mWDmrYzQYLTzyFgmcec53B27DXfHoGHBGwq1lfdKGzlphJPDxd+w+2VuAl33Zuq4
+oriu69l4CHcg64vJAa/Y9BkW3HiAytKaEBHHAuE8W2mHYfW4DwZlBur+lo+aUiNV
+4RFOH1xdej/k4feMXsOnYak93gZIbWyr/8MljbQJLNlE+NkheNzAyTT2a9P04JOv
+GUqv45EO9PIdDBemJxCiK4wmeoS/KhJ/zyictOsc1W659lzdCpCIecu0t/1La5b2
+/MWIkdDmqvE4CSNEmGNykLejKKjvGwk9hTsnpckoo44eNjSiElcItd6d0nZr8o9Q
+h8kK8GF0bRjYYqiXoCO5WCO6h6IcJEVmx58/Y07yQ504Djih5eU5TrbPfVcUNcYy
+fWbcai0/X6SSEJXOuvHtx6s+7Y8f3Uo9mh3UI2we4C7zPABvlGTDMRliamcUc4AD
+8BCahJZuOTN1mkYae+G1IpFW3E12z7+C9bQ6tuundntPuc+GJvEw/Clss6dZ5b3w
++Gf+5Kc9PlPu/UxGTUFntN5AB2GGheERtw6UFG+Wf34aHg8uYPXnOYXK1ssnhXlI
+TxbKSUBiirR/LwoXVapepQdLXfVxirW/QVB42dcH0mjtkgSy5k7teaKZV3eZQhid
+Ja9tGq6cMEngzd7BNWA+zzrjn/Jj5DbnnQhYuBr+w82qfiVqc6VYxTCcs2KJK1lU
+LuwXfygNcksvw+7w0tLqUWNsY1M1ZtmnSWiAW76G6ARugmTevYa0WXLAo/N74c96
+ORW0n8ZSQa7zhROJgefbXsuG4brTRnQMAJc+SzH09BRU8m3xeo8bygUGrN/aDyQy
+DZ37pcWwFXlLRRshYoRqCW7v8TDVnCMSM9aujGnlSyK/l/88BCJuiLziHWPiJgvk
+1x68JoEN/fxLh7O3qTYpUiyfVN8LHB6tqqJe97p12BAzo1HX5WSoq2a/oYl4Hqa2
+VfCwiI11NrUpv1EIKfa2yspRk4JVz38dT+KbxB5mz0MgEL8aq/9cJ4ISersTK2UU
+ZJQPgPvJzbnXU4Vh0ON4P7f724eZvTxzGiiJZAIWrqiR7TyEd4J65r3FbBU1xpLU
+tTgrvv38EkA9+qoAG9CjukGTn1k0T8jNC3vxkn4f2nIMn1n9YbvsiVsjzq1UKzRt
+nxHHzMOfKnt092HsO8dtXwZadrpYcoZk9qVx9xbA1RO9rv9CSqLszWThUOT6xXmG
+3F2ff9jXJjHtAog70TCrU2/bXZmZCmuHG78aLtSUSyNSgCc+Lp9CBQmc0QHrqjCS
+7qwHXhdriOElj0fDwuFOk9pXUfc4t4YDWQGxY7+YWnS7RXCye9YI90U/8TyhLBOs
+JZY2DDlOskC0NoIkqHfb2jyHMtW1BzmQIQ0tfKTxTzyg/dOZKJuIWhvpi9LTHDCI
++Er7OrZ0vf91c5FraxPaXatdw4laiNMak9fgWNsp44jIxYHi0zVQLkjQKDuKVoZo
+Vzk=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256d_key.key b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256d_key.key
new file mode 100644
index 000000000..e41d6f252
--- /dev/null
+++ b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256d_key.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAtc4GlPloet6fQzVqAMuuNudhL2YzePQGN8dEApwGvCTvT5O8
+50MK8RBVCoGc79BhxGFu/l/rsia+wVuEwOn7iSfGoBP25mUWco3SY6LntBTC+n25
+gmfU7MacXuKc4rKclkPI2E+CpeCN33WTVEp+F0onZwygSEngtOZq3HIQyoSJ6YmA
+yePNZbAeCUxLYt0uF8v06CsGOLemrbcOs+UA4QXPmK6PmFKq7pYjgCqnH8m3vZNO
+48DArxVTNYa2GCe4cBw0haNzCsGdMtoMNMOkoFQcSxaPimD96ut2+cFYQ/Jc2kof
+vFHMohzNv8oxHo+xJ0Rc3XJU0jV0RTWoi5xFXQIDAQABAoIBAQCT/Go7JXE4YrI8
+4OOyVhkvM9RV4tkPIYNWL+taPGr3BxGNMvLXRClJ5EN00+BNDNAoLC9O/AE8+HDZ
+r4c2CL/o+umhL98P10UYZfzVgasdWLEFeQVh8ubM/TYXvlp55W20muSHvuDX6RtS
+w7/zItfUWVYNeaeWcBxq5Awj+O1WCoBV8OdWT8av5dyCHquD8rngvxD0gKeHvano
+u6fOZN4tsolrB4GWh8B4A08dh4Ktcv/vxOfhzcS9jpT6+Zc5TdriD2fUYsdiw4dB
+a4rghI61xbEyR8OC0ytOK5HhpXnTLcOv2zk8YmxW4HrkLWNV8xvzNPsZT9p4gWwB
+AOGer07BAoGBAOpOy0rIems7JNFvfnROqW3Nx4rDqCiO+q14t/IBEHQrhdODireO
+lrbUIeK9fnbHjuDoI17UTEtPuR5sdoxV4zyXsTIC37b628GlaoPVh3pyi/6o+Ypg
+p+JFWmuCaNVRxWiGPtW/or/ulmP5XhcRp7QJIvbuD0aIEa1K1i0R/ZHlAoGBAMai
+4rIDB0BnKSQxybSNjfh1ICwzdOuT7SpRD/aOlgcAG/OvyJ1Wk6NoJhL5rJTUA2B9
+es3U3M1jB55wcdQdNdiqVA/reQliLzGXbE5zEQ41oXYJucpTfTU/PbeV/Bj76N+l
+o7lwCllo3EaHc0YKOe8/WnIQsjCoRlOcgzsYfI4ZAoGAMeq7cKEpQ3MEMwI3xHuF
+qPjwC+YHCyz6xr4zIgGMCdPD3P3nLZfZD/Y9idqo+JEnJU8PSgKchmbjn1GoJ9mc
+YapHe8oU8xyaeLTO7mstQ67nmEdTcmGJIrF3w/Oydc/H6K7A8DS1bYJc08uqeeuu
++LIBmu24n2QZr/uDiXKNvOkCgYEAuHRlwxxgzYN+hufdANc6cPCC8cjO2DxDzjn8
+ct6xnsqRKlegGctdyi5avOAxTPscL6wWL7FtYSSG3LBaY7jEWfDBow7tFLOiU5Dj
+uG3N9r4Cs5QQfTvOV3Xkn+idc63p8FTmlrreQWzIsI3zk6THa84O9UFf1yNMOzRq
+AKSadXECgYBEXyOgHyU2irRKX197nUdQd862tWONqaVNk7xiOSdv3CLRDaMkZrci
+mA6diNN+vz3ut57iM7N9Yuqv4Qq/U79wBqElSrjy6Qlr+cVTvwT4RrOXcMfNZ1KN
+Pz/h9l/Hce4Oez0GOLn+BffhCF3gHWz6nwfBgVu0V9xQ8eTaQfh4fw==
+-----END RSA PRIVATE KEY-----
diff --git a/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256d_key.pem b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256d_key.pem
new file mode 100644
index 000000000..e41d6f252
--- /dev/null
+++ b/gamutils/src/test/resources/dummycerts/RSA_sha256_2048/sha256d_key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAtc4GlPloet6fQzVqAMuuNudhL2YzePQGN8dEApwGvCTvT5O8
+50MK8RBVCoGc79BhxGFu/l/rsia+wVuEwOn7iSfGoBP25mUWco3SY6LntBTC+n25
+gmfU7MacXuKc4rKclkPI2E+CpeCN33WTVEp+F0onZwygSEngtOZq3HIQyoSJ6YmA
+yePNZbAeCUxLYt0uF8v06CsGOLemrbcOs+UA4QXPmK6PmFKq7pYjgCqnH8m3vZNO
+48DArxVTNYa2GCe4cBw0haNzCsGdMtoMNMOkoFQcSxaPimD96ut2+cFYQ/Jc2kof
+vFHMohzNv8oxHo+xJ0Rc3XJU0jV0RTWoi5xFXQIDAQABAoIBAQCT/Go7JXE4YrI8
+4OOyVhkvM9RV4tkPIYNWL+taPGr3BxGNMvLXRClJ5EN00+BNDNAoLC9O/AE8+HDZ
+r4c2CL/o+umhL98P10UYZfzVgasdWLEFeQVh8ubM/TYXvlp55W20muSHvuDX6RtS
+w7/zItfUWVYNeaeWcBxq5Awj+O1WCoBV8OdWT8av5dyCHquD8rngvxD0gKeHvano
+u6fOZN4tsolrB4GWh8B4A08dh4Ktcv/vxOfhzcS9jpT6+Zc5TdriD2fUYsdiw4dB
+a4rghI61xbEyR8OC0ytOK5HhpXnTLcOv2zk8YmxW4HrkLWNV8xvzNPsZT9p4gWwB
+AOGer07BAoGBAOpOy0rIems7JNFvfnROqW3Nx4rDqCiO+q14t/IBEHQrhdODireO
+lrbUIeK9fnbHjuDoI17UTEtPuR5sdoxV4zyXsTIC37b628GlaoPVh3pyi/6o+Ypg
+p+JFWmuCaNVRxWiGPtW/or/ulmP5XhcRp7QJIvbuD0aIEa1K1i0R/ZHlAoGBAMai
+4rIDB0BnKSQxybSNjfh1ICwzdOuT7SpRD/aOlgcAG/OvyJ1Wk6NoJhL5rJTUA2B9
+es3U3M1jB55wcdQdNdiqVA/reQliLzGXbE5zEQ41oXYJucpTfTU/PbeV/Bj76N+l
+o7lwCllo3EaHc0YKOe8/WnIQsjCoRlOcgzsYfI4ZAoGAMeq7cKEpQ3MEMwI3xHuF
+qPjwC+YHCyz6xr4zIgGMCdPD3P3nLZfZD/Y9idqo+JEnJU8PSgKchmbjn1GoJ9mc
+YapHe8oU8xyaeLTO7mstQ67nmEdTcmGJIrF3w/Oydc/H6K7A8DS1bYJc08uqeeuu
++LIBmu24n2QZr/uDiXKNvOkCgYEAuHRlwxxgzYN+hufdANc6cPCC8cjO2DxDzjn8
+ct6xnsqRKlegGctdyi5avOAxTPscL6wWL7FtYSSG3LBaY7jEWfDBow7tFLOiU5Dj
+uG3N9r4Cs5QQfTvOV3Xkn+idc63p8FTmlrreQWzIsI3zk6THa84O9UFf1yNMOzRq
+AKSadXECgYBEXyOgHyU2irRKX197nUdQd862tWONqaVNk7xiOSdv3CLRDaMkZrci
+mA6diNN+vz3ut57iM7N9Yuqv4Qq/U79wBqElSrjy6Qlr+cVTvwT4RrOXcMfNZ1KN
+Pz/h9l/Hce4Oez0GOLn+BffhCF3gHWz6nwfBgVu0V9xQ8eTaQfh4fw==
+-----END RSA PRIVATE KEY-----
diff --git a/pom.xml b/pom.xml
index 011604a21..52b063d45 100644
--- a/pom.xml
+++ b/pom.xml
@@ -111,6 +111,7 @@
gxcloudstorage-tests
gxobservability
gxcloudstorage-awss3-v2
+ gamutils