summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java20
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java99
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/Base64Cipher.java32
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/Cipher.java25
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/Encryption.java86
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java27
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/config/AesCipherTest.java92
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/config/EncryptionTest.java59
-rw-r--r--sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/aes_secret_key.txt1
9 files changed, 431 insertions, 10 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
index 75d3620545a..8cf0e3cb576 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
@@ -22,12 +22,18 @@ package org.sonar.api;
/**
* CoreProperties is used to group various properties of Sonar as well
* as default values of configuration in a single place
- *
+ *
* @since 1.11
*/
public interface CoreProperties {
/**
+ * @since 2.15
+ */
+ String ENCRYPTION_PATH_TO_SECRET_KEY = "sonar.pathToSecretKey";
+
+
+ /**
* @since 2.11
*/
String CATEGORY_GENERAL = "general";
@@ -85,7 +91,7 @@ public interface CoreProperties {
/**
* To determine value of this property use {@link org.sonar.api.resources.ProjectFileSystem#getSourceCharset()}.
- *
+ *
* @since 2.6
*/
String ENCODING_PROPERTY = "sonar.sourceEncoding";
@@ -148,8 +154,8 @@ public interface CoreProperties {
String SERVER_BASE_URL = "sonar.core.serverBaseURL";
/**
- * @since 2.10
* @see #SERVER_BASE_URL
+ * @since 2.10
*/
String SERVER_BASE_URL_DEFAULT_VALUE = "http://localhost:9000";
@@ -169,8 +175,8 @@ public interface CoreProperties {
String CPD_ENGINE = "sonar.cpd.engine";
/**
- * @since 2.11
* @see #CPD_ENGINE
+ * @since 2.11
*/
String CPD_ENGINE_DEFAULT_VALUE = "sonar";
@@ -180,8 +186,8 @@ public interface CoreProperties {
String CPD_CROSS_RPOJECT = "sonar.cpd.cross_project";
/**
- * @since 2.11
* @see #CPD_CROSS_RPOJECT
+ * @since 2.11
*/
boolean CPD_CROSS_RPOJECT_DEFAULT_VALUE = false;
@@ -189,7 +195,7 @@ public interface CoreProperties {
/**
* Indicates whether Java bytecode analysis should be skipped.
- *
+ *
* @since 2.0
*/
String DESIGN_SKIP_DESIGN_PROPERTY = "sonar.skipDesign";
@@ -197,7 +203,7 @@ public interface CoreProperties {
/**
* Indicates whether Package Design Analysis should be skipped.
- *
+ *
* @since 2.9
*/
String DESIGN_SKIP_PACKAGE_DESIGN_PROPERTY = "sonar.skipPackageDesign";
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java
new file mode 100644
index 00000000000..e8ed181966c
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java
@@ -0,0 +1,99 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.CoreProperties;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.File;
+import java.io.IOException;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+
+final class AesCipher extends Cipher {
+
+ public static final int KEY_SIZE_IN_BITS = 128;
+ private final Settings settings;
+
+ AesCipher(Settings settings) {
+ this.settings = settings;
+ }
+
+ String encrypt(String clearText) {
+ String path = settings.getClearString(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY);
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES");
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFileFromFile(path));
+ return new String(Base64.encodeBase64(cipher.doFinal(clearText.getBytes(Charsets.UTF_8))));
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+
+ String decrypt(String encryptedText) {
+ String path = settings.getClearString(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY);
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES");
+ cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFileFromFile(path));
+ byte[] cipherData = cipher.doFinal(Base64.decodeBase64(StringUtils.trim(encryptedText)));
+ return new String(cipherData);
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @VisibleForTesting
+ Key loadSecretFileFromFile(String path) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidKeyException {
+ if (StringUtils.isBlank(path)) {
+ throw new IllegalStateException("Secret key not found. Please set the property " + CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY);
+ }
+ File file = new File(path);
+ if (!file.exists() || !file.isFile()) {
+ throw new IllegalStateException("The property " + CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY + " does not link to a valid file: " + path);
+ }
+
+ String s = FileUtils.readFileToString(file);
+ if (StringUtils.isBlank(s)) {
+ throw new IllegalStateException("No secret key in the file: " + path);
+ }
+ return new SecretKeySpec(Base64.decodeBase64(s), "AES");
+ }
+
+ String generateRandomSecretKey() {
+ try {
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ keyGen.init(KEY_SIZE_IN_BITS, new SecureRandom());
+ SecretKey secretKey = keyGen.generateKey();
+ return new String(Base64.encodeBase64(secretKey.getEncoded()));
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to generate random RSA keys", e);
+ }
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/Base64Cipher.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/Base64Cipher.java
new file mode 100644
index 00000000000..a04b40e953b
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/Base64Cipher.java
@@ -0,0 +1,32 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.config;
+
+import org.apache.commons.codec.binary.Base64;
+
+final class Base64Cipher extends Cipher {
+ String encrypt(String clearText) {
+ return new String(Base64.encodeBase64(clearText.getBytes()));
+ }
+
+ String decrypt(String encryptedText) {
+ return new String(Base64.decodeBase64(encryptedText));
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/Cipher.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/Cipher.java
new file mode 100644
index 00000000000..7a39c7c7653
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/Cipher.java
@@ -0,0 +1,25 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.config;
+
+abstract class Cipher {
+ abstract String encrypt(String clearText);
+ abstract String decrypt(String encryptedText);
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/Encryption.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/Encryption.java
new file mode 100644
index 00000000000..def3164985f
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/Encryption.java
@@ -0,0 +1,86 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.config;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @since 2.15
+ */
+public final class Encryption {
+
+ private static final String BASE64_ALGORITHM = "b64";
+ private final Base64Cipher base64Encryption;
+
+ private static final String AES_ALGORITHM = "aes";
+ private final AesCipher aesEncryption;
+
+ private final Map<String, Cipher> encryptions;
+ private static final Pattern ENCRYPTED_PATTERN = Pattern.compile("\\{(.*?)\\}(.*)");
+
+ Encryption(Settings settings) {
+ base64Encryption = new Base64Cipher();
+ aesEncryption = new AesCipher(settings);
+ encryptions = ImmutableMap.of(
+ BASE64_ALGORITHM, base64Encryption,
+ AES_ALGORITHM, aesEncryption
+ );
+ }
+
+ public boolean isEncrypted(String value) {
+ return value.startsWith("{") && value.indexOf("}") > 1;
+ }
+
+ public String encrypt(String clearText) {
+ return encrypt(AES_ALGORITHM, clearText);
+ }
+
+ public String scramble(String clearText) {
+ return encrypt(BASE64_ALGORITHM, clearText);
+ }
+
+ public String generateRandomSecretKey() {
+ return aesEncryption.generateRandomSecretKey();
+ }
+
+ public String decrypt(String encryptedText) {
+ Matcher matcher = ENCRYPTED_PATTERN.matcher(encryptedText);
+ if (matcher.matches()) {
+ Cipher cipher = encryptions.get(matcher.group(1).toLowerCase(Locale.ENGLISH));
+ if (cipher != null) {
+ return cipher.decrypt(matcher.group(2));
+ }
+ }
+ return encryptedText;
+ }
+
+ private String encrypt(String algorithm, String clearText) {
+ Cipher cipher = encryptions.get(algorithm);
+ if (cipher == null) {
+ throw new IllegalArgumentException("Unknown cipher algorithm: " + algorithm);
+ }
+ return String.format("{%s}%s", algorithm, cipher.encrypt(clearText));
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java
index 755345be3f5..8e88562a2bb 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java
@@ -38,15 +38,18 @@ import java.util.*;
*/
public class Settings implements BatchComponent, ServerComponent {
- protected Map<String, String> properties = Maps.newHashMap();
- protected PropertyDefinitions definitions;
+ protected final Map<String, String> properties;
+ protected final PropertyDefinitions definitions;
+ private final Encryption encryption;
public Settings() {
this(new PropertyDefinitions());
}
public Settings(PropertyDefinitions definitions) {
+ this.properties = Maps.newHashMap();
this.definitions = definitions;
+ this.encryption = new Encryption(this);
}
public final String getDefaultValue(String key) {
@@ -65,6 +68,23 @@ public class Settings implements BatchComponent, ServerComponent {
String value = properties.get(key);
if (value == null) {
value = getDefaultValue(key);
+ } else if (encryption.isEncrypted(value)) {
+ try {
+ value = encryption.decrypt(value);
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to decrypt the property " + key + ". Please check your secret key.");
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Does not decrypt value.
+ */
+ protected String getClearString(String key) {
+ String value = properties.get(key);
+ if (value == null) {
+ value = getDefaultValue(key);
}
return value;
}
@@ -217,7 +237,8 @@ public class Settings implements BatchComponent, ServerComponent {
}
public final Settings setProperties(Map<String, String> props) {
- properties = Maps.newHashMap(props);
+ properties.clear();
+ properties.putAll(props);
return this;
}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/AesCipherTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/AesCipherTest.java
new file mode 100644
index 00000000000..47b432f75d5
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/AesCipherTest.java
@@ -0,0 +1,92 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.config;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+
+import java.io.File;
+import java.net.URL;
+import java.security.Key;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public class AesCipherTest {
+
+ @Test
+ public void generateRandomSecretKey() {
+ AesCipher cipher = new AesCipher(new Settings());
+
+ String key = cipher.generateRandomSecretKey();
+
+ assertThat(StringUtils.isNotBlank(key), is(true));
+ assertThat(Base64.isArrayByteBase64(key.getBytes()), is(true));
+ }
+
+ @Test
+ public void encrypt() throws Exception {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY, pathToSecretKey());
+ AesCipher cipher = new AesCipher(settings);
+
+ String encryptedText = cipher.encrypt("sonar");
+ System.out.println(encryptedText);
+ assertThat(StringUtils.isNotBlank(encryptedText), is(true));
+ assertThat(Base64.isArrayByteBase64(encryptedText.getBytes()), is(true));
+ }
+
+ @Test
+ public void decrypt() throws Exception {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY, pathToSecretKey());
+ AesCipher cipher = new AesCipher(settings);
+
+ // the following value has been encrypted with the key /org/sonar/api/config/AesCipherTest/aes_secret_key.txt
+ String clearText = cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+
+ assertThat(clearText, is("this is a secret"));
+ }
+
+ @Test
+ public void encryptThenDecrypt() throws Exception {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY, pathToSecretKey());
+ AesCipher cipher = new AesCipher(settings);
+
+ assertThat(cipher.decrypt(cipher.encrypt("foo")), is("foo"));
+ }
+
+ @Test
+ public void loadSecretKeyFromFile() throws Exception {
+ AesCipher cipher = new AesCipher(new Settings());
+ Key secretKey = cipher.loadSecretFileFromFile(pathToSecretKey());
+ assertThat(secretKey.getAlgorithm(), is("AES"));
+ assertThat(secretKey.getEncoded().length, greaterThan(10));
+ }
+
+ private String pathToSecretKey() throws Exception {
+ URL resource = getClass().getResource("/org/sonar/api/config/AesCipherTest/aes_secret_key.txt");
+ return new File(resource.toURI()).getCanonicalPath();
+ }
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/EncryptionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/EncryptionTest.java
new file mode 100644
index 00000000000..1333120422c
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/EncryptionTest.java
@@ -0,0 +1,59 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.config;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class EncryptionTest {
+
+ @Test
+ public void isEncrypted() {
+ Encryption encryption = new Encryption(new Settings());
+ assertThat(encryption.isEncrypted("{aes}ADASDASAD"), is(true));
+ assertThat(encryption.isEncrypted("{b64}ADASDASAD"), is(true));
+ assertThat(encryption.isEncrypted("{abc}ADASDASAD"), is(true));
+
+ assertThat(encryption.isEncrypted("{}"), is(false));
+ assertThat(encryption.isEncrypted("{foo"), is(false));
+ assertThat(encryption.isEncrypted("foo{aes}"), is(false));
+ }
+
+ @Test
+ public void decrypt() {
+ Encryption encryption = new Encryption(new Settings());
+ assertThat(encryption.decrypt("{b64}Zm9v"), is("foo"));
+ }
+
+ @Test
+ public void decrypt_unknown_algorithm() {
+ Encryption encryption = new Encryption(new Settings());
+ assertThat(encryption.decrypt("{xxx}Zm9v"), is("{xxx}Zm9v"));
+ }
+
+ @Test
+ public void decrypt_uncrypted_text() {
+ Encryption encryption = new Encryption(new Settings());
+ assertThat(encryption.decrypt("foo"), is("foo"));
+
+ }
+}
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/aes_secret_key.txt b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/aes_secret_key.txt
new file mode 100644
index 00000000000..65b98c522da
--- /dev/null
+++ b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/aes_secret_key.txt
@@ -0,0 +1 @@
+0PZz+G+f8mjr3sPn4+AhHg== \ No newline at end of file