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}