#979 encrypt cli configuration with aes instead of pbe

This commit is contained in:
Sebastian Sdorra
2018-04-18 14:41:38 +02:00
parent d9e7de8202
commit 3ee0bcedac
13 changed files with 790 additions and 62 deletions

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 <a href="https://bitbucket.org/sdorra/scm-manager/issues/978/iteration-count-for-password-based">Issue 978</a>
* @see <a href="https://bitbucket.org/sdorra/scm-manager/issues/979/constant-salts-for-pbe-are-insecure">Issue 979</a>
*/
@@ -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

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}