001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security; 019 020import static org.junit.jupiter.api.Assertions.assertNotNull; 021import static org.junit.jupiter.api.Assertions.assertThrows; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023import static org.junit.jupiter.api.Assertions.fail; 024 025import java.security.Key; 026import java.security.KeyException; 027import javax.crypto.spec.SecretKeySpec; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.hbase.HConstants; 030import org.apache.hadoop.hbase.io.crypto.Encryption; 031import org.apache.hadoop.hbase.io.crypto.MockAesKeyProvider; 032import org.apache.hadoop.hbase.io.crypto.aes.AES; 033import org.apache.hadoop.hbase.testclassification.ClientTests; 034import org.apache.hadoop.hbase.testclassification.SmallTests; 035import org.apache.hadoop.hbase.util.Bytes; 036import org.junit.jupiter.api.Tag; 037import org.junit.jupiter.api.Test; 038 039@Tag(ClientTests.TAG) 040@Tag(SmallTests.TAG) 041public class TestEncryptionUtil { 042 043 private static final String INVALID_HASH_ALG = "this-hash-algorithm-not-exists hopefully... :)"; 044 private static final String DEFAULT_HASH_ALGORITHM = "use-default"; 045 046 // There does not seem to be a ready way to test either getKeyFromBytesOrMasterKey 047 // or createEncryptionContext, and the existing code under MobUtils appeared to be 048 // untested. Not ideal! 049 050 @Test 051 public void testKeyWrappingUsingHashAlgDefault() throws Exception { 052 testKeyWrapping(DEFAULT_HASH_ALGORITHM); 053 } 054 055 @Test 056 public void testKeyWrappingUsingHashAlgMD5() throws Exception { 057 testKeyWrapping("MD5"); 058 } 059 060 @Test 061 public void testKeyWrappingUsingHashAlgSHA256() throws Exception { 062 testKeyWrapping("SHA-256"); 063 } 064 065 @Test 066 public void testKeyWrappingUsingHashAlgSHA384() throws Exception { 067 testKeyWrapping("SHA-384"); 068 } 069 070 @Test 071 public void testKeyWrappingWithInvalidHashAlg() throws Exception { 072 assertThrows(RuntimeException.class, () -> testKeyWrapping(INVALID_HASH_ALG)); 073 } 074 075 @Test 076 public void testWALKeyWrappingUsingHashAlgDefault() throws Exception { 077 testWALKeyWrapping(DEFAULT_HASH_ALGORITHM); 078 } 079 080 @Test 081 public void testWALKeyWrappingUsingHashAlgMD5() throws Exception { 082 testWALKeyWrapping("MD5"); 083 } 084 085 @Test 086 public void testWALKeyWrappingUsingHashAlgSHA256() throws Exception { 087 testWALKeyWrapping("SHA-256"); 088 } 089 090 @Test 091 public void testWALKeyWrappingUsingHashAlgSHA384() throws Exception { 092 testWALKeyWrapping("SHA-384"); 093 } 094 095 @Test 096 public void testWALKeyWrappingWithInvalidHashAlg() throws Exception { 097 assertThrows(RuntimeException.class, () -> testWALKeyWrapping(INVALID_HASH_ALG)); 098 } 099 100 @Test 101 public void testWALKeyWrappingWithIncorrectKey() throws Exception { 102 // set up the key provider for testing to resolve a key for our test subject 103 Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this 104 conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName()); 105 106 // generate a test key 107 byte[] keyBytes = new byte[AES.KEY_LENGTH]; 108 Bytes.secureRandom(keyBytes); 109 String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 110 Key key = new SecretKeySpec(keyBytes, algorithm); 111 112 // wrap the test key 113 byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key); 114 assertNotNull(wrappedKeyBytes); 115 116 // unwrap with an incorrect key 117 assertThrows(KeyException.class, 118 () -> EncryptionUtil.unwrapWALKey(conf, "other", wrappedKeyBytes)); 119 } 120 121 @Test 122 public void testHashAlgorithmMismatchWhenFailExpected() throws Exception { 123 Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this 124 conf.setBoolean(Encryption.CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY, true); 125 assertThrows(KeyException.class, () -> testKeyWrappingWithMismatchingAlgorithms(conf)); 126 } 127 128 @Test 129 public void testHashAlgorithmMismatchWhenFailNotExpected() throws Exception { 130 Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this 131 conf.setBoolean(Encryption.CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY, false); 132 testKeyWrappingWithMismatchingAlgorithms(conf); 133 } 134 135 @Test 136 public void testHashAlgorithmMismatchShouldNotFailWithDefaultConfig() throws Exception { 137 Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this 138 testKeyWrappingWithMismatchingAlgorithms(conf); 139 } 140 141 private void testKeyWrapping(String hashAlgorithm) throws Exception { 142 // set up the key provider for testing to resolve a key for our test subject 143 Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this 144 conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName()); 145 if (!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) { 146 conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, hashAlgorithm); 147 } 148 149 // generate a test key 150 byte[] keyBytes = new byte[AES.KEY_LENGTH]; 151 Bytes.secureRandom(keyBytes); 152 String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 153 Key key = new SecretKeySpec(keyBytes, algorithm); 154 155 // wrap the test key 156 byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key); 157 assertNotNull(wrappedKeyBytes); 158 159 // unwrap 160 Key unwrappedKey = EncryptionUtil.unwrapKey(conf, "hbase", wrappedKeyBytes); 161 assertNotNull(unwrappedKey); 162 // only secretkeyspec supported for now 163 assertTrue(unwrappedKey instanceof SecretKeySpec); 164 // did we get back what we wrapped? 165 assertTrue(Bytes.equals(keyBytes, unwrappedKey.getEncoded()), 166 "Unwrapped key bytes do not match original"); 167 168 // unwrap with an incorrect key 169 try { 170 EncryptionUtil.unwrapKey(conf, "other", wrappedKeyBytes); 171 fail("Unwrap with incorrect key did not throw KeyException"); 172 } catch (KeyException e) { 173 // expected 174 } 175 } 176 177 private void testWALKeyWrapping(String hashAlgorithm) throws Exception { 178 // set up the key provider for testing to resolve a key for our test subject 179 Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this 180 conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName()); 181 if (!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) { 182 conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, hashAlgorithm); 183 } 184 185 // generate a test key 186 byte[] keyBytes = new byte[AES.KEY_LENGTH]; 187 Bytes.secureRandom(keyBytes); 188 String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 189 Key key = new SecretKeySpec(keyBytes, algorithm); 190 191 // wrap the test key 192 byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key); 193 assertNotNull(wrappedKeyBytes); 194 195 // unwrap 196 Key unwrappedKey = EncryptionUtil.unwrapWALKey(conf, "hbase", wrappedKeyBytes); 197 assertNotNull(unwrappedKey); 198 // only secretkeyspec supported for now 199 assertTrue(unwrappedKey instanceof SecretKeySpec); 200 // did we get back what we wrapped? 201 assertTrue(Bytes.equals(keyBytes, unwrappedKey.getEncoded()), 202 "Unwrapped key bytes do not match original"); 203 } 204 205 private void testKeyWrappingWithMismatchingAlgorithms(Configuration conf) throws Exception { 206 // we use MD5 to hash the encryption key during wrapping 207 conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName()); 208 conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, "MD5"); 209 210 // generate a test key 211 byte[] keyBytes = new byte[AES.KEY_LENGTH]; 212 Bytes.secureRandom(keyBytes); 213 String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 214 Key key = new SecretKeySpec(keyBytes, algorithm); 215 216 // wrap the test key 217 byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key); 218 assertNotNull(wrappedKeyBytes); 219 220 // we set the default hash algorithm to SHA-384 during unwrapping 221 conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, "SHA-384"); 222 223 // unwrap 224 // we expect to fail, if CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY == true 225 // otherwise we will use the algorithm written during wrapping 226 Key unwrappedKey = EncryptionUtil.unwrapKey(conf, "hbase", wrappedKeyBytes); 227 assertNotNull(unwrappedKey); 228 229 // did we get back what we wrapped? 230 assertTrue(Bytes.equals(keyBytes, unwrappedKey.getEncoded()), 231 "Unwrapped key bytes do not match original"); 232 } 233 234}