001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.util;
020
021import java.io.ByteArrayInputStream;
022import java.io.ByteArrayOutputStream;
023import java.io.IOException;
024import java.util.Map;
025import java.util.concurrent.ConcurrentHashMap;
026
027import org.apache.hadoop.hbase.HBaseInterfaceAudience;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.io.crypto.DefaultCipherProvider;
034import org.apache.hadoop.hbase.io.crypto.Encryption;
035import org.apache.hadoop.hbase.io.crypto.KeyStoreKeyProvider;
036import org.apache.hadoop.hbase.security.EncryptionUtil;
037
038@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
039public class EncryptionTest {
040  private static final Logger LOG = LoggerFactory.getLogger(EncryptionTest.class);
041
042  static final Map<String, Boolean> keyProviderResults = new ConcurrentHashMap<>();
043  static final Map<String, Boolean> cipherProviderResults = new ConcurrentHashMap<>();
044  static final Map<String, Boolean> cipherResults = new ConcurrentHashMap<>();
045
046  private EncryptionTest() {
047  }
048
049  /**
050   * Check that the configured key provider can be loaded and initialized, or
051   * throw an exception.
052   *
053   * @param conf
054   * @throws IOException
055   */
056  public static void testKeyProvider(final Configuration conf) throws IOException {
057    String providerClassName = conf.get(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY,
058      KeyStoreKeyProvider.class.getName());
059    Boolean result = keyProviderResults.get(providerClassName);
060    if (result == null) {
061      try {
062        Encryption.getKeyProvider(conf);
063        keyProviderResults.put(providerClassName, true);
064      } catch (Exception e) { // most likely a RuntimeException
065        keyProviderResults.put(providerClassName, false);
066        throw new IOException("Key provider " + providerClassName + " failed test: " +
067          e.getMessage(), e);
068      }
069    } else if (result.booleanValue() == false) {
070      throw new IOException("Key provider " + providerClassName + " previously failed test");
071    }
072  }
073
074  /**
075   * Check that the configured cipher provider can be loaded and initialized, or
076   * throw an exception.
077   *
078   * @param conf
079   * @throws IOException
080   */
081  public static void testCipherProvider(final Configuration conf) throws IOException {
082    String providerClassName = conf.get(HConstants.CRYPTO_CIPHERPROVIDER_CONF_KEY,
083      DefaultCipherProvider.class.getName());
084    Boolean result = cipherProviderResults.get(providerClassName);
085    if (result == null) {
086      try {
087        Encryption.getCipherProvider(conf);
088        cipherProviderResults.put(providerClassName, true);
089      } catch (Exception e) { // most likely a RuntimeException
090        cipherProviderResults.put(providerClassName, false);
091        throw new IOException("Cipher provider " + providerClassName + " failed test: " +
092          e.getMessage(), e);
093      }
094    } else if (result.booleanValue() == false) {
095      throw new IOException("Cipher provider " + providerClassName + " previously failed test");
096    }
097  }
098
099  /**
100   * Check that the specified cipher can be loaded and initialized, or throw
101   * an exception. Verifies key and cipher provider configuration as a
102   * prerequisite for cipher verification.
103   *
104   * @param conf
105   * @param cipher
106   * @param key
107   * @throws IOException
108   */
109  public static void testEncryption(final Configuration conf, final String cipher,
110      byte[] key) throws IOException {
111    if (cipher == null) {
112      return;
113    }
114    testKeyProvider(conf);
115    testCipherProvider(conf);
116    Boolean result = cipherResults.get(cipher);
117    if (result == null) {
118      try {
119        Encryption.Context context = Encryption.newContext(conf);
120        context.setCipher(Encryption.getCipher(conf, cipher));
121        if (key == null) {
122          // Make a random key since one was not provided
123          context.setKey(context.getCipher().getRandomKey());
124        } else {
125          // This will be a wrapped key from schema
126          context.setKey(EncryptionUtil.unwrapKey(conf,
127            conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase"),
128            key));
129        }
130        byte[] iv = null;
131        if (context.getCipher().getIvLength() > 0) {
132          iv = new byte[context.getCipher().getIvLength()];
133          Bytes.random(iv);
134        }
135        byte[] plaintext = new byte[1024];
136        Bytes.random(plaintext);
137        ByteArrayOutputStream out = new ByteArrayOutputStream();
138        Encryption.encrypt(out, new ByteArrayInputStream(plaintext), context, iv);
139        byte[] ciphertext = out.toByteArray();
140        out.reset();
141        Encryption.decrypt(out, new ByteArrayInputStream(ciphertext), plaintext.length,
142          context, iv);
143        byte[] test = out.toByteArray();
144        if (!Bytes.equals(plaintext, test)) {
145          throw new IOException("Did not pass encrypt/decrypt test");
146        }
147        cipherResults.put(cipher, true);
148      } catch (Exception e) {
149        cipherResults.put(cipher, false);
150        throw new IOException("Cipher " + cipher + " failed test: " + e.getMessage(), e);
151      }
152    } else if (result.booleanValue() == false) {
153      throw new IOException("Cipher " + cipher + " previously failed test");
154    }
155  }
156}