/*
 * 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.hadoop.crypto.key;

import com.microsoft.azure.keyvault.KeyVaultClient;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AlgorithmParameters;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.apache.hadoop.conf.Configuration;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.crypto.key.KeyProvider.Metadata;
import org.apache.hadoop.crypto.key.RangerKeyStoreProvider.KeyMetadata;
import org.apache.log4j.Logger;
import org.apache.ranger.entity.XXRangerKeyStore;
import org.apache.ranger.kms.dao.DaoManager;
import org.apache.ranger.kms.dao.RangerKMSDao;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

/**
 * This class provides the Database store implementation.
 */

public class RangerKeyStore extends KeyStoreSpi {

    static final Logger logger = Logger.getLogger(RangerKeyStore.class);
    private static final String KEY_METADATA = "KeyMetadata";
    private static final String KEY_NAME_VALIDATION = "[a-z,A-Z,0-9](?!.*--)(?!.*__)(?!.*-_)(?!.*_-)[\\w\\-\\_]*";
    private static final Pattern pattern = Pattern.compile(KEY_NAME_VALIDATION);
    private static final String AZURE_KEYVAULT_ENABLED = "ranger.kms.azurekeyvault.enabled";
    private boolean azureKeyVaultEnabled = false;

    private DaoManager daoManager;
    private RangerKeyVaultKeyGenerator kvKeyGen;

    // keys
    private static class KeyEntry {
        Date date = new Date(); // the creation date of this entry
    }

    // Secret key
	private static final class SecretKeyEntry {
		Date date = new Date(); // the creation date of this entry
		SealedObject sealedKey;
		String cipher_field;
		int bit_length;
		String description;
		String attributes;
		int version;
	}

	private static final class SecretKeyByteEntry {
		Date date = new Date();
		byte[] key;
		String cipher_field;
		int bit_length;
		String description;
		String attributes;
		int version;
	}

    private Map<String, Object> keyEntries = new ConcurrentHashMap<>();
    private Map<String, Object> deltaEntries = new ConcurrentHashMap<>();

    RangerKeyStore() {
    }

    public RangerKeyStore(DaoManager daoManager) {
        this.daoManager = daoManager;
    }
    
    public RangerKeyStore(DaoManager daoManager, Configuration conf, KeyVaultClient kvClient) {
        this.daoManager = daoManager;
        this.kvKeyGen = new RangerKeyVaultKeyGenerator(conf, kvClient);
        if(conf != null
				&& StringUtils.isNotEmpty(conf
						.get(AZURE_KEYVAULT_ENABLED))
				&& conf.get(AZURE_KEYVAULT_ENABLED).equalsIgnoreCase(
						"true")){
        	azureKeyVaultEnabled = true;
        }
    }

    String convertAlias(String alias) {
        return alias.toLowerCase();
    }

    @Override
    public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
        if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStore.engineGetKey()");
        }
        Key key = null;
        Object entry = keyEntries.get(convertAlias(alias));

        if (!(entry instanceof SecretKeyEntry)) {
            return null;
        }
        try {
            key = unsealKey(((SecretKeyEntry) entry).sealedKey, password);
        } catch (Exception e) {
            logger.error("==> RangerKeyStore.engineGetKey() error: ", e);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("<== RangerKeyStore.engineGetKey()");
        }
        return key;
    }
    
	public byte[] engineGetDecryptedZoneKeyByte(String alias) throws Exception {
		try {
			Object entry = keyEntries.get(convertAlias(alias));
			if (!(entry instanceof SecretKeyByteEntry)) {
				return null;
			}
			SecretKeyByteEntry key = (SecretKeyByteEntry) entry;
			byte[] decryptKeyByte = kvKeyGen.dencryptZoneKey(key.key);
			return decryptKeyByte;
		} catch (Exception ex) {
			throw new Exception("Error while decrpting zone key. Name : "
					+ alias + " Error : " + ex);
		}
	}
	
	public Key engineGetDecryptedZoneKey(String alias) throws Exception {
		byte[] decryptKeyByte = engineGetDecryptedZoneKeyByte(alias);
		Metadata metadata = engineGetKeyMetadata(alias); 
		Key k = new KeyByteMetadata(metadata, decryptKeyByte);
		return k;
	}
	
	public Metadata engineGetKeyMetadata(String alias) {
		Object entry = keyEntries.get(convertAlias(alias));
		if (!(entry instanceof SecretKeyByteEntry)) {
			return null;
		}
		SecretKeyByteEntry key = (SecretKeyByteEntry) entry;
		ObjectMapper mapper = new ObjectMapper();
		Map<String, String> attributesMap = null;
		try {
			attributesMap = mapper.readValue(key.attributes,
					new TypeReference<Map<String, String>>() {
					});
		} catch (JsonParseException e) {
			logger.error("Invalid attribute string data: " + e.getMessage());

		} catch (JsonMappingException e) {
			logger.error("Invalid attribute string data: " + e.getMessage());
		} catch (IOException e) {
			logger.error("Invalid attribute string data: " + e.getMessage());
		}
		Metadata meta = new Metadata(key.cipher_field, key.bit_length,
				key.description, attributesMap, key.date, key.version);
		return meta;
	}
	
	public void addSecureKeyByteEntry(String alias, Key key, String cipher,
			int bitLength, String description, int version, String attributes)
			throws KeyStoreException {
		SecretKeyByteEntry entry = new SecretKeyByteEntry();
		synchronized (deltaEntries) {
			try {
				entry.date = new Date();
				// encrypt and store the key
				entry.key = kvKeyGen.encryptZoneKey(key);
				entry.cipher_field = cipher;
				entry.bit_length = bitLength;
				entry.description = description;
				entry.version = version;
				entry.attributes = attributes;
				deltaEntries.put(convertAlias(alias), entry);

			} catch (Exception e) {
				logger.error(e.getMessage());
				throw new KeyStoreException(e.getMessage());
			}
		}
		synchronized (keyEntries) {
			try {
				keyEntries.put(convertAlias(alias), entry);
			} catch (Exception e) {
				logger.error(e.getMessage());
				throw new KeyStoreException(e.getMessage());
			}
		}
	}
	
    @Override
    public Date engineGetCreationDate(String alias) {
        Object entry = keyEntries.get(convertAlias(alias));
        Date date = null;
        if (entry != null) {
            KeyEntry keyEntry = (KeyEntry) entry;
            if (keyEntry.date != null) {
                date = new Date(keyEntry.date.getTime());
            }
        }
        return date;
    }

    public void addKeyEntry(String alias, Key key, char[] password, String cipher, int bitLength, String description, int version, String attributes)
            throws KeyStoreException {
        if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStore.addKeyEntry()");
            logger.debug("Adding entry for alias:" + alias);
        }
        SecretKeyEntry entry = new SecretKeyEntry();
        synchronized (deltaEntries) {
            try {
                entry.date = new Date();
                // seal and store the key
                entry.sealedKey = sealKey(key, password);

                entry.cipher_field = cipher;
                entry.bit_length = bitLength;
                entry.description = description;
                entry.version = version;
                entry.attributes = attributes;
                deltaEntries.put(convertAlias(alias), entry);
            } catch (Exception e) {
                logger.error("==> RangerKeyStore.addKeyEntry() error: ", e);
                throw new KeyStoreException(e.getMessage());
            }
        }
        synchronized (keyEntries) {
            try {
                keyEntries.put(convertAlias(alias), entry);
            } catch (Exception e) {
                logger.error("==> RangerKeyStore.addKeyEntry() error: ", e);
                throw new KeyStoreException(e.getMessage());
            }
        }
    }

    private SealedObject sealKey(Key key, char[] password) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStore.sealKey()");
        }
        // Create SecretKey
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES");
        PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
        SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
        pbeKeySpec.clearPassword();

        // Generate random bytes + set up the PBEParameterSpec
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[8];
        random.nextBytes(salt);
        PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20);

        // Seal the Key
        Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeSpec);
        if (logger.isDebugEnabled()) {
            logger.debug("<== RangerKeyStore.sealKey()");
        }
        return new RangerSealedObject(key, cipher);
    }

    private Key unsealKey(SealedObject sealedKey, char[] password) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStore.unsealKey()");
        }
        // Create SecretKey
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES");
        PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
        SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
        pbeKeySpec.clearPassword();

        // Get the AlgorithmParameters from RangerSealedObject
        AlgorithmParameters algorithmParameters = null;
        if (sealedKey instanceof RangerSealedObject) {
            algorithmParameters = ((RangerSealedObject) sealedKey).getParameters();
        } else {
            algorithmParameters = new RangerSealedObject(sealedKey).getParameters();
        }

        // Unseal the Key
        Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, algorithmParameters);
        if (logger.isDebugEnabled()) {
            logger.debug("<== RangerKeyStore.unsealKey()");
        }
        return (Key) sealedKey.getObject(cipher);
    }

    @Override
    public void engineDeleteEntry(String alias)
            throws KeyStoreException {
        synchronized (keyEntries) {
            dbOperationDelete(convertAlias(alias));
            keyEntries.remove(convertAlias(alias));
        }
        synchronized (deltaEntries) {
            deltaEntries.remove(convertAlias(alias));
        }
    }


    private void dbOperationDelete(String alias) {
        if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStore.dbOperationDelete(" + alias + ")");
        }
        try {
            if (daoManager != null) {
                RangerKMSDao rangerKMSDao = new RangerKMSDao(daoManager);
                rangerKMSDao.deleteByAlias(alias);
            }
        } catch (Exception e) {
            logger.error("==> RangerKeyStore.dbOperationDelete() error : ", e);
        }
    }


    @Override
    public Enumeration<String> engineAliases() {
        return Collections.enumeration(keyEntries.keySet());
    }

    @Override
    public boolean engineContainsAlias(String alias) {
        return keyEntries.containsKey(convertAlias(alias));
    }

    @Override
    public int engineSize() {
        return keyEntries.size();
    }

    @Override
    public void engineStore(OutputStream stream, char[] password)
			throws IOException, NoSuchAlgorithmException, CertificateException {
		if (logger.isDebugEnabled()) {
			logger.debug("==> RangerKeyStore.engineStore()");
		}
		synchronized (deltaEntries) {
			if (azureKeyVaultEnabled) {
				for (Entry<String, Object> entry : deltaEntries.entrySet()) {
					Long creationDate = ((SecretKeyByteEntry) entry.getValue()).date
							.getTime();
					SecretKeyByteEntry secretSecureKey = (SecretKeyByteEntry) entry
							.getValue();
					XXRangerKeyStore xxRangerKeyStore = mapObjectToEntity(
							entry.getKey(), creationDate, secretSecureKey.key,
							secretSecureKey.cipher_field,
							secretSecureKey.bit_length,
							secretSecureKey.description,
							secretSecureKey.version, secretSecureKey.attributes);
					dbOperationStore(xxRangerKeyStore);
				}

			} else {
				// password is mandatory when storing
				if (password == null) {
					throw new IllegalArgumentException(
							"Ranger Master Key can't be null");
				}

				MessageDigest md = getKeyedMessageDigest(password);

				byte digest[] = md.digest();
				for (Entry<String, Object> entry : deltaEntries.entrySet()) {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					DataOutputStream dos = new DataOutputStream(
							new DigestOutputStream(baos, md));

					ObjectOutputStream oos = null;
					try {

						oos = new ObjectOutputStream(dos);
						oos.writeObject(((SecretKeyEntry) entry.getValue()).sealedKey);

						dos.write(digest);
						dos.flush();
						Long creationDate = ((SecretKeyEntry) entry.getValue()).date
								.getTime();
						SecretKeyEntry secretKey = (SecretKeyEntry) entry
								.getValue();
						XXRangerKeyStore xxRangerKeyStore = mapObjectToEntity(
								entry.getKey(), creationDate,
								baos.toByteArray(), secretKey.cipher_field,
								secretKey.bit_length, secretKey.description,
								secretKey.version, secretKey.attributes);
						dbOperationStore(xxRangerKeyStore);
					} finally {
						if (oos != null) {
							oos.close();
						} else {
							dos.close();
						}
					}
				}
			}
			clearDeltaEntires();
		}
	}

    private XXRangerKeyStore mapObjectToEntity(String alias, Long creationDate,
                                               byte[] byteArray, String cipher_field, int bit_length,
                                               String description, int version, String attributes) {
        XXRangerKeyStore xxRangerKeyStore = new XXRangerKeyStore();
        xxRangerKeyStore.setAlias(alias);
        xxRangerKeyStore.setCreatedDate(creationDate);
        xxRangerKeyStore.setEncoded(DatatypeConverter.printBase64Binary(byteArray));
        xxRangerKeyStore.setCipher(cipher_field);
        xxRangerKeyStore.setBitLength(bit_length);
        xxRangerKeyStore.setDescription(description);
        xxRangerKeyStore.setVersion(version);
        xxRangerKeyStore.setAttributes(attributes);
        return xxRangerKeyStore;
    }

    public void dbOperationStore(XXRangerKeyStore rangerKeyStore) {
        if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStore.dbOperationStore()");
        }
        try {
            if (daoManager != null) {
                RangerKMSDao rangerKMSDao = new RangerKMSDao(daoManager);
                XXRangerKeyStore xxRangerKeyStore = rangerKMSDao.findByAlias(rangerKeyStore.getAlias());
                boolean keyStoreExists = true;
                if (xxRangerKeyStore == null) {
                    xxRangerKeyStore = new XXRangerKeyStore();
                    keyStoreExists = false;
                }
                xxRangerKeyStore = mapToEntityBean(rangerKeyStore, xxRangerKeyStore);
                if (keyStoreExists) {
                    xxRangerKeyStore = rangerKMSDao.update(xxRangerKeyStore);
                } else {
                    xxRangerKeyStore = rangerKMSDao.create(xxRangerKeyStore);
                }
            }
        } catch (Exception e) {
            logger.error("==> RangerKeyStore.dbOperationStore() error : ", e);
        }
    }

    private XXRangerKeyStore mapToEntityBean(XXRangerKeyStore rangerKMSKeyStore, XXRangerKeyStore xxRangerKeyStore) {
        xxRangerKeyStore.setAlias(rangerKMSKeyStore.getAlias());
        xxRangerKeyStore.setCreatedDate(rangerKMSKeyStore.getCreatedDate());
        xxRangerKeyStore.setEncoded(rangerKMSKeyStore.getEncoded());
        xxRangerKeyStore.setCipher(rangerKMSKeyStore.getCipher());
        xxRangerKeyStore.setBitLength(rangerKMSKeyStore.getBitLength());
        xxRangerKeyStore.setDescription(rangerKMSKeyStore.getDescription());
        xxRangerKeyStore.setVersion(rangerKMSKeyStore.getVersion());
        xxRangerKeyStore.setAttributes(rangerKMSKeyStore.getAttributes());
        return xxRangerKeyStore;
    }


    @Override
    public void engineLoad(InputStream stream, char[] password)
			throws IOException, NoSuchAlgorithmException, CertificateException {
		if (logger.isDebugEnabled()) {
			logger.debug("==> RangerKeyStore.engineLoad()");
		}

		synchronized (keyEntries) {
			List<XXRangerKeyStore> rangerKeyDetails = dbOperationLoad();

			if (rangerKeyDetails == null || rangerKeyDetails.size() < 1) {
				if (logger.isDebugEnabled()) {
					logger.debug("RangerKeyStore might be null or key is not present in the database.");
				}
				return;
			}

			keyEntries.clear();
			if (azureKeyVaultEnabled) {
				for (XXRangerKeyStore rangerKey : rangerKeyDetails) {
					String encodedStr = rangerKey.getEncoded();
					byte[] encodedByte = DatatypeConverter
							.parseBase64Binary(encodedStr);
					String alias;
					SecretKeyByteEntry entry = new SecretKeyByteEntry();
					alias = rangerKey.getAlias();
					entry.date = new Date(rangerKey.getCreatedDate());
					entry.cipher_field = rangerKey.getCipher();
					entry.bit_length = rangerKey.getBitLength();
					entry.description = rangerKey.getDescription();
					entry.version = rangerKey.getVersion();
					entry.attributes = rangerKey.getAttributes();
					entry.key = encodedByte;
					keyEntries.put(alias, entry);
				}
			} else {
				DataInputStream dis;
				MessageDigest md = null;
				if (password != null) {
					md = getKeyedMessageDigest(password);
				}

				byte computed[] = {};
				if (md != null) {
					computed = md.digest();
				}
				for (XXRangerKeyStore rangerKey : rangerKeyDetails) {

					String encoded = rangerKey.getEncoded();
					byte[] data = DatatypeConverter.parseBase64Binary(encoded);

					if (data != null && data.length > 0) {
						stream = new ByteArrayInputStream(data);
					} else {
						logger.error("No Key found for alias "
								+ rangerKey.getAlias());
					}

					if (computed != null) {
						int counter = 0;
						for (int i = computed.length - 1; i >= 0; i--) {
							if (computed[i] != data[data.length - (1 + counter)]) {
								Throwable t = new UnrecoverableKeyException(
										"Password verification failed");
								logger.error(
										"Keystore was tampered with, or password was incorrect.",
										t);
								throw (IOException) new IOException(
										"Keystore was tampered with, or "
												+ "password was incorrect")
										.initCause(t);
							} else {
								counter++;
							}
						}
					}

					if (password != null) {
						dis = new DataInputStream(new DigestInputStream(stream,
								md));
					} else {
						dis = new DataInputStream(stream);
					}

					ObjectInputStream ois = null;
					try {
						String alias;

						SecretKeyEntry entry = new SecretKeyEntry();

						// read the alias
						alias = rangerKey.getAlias();

						// read the (entry creation) date
						entry.date = new Date(rangerKey.getCreatedDate());
						entry.cipher_field = rangerKey.getCipher();
						entry.bit_length = rangerKey.getBitLength();
						entry.description = rangerKey.getDescription();
						entry.version = rangerKey.getVersion();
						entry.attributes = rangerKey.getAttributes();
						// read the sealed key
						try {
							ois = new ObjectInputStream(dis);
							entry.sealedKey = (SealedObject) ois.readObject();
						} catch (ClassNotFoundException cnfe) {
							throw new IOException(cnfe.getMessage());
						}
						// Add the entry to the list
						keyEntries.put(alias, entry);
					} finally {
						if (ois != null) {
							ois.close();
						} else {
							dis.close();
						}
					}
				}
			}
		}
	}

    private List<XXRangerKeyStore> dbOperationLoad() throws IOException {
    	if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStore.dbOperationLoad()");
        }
        try {
            if (daoManager != null) {
                RangerKMSDao rangerKMSDao = new RangerKMSDao(daoManager);
                if (logger.isDebugEnabled()) {
                    logger.debug("<== RangerKeyStore.dbOperationLoad()");
                }
                return rangerKMSDao.getAllKeys();
            }
        } catch (Exception e) {
            logger.error("==> RangerKeyStore.dbOperationLoad() error:", e);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("<== RangerKeyStore.dbOperationLoad()");
        }
        return null;
    }

    /**
     * To guard against tampering with the keystore, we append a keyed
     * hash with a bit of whitener.
     */

    private final String SECRET_KEY_HASH_WORD = "Apache Ranger";

    private MessageDigest getKeyedMessageDigest(char[] aKeyPassword)
            throws NoSuchAlgorithmException, UnsupportedEncodingException {
        int i, j;

        MessageDigest md = MessageDigest.getInstance("SHA");
        byte[] keyPasswordBytes = new byte[aKeyPassword.length * 2];
        for (i = 0, j = 0; i < aKeyPassword.length; i++) {
            keyPasswordBytes[j++] = (byte) (aKeyPassword[i] >> 8);
            keyPasswordBytes[j++] = (byte) aKeyPassword[i];
        }
        md.update(keyPasswordBytes);
        for (i = 0; i < keyPasswordBytes.length; i++)
            keyPasswordBytes[i] = 0;
        md.update(SECRET_KEY_HASH_WORD.getBytes("UTF8"));
        return md;
    }

    @Override
    public void engineSetKeyEntry(String arg0, byte[] arg1, Certificate[] arg2)
            throws KeyStoreException {
    }

    @Override
    public Certificate engineGetCertificate(String alias) {
        return null;
    }

    @Override
    public String engineGetCertificateAlias(Certificate cert) {
        return null;
    }

    @Override
    public Certificate[] engineGetCertificateChain(String alias) {
        return null;
    }

    @Override
    public boolean engineIsCertificateEntry(String alias) {
        return false;
    }

    @Override
    public boolean engineIsKeyEntry(String alias) {
        return false;
    }

    @Override
    public void engineSetCertificateEntry(String alias, Certificate cert)
            throws KeyStoreException {
    }

    @Override
    public void engineSetKeyEntry(String alias, Key key, char[] password,
                                  Certificate[] chain) throws KeyStoreException {
    }

    //
    // The method is created to support JKS migration (from hadoop-common KMS keystore to RangerKMS keystore)
    //

    private static final String METADATA_FIELDNAME = "metadata";
    private static final int NUMBER_OF_BITS_PER_BYTE = 8;

    public void engineLoadKeyStoreFile(InputStream stream, char[] storePass,
                                       char[] keyPass, char[] masterKey, String fileFormat)
			throws IOException, NoSuchAlgorithmException, CertificateException {
		if (logger.isDebugEnabled()) {
			logger.debug("==> RangerKeyStoreProvider.engineLoadKeyStoreFile()");
		}
		synchronized (deltaEntries) {
			KeyStore ks;
			if (azureKeyVaultEnabled) {
				try {
					ks = KeyStore.getInstance(fileFormat);
					ks.load(stream, storePass);
					deltaEntries.clear();
					for (Enumeration<String> name = ks.aliases(); name
							.hasMoreElements();) {
						SecretKeyByteEntry entry = new SecretKeyByteEntry();
						String alias = (String) name.nextElement();
						Key k = ks.getKey(alias, keyPass);
						SecretKey secretKey = null;
						if (k instanceof JavaKeyStoreProvider.KeyMetadata) {
							JavaKeyStoreProvider.KeyMetadata keyMetadata = (JavaKeyStoreProvider.KeyMetadata) k;
							Field f = JavaKeyStoreProvider.KeyMetadata.class
									.getDeclaredField(METADATA_FIELDNAME);
							f.setAccessible(true);
							Metadata metadata = (Metadata) f.get(keyMetadata);
							entry.bit_length = metadata.getBitLength();
							entry.cipher_field = metadata.getAlgorithm();
							entry.version = metadata.getVersions();
							Constructor<RangerKeyStoreProvider.KeyMetadata> constructor = RangerKeyStoreProvider.KeyMetadata.class
									.getDeclaredConstructor(Metadata.class);
							constructor.setAccessible(true);
							RangerKeyStoreProvider.KeyMetadata nk = constructor
									.newInstance(metadata);
							k = nk;
							secretKey = new SecretKeySpec(k.getEncoded(),
									getAlgorithm(metadata.getAlgorithm()));
						} else if (k instanceof KeyByteMetadata) {
							Metadata metadata = ((KeyByteMetadata) k).metadata;
							entry.cipher_field = metadata.getCipher();
							entry.version = metadata.getVersions();
							entry.bit_length = metadata.getBitLength();
							if (k.getEncoded() != null && k.getEncoded().length > 0) {
								secretKey = new SecretKeySpec(k.getEncoded(),
										getAlgorithm(metadata.getAlgorithm()));
							} else {
								KeyGenerator keyGenerator = KeyGenerator
										.getInstance(getAlgorithm(metadata.getCipher()));
								keyGenerator.init(metadata.getBitLength());
								byte[] keyByte = keyGenerator.generateKey().getEncoded();
								secretKey = new SecretKeySpec(keyByte,
										getAlgorithm(metadata.getCipher()));
							}
						} else if (k instanceof KeyMetadata) {
							Metadata metadata = ((KeyMetadata) k).metadata;
							entry.bit_length = metadata.getBitLength();
							entry.cipher_field = metadata.getCipher();
							entry.version = metadata.getVersions();

							if (k.getEncoded() != null
									&& k.getEncoded().length > 0) {
								secretKey = new SecretKeySpec(k.getEncoded(),
										getAlgorithm(metadata.getAlgorithm()));
							} else {
								KeyGenerator keyGenerator = KeyGenerator
										.getInstance(getAlgorithm(metadata
												.getCipher()));
								keyGenerator.init(metadata.getBitLength());
								byte[] keyByte = keyGenerator.generateKey()
										.getEncoded();
								secretKey = new SecretKeySpec(keyByte,
										getAlgorithm(metadata.getCipher()));
							}

						}else {
							entry.bit_length = (k.getEncoded().length * NUMBER_OF_BITS_PER_BYTE);
							entry.cipher_field = k.getAlgorithm();
							if (alias.split("@").length == 2) {
								entry.version = Integer.parseInt(alias
										.split("@")[1]) + 1;
							} else {
								entry.version = 1;
							}
							
							if(k.getEncoded() != null && k.getEncoded().length > 0){
								secretKey = new SecretKeySpec(k.getEncoded(),
										getAlgorithm(k.getAlgorithm()));
							}
						}

						String keyName = alias.split("@")[0];
						validateKeyName(keyName);
						entry.attributes = "{\"key.acl.name\":\"" + keyName
								+ "\"}";
						entry.key = kvKeyGen.encryptZoneKey(secretKey);
						entry.date = ks.getCreationDate(alias);
						entry.description = k.getFormat() + " - "
								+ ks.getType();
						deltaEntries.put(alias, entry);
					}
				} catch (Throwable t) {
					logger.error("Unable to load keystore file ", t);
					throw new IOException(t);
				}
			} else {
				try {
					ks = KeyStore.getInstance(fileFormat);
					ks.load(stream, storePass);
					deltaEntries.clear();
					for (Enumeration<String> name = ks.aliases(); name
							.hasMoreElements();) {
						SecretKeyEntry entry = new SecretKeyEntry();
						String alias = (String) name.nextElement();
						Key k = ks.getKey(alias, keyPass);

						if (k instanceof JavaKeyStoreProvider.KeyMetadata) {
							JavaKeyStoreProvider.KeyMetadata keyMetadata = (JavaKeyStoreProvider.KeyMetadata) k;
							Field f = JavaKeyStoreProvider.KeyMetadata.class
									.getDeclaredField(METADATA_FIELDNAME);
							f.setAccessible(true);
							Metadata metadata = (Metadata) f.get(keyMetadata);
							entry.bit_length = metadata.getBitLength();
							entry.cipher_field = metadata.getAlgorithm();
							entry.version = metadata.getVersions();
							Constructor<RangerKeyStoreProvider.KeyMetadata> constructor = RangerKeyStoreProvider.KeyMetadata.class
									.getDeclaredConstructor(Metadata.class);
							constructor.setAccessible(true);
							RangerKeyStoreProvider.KeyMetadata nk = constructor
									.newInstance(metadata);
							k = nk;
						} else if (k instanceof KeyMetadata) {
							Metadata metadata = ((KeyMetadata) k).metadata;
							entry.bit_length = metadata.getBitLength();
							entry.cipher_field = metadata.getCipher();
							entry.version = metadata.getVersions();
						} else {
							entry.bit_length = (k.getEncoded().length * NUMBER_OF_BITS_PER_BYTE);
							entry.cipher_field = k.getAlgorithm();
							entry.version = (alias.split("@").length == 2) ? (Integer
									.parseInt(alias.split("@")[1]) + 1) : 1;
						}
						String keyName = alias.split("@")[0];
						validateKeyName(keyName);
						entry.attributes = "{\"key.acl.name\":\"" + keyName
								+ "\"}";
						Class<?> c = null;
						Object o = null;
						try {
							c = Class
									.forName("com.sun.crypto.provider.KeyProtector");
							Constructor<?> constructor = c
									.getDeclaredConstructor(char[].class);
							constructor.setAccessible(true);
							o = constructor.newInstance(masterKey);
							// seal and store the key
							Method m = c.getDeclaredMethod("seal", Key.class);
							m.setAccessible(true);
							entry.sealedKey = (SealedObject) m.invoke(o, k);
						} catch (ClassNotFoundException | NoSuchMethodException
								| SecurityException | InstantiationException
								| IllegalAccessException
								| IllegalArgumentException
								| InvocationTargetException e) {
							logger.error(e.getMessage());
							throw new IOException(e.getMessage());
						}

						entry.date = ks.getCreationDate(alias);
						entry.description = k.getFormat() + " - "
								+ ks.getType();
						deltaEntries.put(alias, entry);
					}
				} catch (Throwable t) {
					logger.error("Unable to load keystore file ", t);
					throw new IOException(t);
				}
			}
		}
	}

    public void engineLoadToKeyStoreFile(OutputStream stream, char[] storePass,
                                         char[] keyPass, char[] masterKey, String fileFormat)
            throws IOException, NoSuchAlgorithmException, CertificateException {
        if (logger.isDebugEnabled()) {
            logger.debug("==> RangerKeyStoreProvider.engineLoadToKeyStoreFile()");
        }

        synchronized (keyEntries) {
            KeyStore ks;
            try {
                ks = KeyStore.getInstance(fileFormat);
                if (ks != null) {
                    ks.load(null, storePass);
                    String alias = null;
                    engineLoad(null, masterKey);
                    Enumeration<String> e = engineAliases();
                    Key key;
                    while (e.hasMoreElements()) {
                        alias = e.nextElement();
                        if(azureKeyVaultEnabled){
                        	key = engineGetDecryptedZoneKey(alias);
						} else {
							key = engineGetKey(alias, masterKey);
							if (key instanceof KeyMetadata) {
								Metadata meta = ((KeyMetadata) key).metadata;
								if (meta != null) {
									key = new KeyMetadata(meta);
								}
							}

						}
                        ks.setKeyEntry(alias, key, keyPass, null);
                        
                    }
                    ks.store(stream, storePass);
                }
            } catch (Throwable t) {
                logger.error("Unable to load keystore file ", t);
                throw new IOException(t);
            }
        }
    }

    private void validateKeyName(String name) {
        Matcher matcher = pattern.matcher(name);
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    "Key Name : "
                            + name
                            + ", should start with alpha/numeric letters and can have special characters - (hypen) or _ (underscore)");
        }
    }

    public void clearDeltaEntires() {
        deltaEntries.clear();
    }
    
    private Object getKeyEntry(String alias) {
    	   	return keyEntries.get(alias);
    }

	public XXRangerKeyStore convertKeysBetweenRangerKMSAndAzureKeyVault(
			String alias, Key key,
			RangerKeyVaultKeyGenerator rangerKVKeyGenerator) {
		try {
			XXRangerKeyStore xxRangerKeyStore;
			SecretKeyEntry secretKey = (SecretKeyEntry) getKeyEntry(alias);
			if (key instanceof KeyMetadata) {
				Metadata meta = ((KeyMetadata) key).metadata;
				KeyGenerator keyGenerator = KeyGenerator
						.getInstance(getAlgorithm(meta.getCipher()));
				keyGenerator.init(meta.getBitLength());
				byte[] keyByte = keyGenerator.generateKey().getEncoded();
				Key ezkey = new SecretKeySpec(keyByte,
						getAlgorithm(meta.getCipher()));
				byte[] encryptedKey = rangerKVKeyGenerator
						.encryptZoneKey(ezkey);
				Long creationDate = new Date().getTime();
				String attributes = secretKey.attributes;
				xxRangerKeyStore = mapObjectToEntity(alias, creationDate,
						encryptedKey, meta.getCipher(), meta.getBitLength(),
						meta.getDescription(), meta.getVersions(),
						attributes);
			} else {
				byte[] encryptedKey = rangerKVKeyGenerator.encryptZoneKey(key);
				Long creationDate = secretKey.date.getTime();
				int version = secretKey.version;
				if ((alias.split("@").length == 2)
						&& (((Integer.parseInt(alias.split("@")[1])) + 1) != secretKey.version)) {
					version++;
				}
				xxRangerKeyStore = mapObjectToEntity(alias, creationDate,
						encryptedKey, secretKey.cipher_field,
						secretKey.bit_length, secretKey.description, version,
						secretKey.attributes);
			}
			return xxRangerKeyStore;
		} catch (Throwable t) {
			throw new RuntimeException(
					"Migration failed between key secure and Ranger DB : ", t);
		}
	}

	public String getAlgorithm(String cipher) {
		int slash = cipher.indexOf(47);
		if (slash == -1) {
			return cipher;
		}
		return cipher.substring(0, slash);
	}

    /**
     * Encapsulate the encrypted key, so that we can retrieve the AlgorithmParameters object on the decryption side
     */
    private static class RangerSealedObject extends SealedObject {

        /**
         *
         */
        private static final long serialVersionUID = -7551578543434362070L;

        protected RangerSealedObject(SealedObject so) {
            super(so);
        }

        protected RangerSealedObject(Serializable object, Cipher cipher) throws IllegalBlockSizeException, IOException {
            super(object, cipher);
        }

        public AlgorithmParameters getParameters() throws NoSuchAlgorithmException, IOException {
            AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("PBEWithMD5AndTripleDES");
            algorithmParameters.init(super.encodedParams);
            return algorithmParameters;
        }

    }
    
    public static class KeyByteMetadata implements Key, Serializable {
        private Metadata metadata;
        private byte[] keyByte;
        
        private final static long serialVersionUID = 8405872419967874451L;

        private KeyByteMetadata(Metadata meta, byte[] encoded) {
            this.metadata = meta;
            this.keyByte = encoded;
        }

        @Override
        public String getAlgorithm() {
            return metadata.getCipher();
        }

        @Override
        public String getFormat() {
            return KEY_METADATA;
        }

        @Override
        public byte[] getEncoded() {
            return this.keyByte;
        }

        private void writeObject(ObjectOutputStream out) throws IOException {
        	byte[] serialized = metadata.serialize();
            
            out.writeInt(serialized.length);
            out.write(serialized);
            out.writeInt(keyByte.length);
            out.write(keyByte);
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        	  byte[] metadataBuf = new byte[in.readInt()];
              in.readFully(metadataBuf);
              metadata = new Metadata(metadataBuf);
              byte[] keybyteBuf = new byte[in.readInt()];
              in.readFully(keybyteBuf);
              keyByte = keybyteBuf;
        }

    }
}