Skip to content

Commit

Permalink
Add Initialization vector(IV) used in encryption with a given key
Browse files Browse the repository at this point in the history
  • Loading branch information
Or-Geva committed Nov 30, 2023
1 parent c8304af commit daf8f3e
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand All @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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"));
}
}

0 comments on commit daf8f3e

Please sign in to comment.