$JENKINS_HOME/secrets/master.key but another location can be provided using the system property jenkins.master.key.file.
+ *
+ * It is also possible to prevent the generation of the master key file using the system property -Djenkins.master.key.readOnly. In this case, the master key file must be provided or startup will fail.
*
* @author Kohsuke Kawaguchi
*/
// @MetaInfServices --- not annotated because this is the fallback implementation
public class DefaultConfidentialStore extends ConfidentialStore {
+ static final String MASTER_KEY_FILE_SYSTEM_PROPERTY = DefaultConfidentialStore.class.getName() + ".file";
+ static final String MASTER_KEY_READONLY_SYSTEM_PROPERTY_NAME = DefaultConfidentialStore.class.getName() + ".readOnly";
+
private final SecureRandom sr = new SecureRandom();
+ @NonNull
+ private static File getMasterKeyFile(File rootDir) {
+ var jenkinsMasterKey = SystemProperties.getString(MASTER_KEY_FILE_SYSTEM_PROPERTY);
+ if (jenkinsMasterKey != null) {
+ return new File(jenkinsMasterKey);
+ } else {
+ return new File(rootDir, "master.key");
+ }
+ }
+
/**
* Directory that stores individual keys.
*/
@@ -51,6 +68,10 @@ public class DefaultConfidentialStore extends ConfidentialStore {
}
public DefaultConfidentialStore(File rootDir) throws IOException, InterruptedException {
+ this(rootDir, getMasterKeyFile(rootDir));
+ }
+
+ protected DefaultConfidentialStore(File rootDir, File keyFile) throws IOException, InterruptedException {
this.rootDir = rootDir;
if (rootDir.mkdirs()) {
// protect this directory. but don't change the permission of the existing directory
@@ -58,11 +79,15 @@ public class DefaultConfidentialStore extends ConfidentialStore {
new FilePath(rootDir).chmod(0700);
}
- TextFile masterSecret = new TextFile(new File(rootDir, "master.key"));
+ TextFile masterSecret = new TextFile(keyFile);
if (!masterSecret.exists()) {
- // we are only going to use small number of bits (since export control limits AES key length)
- // but let's generate a long enough key anyway
- masterSecret.write(Util.toHexString(randomBytes(128)));
+ if (SystemProperties.getBoolean(MASTER_KEY_READONLY_SYSTEM_PROPERTY_NAME)) {
+ throw new IOException(masterSecret + " does not exist and system property " + MASTER_KEY_READONLY_SYSTEM_PROPERTY_NAME + " is set. You must provide a valid master key file.");
+ } else {
+ // we are only going to use small number of bits (since export control limits AES key length)
+ // but let's generate a long enough key anyway
+ masterSecret.write(Util.toHexString(randomBytes(128)));
+ }
}
this.masterKey = Util.toAes128Key(masterSecret.readTrim());
}
diff --git a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
index 968ee9320f..17d9dd4335 100644
--- a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
+++ b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
@@ -28,6 +28,7 @@ import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.BulkChange;
import hudson.Extension;
+import hudson.Util;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
@@ -39,7 +40,6 @@ import java.util.Objects;
import jenkins.model.Jenkins;
import jenkins.security.LastGrantedAuthoritiesProperty;
import jenkins.util.SystemProperties;
-import org.apache.commons.codec.binary.Hex;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -103,7 +103,7 @@ public class UserSeedProperty extends UserProperty {
byte[] bytes = new byte[SEED_NUM_BYTES];
while (Objects.equals(newSeed, currentSeed)) {
RANDOM.nextBytes(bytes);
- newSeed = new String(Hex.encodeHex(bytes));
+ newSeed = Util.toHexString(bytes);
}
this.seed = newSeed;
}
diff --git a/core/src/main/java/jenkins/telemetry/Telemetry.java b/core/src/main/java/jenkins/telemetry/Telemetry.java
index de081c1695..968ca0f08d 100644
--- a/core/src/main/java/jenkins/telemetry/Telemetry.java
+++ b/core/src/main/java/jenkins/telemetry/Telemetry.java
@@ -32,6 +32,7 @@ import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.PluginWrapper;
import hudson.ProxyConfiguration;
+import hudson.Util;
import hudson.model.AsyncPeriodicWork;
import hudson.model.TaskListener;
import hudson.model.UsageStatistics;
@@ -51,7 +52,6 @@ import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
-import org.apache.commons.codec.digest.DigestUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -221,7 +221,7 @@ public abstract class Telemetry implements ExtensionPoint {
wrappedData.put("type", telemetry.getId());
wrappedData.put("payload", data);
String correlationId = ExtensionList.lookupSingleton(Correlator.class).getCorrelationId();
- wrappedData.put("correlator", DigestUtils.sha256Hex(correlationId + telemetry.getId()));
+ wrappedData.put("correlator", Util.getHexOfSHA256DigestOf(correlationId + telemetry.getId()));
String body = wrappedData.toString();
if (LOGGER.isLoggable(Level.FINEST)) {
diff --git a/core/src/main/java/jenkins/util/JSONSignatureValidator.java b/core/src/main/java/jenkins/util/JSONSignatureValidator.java
index e6f5429f43..d3f416a91f 100644
--- a/core/src/main/java/jenkins/util/JSONSignatureValidator.java
+++ b/core/src/main/java/jenkins/util/JSONSignatureValidator.java
@@ -1,6 +1,7 @@
package jenkins.util;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import hudson.Util;
import hudson.util.FormValidation;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -33,8 +34,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
-import org.apache.commons.codec.DecoderException;
-import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.output.TeeOutputStream;
import org.jvnet.hudson.crypto.CertificateUtil;
import org.jvnet.hudson.crypto.SignatureOutputStream;
@@ -221,10 +220,10 @@ public class JSONSignatureValidator {
// This approach might look unnecessarily clever, but short of having redundant Signature instances,
// there doesn't seem to be a better approach for this.
try {
- if (signature.verify(Hex.decodeHex(providedSignature.toCharArray()))) {
+ if (signature.verify(Util.fromHexString(providedSignature))) {
return true;
}
- } catch (SignatureException | DecoderException ignore) {
+ } catch (SignatureException | IllegalArgumentException ignore) {
// ignore
}
@@ -242,7 +241,7 @@ public class JSONSignatureValidator {
* Utility method supporting both possible digest formats: Base64 and Hex
*/
private boolean digestMatches(byte[] digest, String providedDigest) {
- return providedDigest.equalsIgnoreCase(Hex.encodeHexString(digest)) || providedDigest.equalsIgnoreCase(Base64.getEncoder().encodeToString(digest));
+ return providedDigest.equalsIgnoreCase(Util.toHexString(digest)) || providedDigest.equalsIgnoreCase(Base64.getEncoder().encodeToString(digest));
}
diff --git a/core/src/main/resources/META-INF/web-fragment.xml b/core/src/main/resources/META-INF/web-fragment.xml
new file mode 100644
index 0000000000..c9acb6e151
--- /dev/null
+++ b/core/src/main/resources/META-INF/web-fragment.xml
@@ -0,0 +1,279 @@
+
+
+
+