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.Assert.assertNotNull;
021import static org.junit.Assert.assertTrue;
022import static org.junit.Assert.fail;
023
024import java.security.Key;
025import java.security.KeyException;
026import javax.crypto.spec.SecretKeySpec;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
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.ClassRule;
037import org.junit.Test;
038import org.junit.experimental.categories.Category;
039
040@Category({ ClientTests.class, SmallTests.class })
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  @ClassRule
047  public static final HBaseClassTestRule CLASS_RULE =
048    HBaseClassTestRule.forClass(TestEncryptionUtil.class);
049
050  // There does not seem to be a ready way to test either getKeyFromBytesOrMasterKey
051  // or createEncryptionContext, and the existing code under MobUtils appeared to be
052  // untested. Not ideal!
053
054  @Test
055  public void testKeyWrappingUsingHashAlgDefault() throws Exception {
056    testKeyWrapping(DEFAULT_HASH_ALGORITHM);
057  }
058
059  @Test
060  public void testKeyWrappingUsingHashAlgMD5() throws Exception {
061    testKeyWrapping("MD5");
062  }
063
064  @Test
065  public void testKeyWrappingUsingHashAlgSHA256() throws Exception {
066    testKeyWrapping("SHA-256");
067  }
068
069  @Test
070  public void testKeyWrappingUsingHashAlgSHA384() throws Exception {
071    testKeyWrapping("SHA-384");
072  }
073
074  @Test(expected = RuntimeException.class)
075  public void testKeyWrappingWithInvalidHashAlg() throws Exception {
076    testKeyWrapping(INVALID_HASH_ALG);
077  }
078
079  @Test
080  public void testWALKeyWrappingUsingHashAlgDefault() throws Exception {
081    testWALKeyWrapping(DEFAULT_HASH_ALGORITHM);
082  }
083
084  @Test
085  public void testWALKeyWrappingUsingHashAlgMD5() throws Exception {
086    testWALKeyWrapping("MD5");
087  }
088
089  @Test
090  public void testWALKeyWrappingUsingHashAlgSHA256() throws Exception {
091    testWALKeyWrapping("SHA-256");
092  }
093
094  @Test
095  public void testWALKeyWrappingUsingHashAlgSHA384() throws Exception {
096    testWALKeyWrapping("SHA-384");
097  }
098
099  @Test(expected = RuntimeException.class)
100  public void testWALKeyWrappingWithInvalidHashAlg() throws Exception {
101    testWALKeyWrapping(INVALID_HASH_ALG);
102  }
103
104  @Test(expected = KeyException.class)
105  public void testWALKeyWrappingWithIncorrectKey() throws Exception {
106    // set up the key provider for testing to resolve a key for our test subject
107    Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this
108    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName());
109
110    // generate a test key
111    byte[] keyBytes = new byte[AES.KEY_LENGTH];
112    Bytes.secureRandom(keyBytes);
113    String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
114    Key key = new SecretKeySpec(keyBytes, algorithm);
115
116    // wrap the test key
117    byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key);
118    assertNotNull(wrappedKeyBytes);
119
120    // unwrap with an incorrect key
121    EncryptionUtil.unwrapWALKey(conf, "other", wrappedKeyBytes);
122  }
123
124  @Test(expected = KeyException.class)
125  public void testHashAlgorithmMismatchWhenFailExpected() throws Exception {
126    Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this
127    conf.setBoolean(Encryption.CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY, true);
128    testKeyWrappingWithMismatchingAlgorithms(conf);
129  }
130
131  @Test
132  public void testHashAlgorithmMismatchWhenFailNotExpected() throws Exception {
133    Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this
134    conf.setBoolean(Encryption.CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY, false);
135    testKeyWrappingWithMismatchingAlgorithms(conf);
136  }
137
138  @Test
139  public void testHashAlgorithmMismatchShouldNotFailWithDefaultConfig() throws Exception {
140    Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this
141    testKeyWrappingWithMismatchingAlgorithms(conf);
142  }
143
144  private void testKeyWrapping(String hashAlgorithm) throws Exception {
145    // set up the key provider for testing to resolve a key for our test subject
146    Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this
147    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName());
148    if (!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) {
149      conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, hashAlgorithm);
150    }
151
152    // generate a test key
153    byte[] keyBytes = new byte[AES.KEY_LENGTH];
154    Bytes.secureRandom(keyBytes);
155    String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
156    Key key = new SecretKeySpec(keyBytes, algorithm);
157
158    // wrap the test key
159    byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key);
160    assertNotNull(wrappedKeyBytes);
161
162    // unwrap
163    Key unwrappedKey = EncryptionUtil.unwrapKey(conf, "hbase", wrappedKeyBytes);
164    assertNotNull(unwrappedKey);
165    // only secretkeyspec supported for now
166    assertTrue(unwrappedKey instanceof SecretKeySpec);
167    // did we get back what we wrapped?
168    assertTrue("Unwrapped key bytes do not match original",
169      Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
170
171    // unwrap with an incorrect key
172    try {
173      EncryptionUtil.unwrapKey(conf, "other", wrappedKeyBytes);
174      fail("Unwrap with incorrect key did not throw KeyException");
175    } catch (KeyException e) {
176      // expected
177    }
178  }
179
180  private void testWALKeyWrapping(String hashAlgorithm) throws Exception {
181    // set up the key provider for testing to resolve a key for our test subject
182    Configuration conf = new Configuration(); // we don't need HBaseConfiguration for this
183    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName());
184    if (!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) {
185      conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, hashAlgorithm);
186    }
187
188    // generate a test key
189    byte[] keyBytes = new byte[AES.KEY_LENGTH];
190    Bytes.secureRandom(keyBytes);
191    String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
192    Key key = new SecretKeySpec(keyBytes, algorithm);
193
194    // wrap the test key
195    byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key);
196    assertNotNull(wrappedKeyBytes);
197
198    // unwrap
199    Key unwrappedKey = EncryptionUtil.unwrapWALKey(conf, "hbase", wrappedKeyBytes);
200    assertNotNull(unwrappedKey);
201    // only secretkeyspec supported for now
202    assertTrue(unwrappedKey instanceof SecretKeySpec);
203    // did we get back what we wrapped?
204    assertTrue("Unwrapped key bytes do not match original",
205      Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
206  }
207
208  private void testKeyWrappingWithMismatchingAlgorithms(Configuration conf) throws Exception {
209    // we use MD5 to hash the encryption key during wrapping
210    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName());
211    conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, "MD5");
212
213    // generate a test key
214    byte[] keyBytes = new byte[AES.KEY_LENGTH];
215    Bytes.secureRandom(keyBytes);
216    String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
217    Key key = new SecretKeySpec(keyBytes, algorithm);
218
219    // wrap the test key
220    byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key);
221    assertNotNull(wrappedKeyBytes);
222
223    // we set the default hash algorithm to SHA-384 during unwrapping
224    conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, "SHA-384");
225
226    // unwrap
227    // we expect to fail, if CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY == true
228    // otherwise we will use the algorithm written during wrapping
229    Key unwrappedKey = EncryptionUtil.unwrapKey(conf, "hbase", wrappedKeyBytes);
230    assertNotNull(unwrappedKey);
231
232    // did we get back what we wrapped?
233    assertTrue("Unwrapped key bytes do not match original",
234      Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
235  }
236
237}