// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.framework.security.keys;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.inject.Inject;
import javax.net.ssl.KeyManager;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;

import org.apache.cloudstack.framework.config.ConfigDepot;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;

import com.cloud.utils.db.DB;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;

/**
 * To be perfectly honest, I'm not sure why we need this class.  This used
 * to be in ManagementServerImpl.  I moved the functionality because it seems
 * many features will need this.  However, the right thing will be for setup
 * and upgrade to take care of key generation.  Here, the methods appear to
 * mainly be used for dynamic generation.  I added this class because after
 * talking to Kelven, we think there will be other functionalities we need
 * to centralize to this class.  We'll see how that works out.
 *
 * There's multiple problems here that we need to fix.
 *   - Multiple servers can be generating keys.  This is not atomic.
 *   - The functionality of generating the keys should be moved over to setup/upgrade.
 *
 */
public class KeysManagerImpl implements KeysManager, Configurable {
    private static final Logger s_logger = Logger.getLogger(KeysManagerImpl.class);

    @Inject
    ConfigurationDao _configDao;
    @Inject
    ConfigDepot _configDepot;

    @Override
    public String getHashKey() {
        String value = HashKey.value();
        if (value == null) {
            _configDepot.set(HashKey, getBase64EncodedRandomKey(128));
        }

        return HashKey.value();
    }

    @Override
    public String getEncryptionKey() {
        String value = EncryptionKey.value();
        if (value == null) {
            _configDepot.set(EncryptionKey, getBase64EncodedRandomKey(128));
        }
        return EncryptionKey.value();
    }

    @Override
    public String getEncryptionIV() {
        String value = EncryptionIV.value();
        if (value == null) {
            _configDepot.set(EncryptionIV, getBase64EncodedRandomKey(128));
        }
        return EncryptionIV.value();
    }

    private static String getBase64EncodedRandomKey(int nBits) {
        SecureRandom random;
        try {
            random = SecureRandom.getInstance("SHA1PRNG");
            byte[] keyBytes = new byte[nBits / 8];
            random.nextBytes(keyBytes);
            return Base64.encodeBase64URLSafeString(keyBytes);
        } catch (NoSuchAlgorithmException e) {
            s_logger.error("Unhandled exception: ", e);
        }
        return null;
    }

    @Override
    @DB
    public void resetEncryptionKeyIV() {

        SearchBuilder<ConfigurationVO> sb = _configDao.createSearchBuilder();
        sb.and("name1", sb.entity().getName(), SearchCriteria.Op.EQ);
        sb.or("name2", sb.entity().getName(), SearchCriteria.Op.EQ);
        sb.done();

        SearchCriteria<ConfigurationVO> sc = sb.create();
        sc.setParameters("name1", EncryptionKey.key());
        sc.setParameters("name2", EncryptionIV.key());

        _configDao.expunge(sc);
    }

    @Override
    public String getConfigComponentName() {
        return KeyManager.class.getSimpleName();
    }

    @Override
    public ConfigKey<?>[] getConfigKeys() {
        return new ConfigKey<?>[] {EncryptionKey, EncryptionIV, HashKey};
    }

}

@@ -2628,24 +2628,70 @@
 figD
-epot.set(HashKey
+ao.getValueAndInitIfNotExist(HashKey.key(), HashKey.category()
 , ge
@@ -2710,32 +2710,55 @@
 edRandomKey(128)
+, HashKey.description()
 );%0A        %7D%0A%0A  
@@ -2946,61 +2946,162 @@
 figD
-epot.set(EncryptionKey, getBase64EncodedRandomKey(128
+ao.getValueAndInitIfNotExist(EncryptionKey.key(), EncryptionKey.category(), getBase64EncodedRandomKey(128),%0A                    EncryptionKey.description(
 ));%0A
@@ -3305,60 +3305,159 @@
 figD
-epot.set(EncryptionIV, getBase64EncodedRandomKey(128
+ao.getValueAndInitIfNotExist(EncryptionIV.key(), EncryptionIV.category(), getBase64EncodedRandomKey(128),%0A                    EncryptionIV.description(
 ));%0A

Encrypt security related config values before inserting into DB