diff --git a/sechub-commons-core/build.gradle b/sechub-commons-core/build.gradle index c3a3d14c85..f373ca021d 100644 --- a/sechub-commons-core/build.gradle +++ b/sechub-commons-core/build.gradle @@ -3,6 +3,8 @@ dependencies { api spring_boot_dependency.slf4j_api + implementation 'org.bouncycastle:bcprov-jdk18on:1.73' + testImplementation spring_boot_dependency.junit_jupiter testImplementation spring_boot_dependency.mockito_core diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java index 5be0e7a846..8ce34d1039 100644 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java @@ -10,10 +10,10 @@ import javax.crypto.SecretKey; /** - * Represents a common possiblity to encrypt data. The secret key of an instance - * can unseal and seal objects for example. Be aware: Every crypto access object - * has its own secret key inside! So you need to use the same crypto access - * object for you operations... + * Represents a common possibility to encrypt data. The secret key of an + * instance can unseal and seal objects for example. Be aware: Every crypto + * access object has its own secret key inside! So you need to use the same + * crypto access object for you operations... * * @author Albert Tregnaghi * diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java new file mode 100644 index 0000000000..40e5324c8e --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java @@ -0,0 +1,53 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.util.Arrays; + +/** + * @author Jeremias Eppler + */ +public abstract class AbstractBinaryString implements BinaryString { + byte[] bytes; + + protected AbstractBinaryString(byte[] bytes) { + if (bytes == null) { + throw new IllegalArgumentException("Byte array cannot be null."); + } + + this.bytes = bytes; + } + + protected AbstractBinaryString(String string) { + if (string == null) { + throw new IllegalArgumentException("String cannot be null."); + } + + this.bytes = string.getBytes(); + } + + public abstract String toString(); + + public byte[] getBytes() { + // deep copy + return Arrays.copyOf(bytes, bytes.length); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(bytes); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractBinaryString other = (AbstractBinaryString) obj; + return Arrays.equals(bytes, other.bytes); + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java new file mode 100644 index 0000000000..dbb8926bd4 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java @@ -0,0 +1,141 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** + * Providing access to AES-GCM-SIV + * + * AES-GCM-SIV is a nonce misuse-resistant authenticated encryption algorithm. + * + * For more information refer to + * RFC 8452 + * + * @author Jeremias Eppler + */ +public class AesGcmSiv implements PersistenceCipher { + + private SecretKey secret; + private Provider cryptoProvider; + private PersistenceCipherType cipherType; + + private static final String ALGORITHM = "AES/GCM-SIV/NoPadding"; + + /** + * The recommended initialization vector (iv) for AES-GCM-SIV is 12 bytes or 96 + * bits. + * + * For an explanation have a look at: - + * https://datatracker.ietf.org/doc/html/rfc8452#section-4 - + * https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes + */ + public static final int IV_LENGTH_IN_BYTES = 12; + + public static final int AUTHENTICATION_TAG_LENGTH_IN_BITS = 16 * 8; // 16 bytes (128 bits) + + private AesGcmSiv(SecretKey secret, PersistenceCipherType cipherType) { + this.secret = secret; + this.cipherType = cipherType; + cryptoProvider = new BouncyCastleProvider(); + Security.addProvider(cryptoProvider); + } + + public static AesGcmSiv create(BinaryString secret) throws InvalidKeyException { + AesGcmSiv instance = null; + + byte[] rawSecret = secret.getBytes(); + + if (rawSecret.length == 32 || rawSecret.length == 16) { + PersistenceCipherType cipherType = (rawSecret.length == 32) ? PersistenceCipherType.AES_GCM_SIV_256 : PersistenceCipherType.AES_GCM_SIV_128; + SecretKey secretKey = new SecretKeySpec(rawSecret, 0, rawSecret.length, "AES"); + + instance = new AesGcmSiv(secretKey, cipherType); + } else { + throw new InvalidKeyException("The secret has to be 128 or 256 bits long, but was " + (rawSecret.length * 8) + " bits long."); + } + + return instance; + } + + public BinaryString generateNewInitializationVector() { + return generateNewInitializationVector(BinaryStringEncodingType.BASE64); + } + + public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType) { + byte[] initializationVector = new byte[IV_LENGTH_IN_BYTES]; + + SecureRandom random = new SecureRandom(); + random.nextBytes(initializationVector); + + return BinaryStringFactory.createFromBytes(initializationVector, encodingType); + } + + public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException { + return encrypt(plaintext, initializationVector, BinaryStringEncodingType.BASE64); + } + + public String decrypt(BinaryString ciphertext, BinaryString initializationVector) + throws InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher; + try { + cipher = Cipher.getInstance(ALGORITHM, cryptoProvider); + + SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES"); + + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes()); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); + + byte[] ciphertextBytes = ciphertext.getBytes(); + + byte[] plaintextBytes = cipher.doFinal(ciphertextBytes); + + String plaintext = new String(plaintextBytes); + + return plaintext; + } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) { + throw new IllegalStateException("Decryption not possible, please check the provider", providerException); + } + } + + @Override + public PersistenceCipherType getCipherType() { + return cipherType; + } + + @Override + public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType) + throws InvalidAlgorithmParameterException, InvalidKeyException { + try { + Cipher cipher = Cipher.getInstance(ALGORITHM, cryptoProvider); + + SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES"); + + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes()); + + cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); + + byte[] ciphertext = cipher.doFinal(plaintext.getBytes()); + + return BinaryStringFactory.createFromBytes(ciphertext, encodingType); + } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) { + throw new IllegalStateException("Encryption not possible, please check the provider", providerException); + } catch (BadPaddingException | IllegalBlockSizeException paddingBlockException) { + throw new IllegalStateException("Should not occure. AES in GCM-SIV mode does not require padding.", paddingBlockException); + } + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java new file mode 100644 index 0000000000..bed757bb45 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java @@ -0,0 +1,50 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.util.Base64; + +/** + * A base64 encoded string. + * + * @author Jeremias Eppler + * + */ +public class Base64String extends AbstractBinaryString { + + Base64String(byte[] bytes) { + super(bytes); + } + + Base64String(String string) { + super(string); + } + + @Override + public String toString() { + return Base64.getEncoder().encodeToString(bytes); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + java.util.Arrays.hashCode(bytes); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Base64String other = (Base64String) obj; + return java.util.Arrays.equals(bytes, other.bytes); + } + + @Override + public BinaryStringEncodingType getType() { + return BinaryStringEncodingType.BASE64; + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java new file mode 100644 index 0000000000..2a3446ad7e --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java @@ -0,0 +1,19 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +/** + * A binary string type which knows it's encoding. + * + * The binary string keeps it's internal representation in binary format. In + * addition, the binary string knows it's encoding (e.g. base64 etc.). + * + * @see BinaryStringEncodingType + * + * @author Jeremias Eppler + */ +public interface BinaryString { + public byte[] getBytes(); + + public String toString(); + + public BinaryStringEncodingType getType(); +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java new file mode 100644 index 0000000000..ac1a3e4219 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java @@ -0,0 +1,25 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +/** + * Encoding type of a binary string. + * + * @see BinaryString + * + * @author Jeremias Eppler + */ +public enum BinaryStringEncodingType { + /** + * A string with no encoding. + */ + PLAIN, + + /** + * A string encoded in hexadecimal format + */ + HEX, + + /** + * A string encoded in base64 format + */ + BASE64, +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java new file mode 100644 index 0000000000..828969b3b3 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java @@ -0,0 +1,170 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.util.Base64; +import java.util.HexFormat; + +/** + * Creates {@link BinaryString} differently encoded strings. + * + * It can create {@link BinaryString} from differently encoded strings and + * returns a sub-type of {@link BinaryString} based on the desired output + * encoding. + * + * @author Jeremias Eppler + */ +public class BinaryStringFactory { + private static final BinaryStringEncodingType DEFAULT_ENCODING_TYPE = BinaryStringEncodingType.BASE64; + + public static BinaryString createFromBytes(byte[] bytes) { + return createFromBytes(bytes, DEFAULT_ENCODING_TYPE); + } + + public static BinaryString createFromBytes(byte[] bytes, BinaryStringEncodingType encodingType) { + if (bytes == null) { + throw new IllegalArgumentException("String cannot be null."); + } + + BinaryString binaryString; + + switch (encodingType) { + case BASE64: + binaryString = new Base64String(bytes); + break; + case HEX: + binaryString = new HexString(bytes); + break; + case PLAIN: + binaryString = new PlainString(bytes); + break; + default: + throw new IllegalArgumentException("Unknown binary string type"); + } + + return binaryString; + } + + /** + * Creates a {@link BinaryString} from a plain Java string. + * + * The BinaryString will be in the default encoding. + * + * @param string + * @return + */ + public static BinaryString createFromString(String string) { + return createFromString(string, DEFAULT_ENCODING_TYPE); + } + + /** + * Creates a {@link BinaryString} from a plain Java string. + * + * It returns the string in the specified encoding. + * + * @param string + * @param encodingType + * @return + */ + public static BinaryString createFromString(String string, BinaryStringEncodingType encodingType) { + if (string == null) { + throw new IllegalArgumentException("String cannot be null."); + } + + BinaryString binaryString; + + switch (encodingType) { + case BASE64: + binaryString = new Base64String(string); + break; + case HEX: + binaryString = new HexString(string); + break; + case PLAIN: + binaryString = new PlainString(string); + break; + default: + throw new IllegalArgumentException("Unknown binary string type"); + } + + return binaryString; + } + + /** + * Create a new {@link BinaryString} from a string which is in hexadecimal + * encoding. + * + * The given string needs to be hexadecimal encoded. + * + * For example: Given the string "616263" (orginal value "abc") it will decode + * it and store it as bytes. + * + * @param stringInHexFormat + * @param encodingType + * @return + */ + public static BinaryString createFromHex(String stringInHexFormat) { + return createFromHex(stringInHexFormat, DEFAULT_ENCODING_TYPE); + } + + /** + * Create a new {@link BinaryString} from a string which is in hexadecimal + * encoding. + * + * The given string needs to be hexadecimal encoded. + * + * For example: Given the string "616263" (orginal value "abc") it will decode + * it and store it as bytes. + * + * @param stringInHexFormat + * @param encodingType + * @return + */ + public static BinaryString createFromHex(String stringInHexFormat, BinaryStringEncodingType encodingType) { + if (stringInHexFormat == null) { + throw new IllegalArgumentException("String cannot be null."); + } + + HexFormat hexFormat = HexFormat.of(); + byte[] hexBytes = hexFormat.parseHex(stringInHexFormat); + + return createFromBytes(hexBytes, encodingType); + } + + /** + * Create a new {@link BinaryString} from a string which is base64 encoded. + * + * The given string needs to be base64 encoded. + * + * For example: Given the string "YWJj" (orginal value "abc") it will decode it + * and store it as bytes. + * + * @param stringInBase64Format + * @return + */ + public static BinaryString createFromBase64(String stringInBase64Format) { + return createFromBase64(stringInBase64Format, DEFAULT_ENCODING_TYPE); + } + + /** + * Create a new {@link BinaryString} from a string which is base64 encoded. + * + * The given string needs to be base64 encoded. + * + * For example: Given the string "YWJj" (orginal value "abc") it will decode it + * and store it as bytes. + * + * @param stringInBase64Format + * @param encodingType + * @return + */ + public static BinaryString createFromBase64(String stringInBase64Format, BinaryStringEncodingType encodingType) { + if (stringInBase64Format == null) { + throw new IllegalArgumentException("String cannot be null."); + } + + // if it cannot be decoded, + // it will throw an IllegalArgumentException + byte[] decoded = Base64.getDecoder().decode(stringInBase64Format); + + return createFromBytes(decoded, encodingType); + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java new file mode 100644 index 0000000000..62e7c28940 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java @@ -0,0 +1,32 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.util.HexFormat; + +/** + * Hexadecimal encoded string. + * + * @author Jeremias Eppler + */ +public class HexString extends AbstractBinaryString { + + protected HexFormat hexFormat = HexFormat.of(); + + HexString(byte[] bytes) { + super(bytes); + } + + HexString(String string) { + super(string); + } + + @Override + public String toString() { + return hexFormat.formatHex(bytes); + } + + @Override + public BinaryStringEncodingType getType() { + return BinaryStringEncodingType.HEX; + } + +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java new file mode 100644 index 0000000000..c1dd175fc6 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java @@ -0,0 +1,59 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; + +/** + * A cipher which does not encrypt anything. + * + * It can be used during testing or debugging. + * + * Furthermore, this cipher can be used to change the encoding of the data. For + * example, from plain to Base64. + * + * @author Jeremias Eppler + */ +public class NoneCipher implements PersistenceCipher { + private NoneCipher() { + } + + @Override + public PersistenceCipherType getCipherType() { + return PersistenceCipherType.NONE; + } + + public static PersistenceCipher create(BinaryString secret) throws InvalidKeyException { + return new NoneCipher(); + } + + @Override + public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException { + return encrypt(plaintext, initializationVector, BinaryStringEncodingType.PLAIN); + } + + @Override + public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType) + throws InvalidAlgorithmParameterException, InvalidKeyException { + return BinaryStringFactory.createFromString(plaintext, encodingType); + } + + @Override + public String decrypt(BinaryString ciphertext, BinaryString initializationVector) + throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + + return ciphertext.toString(); + } + + @Override + public BinaryString generateNewInitializationVector() { + return generateNewInitializationVector(BinaryStringEncodingType.PLAIN); + } + + @Override + public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType) { + return BinaryStringFactory.createFromString("", encodingType); + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java new file mode 100644 index 0000000000..286a56b472 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java @@ -0,0 +1,35 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; + +/** + * Interface for cryptographic algorithms used to protect data at rest. + * + * "At rest" refers to data which is usually stored in a database, file or other + * persistent storage. + * + * @author Jeremias Eppler + */ +public interface PersistenceCipher { + public static PersistenceCipher create(BinaryString secret) throws InvalidKeyException { + return null; + } + + public BinaryString generateNewInitializationVector(); + + public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType); + + public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException; + + public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType) + throws InvalidAlgorithmParameterException, InvalidKeyException; + + public String decrypt(BinaryString ciphertext, BinaryString initializationVector) + throws IllegalArgumentException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException; + + public PersistenceCipherType getCipherType(); +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java new file mode 100644 index 0000000000..ee77c3ada5 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java @@ -0,0 +1,43 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.security.InvalidKeyException; + +/** + * The factory creates persistent ciphers used to protect data at rest. + * + * The algorithms protect both the confidentiality and integrity of the + * information at rest. + * + * In addition, the ciphers which it creates are nonce (initialization vector) + * misuse-resistant. + * + * @author Jeremias Eppler + */ +public class PersistenceCipherFactory { + + /** + * Creates a new persistent cipher based on the type and secret. + * + * @param cipherType + * @param secret + * @return + * @throws InvalidKeyException + */ + public static PersistenceCipher create(PersistenceCipherType cipherType, BinaryString secret) throws InvalidKeyException { + PersistenceCipher cipher = null; + + switch (cipherType) { + case NONE: + cipher = NoneCipher.create(secret); + break; + case AES_GCM_SIV_256: + case AES_GCM_SIV_128: + cipher = AesGcmSiv.create(secret); + break; + default: + throw new IllegalArgumentException("Unable to create cipher. Unknown cipher."); + } + + return cipher; + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java new file mode 100644 index 0000000000..2f80ba26d4 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java @@ -0,0 +1,37 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +/** + * Contains the available ciphers. + * + * @author Jeremias Eppler + */ +public enum PersistenceCipherType { + /** + * A special cipher type which does not protect data. + * + * This is intended for testing. + */ + NONE, + + /** + * Advanced Encryption Standard (AES) 128 bit key (secret) in Galois/Counter + * Mode (GCM) and synthetic initialization vector (SIV). + * + * AES GCM-SIV is a nonce (initialization vector) misuse-resistant authenticated + * encryption. + * + * @see https://datatracker.ietf.org/doc/html/rfc8452 + */ + AES_GCM_SIV_128, + + /** + * Advanced Encryption Standard (AES) 256 bit key (secret) in Galois/Counter + * Mode (GCM) and synthetic initialization vector (SIV). + * + * AES GCM-SIV is a nonce (initialization vector) misuse-resistant authenticated + * encryption. + * + * @see https://datatracker.ietf.org/doc/html/rfc8452 + */ + AES_GCM_SIV_256; +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java new file mode 100644 index 0000000000..bec4545519 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java @@ -0,0 +1,36 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +/** + * A wrapper around the string class. + * + * The string class in Java is final and therefore it is impossible to inherited + * from. + * + * @author Jeremias Eppler + */ +public class PlainString extends AbstractBinaryString { + + PlainString(byte[] bytes) { + super(bytes); + } + + PlainString(String string) { + super(string); + } + + @Override + public byte[] getBytes() { + return bytes; + } + + @Override + public BinaryStringEncodingType getType() { + return BinaryStringEncodingType.PLAIN; + } + + @Override + public String toString() { + return new String(bytes); + } + +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java new file mode 100644 index 0000000000..04659d256b --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java @@ -0,0 +1,198 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; + +/** + * The rotation strategy helps to rotate the cipher text. + * + * It can be used to rotate (re-encrpyt) by using: - different initialization + * vectors - different secret keys - different algorithms + * + * or a combination of the above. + * + * @author Jeremias Eppler + */ +public class RotationStrategy { + private PersistenceCipher currentCipher; + private PersistenceCipher newCipher; + private boolean performSecretRotation = false; + + private RotationStrategy(PersistenceCipher currentCipher, PersistenceCipher newCipher, boolean performSecretRotation) { + this.currentCipher = currentCipher; + this.newCipher = newCipher; + this.performSecretRotation = performSecretRotation; + } + + /** + * Create a new rotation strategy which only allows to rotate the initialization + * vector. + * + * @param secret + * @param cipher + * @return + * @throws InvalidKeyException + */ + public static RotationStrategy createInitializationVectorOnlyRotationStrategy(BinaryString secret, PersistenceCipherType cipher) + throws InvalidKeyException { + PersistenceCipher currentCipher = PersistenceCipherFactory.create(cipher, secret); + PersistenceCipher newCipher = PersistenceCipherFactory.create(cipher, secret); + + boolean performSecretRotation = false; + + return new RotationStrategy(currentCipher, newCipher, performSecretRotation); + } + + /** + * Create a new rotation strategy which allows to rotate the secret. + * + * This is useful in case of a secret leak. + * + * @param currentSecret + * @param newSecret + * @param cipher + * @return + * @throws InvalidKeyException + */ + public static RotationStrategy createSecretRotationStrategy(BinaryString currentSecret, BinaryString newSecret, PersistenceCipherType cipher) + throws InvalidKeyException { + PersistenceCipher currentCipher = PersistenceCipherFactory.create(cipher, currentSecret); + PersistenceCipher newCipher = PersistenceCipherFactory.create(cipher, newSecret); + + boolean performSecretRotation = true; + + return new RotationStrategy(currentCipher, newCipher, performSecretRotation); + } + + /** + * Create a new rotation strategy which allows to rotate the secret and ciphers. + * + * This is useful if the underling cryptographic cipher or mode of operation is + * deemed as insecure. + * + * For example, this is the case with Data Encryption Standard (DES) and the + * Triple DES variant. + * + * + * @param currentSecret + * @param newSecret + * @param currentCipherType + * @param newCipherType + * @return + * @throws InvalidKeyException + */ + public static RotationStrategy createCipherAndSecretRotationStrategy(BinaryString currentSecret, BinaryString newSecret, + PersistenceCipherType currentCipherType, PersistenceCipherType newCipherType) throws InvalidKeyException { + PersistenceCipher currentCipher = PersistenceCipherFactory.create(currentCipherType, currentSecret); + PersistenceCipher newCipher = PersistenceCipherFactory.create(newCipherType, newSecret); + + boolean performSecretRotation = true; + + return new RotationStrategy(currentCipher, newCipher, performSecretRotation); + } + + /** + * Rotate the encrypted cipher text using the same initialization vector. + * + * The given cipher text is decrypted and encrypted again using the given + * initialization vector the old and the new cipher text. + * + * @param cipherText + * @param initializationVector + * @return + * @throws InvalidKeyException + * @throws IllegalArgumentException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector) + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + return rotate(cipherText, initializationVector, null, cipherText.getType()); + } + + /** + * Rotate the encrypted cipher text using different initialization vectors. + * + * The given cipher text is decrypted and encrypted again using two different + * initialization vectors. + * + * @param cipherText + * @param initializationVector + * @param newIntializationVector + * @return + * @throws InvalidKeyException + * @throws IllegalArgumentException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector, BinaryString newIntializationVector) + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + return rotate(cipherText, initializationVector, newIntializationVector, cipherText.getType()); + } + + /** + * Rotate the encrypted cipher text using different initialization vectors and + * the given encoding type. + * + * The given cipher text is decrypted and encrypted again using two different + * initialization vectors. + * + * The resulting cipher text is returned in the specified + * {@link BinaryStringEncodingType}. + * + * @param cipherText + * @param initializationVector + * @param newIntializationVector + * @param newBinaryStringEncoding + * @return + * @throws InvalidKeyException + * @throws IllegalArgumentException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector, BinaryString newIntializationVector, + BinaryStringEncodingType newBinaryStringEncoding) + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + if (cipherText == null) { + throw new IllegalArgumentException("The ciphertext cannot be null!"); + } + + if (initializationVector == null) { + throw new IllegalArgumentException("The initialization vector (nonce) cannot be null!"); + } + + String plainText = currentCipher.decrypt(cipherText, initializationVector); + + BinaryString newCipherText = null; + + if (newIntializationVector != null) { + newCipherText = newCipher.encrypt(plainText, newIntializationVector); + } else { + newCipherText = newCipher.encrypt(plainText, initializationVector); + } + + return newCipherText; + } + + public PersistenceCipherType getCurrentCipher() { + return currentCipher.getCipherType(); + } + + public PersistenceCipherType getNewCipher() { + return newCipher.getCipherType(); + } + + public boolean isSecretRotationStrategy() { + return performSecretRotation; + } + + public boolean isCipherRotationStrategy() { + return currentCipher.getCipherType() != newCipher.getCipherType(); + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java new file mode 100644 index 0000000000..fbd13b6983 --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java @@ -0,0 +1,79 @@ +/** + * This package contains classes to protect data at-rest. + * + * The package is named persistence, because the persistence layer is used to + * help store data in a database or other data storage systems. + * + * The requirements for the cryptographic algorithms for data at rest are: + * + * + * + * Authenticated encryption (AE) provides confidentiality and integrity at the + * same time. Confidentiality is provided by encrypting and integrity by hashing + * the data. If during decryption, the hash and the data do not match, the data + * has been corrupted or tampered with. + * + * Because AE algorithms do both, encrypt and hash at the same time, it is + * difficult to use them incorrectly. + * + * Furthermore, it is impossible to ensure a nonce (initialization vector) is + * not used more than once in a multi-server database scenario, therefore a + * nonce misuse-resistant algorithm is required. + * + * In theory one could make sure, that there is a single data entry point which + * keeps track of the used nonces. Before writing the data to the database, the + * single data entry point generates a nonce and looks into the list of used + * nonces whether the nonce is already used or not. If the nonce is already used + * the single data entry point needs to generate a nonce until the nonce is + * unique and has never been used before. The consequence would be, that data + * cannot be written in parallel, which is unacceptable in the case of a + * distributed application, as it would slow down the rate at which data can be + * inserted into the data storage (e.g. database). + * + * To avoid those problems, a nonce misuse-resistant algorithm is required. A + * nonce misuse-algorithm provides security even if the same combination of + * secret key, additional data, nonce and plain text is encrypted and stored + * more than once. + * + * There are a number of authenticated encryption algorithms. However only a few + * are nonce misuse-resistant. Examples for misuse resistant algorithms are: + * + * + * + * Furthermore, symmetric encryption algorithms are not in danger to be + * completely broken by quantum computers (see: + * https://security.stackexchange.com/a/103560). However, it might be necessary + * to use different cryptographic algorithms in the future as attacks on + * existing algorithms become possible. + * + * Bruce Schneier puts it this way: + * + * > "In the face of all that uncertainty, agility is the only way to maintain + * security." (source: + * https://www.schneier.com/essays/archives/2022/08/nists-post-quantum-cryptography-standards-competition.html) + * + * This package also contains classes which can help with algorithm, secret and + * initialization vector rotation: + * + * + * + * @author Jeremias Eppler + */ +package com.mercedesbenz.sechub.commons.core.security.persistence; \ No newline at end of file diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java new file mode 100644 index 0000000000..d196a1c772 --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java @@ -0,0 +1,376 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.*; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.junit.jupiter.api.Test; + +public class AesGcmSivTest { + private static final String LONG_TEXT = "Hello world, this is long text with different emojis. Today, I had for breakfast two πŸ₯, 1 πŸ₯‘ and some πŸ₯ͺ. That made me happy ☺️!"; + + @Test + void generate_new_initialization_vector() throws InvalidKeyException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(32)); + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + BinaryString initializationVector = cipher.generateNewInitializationVector(); + + /* test */ + assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length); + } + + @Test + void create_secret_32_bytes() throws InvalidKeyException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(32)); + + /* execute */ + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* test */ + assertNotNull(cipher); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); + } + + @Test + void create_secret_16_bytes() throws InvalidKeyException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(16)); + + /* execute */ + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* test */ + assertNotNull(cipher); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); + } + + @Test + void create_secret_secret_6_bytes_invalid() { + /* prepare */ + BinaryString secret = new Base64String("abcdef"); + + /* execute + test */ + assertThrows(InvalidKeyException.class, () -> { + AesGcmSiv.create(secret); + }); + } + + @Test + void create_secret_secret_31_bytes_invalid() { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(31)); + + /* execute + test */ + assertThrows(InvalidKeyException.class, () -> { + AesGcmSiv.create(secret); + }); + } + + @Test + void encrypt__aes_256() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException { + + /* prepare */ + BinaryString secret = new Base64String("w".repeat(32)); + String plaintext = "bca"; + String expectedCiphertext = "1qKKtEpM2ppl4wWrJxJo0MiFdw=="; + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); + + /* test */ + assertEquals(expectedCiphertext, ciphertext.toString()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); + } + + @Test + void encrypt__aes_256_initialization_vector_too_short() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + + /* prepare */ + BinaryString secret = new Base64String("w".repeat(32)); + String plaintext = "bca"; + BinaryString initializationVector = new Base64String("abc".repeat(2)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + Exception exception = assertThrows(InvalidAlgorithmParameterException.class, () -> { + cipher.encrypt(plaintext, initializationVector); + }); + + /* test */ + assertEquals("Invalid nonce", exception.getMessage()); + } + + @Test + void encrypt__aes_256_initialization_vector_too_long() throws InvalidKeyException, InvalidAlgorithmParameterException { + + /* prepare */ + BinaryString secret = new Base64String("w".repeat(32)); + String plaintext = "bca"; + BinaryString initializationVector = new Base64String("abc".repeat(50)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + Exception exception = assertThrows(InvalidAlgorithmParameterException.class, () -> { + cipher.encrypt(plaintext, initializationVector); + }); + + /* test */ + assertEquals("Invalid nonce", exception.getMessage()); + } + + @Test + void decrypt__aes_256() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException { + /* prepare */ + BinaryString secret = new Base64String("w".repeat(32)); + String expectedPlaintext = "bca"; + BinaryString ciphertext = BinaryStringFactory.createFromBase64("1qKKtEpM2ppl4wWrJxJo0MiFdw==", BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + String plaintext = cipher.decrypt(ciphertext, initializationVector); + + /* test */ + assertEquals(expectedPlaintext, plaintext); + } + + @Test + void encrypt__aes_128() throws InvalidKeyException, InvalidAlgorithmParameterException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(16)); + String plaintext = "bca"; + String expectedCiphertext = "yGcKhuWbewS+R4tlegECshiTSQ=="; + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); + + /* test */ + assertEquals(expectedCiphertext, ciphertext.toString()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); + } + + @Test + void encrypt__aes_256_hex_format_and_emojis() throws InvalidKeyException, InvalidAlgorithmParameterException { + /* prepare */ + BinaryString secret = new HexString("πŸ₯¦πŸ₯•πŸ₯”πŸ«˜πŸ₯’πŸ«‘πŸŒ½πŸ†"); + String plaintext = "Hello πŸ‘‹, welcome to 🌐."; + String expectedCiphertext = "d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6"; + BinaryString initializationVector = new Base64String("πŸ§…".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector, BinaryStringEncodingType.HEX); + + /* test */ + assertEquals(expectedCiphertext, ciphertext.toString()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); + } + + @Test + void encrypt__aes_128_base64_format_and_emojis() throws InvalidKeyException, InvalidAlgorithmParameterException { + /* prepare */ + BinaryString secret = new Base64String("πŸπŸŒπŸ“πŸ‰"); + + String plaintext = "Hello πŸ‘‹, welcome to 🌐."; + String expectedCiphertext = "Qu7ICJBGMw9dAPPBWx86e5bjOq3YKC+x25n/YkluWZAGdSna08tKaE78pMk="; + BinaryString initializationVector = new Base64String("πŸ§…".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector, BinaryStringEncodingType.BASE64); + + /* test */ + assertEquals(expectedCiphertext, ciphertext.toString()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); + } + + @Test + void decrypt__aes_128() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(16)); + String expectedPlaintext = "bca"; + BinaryString cipherText = BinaryStringFactory.createFromBase64("yGcKhuWbewS+R4tlegECshiTSQ==", BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + String plaintext = cipher.decrypt(cipherText, initializationVector); + + /* test */ + assertEquals(expectedPlaintext, plaintext); + } + + @Test + void decrypt__aes_128_wrong_cipher_text() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(16)); + BinaryString ciphertext = new Base64String("hello world, this is base 64 encoded"); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + Exception exception = assertThrows(AEADBadTagException.class, () -> { + cipher.decrypt(ciphertext, initializationVector); + }); + + /* test */ + assertEquals("mac check failed", exception.getMessage()); + } + + @Test + void decrypt__aes_256_wrong_cipher_text() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException { + + /* prepare */ + BinaryString secret = new Base64String("a".repeat(32)); + BinaryString ciphertext = new Base64String("hello world, this is base 64 encoded"); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + Exception exception = assertThrows(AEADBadTagException.class, () -> { + cipher.decrypt(ciphertext, initializationVector); + }); + + /* test */ + assertEquals("mac check failed", exception.getMessage()); + } + + @Test + void encrypt__aes_128_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + + /* prepare */ + BinaryString secret = new Base64String("a".repeat(16)); + String plaintext = LONG_TEXT; + BinaryString expectedCiphertext = BinaryStringFactory.createFromBase64( + "28/RdEWgYbpbiraiWcSo58+8sfCRRQpSoZiiFqNsYN8tLLVE6AXeQjxh4zazK65G7T0dmFnqrbyx6aRUB+7I6guFXMxqjRij9HdRkae4OalWZVNtCs2+mjBBMNOB5Ke2bgIcYDZbDMRWceBtJnE5PKg7vxrNFgR+8uFw9ejbRVxzGTbkyNeh48QVT9Knk7LpmqQ/eHFTvsvnD0M=", + BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); + + /* test */ + assertEquals(expectedCiphertext, ciphertext); + } + + @Test + void encrypt__aes_256_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + + /* prepare */ + BinaryString secret = new Base64String("a".repeat(32)); + String plaintext = LONG_TEXT; + BinaryString expectedCiphertext = BinaryStringFactory.createFromBase64( + "E2RrqhXtKG39okWxxvw3d4NQ+DXr2+Qa78JvdpHS4+FOckRECTkjoX2JfZNKHP3on0sDO1q8uTc+BY9QJkMK+MsWzp8YT4SR0UxWo7uy5SSPMXOLLcQg0vzTOTdgo00vPQy34vogNYO1V/TTzOzzP6Ng0kT9TDsYUWu+v0y3uZw/ujl2X8bP8Nfrp2ZRMrgfpj7NbjQQd8hD5AY=", + BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); + + /* test */ + assertEquals(expectedCiphertext, ciphertext); + } + + @Test + void decrypt__aes_128_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + + /* prepare */ + BinaryString secret = new Base64String("a".repeat(16)); + String expectedPlaintext = LONG_TEXT; + BinaryString ciphertext = BinaryStringFactory.createFromBase64( + "28/RdEWgYbpbiraiWcSo58+8sfCRRQpSoZiiFqNsYN8tLLVE6AXeQjxh4zazK65G7T0dmFnqrbyx6aRUB+7I6guFXMxqjRij9HdRkae4OalWZVNtCs2+mjBBMNOB5Ke2bgIcYDZbDMRWceBtJnE5PKg7vxrNFgR+8uFw9ejbRVxzGTbkyNeh48QVT9Knk7LpmqQ/eHFTvsvnD0M=", + BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + String plaintext = cipher.decrypt(ciphertext, initializationVector); + + /* test */ + assertEquals(expectedPlaintext, plaintext); + } + + @Test + void decrypt__aes_256_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + + /* prepare */ + BinaryString secret = new Base64String("a".repeat(32)); + String expectedPlaintext = LONG_TEXT; + BinaryString ciphertext = BinaryStringFactory.createFromBase64( + "E2RrqhXtKG39okWxxvw3d4NQ+DXr2+Qa78JvdpHS4+FOckRECTkjoX2JfZNKHP3on0sDO1q8uTc+BY9QJkMK+MsWzp8YT4SR0UxWo7uy5SSPMXOLLcQg0vzTOTdgo00vPQy34vogNYO1V/TTzOzzP6Ng0kT9TDsYUWu+v0y3uZw/ujl2X8bP8Nfrp2ZRMrgfpj7NbjQQd8hD5AY=", + BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* execute */ + String plaintext = cipher.decrypt(ciphertext, initializationVector); + + /* test */ + assertEquals(expectedPlaintext, plaintext); + } + + @Test + void getCipherType_aes_256() throws InvalidKeyException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(32)); + + /* execute */ + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* test */ + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); + } + + @Test + void getCiphersType_aes_128() throws InvalidKeyException { + /* prepare */ + BinaryString secret = new Base64String("a".repeat(16)); + + /* execute */ + AesGcmSiv cipher = AesGcmSiv.create(secret); + + /* test */ + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java new file mode 100644 index 0000000000..d9a1800d60 --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java @@ -0,0 +1,93 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class Base64StringTest { + @Test + void from_string() { + /* prepare */ + String string = "Hello"; + String expectedString = "SGVsbG8="; + + /* execute + test */ + assertEquals(expectedString, new Base64String(string).toString()); + } + + @Test + void from_string_null_throw_illegal_argument_exception() { + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + new Base64String((String) null); + }); + + /* test */ + assertEquals("String cannot be null.", exception.getMessage()); + } + + @Test + void from_string_unicode() { + String string = "Hello πŸ¦„"; + String expectedString = "SGVsbG8g8J+mhA=="; + + assertEquals(expectedString, new Base64String(string).toString()); + } + + @Test + void from_bytes() { + byte[] bytes = "Hello".getBytes(); + String expectedString = "SGVsbG8="; + + assertEquals(expectedString, new Base64String(bytes).toString()); + } + + @Test + void from_bytes_unicode() { + byte[] bytes = "Hello πŸ¦„".getBytes(); + String expectedString = "SGVsbG8g8J+mhA=="; + + assertEquals(expectedString, new Base64String(bytes).toString()); + } + + @Test + void from_bytes_null_throw_illegal_argument_exception() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + new Base64String((byte[]) null); + }); + + assertEquals("Byte array cannot be null.", exception.getMessage()); + } + + @Test + void toString_unicode_test() { + String string = "Hello πŸ¦„"; + String expectedString = "SGVsbG8g8J+mhA=="; + + assertEquals(expectedString, new Base64String(string).toString()); + } + + @Test + void equals_test() { + String string = "I like 🍍"; + + Base64String b64String = new Base64String(string); + Base64String b64String2 = new Base64String(string); + + assertEquals(b64String, b64String2); + assertTrue(b64String.equals(b64String2)); + assertEquals(b64String.hashCode(), b64String2.hashCode()); + } + + @Test + void test_immutablitiy() { + String string = "A 🐺 in a πŸ‘ skin"; + Base64String b64String = new Base64String(string); + + assertFalse(string == b64String.toString()); + assertFalse(string.getBytes() == b64String.getBytes()); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java new file mode 100644 index 0000000000..314127b636 --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java @@ -0,0 +1,270 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class BinaryStringFactoryTest { + @Test + void create_from_string_no_type_given() { + /* prepare */ + String string = "hello"; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromString(string); + + /* test */ + assertNotNull(binaryString); + assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64); + assertTrue(binaryString instanceof Base64String); + assertEquals("aGVsbG8=", binaryString.toString()); + } + + @Test + void create_from_string_base64_type_given() { + /* prepare */ + String string = "Hello 🌌!"; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.BASE64); + + /* test */ + assertNotNull(binaryString); + assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64); + assertTrue(binaryString instanceof Base64String); + assertEquals("SGVsbG8g8J+MjCE=", binaryString.toString()); + } + + @Test + void create_from_base64_no_type_given() { + /* prepare */ + String string = "SGVsbG8g8J+MjCE="; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromBase64(string); + + /* test */ + assertNotNull(binaryString); + assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64); + assertTrue(binaryString instanceof Base64String); + assertEquals("SGVsbG8g8J+MjCE=", binaryString.toString()); + } + + @Test + void createFromBase64_null_input() { + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + BinaryStringFactory.createFromBase64(null); + }); + + /* test */ + assertEquals("String cannot be null.", exception.getMessage()); + } + + @Test + void createFromHex_null_input() { + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + BinaryStringFactory.createFromHex(null); + }); + + /* test */ + assertEquals("String cannot be null.", exception.getMessage()); + } + + @Test + void create_from_string_plain_type_given() { + /* prepare */ + String string = "hello"; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.PLAIN); + + /* test */ + assertNotNull(binaryString); + assertEquals(binaryString.getType(), BinaryStringEncodingType.PLAIN); + assertTrue(binaryString instanceof PlainString); + assertEquals("hello", binaryString.toString()); + } + + @Test + void create_from_string_hex_type_given() { + /* prepare */ + String string = "hello"; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.HEX); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.HEX, binaryString.getType()); + assertTrue(binaryString instanceof HexString); + assertEquals("68656c6c6f", binaryString.toString()); + } + + @Test + void create_from_string_no_type_given_and_input_null() { + /* prepare */ + String string = null; + + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + BinaryStringFactory.createFromString(string); + }); + + /* test */ + assertEquals("String cannot be null.", exception.getMessage()); + } + + @Test + void create_from_bytes_no_type_given() { + /* prepare */ + byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); + assertTrue(binaryString instanceof Base64String); + assertEquals("aGVsbG8=", binaryString.toString()); + } + + @Test + void create_from_bytes_base64_type_given() { + /* prepare */ + byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.BASE64); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); + assertTrue(binaryString instanceof Base64String); + assertEquals("aGVsbG8=", binaryString.toString()); + } + + @Test + void create_from_bytes_plain_type_given() { + /* prepare */ + byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.PLAIN); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType()); + assertTrue(binaryString instanceof PlainString); + assertEquals("hello", binaryString.toString()); + } + + @Test + void create_from_bytes_hex_type_given() { + /* prepare */ + byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.HEX); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.HEX, binaryString.getType()); + assertTrue(binaryString instanceof HexString); + assertEquals("68656c6c6f", binaryString.toString()); + } + + @Test + void create_from_bytes_no_type_given_and_input_null() { + /* prepare */ + byte[] bytes = null; + + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + BinaryStringFactory.createFromBytes(bytes); + }); + + /* test */ + assertEquals("String cannot be null.", exception.getMessage()); + } + + @Test + void createFromHex_from_string_plain_type_given() { + /* prepare */ + String string = "68656c6c6f"; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromHex(string, BinaryStringEncodingType.PLAIN); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType()); + assertTrue(binaryString instanceof PlainString); + assertEquals("hello", binaryString.toString()); + } + + @Test + void createFromHex_base64_type_given() { + /* prepare */ + String string = "68656c6c6f"; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromHex(string, BinaryStringEncodingType.BASE64); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); + assertTrue(binaryString instanceof Base64String); + assertEquals("aGVsbG8=", binaryString.toString()); + } + + @Test + void createFromHex_no_type_given() { + /* prepare */ + String string = "68656c6c6f"; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromHex(string); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); + assertTrue(binaryString instanceof Base64String); + assertEquals("aGVsbG8=", binaryString.toString()); + } + + @Test + void createFromBase64_plain_type_given() { + /* prepare */ + String string = "aGVsbG8="; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromBase64(string, BinaryStringEncodingType.PLAIN); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType()); + assertTrue(binaryString instanceof PlainString); + assertEquals("hello", binaryString.toString()); + } + + @Test + void createFromBase64_hex_type_given() { + /* prepare */ + String string = "aGVsbG8="; + + /* execute */ + BinaryString binaryString = BinaryStringFactory.createFromBase64(string, BinaryStringEncodingType.HEX); + + /* test */ + assertNotNull(binaryString); + assertEquals(BinaryStringEncodingType.HEX, binaryString.getType()); + assertTrue(binaryString instanceof HexString); + assertEquals("68656c6c6f", binaryString.toString()); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java new file mode 100644 index 0000000000..2723e8e9fe --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java @@ -0,0 +1,110 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class HexStringTest { + @Test + void from_string() { + /* prepare */ + String string = "Hello"; + String expectedString = "48656c6c6f"; + + /* execute + test */ + assertEquals(expectedString, new HexString(string).toString()); + } + + @Test + void from_string_null_throw_illegal_argument_exception() { + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + new HexString((String) null); + }); + + /* test */ + assertEquals("String cannot be null.", exception.getMessage()); + } + + @Test + void from_string_unicode() { + /* prepare */ + String string = "Hello πŸ¦„"; + String expectedString = "48656c6c6f20f09fa684"; + + /* execute + test */ + assertEquals(expectedString, new HexString(string).toString()); + } + + @Test + void from_bytes() { + /* prepare */ + byte[] bytes = "Hello".getBytes(); + String expectedString = "48656c6c6f"; + + /* execute + test */ + assertEquals(expectedString, new HexString(bytes).toString()); + } + + @Test + void from_bytes_unicode() { + /* prepare */ + byte[] bytes = "Hello πŸ¦„".getBytes(); + String expectedString = "48656c6c6f20f09fa684"; + + /* execute + test */ + assertEquals(expectedString, new HexString(bytes).toString()); + } + + @Test + void from_bytes_null_throw_illegal_argument_exception() { + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + new HexString((byte[]) null); + }); + + /* test */ + assertEquals("Byte array cannot be null.", exception.getMessage()); + } + + @Test + void toString_unicode_test() { + /* prepare */ + String string = "Hello πŸ¦„"; + String expectedString = "48656c6c6f20f09fa684"; + + /* execute + test */ + assertEquals(expectedString, new HexString(string).toString()); + } + + @Test + void equals_test() { + /* prepare */ + String string = "I like 🍍"; + + /* execute */ + HexString hexString = new HexString(string); + HexString hexString2 = new HexString(string); + + /* test */ + assertEquals(hexString, hexString2); + assertTrue(hexString.equals(hexString2)); + assertEquals(hexString.hashCode(), hexString2.hashCode()); + } + + @Test + void test_immutablitiy() { + /* prepare */ + String string = "A 🐺 in a πŸ‘ skin"; + + /* execute */ + HexString hexString = new HexString(string); + + /* test */ + assertFalse(string == hexString.toString()); + assertFalse(string.getBytes() == hexString.getBytes()); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java new file mode 100644 index 0000000000..16d34407b2 --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java @@ -0,0 +1,115 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; + +import org.junit.jupiter.api.Test; + +public class NoneCipherTest { + private BinaryString initializationVector = BinaryStringFactory.createFromString("Hello"); + + @Test + void encrypt_null_iv() throws InvalidKeyException, InvalidAlgorithmParameterException { + /* prepare */ + String plaintext = "This is plaintext"; + PersistenceCipher cipher = NoneCipher.create(null); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, null); + + /* test */ + assertEquals(plaintext, ciphertext.toString()); + } + + @Test + void encrypt_with_iv() throws InvalidKeyException, InvalidAlgorithmParameterException { + /* prepare */ + String plaintext = "This is plaintext"; + PersistenceCipher cipher = NoneCipher.create(null); + + /* execute */ + BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); + + /* test */ + assertEquals(plaintext, ciphertext.toString()); + } + + @Test + void decrypt_null_iv() throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlaintext = "This is plaintext"; + BinaryString ciphertext = new PlainString(expectedPlaintext); + PersistenceCipher cipher = NoneCipher.create(null); + + /* execute */ + String plaintext = cipher.decrypt(ciphertext, null); + + /* test */ + assertEquals(expectedPlaintext, plaintext); + } + + @Test + void decrypt_with_iv() throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlaintext = "This is plaintext with a 🐎!"; + BinaryString ciphertext = new PlainString(expectedPlaintext); + PersistenceCipher cipher = NoneCipher.create(null); + + /* execute */ + String plaintext = cipher.decrypt(ciphertext, initializationVector); + + /* test */ + assertEquals(expectedPlaintext, plaintext); + } + + @Test + void getCipher_null() throws InvalidKeyException { + /* prepare */ + PersistenceCipher cipher = NoneCipher.create(null); + + /* execute + test */ + assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); + } + + @Test + void getCipher_string() throws InvalidKeyException { + /* prepare */ + PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello")); + + /* execute + test */ + assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); + } + + @Test + void generateNewInitializationVector_default_type() throws InvalidKeyException { + /* prepare */ + PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello")); + + /* execute */ + BinaryString initializationVector = cipher.generateNewInitializationVector(); + + /* test */ + assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); + assertEquals(BinaryStringEncodingType.PLAIN, initializationVector.getType()); + assertEquals("", initializationVector.toString()); + } + + @Test + void generateNewInitializationVector_type_provided() throws InvalidKeyException { + /* prepare */ + PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello")); + + /* execute */ + BinaryString initializationVector = cipher.generateNewInitializationVector(BinaryStringEncodingType.HEX); + + /* test */ + assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); + assertEquals(BinaryStringEncodingType.HEX, initializationVector.getType()); + assertEquals("", initializationVector.toString()); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java new file mode 100644 index 0000000000..3b9db36f3c --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java @@ -0,0 +1,97 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.security.InvalidKeyException; + +import org.junit.jupiter.api.Test; + +public class PersistenceCipherFactoryTest { + @Test + void create_none_cipher() throws InvalidKeyException { + /* prepare */ + PersistenceCipherType cipherType = PersistenceCipherType.NONE; + Base64String secret = new Base64String("topSecret"); + + /* execute */ + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + + /* test */ + assertEquals(cipher.getCipherType(), cipherType); + } + + @Test + void create_aes_gcm_siv_256() throws InvalidKeyException { + /* prepare */ + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; + BinaryString secret = new PlainString("a".repeat(32)); + + /* execute */ + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + + /* test */ + assertEquals(cipher.getCipherType(), cipherType); + } + + @Test + void create_aes_gcm_siv_128() throws InvalidKeyException { + /* prepare */ + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; + BinaryString secret = new HexString("a".repeat(16)); + + /* execute */ + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + + /* test */ + assertEquals(cipher.getCipherType(), cipherType); + } + + @Test + void create_initializationVector_aes_gcm_siv_256() throws InvalidKeyException { + /* prepare */ + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; + BinaryString secret = new PlainString("a".repeat(32)); + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + + /* execute */ + BinaryString initializationVector = cipher.generateNewInitializationVector(); + + /* test */ + assertEquals(cipher.getCipherType(), cipherType); + assertNotNull(initializationVector); + assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length); + } + + @Test + void create_initializationVector_aes_gcm_siv_128() throws InvalidKeyException { + /* prepare */ + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; + BinaryString secret = new PlainString("a".repeat(16)); + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + + /* execute */ + BinaryString initializationVector = cipher.generateNewInitializationVector(); + + /* test */ + assertEquals(cipher.getCipherType(), cipherType); + assertNotNull(initializationVector); + assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length); + } + + @Test + void create_initializationVector_none_cipher() throws InvalidKeyException { + /* prepare */ + PersistenceCipherType cipherType = PersistenceCipherType.NONE; + BinaryString secret = new PlainString("a".repeat(3)); + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + + /* execute */ + BinaryString initializationVector = cipher.generateNewInitializationVector(); + + /* test */ + assertEquals(cipher.getCipherType(), cipherType); + assertNotNull(initializationVector); + assertEquals("", initializationVector.toString()); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java new file mode 100644 index 0000000000..2697313a9f --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java @@ -0,0 +1,104 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class PlainStringTest { + @Test + void from_string() { + /* prepare */ + String string = "Hello"; + + /* execute + test */ + assertEquals(string, new PlainString(string).toString()); + } + + @Test + void from_string_null_throw_illegal_argument_exception() { + /* execute */ + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + new PlainString((String) null); + }); + + /* test */ + assertEquals("String cannot be null.", exception.getMessage()); + } + + @Test + void from_string_unicode() { + /* prepare */ + String string = "Hello πŸ¦„"; + + /* execute + test */ + assertEquals(string, new PlainString(string).toString()); + } + + @Test + void from_bytes() { + /* prepare */ + byte[] bytes = "Hello".getBytes(); + + /* execute + test */ + assertEquals(new String(bytes), new PlainString(bytes).toString()); + } + + @Test + void from_bytes_unicode() { + /* prepare */ + byte[] bytes = "Hello πŸ¦„".getBytes(); + + /* execute + test */ + assertEquals(new String(bytes), new PlainString(bytes).toString()); + } + + @Test + void from_bytes_null_throw_illegal_argument_exception() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + new PlainString((byte[]) null); + }); + + /* execute + test */ + assertEquals("Byte array cannot be null.", exception.getMessage()); + } + + @Test + void toString_unicode_test() { + /* prepare */ + String string = "Hello πŸ¦„"; + + /* execute + test */ + assertEquals(string, new PlainString(string).toString()); + } + + @Test + void equals_test() { + /* prepare */ + String string = "I like 🍍"; + + /* execute */ + PlainString plainString1 = new PlainString(string); + PlainString plainString2 = new PlainString(string); + + /* test */ + assertEquals(plainString1, plainString2); + assertTrue(plainString1.equals(plainString2)); + assertEquals(plainString1.hashCode(), plainString2.hashCode()); + } + + @Test + void test_immutablitiy() { + /* prepare */ + String string = "A 🐺 in a πŸ‘ skin"; + + /* execute */ + PlainString plainString = new PlainString(string); + + /* test */ + assertFalse(string == plainString.toString()); + assertFalse(string.getBytes() == plainString.getBytes()); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java new file mode 100644 index 0000000000..1c5a861415 --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java @@ -0,0 +1,299 @@ +package com.mercedesbenz.sechub.commons.core.security.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; + +import org.junit.jupiter.api.Test; + +public class RotationTest { + @Test + void secret_rotation_cipher_none() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + BinaryString currentSecret = new PlainString("abc"); + BinaryString newSecret = new PlainString("bca"); + BinaryString cipherText = new PlainString("hello"); + BinaryString initializationVector = new PlainString("iv"); + + PersistenceCipherType cipherType = PersistenceCipherType.NONE; + RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(cipherText, newCipherText); + assertTrue(rotation.isSecretRotationStrategy()); + assertFalse(rotation.isCipherRotationStrategy()); + assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher()); + } + + @Test + void secret_rotation_cipher_aes_gcm_siv_128() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlainText = "Hello, I am ☺️ 4 you."; + BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("8gWa4YPRlshBZCml8a0xvAJ1Y1mNU9iovclpvOhwVj4XiiaZvWKHWkU=", + BinaryStringEncodingType.BASE64); + BinaryString currentSecret = new PlainString("a".repeat(16)); + BinaryString newSecret = new PlainString("z".repeat(16)); + BinaryString cipherText = BinaryStringFactory.createFromBase64("DuwfqoAJrzZiK3u5v0XEnARPOjLugpobvWCxfTV6Y1FkAOECII/J8RU=", + BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new PlainString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; + RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(expectedCipherText, newCipherText); + assertTrue(rotation.isSecretRotationStrategy()); + assertFalse(rotation.isCipherRotationStrategy()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher()); + + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, newSecret); + String plainText = cipher.decrypt(newCipherText, initializationVector); + + assertEquals(expectedPlainText, plainText); + } + + @Test + void secret_rotation_cipher_aes_gcm_siv_256() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlainText = "Hello, I am ☺️ 4 you."; + BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("y5/I7wBmKwqKKazHrRnj2j6v9uuySiHJ9MK3pUvWjkUIC/3jDSFJLPI=", + BinaryStringEncodingType.BASE64); + BinaryString currentSecret = new PlainString("a".repeat(32)); + BinaryString newSecret = new PlainString("z".repeat(32)); + BinaryString cipherText = BinaryStringFactory.createFromBase64("KSIAj+JAD95o77GF91GQShbuh0dyuIMdDjhX1VQFi7DW2KSutD0uOt8=", + BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new HexString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; + RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(expectedCipherText, newCipherText); + assertTrue(rotation.isSecretRotationStrategy()); + assertFalse(rotation.isCipherRotationStrategy()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher()); + + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, newSecret); + String plainText = cipher.decrypt(newCipherText, initializationVector); + + assertEquals(expectedPlainText, plainText); + } + + @Test + void cipher_and_secret_rotation_cipher_none_to_cipher_aes_gcm_siv_128() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlainText = "Hello, I am ☺️ 4 you."; + BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("DuwfqoAJrzZiK3u5v0XEnARPOjLugpobvWCxfTV6Y1FkAOECII/J8RU=", + BinaryStringEncodingType.BASE64); + BinaryString currentSecret = new PlainString("abc"); + BinaryString newSecret = new PlainString("a".repeat(16)); + BinaryString cipherText = new PlainString(expectedPlainText); + BinaryString initializationVector = new PlainString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); + + PersistenceCipherType currentCipherType = PersistenceCipherType.NONE; + PersistenceCipherType newCipherType = PersistenceCipherType.AES_GCM_SIV_128; + RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(expectedCipherText, newCipherText); + assertTrue(rotation.isSecretRotationStrategy()); + assertTrue(rotation.isCipherRotationStrategy()); + assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher()); + + PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret); + String plainText = cipher.decrypt(newCipherText, initializationVector); + + assertEquals(expectedPlainText, plainText); + } + + @Test + void cipher_and_secret_rotation_cipher_aes_gcm_siv_128_to_cipher_aes_gcm_siv_256() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlainText = "Hello πŸ‘‹, welcome to 🌐."; + + BinaryString expectedCipherText = BinaryStringFactory + .createFromHex("d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6", BinaryStringEncodingType.BASE64); + BinaryString currentSecret = new Base64String("πŸπŸŒπŸ“πŸ‰"); + BinaryString newSecret = new HexString("πŸ₯¦πŸ₯•πŸ₯”πŸ«˜πŸ₯’πŸ«‘πŸŒ½πŸ†"); + BinaryString cipherText = BinaryStringFactory.createFromBase64("Qu7ICJBGMw9dAPPBWx86e5bjOq3YKC+x25n/YkluWZAGdSna08tKaE78pMk=", + BinaryStringEncodingType.BASE64); + BinaryString initializationVector = new PlainString("πŸ§…".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); + + PersistenceCipherType currentCipherType = PersistenceCipherType.AES_GCM_SIV_128; + PersistenceCipherType newCipherType = PersistenceCipherType.AES_GCM_SIV_256; + RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(expectedCipherText, newCipherText); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher()); + assertTrue(rotation.isSecretRotationStrategy()); + assertTrue(rotation.isCipherRotationStrategy()); + + PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret); + String plainText = cipher.decrypt(newCipherText, initializationVector); + + assertEquals(expectedPlainText, plainText); + } + + @Test + void cipher_and_secret_rotation_aes_gcm_siv_256_to_none() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlainText = "Hello πŸ‘‹, welcome to 🌐."; + + BinaryString expectedCipherText = BinaryStringFactory.createFromString(expectedPlainText, BinaryStringEncodingType.PLAIN); + BinaryString currentSecret = new HexString("πŸ₯¦πŸ₯•πŸ₯”πŸ«˜πŸ₯’πŸ«‘πŸŒ½πŸ†"); + BinaryString newSecret = new HexString("abc"); + BinaryString cipherText = BinaryStringFactory.createFromHex("d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6", + BinaryStringEncodingType.HEX); + BinaryString initializationVector = new Base64String("πŸ§…".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); + + PersistenceCipherType currentCipherType = PersistenceCipherType.AES_GCM_SIV_256; + PersistenceCipherType newCipherType = PersistenceCipherType.NONE; + RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(expectedCipherText, newCipherText); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher()); + assertTrue(rotation.isSecretRotationStrategy()); + assertTrue(rotation.isCipherRotationStrategy()); + + PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret); + String plainText = cipher.decrypt(newCipherText, initializationVector); + + assertEquals(expectedPlainText, plainText); + } + + @Test + void initializationVector_rotation_cipher_none() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + BinaryString secret = new PlainString("abc"); + BinaryString cipherText = new PlainString("hello"); + BinaryString initializationVector = new PlainString("vi"); + BinaryString newInitializationVector = new PlainString("iv"); + + PersistenceCipherType cipherType = PersistenceCipherType.NONE; + RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(cipherText, newCipherText); + assertFalse(rotation.isSecretRotationStrategy()); + assertFalse(rotation.isCipherRotationStrategy()); + assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher()); + } + + @Test + void initializationVector_rotation_aes_gcm_siv_128() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlainText = "The quick brown fox jumps over the lazy dog."; + + BinaryString expectedCipherText = BinaryStringFactory + .createFromBase64("roRrChR9D/VIaebHZgxGG3tYuCR+yXjPleQr4bDZ7B5qsVzC7EnXTi3rT3qDv09t62W/W4V9CIfQRlWs"); + BinaryString secret = new PlainString("d".repeat(16)); + BinaryString cipherText = BinaryStringFactory.createFromBase64("h3Pz6Kue6V4CUYtkZdtWml0vmXkEXjSpXyud98Z3NJjNGk+qFYJmpLCoyO+qBxWKQfxbmnmup9+vjYJQ"); + BinaryString initializationVector = new HexString("vi".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); + BinaryString newInitializationVector = new Base64String("iv".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); + + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; + RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(expectedCipherText, newCipherText); + assertFalse(rotation.isSecretRotationStrategy()); + assertFalse(rotation.isCipherRotationStrategy()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher()); + + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + String plainText = cipher.decrypt(newCipherText, newInitializationVector); + + assertEquals(expectedPlainText, plainText); + } + + @Test + void initializationVector_rotation_aes_gcm_siv_256() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + String expectedPlainText = "Victor jagt zwΓΆlf BoxkΓ€mpfer quer ΓΌber den großen Sylter Deich"; + + BinaryString expectedCipherText = BinaryStringFactory + .createFromBase64("0eG35+7x+nr4MbsgRsTqOEjRReF6JzM2thWe+3zHleta1cCXzyCxQJ5Jaxeq7TnYDDg3nR/RcQdc00UlqEJw3WODaffeETDJ0bk5LfP19FWr/g=="); + BinaryString secret = new PlainString("ThisIsAStringWith32Characters!?!"); + BinaryString cipherText = BinaryStringFactory + .createFromBase64("AfVfrXdv8JImZ9qrpO7vOJRwveOaYRJNsQaeJiuDg5nX3ZT13EwH7CXlZa8IBNvhrqUvEn3w0L0J+JjYVYPxh80CqCpgjbMl4a8Ql5eyqKQIZQ=="); + BinaryString initializationVector = new HexString("vi".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); + BinaryString newInitializationVector = new Base64String("iv".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); + + PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; + RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType); + + /* execute */ + BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector); + + /* test */ + assertNotNull(rotation); + assertEquals(expectedCipherText, newCipherText); + assertFalse(rotation.isSecretRotationStrategy()); + assertFalse(rotation.isCipherRotationStrategy()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher()); + assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher()); + + PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); + String plainText = cipher.decrypt(newCipherText, newInitializationVector); + + assertEquals(expectedPlainText, plainText); + } +}