Skip to content

Commit ca03eac

Browse files
authored
Encrypt & decrypt build info properties file (#766)
1 parent dc03705 commit ca03eac

File tree

10 files changed

+524
-41
lines changed

10 files changed

+524
-41
lines changed

Diff for: build-info-extractor/src/main/java/org/jfrog/build/extractor/BuildInfoExtractorUtils.java

+86-20
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
import org.jfrog.build.extractor.clientConfiguration.ClientProperties;
1919
import org.jfrog.build.extractor.clientConfiguration.IncludeExcludePatterns;
2020
import org.jfrog.build.extractor.clientConfiguration.PatternMatcher;
21+
import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair;
2122

23+
import javax.crypto.BadPaddingException;
24+
import javax.crypto.IllegalBlockSizeException;
25+
import javax.crypto.NoSuchPaddingException;
2226
import java.io.File;
2327
import java.io.IOException;
2428
import java.io.InputStream;
@@ -27,6 +31,9 @@
2731
import java.io.StringWriter;
2832
import java.nio.charset.StandardCharsets;
2933
import java.nio.file.Files;
34+
import java.security.InvalidAlgorithmParameterException;
35+
import java.security.InvalidKeyException;
36+
import java.security.NoSuchAlgorithmException;
3037
import java.util.HashMap;
3138
import java.util.Map;
3239
import java.util.Properties;
@@ -39,6 +46,7 @@
3946
import static org.apache.commons.lang3.StringUtils.removeEnd;
4047
import static org.jfrog.build.extractor.UrlUtils.encodeUrl;
4148
import static org.jfrog.build.extractor.UrlUtils.encodeUrlPathPart;
49+
import static org.jfrog.build.extractor.clientConfiguration.util.encryption.PropertyEncryptor.decryptPropertiesFromFile;
4250

4351
/**
4452
* @author Noam Y. Tenne
@@ -64,33 +72,57 @@ public abstract class BuildInfoExtractorUtils {
6472
public static final Predicate<Object> MATRIX_PARAM_PREDICATE =
6573
new PrefixPredicate(ClientProperties.PROP_DEPLOY_PARAM_PROP_PREFIX);
6674

67-
public static Properties mergePropertiesWithSystemAndPropertyFile(Properties existingProps) {
68-
return mergePropertiesWithSystemAndPropertyFile(existingProps, null);
75+
public static Properties mergePropertiesWithSystemAndPropertyFile(Properties existingProps, Log log) {
76+
Properties mergedProps = new Properties();
77+
mergedProps.putAll(addSystemProperties(existingProps));
78+
mergedProps.putAll(searchAdditionalPropertiesFile(mergedProps, log));
79+
return mergedProps;
6980
}
7081

71-
public static Properties mergePropertiesWithSystemAndPropertyFile(Properties existingProps, Log log) {
82+
83+
private static Properties addSystemProperties(Properties existingProps) {
84+
Properties props = new Properties();
85+
props.putAll(existingProps);
86+
props.putAll(System.getProperties());
87+
return props;
88+
}
89+
90+
/**
91+
* Retrieves additional properties from a specified build info properties file path.
92+
*
93+
* @param existingProps Existing properties object.
94+
* @param log Logger instance for logging debug information.
95+
* @return Properties object containing additional properties if found; otherwise, an empty properties object.
96+
*/
97+
private static Properties searchAdditionalPropertiesFile(Properties existingProps, Log log) {
7298
Properties props = new Properties();
73-
addPropsFromCommandSystemProp(existingProps, log);
7499
String propsFilePath = getAdditionalPropertiesFile(existingProps, log);
75-
if (StringUtils.isNotBlank(propsFilePath)) {
76-
File propertiesFile = new File(propsFilePath);
77-
InputStream inputStream = null;
78-
try {
79-
if (propertiesFile.exists()) {
80-
inputStream = Files.newInputStream(propertiesFile.toPath());
100+
101+
if (StringUtils.isBlank(propsFilePath)) {
102+
log.debug("[buildinfo] BuildInfo properties file path is not found.");
103+
return props;
104+
}
105+
106+
File propertiesFile = new File(propsFilePath);
107+
if (!propertiesFile.exists()) {
108+
log.debug("[buildinfo] BuildInfo properties file is not exists.");
109+
return props;
110+
}
111+
112+
try {
113+
EncryptionKeyPair keyPair = new EncryptionKeyPair(getPropertiesFileEncryptionKey(existingProps), getPropertiesFileEncryptionKeyIv(existingProps));
114+
if (!keyPair.isEmpty()) {
115+
log.debug("[buildinfo] Found an encryption for buildInfo properties file for this build.");
116+
props.putAll(decryptPropertiesFromFile(propertiesFile.getPath(), keyPair));
117+
} else {
118+
try (InputStream inputStream = Files.newInputStream(propertiesFile.toPath())) {
81119
props.load(inputStream);
82120
}
83-
} catch (IOException e) {
84-
throw new RuntimeException(
85-
"Unable to load build info properties from file: " + propertiesFile.getAbsolutePath(), e);
86-
} finally {
87-
IOUtils.closeQuietly(inputStream);
88121
}
122+
} catch (IOException | InvalidAlgorithmParameterException | IllegalBlockSizeException | NoSuchPaddingException |
123+
BadPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
124+
throw new RuntimeException("Unable to load build info properties from file: " + propertiesFile.getAbsolutePath(), e);
89125
}
90-
91-
props.putAll(existingProps);
92-
props.putAll(System.getProperties());
93-
94126
return props;
95127
}
96128

@@ -237,11 +269,45 @@ public static void saveBuildInfoToFile(BuildInfo buildInfo, File toFile) throws
237269
CommonUtils.writeByCharset(buildInfoJson, toFile, StandardCharsets.UTF_8);
238270
}
239271

272+
/**
273+
* @param additionalProps Additional properties containing the encryption key.
274+
* @return The encryption key obtained from system properties or additional properties.
275+
*/
276+
private static String getPropertiesFileEncryptionKey(Properties additionalProps) {
277+
return getPropertiesFileEncryption(additionalProps, BuildInfoConfigProperties.PROP_PROPS_FILE_KEY);
278+
}
279+
280+
/**
281+
* @param additionalProps Additional properties containing the encryption IV.
282+
* @return The encryption IV obtained from system properties or additional properties.
283+
*/
284+
private static String getPropertiesFileEncryptionKeyIv(Properties additionalProps) {
285+
return getPropertiesFileEncryption(additionalProps, BuildInfoConfigProperties.PROP_PROPS_FILE_KEY_IV);
286+
}
287+
288+
private static String getPropertiesFileEncryption(Properties additionalProps, String key) {
289+
// Check if the encryption key is set in system properties
290+
if (StringUtils.isNotBlank(System.getProperty(key))) {
291+
return System.getProperty(key);
292+
}
293+
if (additionalProps != null) {
294+
// Check for the encryption key directly in additional properties
295+
if (StringUtils.isNotBlank(additionalProps.getProperty(key))) {
296+
return additionalProps.getProperty(key);
297+
}
298+
// Jenkins prefixes these variables with "env." so let's try that
299+
if (StringUtils.isNotBlank(additionalProps.getProperty("env." + key))) {
300+
return additionalProps.getProperty("env." + key);
301+
}
302+
}
303+
return null;
304+
}
305+
240306
private static String getAdditionalPropertiesFile(Properties additionalProps, Log log) {
241307
String key = BuildInfoConfigProperties.PROP_PROPS_FILE;
242308
String filePath = System.getProperty(key);
243309
String propFoundPath = "System.getProperty(" + key + ")";
244-
if (StringUtils.isBlank(filePath) && additionalProps != null) {
310+
if (StringUtils.isBlank(filePath)) {
245311
filePath = additionalProps.getProperty(key);
246312
propFoundPath = "additionalProps.getProperty(" + key + ")";
247313
}

Diff for: build-info-extractor/src/main/java/org/jfrog/build/extractor/ci/BuildInfoConfigProperties.java

+6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ public interface BuildInfoConfigProperties {
1010
*/
1111
String BUILD_INFO_CONFIG_PREFIX = "buildInfoConfig.";
1212
String PROPERTIES_FILE = "propertiesFile";
13+
String PROPERTIES_FILE_KEY = "propertiesFileKey";
14+
String PROPERTIES_FILE_KEY_IV = "propertiesFileKeyIv";
15+
1316
String PROP_PROPS_FILE = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE;
17+
String PROP_PROPS_FILE_KEY = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE_KEY;
18+
String PROP_PROPS_FILE_KEY_IV = BUILD_INFO_CONFIG_PREFIX + PROPERTIES_FILE_KEY_IV;
19+
1420
String EXPORT_FILE = "exportFile";
1521
String PROP_EXPORT_FILE_PATH = BUILD_INFO_CONFIG_PREFIX + EXPORT_FILE;
1622

Diff for: build-info-extractor/src/main/java/org/jfrog/build/extractor/clientConfiguration/ArtifactoryClientConfiguration.java

+36-10
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,27 @@
22

33
import com.google.common.collect.ArrayListMultimap;
44
import com.google.common.collect.ImmutableMap;
5-
import org.apache.commons.io.IOUtils;
65
import org.apache.commons.lang3.StringUtils;
76
import org.jfrog.build.api.util.CommonUtils;
87
import org.jfrog.build.api.util.Log;
98
import org.jfrog.build.extractor.ci.BuildInfo;
109
import org.jfrog.build.extractor.ci.BuildInfoFields;
1110
import org.jfrog.build.extractor.ci.Issue;
1211
import org.jfrog.build.extractor.clientConfiguration.util.IssuesTrackerUtils;
12+
import org.jfrog.build.extractor.clientConfiguration.util.encryption.EncryptionKeyPair;
1313

14+
import javax.crypto.BadPaddingException;
15+
import javax.crypto.IllegalBlockSizeException;
16+
import javax.crypto.NoSuchPaddingException;
1417
import java.io.File;
1518
import java.io.FileOutputStream;
1619
import java.io.IOException;
20+
import java.io.OutputStream;
1721
import java.io.UnsupportedEncodingException;
1822
import java.net.URLEncoder;
23+
import java.security.InvalidAlgorithmParameterException;
24+
import java.security.InvalidKeyException;
25+
import java.security.NoSuchAlgorithmException;
1926
import java.text.ParseException;
2027
import java.text.SimpleDateFormat;
2128
import java.util.Date;
@@ -136,6 +143,7 @@
136143
import static org.jfrog.build.extractor.clientConfiguration.ClientProperties.PROP_RESOLVE_PREFIX;
137144
import static org.jfrog.build.extractor.clientConfiguration.ClientProperties.PROP_SO_TIMEOUT;
138145
import static org.jfrog.build.extractor.clientConfiguration.ClientProperties.PROP_TIMEOUT;
146+
import static org.jfrog.build.extractor.clientConfiguration.util.encryption.PropertyEncryptor.encryptedPropertiesToStream;
139147

140148
/**
141149
* @author freds
@@ -230,20 +238,38 @@ public void persistToPropertiesFile() {
230238
if (StringUtils.isEmpty(getPropertiesFile())) {
231239
return;
232240
}
233-
Properties props = new Properties();
234-
props.putAll(filterMapNullValues(root.props));
235-
props.putAll(filterMapNullValues(rootConfig.props));
236-
FileOutputStream fos = null;
237-
try {
238-
fos = new FileOutputStream(new File(getPropertiesFile()).getCanonicalFile());
239-
props.store(fos, "BuildInfo configuration property file");
241+
try (FileOutputStream fos = new FileOutputStream(new File(getPropertiesFile()).getCanonicalFile())) {
242+
preparePropertiesToPersist().store(fos, "BuildInfo configuration property file");
240243
} catch (IOException e) {
241244
throw new RuntimeException(e);
242-
} finally {
243-
IOUtils.closeQuietly(fos);
244245
}
245246
}
246247

248+
249+
public EncryptionKeyPair persistToEncryptedPropertiesFile(OutputStream os) throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
250+
if (StringUtils.isEmpty(getPropertiesFile())) {
251+
return null;
252+
}
253+
return encryptedPropertiesToStream(os, preparePropertiesToPersist());
254+
}
255+
256+
private Properties preparePropertiesToPersist() {
257+
Properties props = new Properties();
258+
props.putAll(filterMapNullValues(rootConfig.props));
259+
props.putAll(filterMapNullValues(root.props));
260+
261+
// Properties that have the 'artifactory.' prefix are deprecated.
262+
// For backward compatibility reasons, both will be added to the props map.
263+
// The build-info 2.32.3 is the last version to be using the deprecated properties as its primary.
264+
Properties artifactoryProps = new Properties();
265+
for (String key : props.stringPropertyNames()) {
266+
artifactoryProps.put("artifactory." + key, props.getProperty(key));
267+
}
268+
props.putAll(artifactoryProps);
269+
270+
return props;
271+
}
272+
247273
public static Map<String, String> filterMapNullValues(Map<String, String> map) {
248274
Map<String, String> result = new HashMap<String, String>();
249275
for (Map.Entry<String, String> entry : map.entrySet()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.jfrog.build.extractor.clientConfiguration.util.encryption;
2+
3+
import org.apache.commons.lang3.StringUtils;
4+
5+
import java.security.SecureRandom;
6+
import java.util.Base64;
7+
8+
/**
9+
* Represents a pair of secret key and initialization vector (IV) used for encryption and decryption.
10+
*/
11+
public class EncryptionKeyPair {
12+
private static final int AES_256_KEY_LENGTH = 256;
13+
private static final int IV_LENGTH = 128;
14+
private byte[] secretKey;
15+
private byte[] iv;
16+
17+
public EncryptionKeyPair() {
18+
this.secretKey = generateRandomKey(AES_256_KEY_LENGTH);
19+
this.iv = generateRandomKey(IV_LENGTH);
20+
}
21+
22+
public EncryptionKeyPair(String secretKey, String Iv) {
23+
if (StringUtils.isNotBlank(secretKey)) {
24+
this.secretKey = Base64.getDecoder().decode(secretKey);
25+
}
26+
if (StringUtils.isNotBlank(Iv)) {
27+
this.iv = Base64.getDecoder().decode(Iv);
28+
}
29+
}
30+
31+
/**
32+
* Generates a random key of the specified length in bits.
33+
*
34+
* @param lengthInBits The length of the key in bits.
35+
* @return A byte array representing the generated random key.
36+
*/
37+
private static byte[] generateRandomKey(int lengthInBits) {
38+
SecureRandom secureRandom = new SecureRandom();
39+
byte[] key = new byte[lengthInBits / 8];
40+
secureRandom.nextBytes(key);
41+
return key;
42+
}
43+
44+
public byte[] getSecretKey() {
45+
return secretKey;
46+
}
47+
48+
@SuppressWarnings("unused")
49+
public String getStringSecretKey() {
50+
return Base64.getEncoder().encodeToString(secretKey);
51+
}
52+
53+
public byte[] getIv() {
54+
return iv;
55+
}
56+
57+
@SuppressWarnings("unused")
58+
public String getStringIv() {
59+
return Base64.getEncoder().encodeToString(iv);
60+
}
61+
62+
public boolean isEmpty() {
63+
return secretKey == null || secretKey.length == 0 || iv == null || iv.length == 0;
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.jfrog.build.extractor.clientConfiguration.util.encryption;
2+
3+
import javax.crypto.BadPaddingException;
4+
import javax.crypto.Cipher;
5+
import javax.crypto.IllegalBlockSizeException;
6+
import javax.crypto.NoSuchPaddingException;
7+
import javax.crypto.spec.GCMParameterSpec;
8+
import javax.crypto.spec.SecretKeySpec;
9+
import java.security.InvalidAlgorithmParameterException;
10+
import java.security.InvalidKeyException;
11+
import java.security.NoSuchAlgorithmException;
12+
13+
public class Encryptor {
14+
private static final String ALGORITHM = "AES";
15+
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
16+
private static final int GCM_TAG_LENGTH = 128;
17+
18+
/**
19+
* Decrypts the given data using the provided EncryptionKeyPair.
20+
*
21+
* @param data The encrypted data to be decrypted
22+
* @param keyPair The EncryptionKeyPair containing the secret key and IV for decryption
23+
* @return The decrypted data as a byte array
24+
*/
25+
public static byte[] decrypt(byte[] data, EncryptionKeyPair keyPair) throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
26+
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
27+
SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM);
28+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv());
29+
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
30+
return cipher.doFinal(data);
31+
}
32+
33+
/**
34+
* Encrypts the given data using the provided EncryptionKeyPair.
35+
*
36+
* @param data The data to be encrypted
37+
* @param keyPair The EncryptionKeyPair containing the secret key and IV for encryption
38+
* @return The encrypted data as a byte array
39+
*/
40+
public static byte[] encrypt(byte[] data, EncryptionKeyPair keyPair) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
41+
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
42+
SecretKeySpec secretKeySpec = new SecretKeySpec(keyPair.getSecretKey(), ALGORITHM);
43+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, keyPair.getIv());
44+
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
45+
return cipher.doFinal(data);
46+
}
47+
}

0 commit comments

Comments
 (0)