| /* |
| * 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; |
| } |
| |
| } |
| } |