From daf8f3e2059a456cc6254fd2be3b097859b096fa Mon Sep 17 00:00:00 2001 From: Or Geva Date: Thu, 30 Nov 2023 11:42:44 +0200 Subject: [PATCH] Add Initialization vector(IV) used in encryption with a given key --- .../extractor/BuildInfoExtractorUtils.java | 21 +++++-- .../ci/BuildInfoConfigProperties.java | 2 + .../ArtifactoryClientConfiguration.java | 25 ++++---- .../util/encryption/EncryptionKeyPair.java | 63 +++++++++++++++++++ .../SecurePropertiesEncryption.java} | 38 +++++------ .../extractor/BuildExtractorUtilsTest.java | 9 ++- .../ArtifactoryClientConfigurationTest.java | 5 +- .../SecurePropertiesEncryptionTest.java | 37 +++++++++++ 8 files changed, 156 insertions(+), 44 deletions(-) create mode 100644 build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/EncryptionKeyPair.java rename build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/{EncryptionUtils.java => encryption/SecurePropertiesEncryption.java} (77%) create mode 100644 build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/SecurePropertiesEncryptionTest.java diff --git a/build-info-extractor/src/main/java/org/jfrog/build/extractor/BuildInfoExtractorUtils.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/BuildInfoExtractorUtils.java index cb55dfedc..f9eb266f9 100644 --- a/build-info-extractor/src/main/java/org/jfrog/build/extractor/BuildInfoExtractorUtils.java +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/BuildInfoExtractorUtils.java @@ -18,6 +18,7 @@ import org.jfrog.build.extractor.clientConfiguration.ClientProperties; import org.jfrog.build.extractor.clientConfiguration.IncludeExcludePatterns; import org.jfrog.build.extractor.clientConfiguration.PatternMatcher; +import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; import java.io.File; import java.io.IOException; @@ -27,7 +28,6 @@ import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -104,10 +104,10 @@ private static Properties searchAdditionalPropertiesFile(Properties existingProp } try { - String encryptionKey = getPropertiesFileEncryptionKey(existingProps); - if (StringUtils.isNotBlank(encryptionKey)) { + EncryptionKeyPair keyPair = new EncryptionKeyPair(getPropertiesFileEncryptionKey(existingProps), getPropertiesFileEncryptionKeyIv(existingProps)); + if (!keyPair.isEmpty()) { log.debug("[buildinfo] Found an encryption for buildInfo properties file for this build."); - props.putAll(decryptPropertiesFromFile(propertiesFile.getPath(), Base64.getDecoder().decode(encryptionKey))); + props.putAll(decryptPropertiesFromFile(propertiesFile.getPath(), keyPair)); } else { try (InputStream inputStream = Files.newInputStream(propertiesFile.toPath())) { props.load(inputStream); @@ -267,7 +267,18 @@ public static void saveBuildInfoToFile(BuildInfo buildInfo, File toFile) throws * @return The encryption key obtained from system properties or additional properties. */ private static String getPropertiesFileEncryptionKey(Properties additionalProps) { - String key = BuildInfoConfigProperties.PROP_PROPS_FILE_KEY; + return getPropertiesFileEncryption(additionalProps, BuildInfoConfigProperties.PROP_PROPS_FILE_KEY); + } + + /** + * @param additionalProps Additional properties containing the encryption IV. + * @return The encryption IV obtained from system properties or additional properties. + */ + private static String getPropertiesFileEncryptionKeyIv(Properties additionalProps) { + return getPropertiesFileEncryption(additionalProps, BuildInfoConfigProperties.PROP_PROPS_FILE_KEY_IV); + } + + private static String getPropertiesFileEncryption(Properties additionalProps, String key) { // Check if the encryption key is set in system properties if (StringUtils.isNotBlank(System.getProperty(key))) { return System.getProperty(key); diff --git a/build-info-extractor/src/main/java/org/jfrog/build/extractor/ci/BuildInfoConfigProperties.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/ci/BuildInfoConfigProperties.java index 0432a215a..fda2b4828 100644 --- a/build-info-extractor/src/main/java/org/jfrog/build/extractor/ci/BuildInfoConfigProperties.java +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/ci/BuildInfoConfigProperties.java @@ -11,9 +11,11 @@ public interface BuildInfoConfigProperties { String BUILD_INFO_CONFIG_PREFIX = "buildInfoConfig."; String PROPERTIES_FILE = "propertiesFile"; String PROPERTIES_FILE_KEY = "propertiesFileKey"; + String PROPERTIES_FILE_KEY_IV = "propertiesFileKeyIv"; String PROP_PROPS_FILE = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE; String PROP_PROPS_FILE_KEY = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE_KEY; + String PROP_PROPS_FILE_KEY_IV = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE_KEY_IV; String EXPORT_FILE = "exportFile"; String PROP_EXPORT_FILE_PATH = BUILD_INFO_CONFIG_PREFIX + EXPORT_FILE; diff --git a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfiguration.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfiguration.java index 891cdcafd..b1bb68b54 100644 --- a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfiguration.java +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfiguration.java @@ -10,6 +10,7 @@ import org.jfrog.build.extractor.ci.BuildInfoFields; import org.jfrog.build.extractor.ci.Issue; import org.jfrog.build.extractor.clientConfiguration.util.IssuesTrackerUtils; +import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; import java.io.File; import java.io.FileOutputStream; @@ -137,8 +138,8 @@ import static org.jfrog.build.extractor.clientConfiguration.ClientProperties.PROP_RESOLVE_PREFIX; import static org.jfrog.build.extractor.clientConfiguration.ClientProperties.PROP_SO_TIMEOUT; import static org.jfrog.build.extractor.clientConfiguration.ClientProperties.PROP_TIMEOUT; -import static org.jfrog.build.extractor.clientConfiguration.util.EncryptionUtils.decryptProperties; -import static org.jfrog.build.extractor.clientConfiguration.util.EncryptionUtils.encryptedPropertiesToFile; +import static org.jfrog.build.extractor.clientConfiguration.util.encryption.SecurePropertiesEncryption.decryptProperties; +import static org.jfrog.build.extractor.clientConfiguration.util.encryption.SecurePropertiesEncryption.encryptedPropertiesToFile; /** * @author freds @@ -242,22 +243,22 @@ public void persistToPropertiesFile() { } } - public byte[] persistToEncryptedPropertiesFile(OutputStream os) throws IOException { - if (StringUtils.isEmpty(getPropertiesFile())) { - return null; - } - return encryptedPropertiesToFile(os, preparePropertiesToPersist()); - } - /** * Decrypts properties from a file using the provided secret key. * * @param filePath The path to the file containing encrypted properties. - * @param secretKey The secret key used for decryption. + * @param keyPair The secret key and iv used for decryption. * @return A Properties object containing the decrypted properties. */ - public static Properties decryptPropertiesFromFile(String filePath, byte[] secretKey) throws IOException { - return decryptProperties(FileUtils.readFileToByteArray(new File(filePath)), secretKey); + public static Properties decryptPropertiesFromFile(String filePath, EncryptionKeyPair keyPair) throws IOException { + return decryptProperties(FileUtils.readFileToByteArray(new File(filePath)), keyPair); + } + + public EncryptionKeyPair persistToEncryptedPropertiesFile(OutputStream os) throws IOException { + if (StringUtils.isEmpty(getPropertiesFile())) { + return null; + } + return encryptedPropertiesToFile(os, preparePropertiesToPersist()); } private Properties preparePropertiesToPersist() { diff --git a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/EncryptionKeyPair.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/EncryptionKeyPair.java new file mode 100644 index 000000000..fc735c425 --- /dev/null +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/EncryptionKeyPair.java @@ -0,0 +1,63 @@ +package org.jfrog.build.extractor.clientConfiguration.util.encryption; + +import org.apache.commons.lang3.StringUtils; + +import java.security.SecureRandom; +import java.util.Base64; + +/** + * Represents a pair of secret key and initialization vector (IV) used for encryption and decryption. + */ +public class EncryptionKeyPair { + private static final int AES_256_KEY_LENGTH = 256; + private static final int IV_LENGTH = 126; + private byte[] secretKey; + private byte[] Iv; + + public EncryptionKeyPair() { + this.secretKey = generateRandomKey(AES_256_KEY_LENGTH); + this.Iv = generateRandomKey(IV_LENGTH); + } + + public EncryptionKeyPair(String secretKey, String Iv) { + if (StringUtils.isNotBlank(secretKey)) { + this.secretKey = Base64.getDecoder().decode(secretKey); + } + if (StringUtils.isNotBlank(Iv)) { + this.Iv = Base64.getDecoder().decode(Iv); + } + } + + /** + * Generates a random key of the specified length in bits. + * + * @param lengthInBits The length of the key in bits. + * @return A byte array representing the generated random key. + */ + private static byte[] generateRandomKey(int lengthInBits) { + SecureRandom secureRandom = new SecureRandom(); + byte[] key = new byte[lengthInBits / 8]; + secureRandom.nextBytes(key); + return key; + } + + public byte[] getSecretKey() { + return secretKey; + } + + public String getStringSecretKey() { + return Base64.getEncoder().encodeToString(secretKey); + } + + public byte[] getIv() { + return Iv; + } + + public String getStringIv() { + return Base64.getEncoder().encodeToString(Iv); + } + + public boolean isEmpty() { + return secretKey == null || secretKey.length == 0 || Iv == null || Iv.length == 0; + } +} diff --git a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/EncryptionUtils.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/SecurePropertiesEncryption.java similarity index 77% rename from build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/EncryptionUtils.java rename to build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/SecurePropertiesEncryption.java index 32a553855..da98389cf 100644 --- a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/EncryptionUtils.java +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/SecurePropertiesEncryption.java @@ -1,4 +1,4 @@ -package org.jfrog.build.extractor.clientConfiguration.util; +package org.jfrog.build.extractor.clientConfiguration.util.encryption; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -14,28 +14,27 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.util.Properties; -public class EncryptionUtils { +public class SecurePropertiesEncryption { private static final String ALGORITHM = "AES"; private static final String TRANSFORMATION = "AES/GCM/NoPadding"; private static final int GCM_TAG_LENGTH = 128; - private static final int AES_256_KEY_LENGTH = 256; + /** * Decrypts properties from an encrypted byte array using the provided secret key. * * @param encryptedData The encrypted byte array representing properties. - * @param secretKey The secret key used for decryption. + * @param keyPair The secret key and iv used for decryption. * @return A Properties object containing the decrypted properties. */ - public static Properties decryptProperties(byte[] encryptedData, byte[] secretKey) throws IOException { + public static Properties decryptProperties(byte[] encryptedData, EncryptionKeyPair keyPair) throws IOException { try { Cipher cipher = Cipher.getInstance(TRANSFORMATION); - SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, new byte[GCM_TAG_LENGTH / 8]); + SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv()); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec); @@ -65,31 +64,26 @@ private static Properties stringToProperties(String propertiesString) throws IOE * @param properties The Properties object containing the properties to be encrypted. * @return A byte array representing the secret key used for encryption. */ - public static byte[] encryptedPropertiesToFile(OutputStream os, Properties properties) throws IOException { - byte[] secretKey = generateRandomKey(); - os.write(encryptProperties(properties, secretKey)); - return secretKey; + public static EncryptionKeyPair encryptedPropertiesToFile(OutputStream os, Properties properties) throws IOException { + EncryptionKeyPair keyPair = new EncryptionKeyPair(); + os.write(encryptProperties(properties, keyPair)); + return keyPair; } - private static byte[] generateRandomKey() { - SecureRandom secureRandom = new SecureRandom(); - byte[] key = new byte[AES_256_KEY_LENGTH / 8]; - secureRandom.nextBytes(key); - return key; - } + /** * Encrypts properties into a byte array using the provided secret key. * * @param properties The Properties object to be encrypted. - * @param secretKey The secret key used for encryption. + * @param keyPair The secret key and iv used for encryption. * @return A byte array representing the encrypted properties. */ - private static byte[] encryptProperties(Properties properties, byte[] secretKey) { + private static byte[] encryptProperties(Properties properties, EncryptionKeyPair keyPair) { try { Cipher cipher = Cipher.getInstance(TRANSFORMATION); - SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, new byte[GCM_TAG_LENGTH / 8]); + SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv()); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec); diff --git a/build-info-extractor/src/test/java/org/jfrog/build/extractor/BuildExtractorUtilsTest.java b/build-info-extractor/src/test/java/org/jfrog/build/extractor/BuildExtractorUtilsTest.java index 894bad5db..b71626496 100644 --- a/build-info-extractor/src/test/java/org/jfrog/build/extractor/BuildExtractorUtilsTest.java +++ b/build-info-extractor/src/test/java/org/jfrog/build/extractor/BuildExtractorUtilsTest.java @@ -11,6 +11,7 @@ import org.jfrog.build.extractor.ci.Dependency; import org.jfrog.build.extractor.ci.Module; import org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration; +import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -94,8 +95,9 @@ public void getBuildInfoPropertiesFromFile() throws IOException { } public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { - byte[] key = setupEncryptedFileTest(); - System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY, Base64.getEncoder().encodeToString(key)); + EncryptionKeyPair keyPair = setupEncryptedFileTest(); + System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY, Base64.getEncoder().encodeToString(keyPair.getSecretKey())); + System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY_IV, Base64.getEncoder().encodeToString(keyPair.getIv())); Properties fileProps = filterDynamicProperties( mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), BUILD_INFO_PROP_PREDICATE); @@ -105,6 +107,7 @@ public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY); + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY_IV); } public void failToReadEncryptedFileWithNoKey() throws IOException { @@ -116,7 +119,7 @@ public void failToReadEncryptedFileWithNoKey() throws IOException { System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); } - private byte[] setupEncryptedFileTest() throws IOException { + private EncryptionKeyPair setupEncryptedFileTest() throws IOException { Properties props = new Properties(); props.put(POPO_KEY, "buildname"); props.put(MOMO_KEY, "1"); diff --git a/build-info-extractor/src/test/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfigurationTest.java b/build-info-extractor/src/test/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfigurationTest.java index 3645d12fe..bfb0b4bae 100644 --- a/build-info-extractor/src/test/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfigurationTest.java +++ b/build-info-extractor/src/test/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfigurationTest.java @@ -2,6 +2,7 @@ import org.jfrog.build.api.util.NullLog; import org.jfrog.build.extractor.ci.BuildInfoConfigProperties; +import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -58,9 +59,9 @@ public void testReadEncryptedPropertyFile() throws IOException { try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile.toFile())) { // Save encrypted file. - byte[] key = client.persistToEncryptedPropertiesFile(fileOutputStream); + EncryptionKeyPair keyPair = client.persistToEncryptedPropertiesFile(fileOutputStream); // Assert decrypted successfully. - Properties props = decryptPropertiesFromFile(tempFile.toString(), key); + Properties props = decryptPropertiesFromFile(tempFile.toString(), keyPair); assertEquals(props.size(), 18); assertEquals(props.getProperty("proxy.host"), client.getAllProperties().get("proxy.host")); } diff --git a/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/SecurePropertiesEncryptionTest.java b/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/SecurePropertiesEncryptionTest.java new file mode 100644 index 000000000..1a7bd62d2 --- /dev/null +++ b/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/SecurePropertiesEncryptionTest.java @@ -0,0 +1,37 @@ +package org.jfrog.build.extractor.util.encryption; + +import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; +import org.jfrog.build.extractor.clientConfiguration.util.encryption.SecurePropertiesEncryption; +import org.testng.annotations.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Properties; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class SecurePropertiesEncryptionTest { + @Test + public void testEncryptDecryptProperties() throws IOException { + // Create properties to be encrypted + Properties originalProperties = new Properties(); + originalProperties.setProperty("key1", "value1"); + originalProperties.setProperty("key2", "value2"); + + // Encrypt properties and get encryption key pair + ByteArrayOutputStream encryptedOutputStream = new ByteArrayOutputStream(); + EncryptionKeyPair keyPair = SecurePropertiesEncryption.encryptedPropertiesToFile(encryptedOutputStream, originalProperties); + assertNotNull(keyPair); + + // Decrypt properties using the generated key pair + byte[] encryptedData = encryptedOutputStream.toByteArray(); + Properties decryptedProperties = SecurePropertiesEncryption.decryptProperties(encryptedData, keyPair); + assertNotNull(decryptedProperties); + + // Compare original and decrypted properties + assertEquals(originalProperties.size(), decryptedProperties.size()); + assertEquals(originalProperties.getProperty("key1"), decryptedProperties.getProperty("key1")); + assertEquals(originalProperties.getProperty("key2"), decryptedProperties.getProperty("key2")); + } +}