diff --git a/core/src/main/java/hudson/util/HistoricalSecrets.java b/core/src/main/java/hudson/util/HistoricalSecrets.java index 37a6fa39e1..962ab26089 100644 --- a/core/src/main/java/hudson/util/HistoricalSecrets.java +++ b/core/src/main/java/hudson/util/HistoricalSecrets.java @@ -80,5 +80,5 @@ public class HistoricalSecrets { return Util.toAes128Key(secret); } - private static final String MAGIC = "::::MAGIC::::"; + static final String MAGIC = "::::MAGIC::::"; } diff --git a/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy b/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy deleted file mode 100644 index a429bef54e..0000000000 --- a/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy +++ /dev/null @@ -1,120 +0,0 @@ -package hudson.util - -import com.trilead.ssh2.crypto.Base64 -import hudson.FilePath -import hudson.Functions -import jenkins.security.ConfidentialStoreRule -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -import javax.crypto.Cipher - -import static org.junit.Assume.assumeFalse - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class SecretRewriterTest { - @Rule - public MockSecretRule mockSecretRule = new MockSecretRule() - - @Rule - public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule(); - - @Rule public TemporaryFolder tmp = new TemporaryFolder() - - def FOO_PATTERN = /\{[A-Za-z0-9+\/]+={0,2}}<\/foo>/ - def MSG_PATTERN = /\{[A-Za-z0-9+\/]+={0,2}}<\/msg>/ - def FOO_PATTERN2 = /(\{[A-Za-z0-9+\/]+={0,2}}<\/foo>){2}/ - def ABC_FOO_PATTERN = /\s\{[A-Za-z0-9+\/]+={0,2}}<\/foo>\s<\/abc>/ - - @Test - void singleFileRewrite() { - def o = encryptOld('foobar') // old - def n = encryptNew('foobar') // new - roundtrip "${o}", - {assert it ==~ FOO_PATTERN} - - - roundtrip "${o}${o}", - {assert it ==~ FOO_PATTERN2} - - roundtrip "${n}", - {assert it == "${n}"} - - roundtrip " thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret ", - {assert it == "thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret"} - - // to be rewritten, it needs to be between a tag - roundtrip "$o", {assert it == "$o"} - roundtrip "$o", {assert it == "$o"} - - // - roundtrip "\n$o\n", {assert it ==~ ABC_FOO_PATTERN} - } - - void roundtrip(String before, Closure check) { - def sr = new SecretRewriter(null); - def f = File.createTempFile("test", "xml", tmp.root) - f.text = before - sr.rewrite(f,null) - check(f.text.replaceAll(System.getProperty("line.separator"), "\n").trim()) - //assert after.replaceAll(System.getProperty("line.separator"), "\n").trim()==f.text.replaceAll(System.getProperty("line.separator"), "\n").trim() - } - - String encryptOld(str) { - def cipher = Secret.getCipher("AES"); - cipher.init(Cipher.ENCRYPT_MODE, HistoricalSecrets.legacyKey); - return new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8")))) - } - - String encryptNew(str) { - return Secret.fromString(str).encryptedValue - } - - /** - * Directory rewrite and recursion detection - */ - @Test - void recursionDetection() { - assumeFalse("Symlinks don't work on Windows very well", Functions.isWindows()) - def sw = new SecretRewriter(); - def st = StreamTaskListener.fromStdout() - - def o = encryptOld("Hello world") - def n = encryptNew("Hello world") - def payload = "$o" - def answer = "$n" - - // set up some directories with stuff - def t = tmp.newFolder("t") - def dirs = ["a", "b", "c", "c/d", "c/d/e"] - dirs.each { p -> - def d = new File(t, p) - d.mkdir() - new File(d,"foo.xml").text = payload - } - - // stuff outside - def t2 = tmp.newFolder("t2") - new File(t2,"foo.xml").text = payload - - // some recursions as well as valid symlinks - new FilePath(t).child("c/symlink").symlinkTo("..",st) - new FilePath(t).child("b/symlink").symlinkTo(".",st) - new FilePath(t).child("a/symlink").symlinkTo(t2.absolutePath,st) - - assert 6==sw.rewriteRecursive(t, st) - - dirs.each { p-> - assert new File(t,"$p/foo.xml").text.trim() ==~ MSG_PATTERN - } - - // t2 is only reachable by following a symlink. this should be covered, too - assert new File(t2,"foo.xml").text.trim() ==~ MSG_PATTERN - } - -} diff --git a/core/src/test/java/hudson/util/SecretRewriterTest.java b/core/src/test/java/hudson/util/SecretRewriterTest.java new file mode 100644 index 0000000000..5ca97ff060 --- /dev/null +++ b/core/src/test/java/hudson/util/SecretRewriterTest.java @@ -0,0 +1,116 @@ +package hudson.util; + +import com.trilead.ssh2.crypto.Base64; +import hudson.FilePath; +import hudson.Functions; +import hudson.model.TaskListener; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import javax.crypto.Cipher; +import jenkins.security.ConfidentialStoreRule; +import org.apache.commons.io.FileUtils; +import static org.junit.Assert.*; +import static org.junit.Assume.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class SecretRewriterTest { + + @Rule + public MockSecretRule mockSecretRule = new MockSecretRule(); + + @Rule + public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule(); + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private static final Pattern FOO_PATTERN = Pattern.compile("[{][A-Za-z0-9+/]+={0,2}[}]"); + private static final Pattern MSG_PATTERN = Pattern.compile("[{][A-Za-z0-9+/]+={0,2}[}]"); + private static final Pattern FOO_PATTERN2 = Pattern.compile("([{][A-Za-z0-9+/]+={0,2}[}]){2}"); + private static final Pattern ABC_FOO_PATTERN = Pattern.compile("\\s[{][A-Za-z0-9+/]+={0,2}[}]\\s"); + + @Test + public void singleFileRewrite() throws Exception { + String o = encryptOld("foobar"); // old + String n = encryptNew("foobar"); // new + assertTrue(FOO_PATTERN.matcher(roundtrip("" + o + "")).matches()); + assertTrue(FOO_PATTERN2.matcher(roundtrip("" + o + "" + o + "")).matches()); + assertEquals("" + n + "", roundtrip("" + n + "")); + assertEquals("thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret", roundtrip(" thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret ")); + // to be rewritten, it needs to be between a tag + assertEquals("" + o, roundtrip("" + o)); + assertEquals(o + "", roundtrip(o + "")); + assertTrue(ABC_FOO_PATTERN.matcher(roundtrip("\n" + o + "\n")).matches()); + } + + private String roundtrip(String before) throws Exception { + SecretRewriter sr = new SecretRewriter(null); + File f = File.createTempFile("test", "xml", tmp.getRoot()); + FileUtils.write(f, before); + sr.rewrite(f, null); + //assert after.replaceAll(System.getProperty("line.separator"), "\n").trim()==f.text.replaceAll(System.getProperty("line.separator"), "\n").trim() + return FileUtils.readFileToString(f).replaceAll(System.getProperty("line.separator"), "\n").trim(); + } + + private String encryptOld(String str) throws Exception { + Cipher cipher = Secret.getCipher("AES"); + cipher.init(Cipher.ENCRYPT_MODE, HistoricalSecrets.getLegacyKey()); + return new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8")))); + } + + private String encryptNew(String str) { + return Secret.fromString(str).getEncryptedValue(); + } + + /** + * Directory rewrite and recursion detection + */ + @Test + public void recursionDetection() throws Exception { + assumeFalse("Symlinks don't work on Windows very well", Functions.isWindows()); + SecretRewriter sw = new SecretRewriter(); + TaskListener st = StreamTaskListener.fromStdout(); + + String o = encryptOld("Hello world"); + String n = encryptNew("Hello world"); + String payload = "" + o + ""; + String answer = "" + n + ""; + + // set up some directories with stuff + File t = tmp.newFolder("t"); + List dirs = Arrays.asList("a", "b", "c", "c/d", "c/d/e"); + for (String p : dirs) { + File d = new File(t, p); + d.mkdir(); + try { + FileUtils.write(new File(d, "foo.xml"), payload); + } catch (IOException x) { + assert false : x; + } + } + + // stuff outside + File t2 = tmp.newFolder("t2"); + FileUtils.write(new File(t2, "foo.xml"), payload); + + // some recursions as well as valid symlinks + new FilePath(t).child("c/symlink").symlinkTo("..", st); + new FilePath(t).child("b/symlink").symlinkTo(".", st); + new FilePath(t).child("a/symlink").symlinkTo(t2.getAbsolutePath(), st); + + assertEquals(6, sw.rewriteRecursive(t, st)); + + for (String p : dirs) { + assertTrue(MSG_PATTERN.matcher(FileUtils.readFileToString(new File(t, p + "/foo.xml")).trim()).matches()); + } + + // t2 is only reachable by following a symlink. this should be covered, too + assertTrue(MSG_PATTERN.matcher(FileUtils.readFileToString(new File(t2, "foo.xml")).trim()).matches()); + } + +}