aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2012-03-13 23:08:17 +0100
committerSimon Brandhof <simon.brandhof@gmail.com>2012-03-13 23:08:17 +0100
commite9b5cded528c6ad33d91fa61cd1be4b917983738 (patch)
tree4e02c019b8df5f3c55d51414d5606f433fc8367b
parent017f39d3bdda35f97c564cfb31f9dc34ef5708af (diff)
downloadsonarqube-e9b5cded528c6ad33d91fa61cd1be4b917983738.tar.gz
sonarqube-e9b5cded528c6ad33d91fa61cd1be4b917983738.zip
SONAR-2084 complete web UI to generate secret key and to encrypt passwords
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/AesCipher.java35
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/config/Encryption.java29
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/config/AesCipherTest.java70
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/config/EncryptionTest.java7
-rw-r--r--sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/bad_secret_key.txt1
-rw-r--r--sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/non_trimmed_secret_key.txt3
-rw-r--r--sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/other_secret_key.txt1
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/encryption_controller.rb4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/encryption/encrypt.html.erb1
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/encryption/generate_secret.html.erb5
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/encryption/index.html.erb10
13 files changed, 126 insertions, 46 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 8cf0e3cb576..dea53e74b72 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
@@ -30,7 +30,7 @@ public interface CoreProperties {
/**
* @since 2.15
*/
- String ENCRYPTION_PATH_TO_SECRET_KEY = "sonar.pathToSecretKey";
+ String ENCRYPTION_SECRET_KEY_FILE = "sonar.secretKeyFile";
/**
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
index 51232587bbd..52a64c626d3 100644
--- 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
@@ -40,6 +40,9 @@ import java.security.spec.InvalidKeySpecException;
final class AesCipher extends Cipher {
+ // Can't be increased because of Java 6 policy files :
+ // https://confluence.terena.org/display/~visser/No+256+bit+ciphers+for+Java+apps
+ // http://java.sun.com/javase/6/webnotes/install/jre/README
public static final int KEY_SIZE_IN_BITS = 128;
private final Settings settings;
@@ -57,7 +60,6 @@ final class AesCipher extends Cipher {
}
}
-
String decrypt(String encryptedText) {
try {
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES");
@@ -69,34 +71,37 @@ final class AesCipher extends Cipher {
}
}
- public boolean canEncrypt() {
- try {
- return loadSecretFile() != null;
- } catch (Exception e) {
- return false;
+ /**
+ * This method checks the existence of the file, but not the validity of the contained key.
+ */
+ boolean hasSecretKey() {
+ String path = getPathToSecretKey();
+ if (StringUtils.isNotBlank(path)) {
+ File file = new File(path);
+ return file.exists() && file.isFile();
}
+ return false;
}
- Key loadSecretFile() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidKeyException {
- String path = settings.getClearString(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY);
+ private Key loadSecretFile() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidKeyException {
+ String path = getPathToSecretKey();
return loadSecretFileFromFile(path);
}
@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);
+ throw new IllegalStateException("Secret key not found. Please set the property " + CoreProperties.ENCRYPTION_SECRET_KEY_FILE);
}
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);
+ throw new IllegalStateException("The property " + CoreProperties.ENCRYPTION_SECRET_KEY_FILE + " 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");
+ return new SecretKeySpec(Base64.decodeBase64(StringUtils.trim(s)), "AES");
}
String generateRandomSecretKey() {
@@ -107,9 +112,11 @@ final class AesCipher extends Cipher {
return new String(Base64.encodeBase64(secretKey.getEncoded()));
} catch (Exception e) {
- throw new IllegalStateException("Fail to generate random RSA keys", e);
+ throw new IllegalStateException("Fail to generate AES secret key", e);
}
}
-
+ private String getPathToSecretKey() {
+ return settings.getClearString(CoreProperties.ENCRYPTION_SECRET_KEY_FILE);
+ }
}
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
index 6cbc08d01d2..3bc88f0cc20 100644
--- 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
@@ -32,25 +32,28 @@ import java.util.regex.Pattern;
public final class Encryption {
private static final String BASE64_ALGORITHM = "b64";
- private final Base64Cipher base64Encryption;
+ private final Base64Cipher base64Cipher;
private static final String AES_ALGORITHM = "aes";
- private final AesCipher aesEncryption;
+ private final AesCipher aesCipher;
- private final Map<String, Cipher> encryptions;
+ private final Map<String, Cipher> ciphers;
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
+ base64Cipher = new Base64Cipher();
+ aesCipher = new AesCipher(settings);
+ ciphers = ImmutableMap.of(
+ BASE64_ALGORITHM, base64Cipher,
+ AES_ALGORITHM, aesCipher
);
}
- public boolean canEncrypt() {
- return aesEncryption.canEncrypt();
+ /**
+ * Checks the availability of the secret key, that is required to encrypt and decrypt.
+ */
+ public boolean hasSecretKey() {
+ return aesCipher.hasSecretKey();
}
public boolean isEncrypted(String value) {
@@ -66,13 +69,13 @@ public final class Encryption {
}
public String generateRandomSecretKey() {
- return aesEncryption.generateRandomSecretKey();
+ return aesCipher.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));
+ Cipher cipher = ciphers.get(matcher.group(1).toLowerCase(Locale.ENGLISH));
if (cipher != null) {
return cipher.decrypt(matcher.group(2));
}
@@ -81,7 +84,7 @@ public final class Encryption {
}
private String encrypt(String algorithm, String clearText) {
- Cipher cipher = encryptions.get(algorithm);
+ Cipher cipher = ciphers.get(algorithm);
if (cipher == null) {
throw new IllegalArgumentException("Unknown cipher algorithm: " + algorithm);
}
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
index 47b432f75d5..2adf627e289 100644
--- 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
@@ -24,13 +24,16 @@ import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import org.sonar.api.CoreProperties;
+import javax.crypto.BadPaddingException;
import java.io.File;
import java.net.URL;
+import java.security.InvalidKeyException;
import java.security.Key;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
public class AesCipherTest {
@@ -47,11 +50,11 @@ public class AesCipherTest {
@Test
public void encrypt() throws Exception {
Settings settings = new Settings();
- settings.setProperty(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY, pathToSecretKey());
+ settings.setProperty(CoreProperties.ENCRYPTION_SECRET_KEY_FILE, pathToSecretKey());
AesCipher cipher = new AesCipher(settings);
- String encryptedText = cipher.encrypt("sonar");
- System.out.println(encryptedText);
+ String encryptedText = cipher.encrypt("this is a secret");
+
assertThat(StringUtils.isNotBlank(encryptedText), is(true));
assertThat(Base64.isArrayByteBase64(encryptedText.getBytes()), is(true));
}
@@ -59,7 +62,7 @@ public class AesCipherTest {
@Test
public void decrypt() throws Exception {
Settings settings = new Settings();
- settings.setProperty(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY, pathToSecretKey());
+ settings.setProperty(CoreProperties.ENCRYPTION_SECRET_KEY_FILE, pathToSecretKey());
AesCipher cipher = new AesCipher(settings);
// the following value has been encrypted with the key /org/sonar/api/config/AesCipherTest/aes_secret_key.txt
@@ -69,9 +72,42 @@ public class AesCipherTest {
}
@Test
+ public void decrypt_bad_key() throws Exception {
+ URL resource = getClass().getResource("/org/sonar/api/config/AesCipherTest/bad_secret_key.txt");
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.ENCRYPTION_SECRET_KEY_FILE, new File(resource.toURI()).getCanonicalPath());
+ AesCipher cipher = new AesCipher(settings);
+
+ try {
+ cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+ fail();
+
+ } catch (RuntimeException e) {
+ assertThat(e.getCause(), is(InvalidKeyException.class));
+ }
+ }
+
+ @Test
+ public void decrypt_other_key() throws Exception {
+ URL resource = getClass().getResource("/org/sonar/api/config/AesCipherTest/other_secret_key.txt");
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.ENCRYPTION_SECRET_KEY_FILE, new File(resource.toURI()).getCanonicalPath());
+ AesCipher cipher = new AesCipher(settings);
+
+ try {
+ // text encrypted with another key
+ cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+ fail();
+
+ } catch (RuntimeException e) {
+ assertThat(e.getCause(), is(BadPaddingException.class));
+ }
+ }
+
+ @Test
public void encryptThenDecrypt() throws Exception {
Settings settings = new Settings();
- settings.setProperty(CoreProperties.ENCRYPTION_PATH_TO_SECRET_KEY, pathToSecretKey());
+ settings.setProperty(CoreProperties.ENCRYPTION_SECRET_KEY_FILE, pathToSecretKey());
AesCipher cipher = new AesCipher(settings);
assertThat(cipher.decrypt(cipher.encrypt("foo")), is("foo"));
@@ -85,6 +121,30 @@ public class AesCipherTest {
assertThat(secretKey.getEncoded().length, greaterThan(10));
}
+ @Test
+ public void loadSecretKeyFromFile_trim_content() throws Exception {
+ URL resource = getClass().getResource("/org/sonar/api/config/AesCipherTest/non_trimmed_secret_key.txt");
+ String path = new File(resource.toURI()).getCanonicalPath();
+ AesCipher cipher = new AesCipher(new Settings());
+
+ Key secretKey = cipher.loadSecretFileFromFile(path);
+
+ assertThat(secretKey.getAlgorithm(), is("AES"));
+ assertThat(secretKey.getEncoded().length, greaterThan(10));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void loadSecretKeyFromFile_file_does_not_exist() throws Exception {
+ AesCipher cipher = new AesCipher(new Settings());
+ cipher.loadSecretFileFromFile("/file/does/not/exist");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void loadSecretKeyFromFile_no_property() throws Exception {
+ AesCipher cipher = new AesCipher(new Settings());
+ cipher.loadSecretFileFromFile(null);
+ }
+
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
index 1333120422c..93a04537d7c 100644
--- 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
@@ -39,6 +39,12 @@ public class EncryptionTest {
}
@Test
+ public void scramble() {
+ Encryption encryption = new Encryption(new Settings());
+ assertThat(encryption.scramble("foo"), is("{b64}Zm9v"));
+ }
+
+ @Test
public void decrypt() {
Encryption encryption = new Encryption(new Settings());
assertThat(encryption.decrypt("{b64}Zm9v"), is("foo"));
@@ -54,6 +60,5 @@ public class EncryptionTest {
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/bad_secret_key.txt b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/bad_secret_key.txt
new file mode 100644
index 00000000000..b33e179e5c8
--- /dev/null
+++ b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/bad_secret_key.txt
@@ -0,0 +1 @@
+badbadbad== \ No newline at end of file
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/non_trimmed_secret_key.txt b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/non_trimmed_secret_key.txt
new file mode 100644
index 00000000000..ab83e4adc03
--- /dev/null
+++ b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/non_trimmed_secret_key.txt
@@ -0,0 +1,3 @@
+
+ 0PZz+G+f8mjr3sPn4+AhHg==
+
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/other_secret_key.txt b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/other_secret_key.txt
new file mode 100644
index 00000000000..23f5ecf5104
--- /dev/null
+++ b/sonar-plugin-api/src/test/resources/org/sonar/api/config/AesCipherTest/other_secret_key.txt
@@ -0,0 +1 @@
+IBxEUxZ41c8XTxyaah1Qlg== \ No newline at end of file
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 0e7774cdf34..82f899f3e22 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -399,8 +399,8 @@ public final class JRubyFacade {
LoggerFactory.getLogger(getClass()).error(message);
}
- public boolean canEncrypt() {
- return getContainer().getComponentByType(Settings.class).getEncryption().canEncrypt();
+ public boolean hasSecretKey() {
+ return getContainer().getComponentByType(Settings.class).getEncryption().hasSecretKey();
}
public String encrypt(String clearText) {
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/encryption_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/encryption_controller.rb
index 789b60972a6..cc7d49ee848 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/encryption_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/encryption_controller.rb
@@ -24,7 +24,7 @@ class EncryptionController < ApplicationController
verify :method => :post, :only => [:generate_secret, :encrypt], :redirect_to => {:action => :index}
def index
- @can_encrypt=java_facade.canEncrypt()
+ @has_secret_key=java_facade.hasSecretKey()
end
def generate_secret
@@ -37,7 +37,7 @@ class EncryptionController < ApplicationController
end
def encrypt
- bad_request('No secret key') unless java_facade.canEncrypt()
+ bad_request('No secret key') unless java_facade.hasSecretKey()
@encrypted=java_facade.encrypt(params[:text])
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/encrypt.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/encrypt.html.erb
new file mode 100644
index 00000000000..83d55e7db1c
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/encrypt.html.erb
@@ -0,0 +1 @@
+<code><%= @encrypted -%></code> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/generate_secret.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/generate_secret.html.erb
index 7323895aedc..15b8f4b5fbf 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/generate_secret.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/generate_secret.html.erb
@@ -1,4 +1 @@
-<p>
- Secret is:
- <input type="text" value="<%= @secret -%>"/>
-</p> \ No newline at end of file
+<span class=""><%= @secret -%></span> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/index.html.erb
index 06fd70d605f..b81da062400 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/encryption/index.html.erb
@@ -1,10 +1,12 @@
-<p>bla bla</p>
-
-<% if @can_encrypt %>
- <form action="<%= ApplicationController.root_context -%>/encryption/encrypt" method="POST">
+<% if @has_secret_key %>
+ <%= form_remote_tag :url => {:action => 'encrypt'},
+ :update => { :success => 'encryption_result', :failure => 'encryption_error' },
+ :failure => "$('encryption_error').show()" -%>
<input type="text" name="text" id="text"/>
<input type="submit" value="Encrypt" id="submit_encrypt"/>
</form>
+ <div id="encryption_result"></div>
+ <div id="encryption_error" class="error" style="display:none"></div>
<% else %>
<form action="<%= ApplicationController.root_context -%>/encryption/generate_secret" method="POST">
<input type="submit" value="Generate secret" id="submit_generate_secret"/>