diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/AesCipherStreamHandler.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/AesCipherStreamHandler.java
new file mode 100644
index 0000000000..5955b8b762
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/AesCipherStreamHandler.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+package sonia.scm.cli.config;
+
+import com.google.common.base.Charsets;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+/**
+ * Implementation of {@link CipherStreamHandler} which uses AES. This version is used since version 1.60 for the
+ * cli client encryption.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.60
+ */
+public class AesCipherStreamHandler implements CipherStreamHandler {
+
+ private static final String ALGORITHM = "AES/GCM/NoPadding";
+
+ private final SecureRandom random = new SecureRandom();
+
+ private final byte[] secretKey;
+
+ AesCipherStreamHandler(String secretKey) {
+ this.secretKey = secretKey.getBytes(Charsets.UTF_8);
+ }
+
+ @Override
+ public OutputStream encrypt(OutputStream outputStream) throws IOException {
+ Cipher cipher = createCipherForEncryption();
+ outputStream.write(cipher.getIV());
+ return new CipherOutputStream(outputStream, cipher);
+ }
+
+ @Override
+ public InputStream decrypt(InputStream inputStream) throws IOException {
+ Cipher cipher = createCipherForDecryption(inputStream);
+ return new CipherInputStream(inputStream, cipher);
+ }
+
+ private Cipher createCipherForDecryption(InputStream inputStream) throws IOException {
+ byte[] iv =new byte[12];
+ inputStream.read(iv);
+ return createCipher(Cipher.DECRYPT_MODE, iv);
+ }
+
+ private Cipher createCipherForEncryption() {
+ byte[] iv = generateIV();
+ return createCipher(Cipher.ENCRYPT_MODE, iv);
+ }
+
+ private byte[] generateIV() {
+ // use 12 byte as described at nist
+ // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
+ byte[] iv = new byte[12];
+ random.nextBytes(iv);
+ return iv;
+ }
+
+ private Cipher createCipher(int mode, byte[] iv) {
+ try {
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+ GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
+ cipher.init(mode, new SecretKeySpec(secretKey, "AES"), parameterSpec);
+ return cipher;
+ } catch (Exception ex) {
+ throw new ScmConfigException("failed to create cipher", ex);
+ }
+ }
+}
diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/CipherStreamHandler.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/CipherStreamHandler.java
index e687988be3..2321b3d9d9 100644
--- a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/CipherStreamHandler.java
+++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/CipherStreamHandler.java
@@ -32,11 +32,15 @@
package sonia.scm.cli.config;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The CipherStreamHandler is able to encrypt and decrypt streams.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.60
*/
public interface CipherStreamHandler {
@@ -47,7 +51,7 @@ public interface CipherStreamHandler {
*
* @return raw input stream
*/
- InputStream decrypt(InputStream inputStream);
+ InputStream decrypt(InputStream inputStream) throws IOException;
/**
* Encrypts the given output stream.
@@ -56,5 +60,5 @@ public interface CipherStreamHandler {
*
* @return encrypting output stream
*/
- OutputStream encrypt(OutputStream outputStream);
+ OutputStream encrypt(OutputStream outputStream) throws IOException;
}
diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/ConfigFiles.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/ConfigFiles.java
new file mode 100644
index 0000000000..5087c50746
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/ConfigFiles.java
@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.cli.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import sonia.scm.security.KeyGenerator;
+
+import javax.xml.bind.JAXB;
+import java.io.*;
+import java.util.Arrays;
+
+/**
+ * Util methods for configuration files.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.60
+ */
+final class ConfigFiles {
+
+ private static final KeyGenerator keyGenerator = new SecureRandomKeyGenerator();
+
+ // SCM Config Version 2
+ @VisibleForTesting
+ static final byte[] VERSION_IDENTIFIER = "SCV2".getBytes(Charsets.US_ASCII);
+
+ private ConfigFiles() {
+ }
+
+ /**
+ * Returns {@code true} if the file is encrypted with the v2 format.
+ *
+ * @param file configuration file
+ *
+ * @return {@code true} for format v2
+ *
+ * @throws IOException
+ */
+ static boolean isFormatV2(File file) throws IOException {
+ try (InputStream input = new FileInputStream(file)) {
+ byte[] bytes = new byte[VERSION_IDENTIFIER.length];
+ input.read(bytes);
+ return Arrays.equals(VERSION_IDENTIFIER, bytes);
+ }
+ }
+
+ /**
+ * Decrypt and parse v1 configuration file.
+ *
+ * @param keyStore key store
+ * @param file configuration file
+ *
+ * @return client configuration
+ *
+ * @throws IOException
+ */
+ static ScmClientConfig parseV1(KeyStore keyStore, File file) throws IOException {
+ String secretKey = secretKey(keyStore);
+ CipherStreamHandler cipherStreamHandler = new WeakCipherStreamHandler(secretKey);
+ return decrypt(cipherStreamHandler, new FileInputStream(file));
+ }
+
+ /**
+ * Decrypt and parse v12configuration file.
+ *
+ * @param keyStore key store
+ * @param file configuration file
+ *
+ * @return client configuration
+ *
+ * @throws IOException
+ */
+ static ScmClientConfig parseV2(KeyStore keyStore, File file) throws IOException {
+ String secretKey = secretKey(keyStore);
+ CipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(secretKey);
+ try (InputStream input = new FileInputStream(file)) {
+ input.skip(VERSION_IDENTIFIER.length);
+ return decrypt(cipherStreamHandler, input);
+ }
+ }
+
+ /**
+ * Store encrypt and write the configuration to the given file.
+ * Note the method uses always the latest available format.
+ *
+ * @param keyStore key store
+ * @param config configuration
+ * @param file configuration file
+ *
+ * @throws IOException
+ */
+ static void store(KeyStore keyStore, ScmClientConfig config, File file) throws IOException {
+ String secretKey = keyGenerator.createKey();
+ CipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(secretKey);
+ try (OutputStream output = new FileOutputStream(file)) {
+ output.write(VERSION_IDENTIFIER);
+ encrypt(cipherStreamHandler, output, config);
+ }
+ keyStore.set(secretKey);
+ }
+
+ private static String secretKey(KeyStore keyStore) {
+ String secretKey = keyStore.get();
+ Preconditions.checkState(!Strings.isNullOrEmpty(secretKey), "no stored secret key found");
+ return secretKey;
+ }
+
+ private static ScmClientConfig decrypt(CipherStreamHandler cipherStreamHandler, InputStream input) throws IOException {
+ try ( InputStream decryptedInputStream = cipherStreamHandler.decrypt(input) ) {
+ return JAXB.unmarshal(decryptedInputStream, ScmClientConfig.class);
+ }
+ }
+
+ private static void encrypt(CipherStreamHandler cipherStreamHandler, OutputStream output, ScmClientConfig clientConfig) throws IOException {
+ try ( OutputStream encryptedOutputStream = cipherStreamHandler.encrypt(output) ) {
+ JAXB.marshal(clientConfig, encryptedOutputStream);
+ }
+ }
+
+}
diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/ScmClientConfigFileHandler.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/ScmClientConfigFileHandler.java
index bbb0fd85c1..00a0eb6cf6 100644
--- a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/ScmClientConfigFileHandler.java
+++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/ScmClientConfigFileHandler.java
@@ -35,15 +35,10 @@ package sonia.scm.cli.config;
//~--- non-JDK imports --------------------------------------------------------
-import sonia.scm.security.KeyGenerator;
-import sonia.scm.security.UUIDKeyGenerator;
import sonia.scm.util.Util;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Marshaller;
-import javax.xml.bind.Unmarshaller;
-import java.io.*;
+import java.io.File;
+import java.io.IOException;
//~--- JDK imports ------------------------------------------------------------
@@ -63,40 +58,20 @@ public class ScmClientConfigFileHandler
//~--- constructors ---------------------------------------------------------
private final KeyStore keyStore;
- private final KeyGenerator keyGenerator;
private final File file;
- private final JAXBContext context;
-
- private final CipherStreamHandler cipherStreamHandler;
/**
- * Constructs ...
+ * Constructs a new ScmClientConfigFileHandler
*
*/
public ScmClientConfigFileHandler() {
- this(new PrefsKeyStore(), new UUIDKeyGenerator(), getDefaultConfigFile());
+ this(new PrefsKeyStore(), getDefaultConfigFile());
}
- ScmClientConfigFileHandler(KeyStore keyStore, KeyGenerator keyGenerator, File file) {
+ ScmClientConfigFileHandler(KeyStore keyStore,File file) {
this.keyStore = keyStore;
- this.keyGenerator = keyGenerator;
this.file = file;
-
- String key = keyStore.get();
-
- if (Util.isEmpty(key)) {
- key = keyGenerator.createKey();
- keyStore.set(key);
- }
-
- cipherStreamHandler = new WeakCipherStreamHandler(key.toCharArray());
-
- try {
- context = JAXBContext.newInstance(ScmClientConfig.class);
- } catch (JAXBException ex) {
- throw new ScmConfigException("could not create JAXBContext for ScmClientConfig", ex);
- }
}
//~--- methods --------------------------------------------------------------
@@ -132,17 +107,27 @@ public class ScmClientConfigFileHandler
ScmClientConfig config = null;
if (file.exists()) {
- try (InputStream input = cipherStreamHandler.decrypt(new FileInputStream(file))) {
- Unmarshaller um = context.createUnmarshaller();
- config = (ScmClientConfig) um.unmarshal(input);
- } catch (IOException | JAXBException ex) {
- throw new ScmConfigException("could not read config file", ex);
- }
+ config = readFromFile();
}
return config;
}
+ private ScmClientConfig readFromFile() {
+ ScmClientConfig config;
+ try {
+ if (ConfigFiles.isFormatV2(file)) {
+ config = ConfigFiles.parseV2(keyStore, file);
+ } else {
+ config = ConfigFiles.parseV1(keyStore, file);
+ ConfigFiles.store(keyStore, config, file);
+ }
+ } catch (IOException ex) {
+ throw new ScmConfigException("could not read config file", ex);
+ }
+ return config;
+ }
+
/**
* Method description
*
@@ -150,10 +135,9 @@ public class ScmClientConfigFileHandler
* @param config
*/
public void write(ScmClientConfig config) {
- try (OutputStream output = cipherStreamHandler.encrypt(new FileOutputStream(file))) {
- Marshaller m = context.createMarshaller();
- m.marshal(config, output);
- } catch (IOException | JAXBException ex) {
+ try {
+ ConfigFiles.store(keyStore, config, file);
+ } catch (IOException ex) {
throw new ScmConfigException("could not write config file", ex);
}
}
diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/SecureRandomKeyGenerator.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/SecureRandomKeyGenerator.java
new file mode 100644
index 0000000000..19ae135461
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/SecureRandomKeyGenerator.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+package sonia.scm.cli.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import sonia.scm.security.KeyGenerator;
+
+import java.security.SecureRandom;
+import java.util.Locale;
+
+/**
+ * Create keys by using {@link SecureRandom}. The SecureRandomKeyGenerator produces aes compatible keys.
+ * Warning the class is not thread safe.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.60
+ */
+public class SecureRandomKeyGenerator implements KeyGenerator {
+
+ private SecureRandom random = new SecureRandom();
+
+ // key length 16 for aes128
+ @VisibleForTesting
+ static final int KEY_LENGTH = 16;
+
+ private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ private static final String LOWER = UPPER.toLowerCase(Locale.ENGLISH);
+ private static final String DIGITS = "0123456789";
+ private static final char[] ALL = (UPPER + LOWER + DIGITS).toCharArray();
+
+ @Override
+ public String createKey() {
+ char[] key = new char[KEY_LENGTH];
+ for (int idx = 0; idx < KEY_LENGTH; ++idx) {
+ key[idx] = ALL[random.nextInt(ALL.length)];
+ }
+ return new String(key);
+ }
+}
diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/WeakCipherStreamHandler.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/WeakCipherStreamHandler.java
index a9e3e5ef42..175d1986c6 100644
--- a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/WeakCipherStreamHandler.java
+++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/config/WeakCipherStreamHandler.java
@@ -45,6 +45,9 @@ import java.security.spec.InvalidKeySpecException;
* Weak implementation of {@link CipherStreamHandler}. This is the old implementation, which was used in versions prior
* 1.60.
*
+ * @author Sebastian Sdorra
+ * @since 1.60
+ *
* @see Issue 978
* @see Issue 979
*/
@@ -61,8 +64,8 @@ public class WeakCipherStreamHandler implements CipherStreamHandler {
*
* @param secretKey secret key
*/
- public WeakCipherStreamHandler(char[] secretKey) {
- this.secretKey = secretKey;
+ WeakCipherStreamHandler(String secretKey) {
+ this.secretKey = secretKey.toCharArray();
}
@Override
diff --git a/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/AesCipherStreamHandlerTest.java b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/AesCipherStreamHandlerTest.java
new file mode 100644
index 0000000000..14000faa33
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/AesCipherStreamHandlerTest.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+package sonia.scm.cli.config;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+import org.junit.Test;
+import sonia.scm.security.KeyGenerator;
+
+import java.io.*;
+
+import static org.junit.Assert.assertEquals;
+
+public class AesCipherStreamHandlerTest {
+
+ private final KeyGenerator keyGenerator = new SecureRandomKeyGenerator();
+
+ @Test
+ public void testEncryptAndDecrypt() throws IOException {
+ AesCipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(keyGenerator.createKey());
+
+ // douglas adams
+ String content = "If you try and take a cat apart to see how it works, the first thing you have on your hands is a nonworking cat.";
+
+ // encrypt
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ OutputStream encryptedOutput = cipherStreamHandler.encrypt(output);
+ encryptedOutput.write(content.getBytes(Charsets.UTF_8));
+ encryptedOutput.close();
+
+ InputStream input = new ByteArrayInputStream(output.toByteArray());
+ input = cipherStreamHandler.decrypt(input);
+ byte[] decrypted = ByteStreams.toByteArray(input);
+
+ assertEquals(content, new String(decrypted, Charsets.UTF_8));
+ }
+
+}
diff --git a/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ClientConfigurationTests.java b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ClientConfigurationTests.java
new file mode 100644
index 0000000000..20422df7ed
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ClientConfigurationTests.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+package sonia.scm.cli.config;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+
+import javax.xml.bind.JAXB;
+import java.io.*;
+
+import static org.junit.Assert.assertEquals;
+
+final class ClientConfigurationTests {
+
+ private ClientConfigurationTests() {
+ }
+
+ static void testCipherStream(CipherStreamHandler cipherStreamHandler, String content) throws IOException {
+ byte[] encrypted = encrypt(cipherStreamHandler, content);
+ String decrypted = decrypt(cipherStreamHandler, encrypted);
+ assertEquals(content, decrypted);
+ }
+
+
+ static byte[] encrypt(CipherStreamHandler cipherStreamHandler, String content) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ OutputStream encryptedOutput = cipherStreamHandler.encrypt(output);
+ encryptedOutput.write(content.getBytes(Charsets.UTF_8));
+ encryptedOutput.close();
+ return output.toByteArray();
+ }
+
+ static String decrypt(CipherStreamHandler cipherStreamHandler, byte[] encrypted) throws IOException {
+ InputStream input = new ByteArrayInputStream(encrypted);
+ input = cipherStreamHandler.decrypt(input);
+ byte[] decrypted = ByteStreams.toByteArray(input);
+ input.close();
+
+ return new String(decrypted, Charsets.UTF_8);
+ }
+
+ static void assertSampleConfig(ScmClientConfig config) {
+ ServerConfig defaultConfig;
+ defaultConfig = config.getDefaultConfig();
+
+ assertEquals("http://localhost:8080/scm", defaultConfig.getServerUrl());
+ assertEquals("admin", defaultConfig.getUsername());
+ assertEquals("admin123", defaultConfig.getPassword());
+ }
+
+ static ScmClientConfig createSampleConfig() {
+ ScmClientConfig config = new ScmClientConfig();
+ ServerConfig defaultConfig = config.getDefaultConfig();
+ defaultConfig.setServerUrl("http://localhost:8080/scm");
+ defaultConfig.setUsername("admin");
+ defaultConfig.setPassword("admin123");
+ return config;
+ }
+
+ static void encrypt(CipherStreamHandler cipherStreamHandler, ScmClientConfig config, File file) throws IOException {
+ try (OutputStream output = cipherStreamHandler.encrypt(new FileOutputStream(file))) {
+ JAXB.marshal(config, output);
+ }
+ }
+
+}
diff --git a/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ConfigFilesTest.java b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ConfigFilesTest.java
new file mode 100644
index 0000000000..5fbdfdc0c0
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ConfigFilesTest.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.cli.config;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+public class ConfigFilesTest {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void testIsFormatV2() throws IOException {
+ byte[] content = "The door was the way to... to... The Door was The Way".getBytes(Charsets.UTF_8);
+
+ File fileV1 = temporaryFolder.newFile();
+ Files.write(content, fileV1);
+
+ assertFalse(ConfigFiles.isFormatV2(fileV1));
+
+ File fileV2 = temporaryFolder.newFile();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write(ConfigFiles.VERSION_IDENTIFIER);
+ baos.write(content);
+ Files.write(baos.toByteArray(), fileV2);
+
+ assertTrue(ConfigFiles.isFormatV2(fileV2));
+ }
+
+ @Test
+ public void testParseV1() throws IOException {
+ InMemoryKeyStore keyStore = createKeyStore();
+ WeakCipherStreamHandler handler = new WeakCipherStreamHandler(keyStore.get());
+
+ ScmClientConfig config = ClientConfigurationTests.createSampleConfig();
+ File file = temporaryFolder.newFile();
+ ClientConfigurationTests.encrypt(handler, config, file);
+
+ config = ConfigFiles.parseV1(keyStore, file);
+ ClientConfigurationTests.assertSampleConfig(config);
+ }
+
+ @Test
+ public void storeAndParseV2() throws IOException {
+ InMemoryKeyStore keyStore = new InMemoryKeyStore();
+ ScmClientConfig config = ClientConfigurationTests.createSampleConfig();
+ File file = temporaryFolder.newFile();
+
+ ConfigFiles.store(keyStore, config, file);
+
+ String key = keyStore.get();
+ assertNotNull(key);
+
+ config = ConfigFiles.parseV2(keyStore, file);
+ ClientConfigurationTests.assertSampleConfig(config);
+ }
+
+ private InMemoryKeyStore createKeyStore() {
+ String secretKey = new SecureRandomKeyGenerator().createKey();
+ InMemoryKeyStore keyStore = new InMemoryKeyStore();
+ keyStore.set(secretKey);
+ return keyStore;
+ }
+
+}
diff --git a/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/InMemoryKeyStore.java b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/InMemoryKeyStore.java
new file mode 100644
index 0000000000..1d0069a087
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/InMemoryKeyStore.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+package sonia.scm.cli.config;
+
+public class InMemoryKeyStore implements KeyStore {
+
+ private String secretKey;
+
+ @Override
+ public void set(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ @Override
+ public String get() {
+ return secretKey;
+ }
+
+ @Override
+ public void remove() {
+ this.secretKey = null;
+ }
+}
diff --git a/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ScmClientConfigFileHandlerTest.java b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ScmClientConfigFileHandlerTest.java
index ec598fd2d4..a9f6a46418 100644
--- a/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ScmClientConfigFileHandlerTest.java
+++ b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/ScmClientConfigFileHandlerTest.java
@@ -31,6 +31,8 @@
package sonia.scm.cli.config;
+import com.google.common.io.Files;
+import com.google.common.io.Resources;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -38,10 +40,9 @@ import sonia.scm.security.UUIDKeyGenerator;
import java.io.File;
import java.io.IOException;
+import java.net.URL;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
public class ScmClientConfigFileHandlerTest {
@@ -53,7 +54,7 @@ public class ScmClientConfigFileHandlerTest {
File configFile = temporaryFolder.newFile();
ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
- new InMemoryKeyStore(), new UUIDKeyGenerator(), configFile
+ new InMemoryKeyStore(), configFile
);
ScmClientConfig config = new ScmClientConfig();
@@ -76,23 +77,62 @@ public class ScmClientConfigFileHandlerTest {
assertFalse(configFile.exists());
}
- private static class InMemoryKeyStore implements KeyStore {
+ @Test
+ public void testClientConfigFileHandlerWithOldConfiguration() throws IOException {
+ File configFile = temporaryFolder.newFile();
- private String secretKey;
+ // old implementation has used uuids as keys
+ String key = new UUIDKeyGenerator().createKey();
- @Override
- public void set(String secretKey) {
- this.secretKey = secretKey;
- }
+ WeakCipherStreamHandler weakCipherStreamHandler = new WeakCipherStreamHandler(key);
+ ScmClientConfig clientConfig = ClientConfigurationTests.createSampleConfig();
+ ClientConfigurationTests.encrypt(weakCipherStreamHandler, clientConfig, configFile);
- @Override
- public String get() {
- return secretKey;
- }
+ assertFalse(ConfigFiles.isFormatV2(configFile));
- @Override
- public void remove() {
- this.secretKey = null;
- }
+ KeyStore keyStore = new InMemoryKeyStore();
+ keyStore.set(key);
+
+ ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
+ keyStore, configFile
+ );
+
+ ScmClientConfig config = handler.read();
+ ClientConfigurationTests.assertSampleConfig(config);
+
+ // ensure key has changed
+ assertNotEquals(key, keyStore.get());
+
+ // ensure config rewritten with v2
+ assertTrue(ConfigFiles.isFormatV2(configFile));
+ }
+
+ @Test
+ public void testClientConfigFileHandlerWithRealMigration() throws IOException {
+ URL resource = Resources.getResource("sonia/scm/cli/config/scm-cli-config.enc.xml");
+ byte[] bytes = Resources.toByteArray(resource);
+
+ File configFile = temporaryFolder.newFile();
+ Files.write(bytes, configFile);
+
+ String key = "358e018a-0c3c-4339-8266-3874e597305f";
+ KeyStore keyStore = new InMemoryKeyStore();
+ keyStore.set(key);
+
+ ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
+ keyStore, configFile
+ );
+
+ ScmClientConfig config = handler.read();
+ ServerConfig defaultConfig = config.getDefaultConfig();
+ assertEquals("http://hitchhicker.com/scm", defaultConfig.getServerUrl());
+ assertEquals("tricia", defaultConfig.getUsername());
+ assertEquals("trillian123", defaultConfig.getPassword());
+
+ // ensure key has changed
+ assertNotEquals(key, keyStore.get());
+
+ // ensure config rewritten with v2
+ assertTrue(ConfigFiles.isFormatV2(configFile));
}
}
diff --git a/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/SecureRandomKeyGeneratorTest.java b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/SecureRandomKeyGeneratorTest.java
new file mode 100644
index 0000000000..e847e89cb1
--- /dev/null
+++ b/scm-clients/scm-cli-client/src/test/java/sonia/scm/cli/config/SecureRandomKeyGeneratorTest.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.cli.config;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class SecureRandomKeyGeneratorTest {
+
+ @Test
+ public void testCreateKey() {
+ SecureRandomKeyGenerator keyGenerator = new SecureRandomKeyGenerator();
+ assertNotNull(keyGenerator.createKey());
+ assertEquals(SecureRandomKeyGenerator.KEY_LENGTH, keyGenerator.createKey().length());
+ }
+
+}
diff --git a/scm-clients/scm-cli-client/src/test/resources/sonia/scm/cli/config/scm-cli-config.enc.xml b/scm-clients/scm-cli-client/src/test/resources/sonia/scm/cli/config/scm-cli-config.enc.xml
new file mode 100644
index 0000000000..94132772a4
Binary files /dev/null and b/scm-clients/scm-cli-client/src/test/resources/sonia/scm/cli/config/scm-cli-config.enc.xml differ