diff --git a/scm-core/src/main/java/sonia/scm/security/CipherHandler.java b/scm-core/src/main/java/sonia/scm/security/CipherHandler.java index 3703dc3336..8463f4a4e3 100644 --- a/scm-core/src/main/java/sonia/scm/security/CipherHandler.java +++ b/scm-core/src/main/java/sonia/scm/security/CipherHandler.java @@ -33,14 +33,12 @@ package sonia.scm.security; -import com.google.inject.Singleton; /** * * @author Sebastian Sdorra * @since 1.7 */ -@Singleton public interface CipherHandler { diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index 7f6050becb..a3f5716b8d 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -57,6 +57,8 @@ import java.util.ArrayList; import java.util.List; import javax.servlet.ServletContextEvent; +import sonia.scm.security.CipherHandler; +import sonia.scm.security.CipherSingleton; /** * @@ -143,6 +145,10 @@ public class ScmContextListener extends GuiceServletContextListener injector = Guice.createInjector(moduleList); SCMContextProvider context = SCMContext.getContext(); + + // init CipherSingleton + CipherHandler ch = injector.getInstance(CipherHandler.class); + CipherSingleton.init(ch); // init StoreFactory injector.getInstance(StoreFactory.class).init(context); diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 30bab403aa..b85df81d1e 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -64,9 +64,13 @@ import sonia.scm.repository.ChangesetViewerUtil; import sonia.scm.repository.RepositoryBrowserUtil; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.xml.XmlRepositoryManager; +import sonia.scm.security.CipherHandler; +import sonia.scm.security.DefaultCipherHandler; import sonia.scm.security.EncryptionHandler; +import sonia.scm.security.KeyGenerator; import sonia.scm.security.MessageDigestEncryptionHandler; import sonia.scm.security.SecurityContext; +import sonia.scm.security.UUIDKeyGenerator; import sonia.scm.store.JAXBStoreFactory; import sonia.scm.store.StoreFactory; import sonia.scm.template.FreemarkerTemplateHandler; @@ -197,7 +201,9 @@ public class ScmServletModule extends ServletModule bind(ScmConfiguration.class).toInstance(config); bind(PluginLoader.class).toInstance(pluginLoader); bind(PluginManager.class).to(DefaultPluginManager.class); + bind(KeyGenerator.class).to(UUIDKeyGenerator.class); bind(EncryptionHandler.class).to(MessageDigestEncryptionHandler.class); + bind(CipherHandler.class).to(DefaultCipherHandler.class); bindExtProcessor.bindExtensions(binder()); Class fileSystem = diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultCipherHandler.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultCipherHandler.java new file mode 100644 index 0000000000..8d6d2d08d7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultCipherHandler.java @@ -0,0 +1,329 @@ +/** + * 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.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; + +import com.google.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.SCMContextProvider; +import sonia.scm.util.IOUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import com.sun.jersey.core.util.Base64; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class DefaultCipherHandler implements CipherHandler +{ + + /** Field description */ + public static final String CIPHER_TYPE = "AES/CTR/PKCS5PADDING"; + + /** Field description */ + public static final String DIGEST_TYPE = "SHA-512"; + + /** Field description */ + public static final String ENCODING = "UTF-8"; + + /** Field description */ + private static final String CIPHERKEY_FILENAME = ".cipherkey"; + + /** Field description */ + private static final char[] KEY_BASE = new char[] + { + '1', '4', '7', '3', 'F', '2', '1', 'E', '-', 'C', '4', 'C', '4', '-', '4', + '6', 'C', 'C', '-', '8', '7', 'F', '6', '-', '7', 'B', '4', 'F', '0', '5', + 'E', 'C', '7', '7', '2', 'E' + }; + + /** Field description */ + private static final String KEY_TYPE = "AES"; + + /** the logger for DefaultCipherHandler */ + private static final Logger logger = + LoggerFactory.getLogger(DefaultCipherHandler.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param context + * @param keyGenerator + * + * + * @throws IOException + */ + @Inject + public DefaultCipherHandler(SCMContextProvider context, + KeyGenerator keyGenerator) + throws IOException + { + File configDirectory = new File(context.getBaseDirectory(), "config"); + + IOUtil.mkdirs(configDirectory); + cipherKeyFile = new File(configDirectory, CIPHERKEY_FILENAME); + + if (cipherKeyFile.exists()) + { + loadKey(); + } + else + { + key = keyGenerator.createKey().toCharArray(); + storeKey(); + } + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param value + * + * @return + */ + @Override + public String decode(String value) + { + return decode(key, value); + } + + /** + * Method description + * + * + * @param plainKey + * @param value + * + * @return + */ + public String decode(char[] plainKey, String value) + { + String result = null; + + try + { + byte[] encodedInput = Base64.decode(value); + byte[] salt = new byte[8]; + byte[] encoded = new byte[encodedInput.length - 8]; + + System.arraycopy(encodedInput, 0, salt, 0, 8); + System.arraycopy(encodedInput, 8, encoded, 0, encodedInput.length - 8); + + PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 20); + SecretKey secretKey = buildSecretKey(plainKey); + javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_TYPE); + + cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, parameterSpec); + + byte[] decoded = cipher.doFinal(encoded); + + result = new String(decoded, ENCODING); + } + catch (Exception ex) + { + logger.error("could not decode string", ex); + + throw new CipherException(ex); + } + + return result; + } + + /** + * Method description + * + * + * @param value + * + * @return + */ + @Override + public String encode(String value) + { + return encode(key, value); + } + + /** + * Method description + * + * + * @param plainKey + * @param value + * + * @return + */ + public String encode(char[] plainKey, String value) + { + String res = null; + + try + { + byte[] salt = new byte[8]; + + random.nextBytes(salt); + + IvParameterSpec iv = new IvParameterSpec(salt); + SecretKey secretKey = buildSecretKey(key); + javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_TYPE); + + cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, iv); + + byte[] inputBytes = value.getBytes(ENCODING); + byte[] encodedInput = cipher.doFinal(inputBytes); + byte[] result = new byte[salt.length + encodedInput.length]; + + System.arraycopy(salt, 0, result, 0, 8); + System.arraycopy(encodedInput, 0, result, 8, result.length - 8); + res = new String(Base64.encode(result), ENCODING); + } + catch (Exception ex) + { + logger.error("could not encode string", ex); + + throw new CipherException(ex); + } + + return res; + } + + /** + * Method description + * + * + * @param plainKey + * + * @return + * + * @throws NoSuchAlgorithmException + * @throws UnsupportedEncodingException + */ + private SecretKey buildSecretKey(char[] plainKey) + throws UnsupportedEncodingException, NoSuchAlgorithmException + { + byte[] raw = new String(plainKey).getBytes(ENCODING); + MessageDigest digest = MessageDigest.getInstance(DIGEST_TYPE); + + raw = digest.digest(raw); + + return new SecretKeySpec(raw, KEY_TYPE); + } + + /** + * Method description + * + * + * @throws IOException + */ + private void loadKey() throws IOException + { + BufferedReader reader = null; + + try + { + reader = new BufferedReader(new FileReader(cipherKeyFile)); + + String line = reader.readLine(); + + key = decode(KEY_BASE, line).toCharArray(); + } + finally + { + IOUtil.close(reader); + } + } + + /** + * Method description + * + * + * @throws FileNotFoundException + */ + private void storeKey() throws FileNotFoundException + { + String storeKey = encode(KEY_BASE, new String(key)); + PrintWriter output = null; + + try + { + output = new PrintWriter(cipherKeyFile); + output.write(storeKey); + } + finally + { + IOUtil.close(output); + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private File cipherKeyFile; + + /** Field description */ + private char[] key = null; + + /** Field description */ + private SecureRandom random = new SecureRandom(); +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/UUIDKeyGenerator.java b/scm-webapp/src/main/java/sonia/scm/security/UUIDKeyGenerator.java new file mode 100644 index 0000000000..1e7e60bad5 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/UUIDKeyGenerator.java @@ -0,0 +1,58 @@ +/** + * 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.security; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.UUID; + +/** + * + * @author Sebastian Sdorra + */ +public class UUIDKeyGenerator implements KeyGenerator +{ + + /** + * Method description + * + * + * @return + */ + @Override + public String createKey() + { + return UUID.randomUUID().toString(); + } +}