From 153e1f877c1bb47a2cc11b38f9e2ff6738d8c830 Mon Sep 17 00:00:00 2001 From: Nate Edel Date: Thu, 10 Dec 2015 15:43:15 -0800 Subject: [PATCH] HBASE-14964. Remove duplicate code to create/manage encryption keys Backport HBASE-14901 to branch-1 --- .../hadoop/hbase/security/EncryptionUtil.java | 100 +++++++++++++++++++- .../hadoop/hbase/security/TestEncryptionUtil.java | 6 ++ .../hadoop/hbase/io/hfile/HFileReaderV3.java | 25 +---- .../apache/hadoop/hbase/regionserver/HStore.java | 56 +---------- 4 files changed, 107 insertions(+), 80 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java index 2cd2a56..e087218 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java @@ -27,6 +27,11 @@ import java.security.SecureRandom; import javax.crypto.spec.SecretKeySpec; import org.apache.hadoop.hbase.util.ByteStringer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; @@ -41,11 +46,18 @@ import org.apache.hadoop.hbase.util.Bytes; */ @InterfaceAudience.Private @InterfaceStability.Evolving -public class EncryptionUtil { +public final class EncryptionUtil { + static private final Log LOG = LogFactory.getLog(EncryptionUtil.class); static private final SecureRandom RNG = new SecureRandom(); /** + * Private constructor to keep this class from being instantiated. + */ + private EncryptionUtil() { + } + + /** * Protect a key by encrypting it with the secret key of the given subject. * The configuration must be set up correctly for key alias resolution. * @param conf configuration @@ -159,4 +171,90 @@ public class EncryptionUtil { return getUnwrapKey(conf, subject, wrappedKey, cipher); } + /** + * Helper to create an encyption context. + * + * @param conf The current configuration. + * @param family The current column descriptor. + * @return The created encryption context. + * @throws IOException if an encryption key for the column cannot be unwrapped + */ + public static Encryption.Context createEncryptionContext(Configuration conf, + HColumnDescriptor family) throws IOException { + Encryption.Context cryptoContext = Encryption.Context.NONE; + String cipherName = family.getEncryptionType(); + if (cipherName != null) { + Cipher cipher; + Key key; + byte[] keyBytes = family.getEncryptionKey(); + if (keyBytes != null) { + // Family provides specific key material + key = unwrapKey(conf, keyBytes); + // Use the algorithm the key wants + cipher = Encryption.getCipher(conf, key.getAlgorithm()); + if (cipher == null) { + throw new RuntimeException("Cipher '" + key.getAlgorithm() + "' is not available"); + } + // Fail if misconfigured + // We use the encryption type specified in the column schema as a sanity check on + // what the wrapped key is telling us + if (!cipher.getName().equalsIgnoreCase(cipherName)) { + throw new RuntimeException("Encryption for family '" + family.getNameAsString() + + "' configured with type '" + cipherName + "' but key specifies algorithm '" + + cipher.getName() + "'"); + } + } else { + // Family does not provide key material, create a random key + cipher = Encryption.getCipher(conf, cipherName); + if (cipher == null) { + throw new RuntimeException("Cipher '" + cipherName + "' is not available"); + } + key = cipher.getRandomKey(); + } + cryptoContext = Encryption.newContext(conf); + cryptoContext.setCipher(cipher); + cryptoContext.setKey(key); + } + return cryptoContext; + } + + /** + * Helper for {@link #unwrapKey(Configuration, String, byte[])} which automatically uses the + * configured master and alternative keys, rather than having to specify a key type to unwrap + * with. + * + * The configuration must be set up correctly for key alias resolution. + * + * @param conf the current configuration + * @param keyBytes the key encrypted by master (or alternative) to unwrap + * @return the key bytes, decrypted + * @throws IOException if the key cannot be unwrapped + */ + public static Key unwrapKey(Configuration conf, byte[] keyBytes) throws IOException { + Key key; + String masterKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, + User.getCurrent().getShortName()); + try { + // First try the master key + key = unwrapKey(conf, masterKeyName, keyBytes); + } catch (KeyException e) { + // If the current master key fails to unwrap, try the alternate, if + // one is configured + if (LOG.isDebugEnabled()) { + LOG.debug("Unable to unwrap key with current master key '" + masterKeyName + "'"); + } + String alternateKeyName = + conf.get(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY); + if (alternateKeyName != null) { + try { + key = unwrapKey(conf, alternateKeyName, keyBytes); + } catch (KeyException ex) { + throw new IOException(ex); + } + } else { + throw new IOException(e); + } + } + return key; + } } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java index 5d23b1d..9e75b50 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java @@ -19,6 +19,7 @@ package org.apache.hadoop.hbase.security; import static org.junit.Assert.*; +import java.io.IOException; import java.security.Key; import java.security.KeyException; import java.security.SecureRandom; @@ -26,8 +27,10 @@ import java.security.SecureRandom; import javax.crypto.spec.SecretKeySpec; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.apache.hadoop.hbase.io.crypto.Encryption; import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting; import org.apache.hadoop.hbase.io.crypto.aes.AES; import org.apache.hadoop.hbase.util.Bytes; @@ -37,6 +40,9 @@ import org.junit.experimental.categories.Category; @Category(SmallTests.class) public class TestEncryptionUtil { + // There does not seem to be a ready way to test either getKeyFromBytesOrMasterKey + // or createEncryptionContext, and the existing code under MobUtils appeared to be + // untested. Not ideal! @Test public void testKeyWrapping() throws Exception { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileReaderV3.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileReaderV3.java index b5cadb1..ff04d9b 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileReaderV3.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileReaderV3.java @@ -99,30 +99,7 @@ public class HFileReaderV3 extends HFileReaderV2 { byte[] keyBytes = trailer.getEncryptionKey(); if (keyBytes != null) { Encryption.Context cryptoContext = Encryption.newContext(conf); - Key key; - String masterKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, - User.getCurrent().getShortName()); - try { - // First try the master key - key = EncryptionUtil.unwrapKey(conf, masterKeyName, keyBytes); - } catch (KeyException e) { - // If the current master key fails to unwrap, try the alternate, if - // one is configured - if (LOG.isDebugEnabled()) { - LOG.debug("Unable to unwrap key with current master key '" + masterKeyName + "'"); - } - String alternateKeyName = - conf.get(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY); - if (alternateKeyName != null) { - try { - key = EncryptionUtil.unwrapKey(conf, alternateKeyName, keyBytes); - } catch (KeyException ex) { - throw new IOException(ex); - } - } else { - throw new IOException(e); - } - } + Key key = EncryptionUtil.unwrapKey(conf,keyBytes); // Use the algorithm the key wants Cipher cipher = Encryption.getCipher(conf, key.getAlgorithm()); if (cipher == null) { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java index a19407a..76ad5f3 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java @@ -275,61 +275,7 @@ public class HStore implements Store { } // Crypto context for new store files - String cipherName = family.getEncryptionType(); - if (cipherName != null) { - Cipher cipher; - Key key; - byte[] keyBytes = family.getEncryptionKey(); - if (keyBytes != null) { - // Family provides specific key material - String masterKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, - User.getCurrent().getShortName()); - try { - // First try the master key - key = EncryptionUtil.unwrapKey(conf, masterKeyName, keyBytes); - } catch (KeyException e) { - // If the current master key fails to unwrap, try the alternate, if - // one is configured - if (LOG.isDebugEnabled()) { - LOG.debug("Unable to unwrap key with current master key '" + masterKeyName + "'"); - } - String alternateKeyName = - conf.get(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY); - if (alternateKeyName != null) { - try { - key = EncryptionUtil.unwrapKey(conf, alternateKeyName, keyBytes); - } catch (KeyException ex) { - throw new IOException(ex); - } - } else { - throw new IOException(e); - } - } - // Use the algorithm the key wants - cipher = Encryption.getCipher(conf, key.getAlgorithm()); - if (cipher == null) { - throw new RuntimeException("Cipher '" + key.getAlgorithm() + "' is not available"); - } - // Fail if misconfigured - // We use the encryption type specified in the column schema as a sanity check on - // what the wrapped key is telling us - if (!cipher.getName().equalsIgnoreCase(cipherName)) { - throw new RuntimeException("Encryption for family '" + family.getNameAsString() + - "' configured with type '" + cipherName + - "' but key specifies algorithm '" + cipher.getName() + "'"); - } - } else { - // Family does not provide key material, create a random key - cipher = Encryption.getCipher(conf, cipherName); - if (cipher == null) { - throw new RuntimeException("Cipher '" + cipherName + "' is not available"); - } - key = cipher.getRandomKey(); - } - cryptoContext = Encryption.newContext(conf); - cryptoContext.setCipher(cipher); - cryptoContext.setKey(key); - } + cryptoContext = EncryptionUtil.createEncryptionContext(conf, family); } /** -- 1.7.10.2 (Apple Git-33)