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