/** * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.portal.gadget.core; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import org.apache.shindig.auth.BlobCrypterSecurityTokenCodec; import org.apache.shindig.config.ContainerConfigException; import org.apache.shindig.expressions.Expressions; import org.exoplatform.commons.utils.PropertyManager; import org.exoplatform.commons.utils.Safe; import org.exoplatform.container.RootContainer; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import sun.misc.BASE64Encoder; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; /** * <p> * The goal of the container config subclass is to integrate security key files along with exo configuration. * </p> * <p> * The implementation first determine the most relevant locations of key files for performing the lookup. Ideally it will take * ones configured as properties <i>gatein.gadgets.securityTokenKeyFile</i> and <i>gatein.gadgets.signingKeyFile</i> in the * <i>configuration.properties</i>. If these properties are not configured, then the implementation uses the current execution * directory (which should be /bin in tomcat and jboss). * </p> * * <p> * When the lookup file locations are determined, the implementation looks for these key files. If no such files are found, then * it will attempt to create them with a base 64 value encoded from a 32 bytes random sequence generated by {@link SecureRandom} * seeded by the current time. If the file exist already but is a directory then no action is done. * <p> * * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> * @version $Revision$ */ @Singleton public class ExoContainerConfig extends GateInJsonContainerConfig { /** . */ private Log log = ExoLogger.getLogger(ExoContainerConfig.class); /** . */ private static volatile String tokenKey_; private String signingKey_; @Inject public ExoContainerConfig(@Named("shindig.containers.default") String s, Expressions expressions) throws ContainerConfigException { super(s, expressions); // This ensures RootContainer initialized first // to populate properties in configuration.properties into PropertyManager RootContainer.getInstance(); initializeTokenKeyFile(); initializeSigningKeyFile(); } private void initializeTokenKeyFile() { String keyPath = PropertyManager.getProperty("gatein.gadgets.securitytokenkeyfile"); File tokenKeyFile = null; if (keyPath == null) { log.warn("The gadgets token key is not configured. The default key.txt file in /bin will be used"); tokenKeyFile = new File("key.txt"); } else { tokenKeyFile = new File(keyPath); } keyPath = tokenKeyFile.getAbsolutePath(); if (tokenKeyFile.exists()) { if (tokenKeyFile.isFile()) { setTokenKeyPath(keyPath); log.info("Found token key file " + keyPath + " for gadgets security"); } else { log.error("Found token path file " + keyPath + " but it's not a key file"); } } else { log.debug("No token key file found at path " + keyPath + ". it's generating a new key and saving it"); File fic = tokenKeyFile.getAbsoluteFile(); File parentFolder = fic.getParentFile(); if (!parentFolder.exists()) { if (!parentFolder.mkdirs()) { log.error("Coult not create parent folder/s for the token key file " + keyPath); return; } } String key = generateKey(); Writer out = null; try { out = new FileWriter(tokenKeyFile); out.write(key); out.write('\n'); setTokenKeyPath(keyPath); log.debug("Generated token key file " + keyPath + " for eXo Gadgets"); } catch (IOException e) { log.error("Could not create token key file " + keyPath, e); } finally { Safe.close(out); } } } private void initializeSigningKeyFile() { String signingKey = PropertyManager.getProperty("gatein.gadgets.signingkeyfile"); File signingKeyFile; if (signingKey == null) { log.warn("The gadgets signing key is not configured. The default signing key in /bin directory will be used."); signingKeyFile = new File("oauthkey.pem"); } else { signingKeyFile = new File(signingKey); } if (signingKeyFile.exists()) { if (signingKeyFile.isFile()) { signingKey_ = signingKeyFile.getAbsolutePath(); log.info("Use signing key " + signingKey_ + " for gadget security"); } else { log.error("Found signing path file " + signingKeyFile.getAbsolutePath() + " but it's not a key file"); } } } private void setTokenKeyPath(String keyPath) { // _keyPath is volatile so no concurrent writes and read are safe synchronized (ExoContainerConfig.class) { if (tokenKey_ != null && !tokenKey_.equals(keyPath)) { throw new IllegalStateException("There is already a configured key path old=" + tokenKey_ + " new=" + keyPath); } tokenKey_ = keyPath; } } @Override public Object getProperty(String container, String property) { if (property.equals(BlobCrypterSecurityTokenCodec.SECURITY_TOKEN_KEY_FILE) && tokenKey_ != null) { return tokenKey_; } if (property.equals(ExoOAuthModule.SIGNING_KEY_FILE) && signingKey_ != null) { return signingKey_; } return super.getProperty(container, property); } /** * It's not public as we don't want to expose it to the outter world. The fact that this class is instantiated by Guice and * the ExoDefaultSecurityTokenGenerator is managed by exo kernel force us to use static reference to share the keyPath * value. * * @return the key path */ static String getTokenKeyPath() { return tokenKey_; } /** * Generate a key of 32 bytes encoded in base64. The generation is based on {@link SecureRandom} seeded with the current * time. * * @return the key */ private static String generateKey() { try { SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); random.setSeed(System.currentTimeMillis()); byte[] bytes = new byte[32]; random.nextBytes(bytes); BASE64Encoder encoder = new BASE64Encoder(); return encoder.encode(bytes); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } } }