From a39743713139de9d07e533f14baa0d0aa14f7a3a Mon Sep 17 00:00:00 2001 From: Or Geva Date: Tue, 21 Nov 2023 21:16:35 +0200 Subject: [PATCH 1/9] Encrypted & decrypt build info properties file --- .../extractor/BuildInfoExtractorUtils.java | 75 ++++++++--- .../ci/BuildInfoConfigProperties.java | 4 + .../ArtifactoryClientConfiguration.java | 122 ++++++++++++++++++ .../extractor/BuildExtractorUtilsTest.java | 48 ++++++- .../ArtifactoryClientConfigurationTest.java | 37 ++++++ 5 files changed, 260 insertions(+), 26 deletions(-) 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 11609d149..1e931f5e7 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 @@ -27,6 +27,7 @@ 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; @@ -39,6 +40,7 @@ import static org.apache.commons.lang3.StringUtils.removeEnd; import static org.jfrog.build.extractor.UrlUtils.encodeUrl; import static org.jfrog.build.extractor.UrlUtils.encodeUrlPathPart; +import static org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration.decryptPropertiesFromFile; /** * @author Noam Y. Tenne @@ -64,33 +66,49 @@ public abstract class BuildInfoExtractorUtils { public static final Predicate MATRIX_PARAM_PREDICATE = new PrefixPredicate(ClientProperties.PROP_DEPLOY_PARAM_PROP_PREFIX); - public static Properties mergePropertiesWithSystemAndPropertyFile(Properties existingProps) { - return mergePropertiesWithSystemAndPropertyFile(existingProps, null); + public static Properties mergePropertiesWithSystemAndPropertyFile(Properties existingProps, Log log) { + Properties mergedProps = new Properties(); + mergedProps.putAll(addSystemProperties(existingProps)); + mergedProps.putAll(addAdditionalPropertiesFile(mergedProps, log)); + return mergedProps; } - public static Properties mergePropertiesWithSystemAndPropertyFile(Properties existingProps, Log log) { + + private static Properties addSystemProperties(Properties existingProps) { + Properties props = new Properties(); + props.putAll(existingProps); + props.putAll(System.getProperties()); + return props; + } + + private static Properties addAdditionalPropertiesFile(Properties existingProps, Log log) { Properties props = new Properties(); - addPropsFromCommandSystemProp(existingProps, log); String propsFilePath = getAdditionalPropertiesFile(existingProps, log); - if (StringUtils.isNotBlank(propsFilePath)) { - File propertiesFile = new File(propsFilePath); - InputStream inputStream = null; - try { - if (propertiesFile.exists()) { - inputStream = Files.newInputStream(propertiesFile.toPath()); + + if (StringUtils.isBlank(propsFilePath)) { + log.debug("[buildinfo] BuildInfo properties file path is not found."); + return props; + } + + File propertiesFile = new File(propsFilePath); + if (!propertiesFile.exists()) { + log.debug("[buildinfo] BuildInfo properties file is not exists."); + return props; + } + + try { + String encryptionKey = getPropertiesFileEncryptionKey(existingProps); + if (StringUtils.isNotBlank(encryptionKey)) { + log.debug("[buildinfo] Found an encryption for buildInfo properties file for this build."); + props.putAll(decryptPropertiesFromFile(propertiesFile.getPath(), Base64.getDecoder().decode(encryptionKey))); + } else { + try (InputStream inputStream = Files.newInputStream(propertiesFile.toPath())) { props.load(inputStream); } - } catch (IOException e) { - throw new RuntimeException( - "Unable to load build info properties from file: " + propertiesFile.getAbsolutePath(), e); - } finally { - IOUtils.closeQuietly(inputStream); } + } catch (IOException e) { + throw new RuntimeException("Unable to load build info properties from file: " + propertiesFile.getAbsolutePath(), e); } - - props.putAll(existingProps); - props.putAll(System.getProperties()); - return props; } @@ -237,6 +255,23 @@ public static void saveBuildInfoToFile(BuildInfo buildInfo, File toFile) throws CommonUtils.writeByCharset(buildInfoJson, toFile, StandardCharsets.UTF_8); } + private static String getPropertiesFileEncryptionKey(Properties additionalProps) { + String key = BuildInfoConfigProperties.PROP_PROPS_FILE_KEY; + if (StringUtils.isNotBlank(System.getProperty(key))) { + return System.getProperty(key); + } + if (additionalProps != null) { + if (StringUtils.isNotBlank(additionalProps.getProperty(key))) { + return additionalProps.getProperty(key); + } + // Jenkins prefixes these variables with "env." so let's try that + if (StringUtils.isNotBlank(additionalProps.getProperty("env." + key))) { + return additionalProps.getProperty("env." + key); + } + } + return null; + } + private static String getAdditionalPropertiesFile(Properties additionalProps, Log log) { String key = BuildInfoConfigProperties.PROP_PROPS_FILE; String filePath = System.getProperty(key); @@ -245,7 +280,7 @@ private static String getAdditionalPropertiesFile(Properties additionalProps, Lo filePath = additionalProps.getProperty(key); propFoundPath = "additionalProps.getProperty(" + key + ")"; } - if (StringUtils.isBlank(filePath)) { + if (StringUtils.isBlank(filePath) && additionalProps != null) { // Jenkins prefixes these variables with "env." so let's try that filePath = additionalProps.getProperty("env." + key); if (StringUtils.isBlank(filePath)) { 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 f24bd1b6a..0432a215a 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 @@ -10,7 +10,11 @@ public interface BuildInfoConfigProperties { */ String BUILD_INFO_CONFIG_PREFIX = "buildInfoConfig."; String PROPERTIES_FILE = "propertiesFile"; + String PROPERTIES_FILE_KEY = "propertiesFileKey"; + String PROP_PROPS_FILE = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE; + String PROP_PROPS_FILE_KEY = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE_KEY; + 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 fb1d4b571..ab47bb446 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 @@ -2,6 +2,7 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.jfrog.build.api.util.CommonUtils; @@ -11,11 +12,23 @@ import org.jfrog.build.extractor.ci.Issue; import org.jfrog.build.extractor.clientConfiguration.util.IssuesTrackerUtils; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -141,6 +154,8 @@ * @author freds */ public class ArtifactoryClientConfiguration { + private static final String ALGORITHM = "AES"; + private static final String TRANSFORMATION = "AES"; // Try checksum deploy of files greater than 10KB public static final transient int DEFAULT_MIN_CHECKSUM_DEPLOY_SIZE_KB = 10; public static final String DEFAULT_NUGET_PROTOCOL = "v2"; @@ -244,6 +259,113 @@ public void persistToPropertiesFile() { } } + /** + * Encrypts properties to a file represented as a byte array and returns the secret key used for encryption. + * + * @param os The output stream where the encrypted properties will be written. + * @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; + } + + /** + * 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. + * @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); + } + + /** + * 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. + * @return A byte array representing the encrypted properties. + */ + private static byte[] encryptProperties(Properties properties, byte[] secretKey) { + try { + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + + String propertiesString = propertiesToString(properties); + return cipher.doFinal(propertiesString.getBytes()); + } catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException | + InvalidKeyException e) { + throw new RuntimeException(e); + } + } + + private static byte[] generateRandomKey() { + SecureRandom secureRandom = new SecureRandom(); + byte[] key = new byte[16]; + secureRandom.nextBytes(key); + return key; + } + + private static String propertiesToString(Properties properties) { + StringBuilder stringBuilder = new StringBuilder(); + for (String key : properties.stringPropertyNames()) { + stringBuilder.append(key).append("=").append(properties.getProperty(key)).append("\n"); + } + return stringBuilder.toString(); + } + + /** + * 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. + * @return A Properties object containing the decrypted properties. + */ + private static Properties decryptProperties(byte[] encryptedData, byte[] secretKey) throws IOException { + try { + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + + String decryptedString = new String(cipher.doFinal(encryptedData)); + + return stringToProperties(decryptedString); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | BadPaddingException | IllegalBlockSizeException e) { + e.printStackTrace(); + } + return null; + } + + private static Properties stringToProperties(String propertiesString) throws IOException { + Properties properties = new Properties(); + try (InputStream inputStream = new ByteArrayInputStream(propertiesString.getBytes(StandardCharsets.UTF_8))) { + properties.load(inputStream); + } + return properties; + } + + public byte[] persistToEncryptedPropertiesFile(OutputStream os) throws IOException { + if (StringUtils.isEmpty(getPropertiesFile())) { + return null; + } + Properties properties = new Properties(); + properties.putAll(filterMapNullValues(rootConfig.props)); + properties.putAll(filterMapNullValues(root.props)); + // Properties that have the 'artifactory.' prefix are deprecated. + // For backward compatibility reasons, both will be added to the props map. + // The build-info 2.32.3 is the last version to be using the deprecated properties as its primary. + for (String key : properties.stringPropertyNames()) { + properties.put("artifactory." + key, properties.getProperty(key)); + } + return encryptedPropertiesToFile(os, properties); + } + public static Map filterMapNullValues(Map map) { Map result = new HashMap(); for (Map.Entry entry : map.entrySet()) { 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 0fc05cd88..4f3d050a1 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 @@ -4,7 +4,10 @@ import org.jfrog.build.extractor.builder.BuildInfoBuilder; import org.jfrog.build.extractor.builder.DependencyBuilder; import org.jfrog.build.extractor.builder.ModuleBuilder; -import org.jfrog.build.extractor.ci.*; +import org.jfrog.build.extractor.ci.BuildInfo; +import org.jfrog.build.extractor.ci.BuildInfoConfigProperties; +import org.jfrog.build.extractor.ci.BuildInfoProperties; +import org.jfrog.build.extractor.ci.Dependency; import org.jfrog.build.extractor.ci.Module; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -15,10 +18,21 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Base64; import java.util.Properties; -import static org.jfrog.build.extractor.BuildInfoExtractorUtils.*; -import static org.testng.Assert.*; +import static org.jfrog.build.IntegrationTestsBase.getLog; +import static org.jfrog.build.extractor.BuildInfoExtractorUtils.BUILD_INFO_PROP_PREDICATE; +import static org.jfrog.build.extractor.BuildInfoExtractorUtils.buildInfoToJsonString; +import static org.jfrog.build.extractor.BuildInfoExtractorUtils.createBuildInfoUrl; +import static org.jfrog.build.extractor.BuildInfoExtractorUtils.filterDynamicProperties; +import static org.jfrog.build.extractor.BuildInfoExtractorUtils.getEnvProperties; +import static org.jfrog.build.extractor.BuildInfoExtractorUtils.jsonStringToBuildInfo; +import static org.jfrog.build.extractor.BuildInfoExtractorUtils.mergePropertiesWithSystemAndPropertyFile; +import static org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration.encryptedPropertiesToFile; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; /** * Test the build info extractor @@ -48,7 +62,7 @@ public void getBuildInfoPropertiesFromSystemProps() { System.setProperty(POPO_KEY, "buildname"); System.setProperty(MOMO_KEY, "1"); - Properties props = filterDynamicProperties(mergePropertiesWithSystemAndPropertyFile(new Properties()), BUILD_INFO_PROP_PREDICATE); + Properties props = filterDynamicProperties(mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), BUILD_INFO_PROP_PREDICATE); assertEquals(props.size(), 2, "there should only be 2 properties after the filtering"); assertEquals(props.getProperty(POPO_KEY), "buildname", "popo property does not match"); @@ -68,7 +82,29 @@ public void getBuildInfoPropertiesFromFile() throws IOException { System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); Properties fileProps = filterDynamicProperties( - mergePropertiesWithSystemAndPropertyFile(new Properties()), + mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), + BUILD_INFO_PROP_PREDICATE); + + assertEquals(fileProps.size(), 2, "there should only be 2 properties after the filtering"); + assertEquals(fileProps.getProperty(POPO_KEY), "buildname", "popo property does not match"); + assertEquals(fileProps.getProperty(MOMO_KEY), "1", "momo property does not match"); + + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); + } + + public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { + Properties props = new Properties(); + props.put(POPO_KEY, "buildname"); + props.put(MOMO_KEY, "1"); + try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile.toFile())) { + byte[] key = encryptedPropertiesToFile(fileOutputStream, props); + System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY, Base64.getEncoder().encodeToString((key))); + } + + System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); + + Properties fileProps = filterDynamicProperties( + mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), BUILD_INFO_PROP_PREDICATE); assertEquals(fileProps.size(), 2, "there should only be 2 properties after the filtering"); @@ -94,7 +130,7 @@ public void getBuildInfoProperties() throws IOException { System.setProperty(gogoKey, "2"); Properties buildInfoProperties = filterDynamicProperties( - mergePropertiesWithSystemAndPropertyFile(new Properties()), + mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), BUILD_INFO_PROP_PREDICATE); assertEquals(buildInfoProperties.size(), 4, "There should be 4 properties"); 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 45d75c463..3645d12fe 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 @@ -1,15 +1,25 @@ package org.jfrog.build.extractor.clientConfiguration; import org.jfrog.build.api.util.NullLog; +import org.jfrog.build.extractor.ci.BuildInfoConfigProperties; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Properties; +import static org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration.decryptPropertiesFromFile; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; @Test public class ArtifactoryClientConfigurationTest { + private Path tempFile; + @Test(description = "Test http and https proxy handler.") public void testProxyHandler() { ArtifactoryClientConfiguration client = createProxyClient(); @@ -29,6 +39,33 @@ public void testProxyHandler() { assertNull(httpsProxy.getPassword()); } + @BeforeMethod + private void setUp() throws IOException { + tempFile = Files.createTempFile("BuildInfoExtractorUtilsTest", "").toAbsolutePath(); + } + + @AfterMethod + private void tearDown() throws IOException { + Files.deleteIfExists(tempFile); + } + + @Test(description = "Test read encrypted property file") + public void testReadEncryptedPropertyFile() throws IOException { + // Prepare + ArtifactoryClientConfiguration client = createProxyClient(); + tempFile = Files.createTempFile("BuildInfoExtractorUtilsTest", "").toAbsolutePath(); + client.root.props.put(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); + + try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile.toFile())) { + // Save encrypted file. + byte[] key = client.persistToEncryptedPropertiesFile(fileOutputStream); + // Assert decrypted successfully. + Properties props = decryptPropertiesFromFile(tempFile.toString(), key); + assertEquals(props.size(), 18); + assertEquals(props.getProperty("proxy.host"), client.getAllProperties().get("proxy.host")); + } + } + private ArtifactoryClientConfiguration createProxyClient() { ArtifactoryClientConfiguration client = new ArtifactoryClientConfiguration(new NullLog()); Properties props = new Properties(); From 55e28376832f8cfac0d04721bdc41f62a1bc5644 Mon Sep 17 00:00:00 2001 From: Or Geva Date: Tue, 21 Nov 2023 21:28:59 +0200 Subject: [PATCH 2/9] Add java docs --- .../extractor/BuildInfoExtractorUtils.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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 1e931f5e7..d48407d11 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 @@ -69,7 +69,7 @@ public abstract class BuildInfoExtractorUtils { public static Properties mergePropertiesWithSystemAndPropertyFile(Properties existingProps, Log log) { Properties mergedProps = new Properties(); mergedProps.putAll(addSystemProperties(existingProps)); - mergedProps.putAll(addAdditionalPropertiesFile(mergedProps, log)); + mergedProps.putAll(searchAdditionalPropertiesFile(mergedProps, log)); return mergedProps; } @@ -81,7 +81,14 @@ private static Properties addSystemProperties(Properties existingProps) { return props; } - private static Properties addAdditionalPropertiesFile(Properties existingProps, Log log) { + /** + * Retrieves additional properties from a specified build info properties file path. + * + * @param existingProps Existing properties object. + * @param log Logger instance for logging debug information. + * @return Properties object containing additional properties if found; otherwise, an empty properties object. + */ + private static Properties searchAdditionalPropertiesFile(Properties existingProps, Log log) { Properties props = new Properties(); String propsFilePath = getAdditionalPropertiesFile(existingProps, log); @@ -255,12 +262,18 @@ public static void saveBuildInfoToFile(BuildInfo buildInfo, File toFile) throws CommonUtils.writeByCharset(buildInfoJson, toFile, StandardCharsets.UTF_8); } + /** + * @return The encryption key obtained from system properties or additional properties. + */ + private static String getPropertiesFileEncryptionKey(Properties additionalProps) { String key = BuildInfoConfigProperties.PROP_PROPS_FILE_KEY; + // Check if the encryption key is set in system properties if (StringUtils.isNotBlank(System.getProperty(key))) { return System.getProperty(key); } if (additionalProps != null) { + // Check for the encryption key directly in additional properties if (StringUtils.isNotBlank(additionalProps.getProperty(key))) { return additionalProps.getProperty(key); } From 480d232b2fede4af48ecdf4762280053b59f83c4 Mon Sep 17 00:00:00 2001 From: Or Geva Date: Tue, 21 Nov 2023 22:11:33 +0200 Subject: [PATCH 3/9] Add test --- .../extractor/BuildExtractorUtilsTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 4f3d050a1..f210e9d85 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 @@ -111,6 +111,26 @@ public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { assertEquals(fileProps.getProperty(POPO_KEY), "buildname", "popo property does not match"); assertEquals(fileProps.getProperty(MOMO_KEY), "1", "momo property does not match"); + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY); + } + + public void failToReadEncryptedFileWithNoKey() throws IOException { + Properties props = new Properties(); + props.put(POPO_KEY, "buildname"); + props.put(MOMO_KEY, "1"); + try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile.toFile())) { + encryptedPropertiesToFile(fileOutputStream, props); + } + + System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); + + Properties fileProps = filterDynamicProperties( + mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), + BUILD_INFO_PROP_PREDICATE); + + assertEquals(fileProps.size(), 0, "0 properties should be present, the file is encrypted, and the key is not available"); + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); } From 83b83176b237f85a8bd14e6a17b71fe4e9f0c7ad Mon Sep 17 00:00:00 2001 From: Or Geva Date: Wed, 22 Nov 2023 12:20:59 +0200 Subject: [PATCH 4/9] Remove code duplications --- .../ArtifactoryClientConfiguration.java | 72 +++++++++---------- .../extractor/BuildExtractorUtilsTest.java | 42 +++++------ 2 files changed, 55 insertions(+), 59 deletions(-) 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 ab47bb446..2f9bd94e1 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 @@ -3,7 +3,6 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.jfrog.build.api.util.CommonUtils; import org.jfrog.build.api.util.Log; @@ -241,24 +240,6 @@ public Log getLog() { return root.getLog(); } - public void persistToPropertiesFile() { - if (StringUtils.isEmpty(getPropertiesFile())) { - return; - } - Properties props = new Properties(); - props.putAll(filterMapNullValues(root.props)); - props.putAll(filterMapNullValues(rootConfig.props)); - FileOutputStream fos = null; - try { - fos = new FileOutputStream(new File(getPropertiesFile()).getCanonicalFile()); - props.store(fos, "BuildInfo configuration property file"); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - IOUtils.closeQuietly(fos); - } - } - /** * Encrypts properties to a file represented as a byte array and returns the secret key used for encryption. * @@ -266,12 +247,47 @@ public void persistToPropertiesFile() { * @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 { + private static byte[] encryptedPropertiesToFile(OutputStream os, Properties properties) throws IOException { byte[] secretKey = generateRandomKey(); os.write(encryptProperties(properties, secretKey)); return secretKey; } + public void persistToPropertiesFile() { + if (StringUtils.isEmpty(getPropertiesFile())) { + return; + } + try (FileOutputStream fos = new FileOutputStream(new File(getPropertiesFile()).getCanonicalFile())) { + preparePropertiesToPersist().store(fos, "BuildInfo configuration property file"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public byte[] persistToEncryptedPropertiesFile(OutputStream os) throws IOException { + if (StringUtils.isEmpty(getPropertiesFile())) { + return null; + } + return encryptedPropertiesToFile(os, preparePropertiesToPersist()); + } + + private Properties preparePropertiesToPersist() { + Properties props = new Properties(); + props.putAll(filterMapNullValues(rootConfig.props)); + props.putAll(filterMapNullValues(root.props)); + + // Properties that have the 'artifactory.' prefix are deprecated. + // For backward compatibility reasons, both will be added to the props map. + // The build-info 2.32.3 is the last version to be using the deprecated properties as its primary. + Properties artifactoryProps = new Properties(); + for (String key : props.stringPropertyNames()) { + artifactoryProps.put("artifactory." + key, props.getProperty(key)); + } + props.putAll(artifactoryProps); + + return props; + } + /** * Decrypts properties from a file using the provided secret key. * @@ -350,22 +366,6 @@ private static Properties stringToProperties(String propertiesString) throws IOE return properties; } - public byte[] persistToEncryptedPropertiesFile(OutputStream os) throws IOException { - if (StringUtils.isEmpty(getPropertiesFile())) { - return null; - } - Properties properties = new Properties(); - properties.putAll(filterMapNullValues(rootConfig.props)); - properties.putAll(filterMapNullValues(root.props)); - // Properties that have the 'artifactory.' prefix are deprecated. - // For backward compatibility reasons, both will be added to the props map. - // The build-info 2.32.3 is the last version to be using the deprecated properties as its primary. - for (String key : properties.stringPropertyNames()) { - properties.put("artifactory." + key, properties.getProperty(key)); - } - return encryptedPropertiesToFile(os, properties); - } - public static Map filterMapNullValues(Map map) { Map result = new HashMap(); for (Map.Entry entry : map.entrySet()) { 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 f210e9d85..894bad5db 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 @@ -1,6 +1,7 @@ package org.jfrog.build.extractor; import org.apache.commons.lang3.ArrayUtils; +import org.jfrog.build.api.util.NullLog; import org.jfrog.build.extractor.builder.BuildInfoBuilder; import org.jfrog.build.extractor.builder.DependencyBuilder; import org.jfrog.build.extractor.builder.ModuleBuilder; @@ -9,6 +10,7 @@ import org.jfrog.build.extractor.ci.BuildInfoProperties; import org.jfrog.build.extractor.ci.Dependency; import org.jfrog.build.extractor.ci.Module; +import org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -29,7 +31,6 @@ import static org.jfrog.build.extractor.BuildInfoExtractorUtils.getEnvProperties; import static org.jfrog.build.extractor.BuildInfoExtractorUtils.jsonStringToBuildInfo; import static org.jfrog.build.extractor.BuildInfoExtractorUtils.mergePropertiesWithSystemAndPropertyFile; -import static org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration.encryptedPropertiesToFile; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; @@ -93,20 +94,11 @@ public void getBuildInfoPropertiesFromFile() throws IOException { } public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { - Properties props = new Properties(); - props.put(POPO_KEY, "buildname"); - props.put(MOMO_KEY, "1"); - try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile.toFile())) { - byte[] key = encryptedPropertiesToFile(fileOutputStream, props); - System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY, Base64.getEncoder().encodeToString((key))); - } - - System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); - + byte[] key = setupEncryptedFileTest(); + System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY, Base64.getEncoder().encodeToString(key)); Properties fileProps = filterDynamicProperties( mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), BUILD_INFO_PROP_PREDICATE); - assertEquals(fileProps.size(), 2, "there should only be 2 properties after the filtering"); assertEquals(fileProps.getProperty(POPO_KEY), "buildname", "popo property does not match"); assertEquals(fileProps.getProperty(MOMO_KEY), "1", "momo property does not match"); @@ -116,24 +108,28 @@ public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { } public void failToReadEncryptedFileWithNoKey() throws IOException { - Properties props = new Properties(); - props.put(POPO_KEY, "buildname"); - props.put(MOMO_KEY, "1"); - try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile.toFile())) { - encryptedPropertiesToFile(fileOutputStream, props); - } - - System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); - + setupEncryptedFileTest(); Properties fileProps = filterDynamicProperties( mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), BUILD_INFO_PROP_PREDICATE); - assertEquals(fileProps.size(), 0, "0 properties should be present, the file is encrypted, and the key is not available"); - System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); } + private byte[] setupEncryptedFileTest() throws IOException { + Properties props = new Properties(); + props.put(POPO_KEY, "buildname"); + props.put(MOMO_KEY, "1"); + props.put(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); + System.setProperty(BuildInfoConfigProperties.PROP_PROPS_FILE, tempFile.toString()); + ArtifactoryClientConfiguration client = new ArtifactoryClientConfiguration(new NullLog()); + client.fillFromProperties(props); + + try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile.toFile())) { + return client.persistToEncryptedPropertiesFile(fileOutputStream); + } + } + public void getBuildInfoProperties() throws IOException { Properties props = new Properties(); props.put(POPO_KEY, "buildname"); From b7cf2c3b9732ab43202ffb8e5ba4c48cd6a59974 Mon Sep 17 00:00:00 2001 From: Or Geva Date: Tue, 28 Nov 2023 21:14:51 +0200 Subject: [PATCH 5/9] Apply CR changes --- .../extractor/BuildInfoExtractorUtils.java | 6 +- .../ArtifactoryClientConfiguration.java | 117 +++--------------- .../util/EncryptionUtils.java | 101 +++++++++++++++ 3 files changed, 118 insertions(+), 106 deletions(-) create mode 100644 build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/EncryptionUtils.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 d48407d11..cb55dfedc 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 @@ -263,9 +263,9 @@ public static void saveBuildInfoToFile(BuildInfo buildInfo, File toFile) throws } /** + * @param additionalProps Additional properties containing the encryption key. * @return The encryption key obtained from system properties or additional properties. */ - private static String getPropertiesFileEncryptionKey(Properties additionalProps) { String key = BuildInfoConfigProperties.PROP_PROPS_FILE_KEY; // Check if the encryption key is set in system properties @@ -289,11 +289,11 @@ private static String getAdditionalPropertiesFile(Properties additionalProps, Lo String key = BuildInfoConfigProperties.PROP_PROPS_FILE; String filePath = System.getProperty(key); String propFoundPath = "System.getProperty(" + key + ")"; - if (StringUtils.isBlank(filePath) && additionalProps != null) { + if (StringUtils.isBlank(filePath)) { filePath = additionalProps.getProperty(key); propFoundPath = "additionalProps.getProperty(" + key + ")"; } - if (StringUtils.isBlank(filePath) && additionalProps != null) { + if (StringUtils.isBlank(filePath)) { // Jenkins prefixes these variables with "env." so let's try that filePath = additionalProps.getProperty("env." + key); if (StringUtils.isBlank(filePath)) { 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 2f9bd94e1..891cdcafd 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 @@ -11,23 +11,12 @@ import org.jfrog.build.extractor.ci.Issue; import org.jfrog.build.extractor.clientConfiguration.util.IssuesTrackerUtils; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.SecretKeySpec; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -148,13 +137,13 @@ 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; /** * @author freds */ public class ArtifactoryClientConfiguration { - private static final String ALGORITHM = "AES"; - private static final String TRANSFORMATION = "AES"; // Try checksum deploy of files greater than 10KB public static final transient int DEFAULT_MIN_CHECKSUM_DEPLOY_SIZE_KB = 10; public static final String DEFAULT_NUGET_PROTOCOL = "v2"; @@ -240,18 +229,7 @@ public Log getLog() { return root.getLog(); } - /** - * Encrypts properties to a file represented as a byte array and returns the secret key used for encryption. - * - * @param os The output stream where the encrypted properties will be written. - * @param properties The Properties object containing the properties to be encrypted. - * @return A byte array representing the secret key used for encryption. - */ - private static byte[] encryptedPropertiesToFile(OutputStream os, Properties properties) throws IOException { - byte[] secretKey = generateRandomKey(); - os.write(encryptProperties(properties, secretKey)); - return secretKey; - } + public void persistToPropertiesFile() { if (StringUtils.isEmpty(getPropertiesFile())) { @@ -271,6 +249,17 @@ public byte[] persistToEncryptedPropertiesFile(OutputStream os) throws IOExcepti 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. + * @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); + } + private Properties preparePropertiesToPersist() { Properties props = new Properties(); props.putAll(filterMapNullValues(rootConfig.props)); @@ -288,84 +277,6 @@ private Properties preparePropertiesToPersist() { return props; } - /** - * 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. - * @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); - } - - /** - * 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. - * @return A byte array representing the encrypted properties. - */ - private static byte[] encryptProperties(Properties properties, byte[] secretKey) { - try { - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); - - String propertiesString = propertiesToString(properties); - return cipher.doFinal(propertiesString.getBytes()); - } catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException | - InvalidKeyException e) { - throw new RuntimeException(e); - } - } - - private static byte[] generateRandomKey() { - SecureRandom secureRandom = new SecureRandom(); - byte[] key = new byte[16]; - secureRandom.nextBytes(key); - return key; - } - - private static String propertiesToString(Properties properties) { - StringBuilder stringBuilder = new StringBuilder(); - for (String key : properties.stringPropertyNames()) { - stringBuilder.append(key).append("=").append(properties.getProperty(key)).append("\n"); - } - return stringBuilder.toString(); - } - - /** - * 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. - * @return A Properties object containing the decrypted properties. - */ - private static Properties decryptProperties(byte[] encryptedData, byte[] secretKey) throws IOException { - try { - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); - - String decryptedString = new String(cipher.doFinal(encryptedData)); - - return stringToProperties(decryptedString); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | BadPaddingException | IllegalBlockSizeException e) { - e.printStackTrace(); - } - return null; - } - - private static Properties stringToProperties(String propertiesString) throws IOException { - Properties properties = new Properties(); - try (InputStream inputStream = new ByteArrayInputStream(propertiesString.getBytes(StandardCharsets.UTF_8))) { - properties.load(inputStream); - } - return properties; - } - public static Map filterMapNullValues(Map map) { Map result = new HashMap(); for (Map.Entry entry : map.entrySet()) { 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/EncryptionUtils.java new file mode 100644 index 000000000..1efe3a0ab --- /dev/null +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/EncryptionUtils.java @@ -0,0 +1,101 @@ +package org.jfrog.build.extractor.clientConfiguration.util; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Properties; + +public class EncryptionUtils { + private static final String ALGORITHM = "CBC"; + private static final String TRANSFORMATION = "CBC"; + + /** + * 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. + * @return A Properties object containing the decrypted properties. + */ + public static Properties decryptProperties(byte[] encryptedData, byte[] secretKey) throws IOException { + try { + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + + String decryptedString = new String(cipher.doFinal(encryptedData)); + + return stringToProperties(decryptedString); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | BadPaddingException | IllegalBlockSizeException e) { + e.printStackTrace(); + } + return null; + } + + private static Properties stringToProperties(String propertiesString) throws IOException { + Properties properties = new Properties(); + try (InputStream inputStream = new ByteArrayInputStream(propertiesString.getBytes(StandardCharsets.UTF_8))) { + properties.load(inputStream); + } + return properties; + } + + /** + * Encrypts properties to a file represented as a byte array and returns the secret key used for encryption. + * + * @param os The output stream where the encrypted properties will be written. + * @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; + } + + private static byte[] generateRandomKey() { + SecureRandom secureRandom = new SecureRandom(); + byte[] key = new byte[16]; + 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. + * @return A byte array representing the encrypted properties. + */ + private static byte[] encryptProperties(Properties properties, byte[] secretKey) { + try { + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + + String propertiesString = propertiesToString(properties); + return cipher.doFinal(propertiesString.getBytes()); + } catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException | + InvalidKeyException e) { + throw new RuntimeException(e); + } + } + + private static String propertiesToString(Properties properties) { + StringBuilder stringBuilder = new StringBuilder(); + for (String key : properties.stringPropertyNames()) { + stringBuilder.append(key).append("=").append(properties.getProperty(key)).append("\n"); + } + return stringBuilder.toString(); + } +} From d473ce534b36d9b104b50687c123f5c0045b77a2 Mon Sep 17 00:00:00 2001 From: Or Geva Date: Wed, 29 Nov 2023 09:35:42 +0200 Subject: [PATCH 6/9] Add test --- .../extractor/clientConfiguration/util/EncryptionUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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/EncryptionUtils.java index 1efe3a0ab..60c97b47c 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/EncryptionUtils.java @@ -16,8 +16,8 @@ import java.util.Properties; public class EncryptionUtils { - private static final String ALGORITHM = "CBC"; - private static final String TRANSFORMATION = "CBC"; + private static final String ALGORITHM = "AES"; + private static final String TRANSFORMATION = "AES/CBC/PKCS5PADDING"; /** * Decrypts properties from an encrypted byte array using the provided secret key. From c8304af3f8148a4457fda6f5b53d130b3ec6f124 Mon Sep 17 00:00:00 2001 From: Or Geva Date: Wed, 29 Nov 2023 12:25:41 +0200 Subject: [PATCH 7/9] Change encryption algorithm to gcm 256 bit --- .../util/EncryptionUtils.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) 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/EncryptionUtils.java index 60c97b47c..32a553855 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/EncryptionUtils.java @@ -4,12 +4,14 @@ import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -17,7 +19,10 @@ public class EncryptionUtils { private static final String ALGORITHM = "AES"; - private static final String TRANSFORMATION = "AES/CBC/PKCS5PADDING"; + 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. @@ -30,13 +35,16 @@ public static Properties decryptProperties(byte[] encryptedData, byte[] secretKe try { Cipher cipher = Cipher.getInstance(TRANSFORMATION); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, new byte[GCM_TAG_LENGTH / 8]); + + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec); - String decryptedString = new String(cipher.doFinal(encryptedData)); + byte[] decryptedBytes = cipher.doFinal(encryptedData); + String decryptedString = new String(decryptedBytes, StandardCharsets.UTF_8); return stringToProperties(decryptedString); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | BadPaddingException | IllegalBlockSizeException e) { + | BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) { e.printStackTrace(); } return null; @@ -65,7 +73,7 @@ public static byte[] encryptedPropertiesToFile(OutputStream os, Properties prope private static byte[] generateRandomKey() { SecureRandom secureRandom = new SecureRandom(); - byte[] key = new byte[16]; + byte[] key = new byte[AES_256_KEY_LENGTH / 8]; secureRandom.nextBytes(key); return key; } @@ -81,12 +89,14 @@ private static byte[] encryptProperties(Properties properties, byte[] secretKey) try { Cipher cipher = Cipher.getInstance(TRANSFORMATION); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, new byte[GCM_TAG_LENGTH / 8]); + + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec); String propertiesString = propertiesToString(properties); - return cipher.doFinal(propertiesString.getBytes()); - } catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException | - InvalidKeyException e) { + return cipher.doFinal(propertiesString.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } } From daf8f3e2059a456cc6254fd2be3b097859b096fa Mon Sep 17 00:00:00 2001 From: Or Geva Date: Thu, 30 Nov 2023 11:42:44 +0200 Subject: [PATCH 8/9] 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")); + } +} From fcd2bd6474ccf8fd7882c70b98adab8ca1eac25e Mon Sep 17 00:00:00 2001 From: Or Geva Date: Thu, 30 Nov 2023 23:13:10 +0200 Subject: [PATCH 9/9] Apply CR changes --- .../extractor/BuildInfoExtractorUtils.java | 11 +- .../ArtifactoryClientConfiguration.java | 26 ++--- .../util/encryption/EncryptionKeyPair.java | 16 +-- .../util/encryption/Encryptor.java | 47 ++++++++ .../util/encryption/PropertyEncryptor.java | 83 ++++++++++++++ .../SecurePropertiesEncryption.java | 105 ------------------ .../extractor/BuildExtractorUtilsTest.java | 27 +++-- .../ArtifactoryClientConfigurationTest.java | 12 +- .../util/encryption/EncryptorTest.java | 34 ++++++ .../encryption/PropertyEncryptorTest.java | 56 ++++++++++ .../SecurePropertiesEncryptionTest.java | 37 ------ 11 files changed, 270 insertions(+), 184 deletions(-) create mode 100644 build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/Encryptor.java create mode 100644 build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/PropertyEncryptor.java delete mode 100644 build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/SecurePropertiesEncryption.java create mode 100644 build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/EncryptorTest.java create mode 100644 build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/PropertyEncryptorTest.java delete 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 f9eb266f9..8f6de57d3 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 @@ -20,6 +20,9 @@ import org.jfrog.build.extractor.clientConfiguration.PatternMatcher; import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -28,6 +31,9 @@ import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -40,7 +46,7 @@ import static org.apache.commons.lang3.StringUtils.removeEnd; import static org.jfrog.build.extractor.UrlUtils.encodeUrl; import static org.jfrog.build.extractor.UrlUtils.encodeUrlPathPart; -import static org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration.decryptPropertiesFromFile; +import static org.jfrog.build.extractor.clientConfiguration.util.encryption.PropertyEncryptor.decryptPropertiesFromFile; /** * @author Noam Y. Tenne @@ -113,7 +119,8 @@ private static Properties searchAdditionalPropertiesFile(Properties existingProp props.load(inputStream); } } - } catch (IOException e) { + } catch (IOException | InvalidAlgorithmParameterException | IllegalBlockSizeException | NoSuchPaddingException | + BadPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException("Unable to load build info properties from file: " + propertiesFile.getAbsolutePath(), e); } return props; 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 b1bb68b54..2bb90e989 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 @@ -2,7 +2,6 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMap; -import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.jfrog.build.api.util.CommonUtils; import org.jfrog.build.api.util.Log; @@ -12,12 +11,18 @@ import org.jfrog.build.extractor.clientConfiguration.util.IssuesTrackerUtils; import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -138,8 +143,7 @@ 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.encryption.SecurePropertiesEncryption.decryptProperties; -import static org.jfrog.build.extractor.clientConfiguration.util.encryption.SecurePropertiesEncryption.encryptedPropertiesToFile; +import static org.jfrog.build.extractor.clientConfiguration.util.encryption.PropertyEncryptor.encryptedPropertiesToStream; /** * @author freds @@ -230,8 +234,6 @@ public Log getLog() { return root.getLog(); } - - public void persistToPropertiesFile() { if (StringUtils.isEmpty(getPropertiesFile())) { return; @@ -243,22 +245,12 @@ public void persistToPropertiesFile() { } } - /** - * Decrypts properties from a file using the provided secret key. - * - * @param filePath The path to the file containing encrypted properties. - * @param keyPair The secret key and iv used for decryption. - * @return A Properties object containing the decrypted properties. - */ - 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 { + public EncryptionKeyPair persistToEncryptedPropertiesFile(OutputStream os) throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { if (StringUtils.isEmpty(getPropertiesFile())) { return null; } - return encryptedPropertiesToFile(os, preparePropertiesToPersist()); + return encryptedPropertiesToStream(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 index fc735c425..7de47aeb6 100644 --- 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 @@ -10,13 +10,13 @@ */ public class EncryptionKeyPair { private static final int AES_256_KEY_LENGTH = 256; - private static final int IV_LENGTH = 126; + private static final int IV_LENGTH = 128; private byte[] secretKey; - private byte[] Iv; + private byte[] iv; public EncryptionKeyPair() { this.secretKey = generateRandomKey(AES_256_KEY_LENGTH); - this.Iv = generateRandomKey(IV_LENGTH); + this.iv = generateRandomKey(IV_LENGTH); } public EncryptionKeyPair(String secretKey, String Iv) { @@ -24,7 +24,7 @@ public EncryptionKeyPair(String secretKey, String Iv) { this.secretKey = Base64.getDecoder().decode(secretKey); } if (StringUtils.isNotBlank(Iv)) { - this.Iv = Base64.getDecoder().decode(Iv); + this.iv = Base64.getDecoder().decode(Iv); } } @@ -45,19 +45,21 @@ public byte[] getSecretKey() { return secretKey; } + @SuppressWarnings("unused") public String getStringSecretKey() { return Base64.getEncoder().encodeToString(secretKey); } public byte[] getIv() { - return Iv; + return iv; } + @SuppressWarnings("unused") public String getStringIv() { - return Base64.getEncoder().encodeToString(Iv); + return Base64.getEncoder().encodeToString(iv); } public boolean isEmpty() { - return secretKey == null || secretKey.length == 0 || Iv == null || Iv.length == 0; + 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/encryption/Encryptor.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/Encryptor.java new file mode 100644 index 000000000..f86e88861 --- /dev/null +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/Encryptor.java @@ -0,0 +1,47 @@ +package org.jfrog.build.extractor.clientConfiguration.util.encryption; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class Encryptor { + private static final String ALGORITHM = "AES"; + private static final String TRANSFORMATION = "AES/GCM/NoPadding"; + private static final int GCM_TAG_LENGTH = 128; + + /** + * Decrypts the given data using the provided EncryptionKeyPair. + * + * @param data The encrypted data to be decrypted + * @param keyPair The EncryptionKeyPair containing the secret key and IV for decryption + * @return The decrypted data as a byte array + */ + public static byte[] decrypt(byte[] data, EncryptionKeyPair keyPair) throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv()); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec); + return cipher.doFinal(data); + } + + /** + * Encrypts the given data using the provided EncryptionKeyPair. + * + * @param data The data to be encrypted + * @param keyPair The EncryptionKeyPair containing the secret key and IV for encryption + * @return The encrypted data as a byte array + */ + public static byte[] encrypt(byte[] data, EncryptionKeyPair keyPair) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv()); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec); + return cipher.doFinal(data); + } +} diff --git a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/PropertyEncryptor.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/PropertyEncryptor.java new file mode 100644 index 000000000..a04cce87e --- /dev/null +++ b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/PropertyEncryptor.java @@ -0,0 +1,83 @@ +package org.jfrog.build.extractor.clientConfiguration.util.encryption; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +import static org.jfrog.build.extractor.clientConfiguration.util.encryption.Encryptor.decrypt; +import static org.jfrog.build.extractor.clientConfiguration.util.encryption.Encryptor.encrypt; + +public class PropertyEncryptor { + + /** + * Decrypts properties from a file using the provided secret key. + * + * @param filePath The path to the file containing encrypted properties. + * @param keyPair The secret key and iv used for decryption. + * @return A Properties object containing the decrypted properties. + */ + public static Properties decryptPropertiesFromFile(String filePath, EncryptionKeyPair keyPair) throws IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException { + if (StringUtils.isBlank(filePath)) { + return null; + } + if (!new File(filePath).exists()) { + throw new IOException("File " + filePath + " does not exist"); + } + return decryptProperties(FileUtils.readFileToByteArray(new File(filePath)), keyPair); + } + + /** + * Decrypts properties from an encrypted byte array using the provided secret key. + * + * @param encryptedData The encrypted byte array representing properties. + * @param keyPair The secret key and iv used for decryption. + * @return A Properties object containing the decrypted properties. + */ + private static Properties decryptProperties(byte[] encryptedData, EncryptionKeyPair keyPair) throws IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException { + byte[] decryptedBytes = decrypt(encryptedData, keyPair); + String decryptedString = new String(decryptedBytes, StandardCharsets.UTF_8); + return stringToProperties(decryptedString); + } + + /** + * Encrypts properties to a file represented as a byte array and returns the secret key used for encryption. + * + * @param os The output stream where the encrypted properties will be written. + * @param properties The Properties object containing the properties to be encrypted. + * @return A byte array representing the secret key used for encryption. + */ + public static EncryptionKeyPair encryptedPropertiesToStream(OutputStream os, Properties properties) throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + EncryptionKeyPair keyPair = new EncryptionKeyPair(); + os.write(encrypt(propertiesToString(properties).getBytes(StandardCharsets.UTF_8), keyPair)); + return keyPair; + } + + private static Properties stringToProperties(String propertiesString) throws IOException { + Properties properties = new Properties(); + try (InputStream inputStream = new ByteArrayInputStream(propertiesString.getBytes(StandardCharsets.UTF_8))) { + properties.load(inputStream); + } + return properties; + } + + private static String propertiesToString(Properties properties) { + StringBuilder stringBuilder = new StringBuilder(); + for (String key : properties.stringPropertyNames()) { + stringBuilder.append(key).append("=").append(properties.getProperty(key)).append("\n"); + } + return stringBuilder.toString(); + } +} diff --git a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/SecurePropertiesEncryption.java b/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/SecurePropertiesEncryption.java deleted file mode 100644 index da98389cf..000000000 --- a/build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/util/encryption/SecurePropertiesEncryption.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.jfrog.build.extractor.clientConfiguration.util.encryption; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Properties; - -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; - - - - /** - * Decrypts properties from an encrypted byte array using the provided secret key. - * - * @param encryptedData The encrypted byte array representing properties. - * @param keyPair The secret key and iv used for decryption. - * @return A Properties object containing the decrypted properties. - */ - public static Properties decryptProperties(byte[] encryptedData, EncryptionKeyPair keyPair) throws IOException { - try { - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv()); - - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec); - - byte[] decryptedBytes = cipher.doFinal(encryptedData); - String decryptedString = new String(decryptedBytes, StandardCharsets.UTF_8); - - return stringToProperties(decryptedString); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) { - e.printStackTrace(); - } - return null; - } - - private static Properties stringToProperties(String propertiesString) throws IOException { - Properties properties = new Properties(); - try (InputStream inputStream = new ByteArrayInputStream(propertiesString.getBytes(StandardCharsets.UTF_8))) { - properties.load(inputStream); - } - return properties; - } - - /** - * Encrypts properties to a file represented as a byte array and returns the secret key used for encryption. - * - * @param os The output stream where the encrypted properties will be written. - * @param properties The Properties object containing the properties to be encrypted. - * @return A byte array representing the secret key used for encryption. - */ - public static EncryptionKeyPair encryptedPropertiesToFile(OutputStream os, Properties properties) throws IOException { - EncryptionKeyPair keyPair = new EncryptionKeyPair(); - os.write(encryptProperties(properties, keyPair)); - return keyPair; - } - - - - /** - * Encrypts properties into a byte array using the provided secret key. - * - * @param properties The Properties object to be encrypted. - * @param keyPair The secret key and iv used for encryption. - * @return A byte array representing the encrypted properties. - */ - private static byte[] encryptProperties(Properties properties, EncryptionKeyPair keyPair) { - try { - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv()); - - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec); - - String propertiesString = propertiesToString(properties); - return cipher.doFinal(propertiesString.getBytes(StandardCharsets.UTF_8)); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) { - throw new RuntimeException(e); - } - } - - private static String propertiesToString(Properties properties) { - StringBuilder stringBuilder = new StringBuilder(); - for (String key : properties.stringPropertyNames()) { - stringBuilder.append(key).append("=").append(properties.getProperty(key)).append("\n"); - } - return stringBuilder.toString(); - } -} 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 b71626496..d81606a8c 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 @@ -17,10 +17,16 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Properties; @@ -58,6 +64,10 @@ private void setUp() throws IOException { @AfterMethod private void tearDown() throws IOException { Files.deleteIfExists(tempFile); + + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY); + System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE_KEY_IV); } public void getBuildInfoPropertiesFromSystemProps() { @@ -90,11 +100,9 @@ public void getBuildInfoPropertiesFromFile() throws IOException { assertEquals(fileProps.size(), 2, "there should only be 2 properties after the filtering"); assertEquals(fileProps.getProperty(POPO_KEY), "buildname", "popo property does not match"); assertEquals(fileProps.getProperty(MOMO_KEY), "1", "momo property does not match"); - - System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); } - public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { + public void getBuildInfoPropertiesFromEncryptedFile() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { 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())); @@ -104,22 +112,17 @@ public void getBuildInfoPropertiesFromEncryptedFile() throws IOException { assertEquals(fileProps.size(), 2, "there should only be 2 properties after the filtering"); assertEquals(fileProps.getProperty(POPO_KEY), "buildname", "popo property does not match"); assertEquals(fileProps.getProperty(MOMO_KEY), "1", "momo property does not match"); - - 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 { + public void failToReadEncryptedFileWithNoKey() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { setupEncryptedFileTest(); Properties fileProps = filterDynamicProperties( mergePropertiesWithSystemAndPropertyFile(new Properties(), getLog()), BUILD_INFO_PROP_PREDICATE); assertEquals(fileProps.size(), 0, "0 properties should be present, the file is encrypted, and the key is not available"); - System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); } - private EncryptionKeyPair setupEncryptedFileTest() throws IOException { + private EncryptionKeyPair setupEncryptedFileTest() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { Properties props = new Properties(); props.put(POPO_KEY, "buildname"); props.put(MOMO_KEY, "1"); @@ -158,7 +161,6 @@ public void getBuildInfoProperties() throws IOException { assertEquals(buildInfoProperties.getProperty(kokoKey), "parent", "koko parent name property does not match"); assertEquals(buildInfoProperties.getProperty(gogoKey), "2", "gogo parent number property does not match"); - System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); System.clearProperty(kokoKey); System.clearProperty(gogoKey); } @@ -175,8 +177,6 @@ public void getEnvPropertiesFromFile() throws IOException { Properties fileProps = getEnvProperties(new Properties(), null); assertEquals(fileProps.getProperty(ENV_POPO_KEY), "buildname", "popo property does not match"); assertEquals(fileProps.getProperty(ENV_MOMO_KEY), "1", "momo property does not match"); - - System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); } public void getEnvAndSysPropertiesFromFile() throws IOException { @@ -200,7 +200,6 @@ public void getEnvAndSysPropertiesFromFile() throws IOException { assertEquals(buildInfoProperties.getProperty("koko"), "parent", "koko parent name property does not match"); assertEquals(buildInfoProperties.getProperty("gogo"), "2", "gogo parent number property does not match"); - System.clearProperty(BuildInfoConfigProperties.PROP_PROPS_FILE); System.clearProperty(kokoKey); System.clearProperty(gogoKey); } 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 bfb0b4bae..832c9a4a0 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 @@ -7,14 +7,21 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.Properties; -import static org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration.decryptPropertiesFromFile; +import static org.jfrog.build.extractor.clientConfiguration.util.encryption.PropertyEncryptor.decryptPropertiesFromFile; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; @Test @@ -51,7 +58,7 @@ private void tearDown() throws IOException { } @Test(description = "Test read encrypted property file") - public void testReadEncryptedPropertyFile() throws IOException { + public void testReadEncryptedPropertyFile() throws IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException { // Prepare ArtifactoryClientConfiguration client = createProxyClient(); tempFile = Files.createTempFile("BuildInfoExtractorUtilsTest", "").toAbsolutePath(); @@ -62,6 +69,7 @@ public void testReadEncryptedPropertyFile() throws IOException { EncryptionKeyPair keyPair = client.persistToEncryptedPropertiesFile(fileOutputStream); // Assert decrypted successfully. Properties props = decryptPropertiesFromFile(tempFile.toString(), keyPair); + assertNotNull(props); 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/EncryptorTest.java b/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/EncryptorTest.java new file mode 100644 index 000000000..a00c0d6f0 --- /dev/null +++ b/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/EncryptorTest.java @@ -0,0 +1,34 @@ +package org.jfrog.build.extractor.util.encryption; + +import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; +import org.jfrog.build.extractor.clientConfiguration.util.encryption.Encryptor; +import org.testng.annotations.Test; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import static org.testng.AssertJUnit.assertEquals; + +public class EncryptorTest { + @Test + public void testEncryptionDecryption() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + EncryptionKeyPair keyPair = new EncryptionKeyPair(); + // Data to be encrypted + String originalData = "This is a secret message"; + byte[] dataToEncrypt = originalData.getBytes(); + + // Encrypt the data + byte[] encryptedData = Encryptor.encrypt(dataToEncrypt, keyPair); + + // Decrypt the data + byte[] decryptedData = Encryptor.decrypt(encryptedData, keyPair); + + // Verify if decrypted data matches the original data + String decryptedMessage = new String(decryptedData); + assertEquals(originalData, decryptedMessage); + } +} diff --git a/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/PropertyEncryptorTest.java b/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/PropertyEncryptorTest.java new file mode 100644 index 000000000..32fabfd86 --- /dev/null +++ b/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/PropertyEncryptorTest.java @@ -0,0 +1,56 @@ +package org.jfrog.build.extractor.util.encryption; + +import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair; +import org.jfrog.build.extractor.clientConfiguration.util.encryption.PropertyEncryptor; +import org.testng.annotations.Test; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class PropertyEncryptorTest { + + @Test + public void testDecryptPropertiesFromFile() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + // Create a temporary file with encrypted properties + Path tempFile = Files.createTempFile("encrypted_properties", ".properties"); + File file = tempFile.toFile(); + + Properties properties = new Properties(); + properties.setProperty("key1", "value1"); + properties.setProperty("key2", "value2"); + EncryptionKeyPair keyPair; + try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { + // Encrypt properties and write to the file + keyPair = PropertyEncryptor.encryptedPropertiesToStream(fileOutputStream, properties); + } + + String encryptedFilePath = file.getAbsolutePath(); + + try { + // Decrypt properties from the encrypted file + Properties decryptedProperties = PropertyEncryptor.decryptPropertiesFromFile(encryptedFilePath, keyPair); + + // Check if decrypted properties are as expected + assertNotNull(decryptedProperties); + assertEquals("value1", decryptedProperties.getProperty("key1"), "Decrypted property 'key1' should match"); + assertEquals("value2", decryptedProperties.getProperty("key2"), "Decrypted property 'key2' should match"); + } finally { + // Clean up - delete the temporary file + Files.deleteIfExists(Paths.get(encryptedFilePath)); + } + } +} 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 deleted file mode 100644 index 1a7bd62d2..000000000 --- a/build-info-extractor/src/test/java/org/jfrog/build/extractor/util/encryption/SecurePropertiesEncryptionTest.java +++ /dev/null @@ -1,37 +0,0 @@ -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")); - } -}