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.util; 019 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.IOException; 023import java.util.Map; 024import java.util.concurrent.ConcurrentHashMap; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.hbase.HBaseInterfaceAudience; 027import org.apache.hadoop.hbase.HConstants; 028import org.apache.hadoop.hbase.io.crypto.DefaultCipherProvider; 029import org.apache.hadoop.hbase.io.crypto.Encryption; 030import org.apache.hadoop.hbase.io.crypto.KeyStoreKeyProvider; 031import org.apache.hadoop.hbase.security.EncryptionUtil; 032import org.apache.yetus.audience.InterfaceAudience; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS) 037public class EncryptionTest { 038 private static final Logger LOG = LoggerFactory.getLogger(EncryptionTest.class); 039 040 static final Map<String, Boolean> keyProviderResults = new ConcurrentHashMap<>(); 041 static final Map<String, Boolean> cipherProviderResults = new ConcurrentHashMap<>(); 042 static final Map<String, Boolean> cipherResults = new ConcurrentHashMap<>(); 043 044 private EncryptionTest() { 045 } 046 047 /** 048 * Check that the configured key provider can be loaded and initialized, or throw an exception. nn 049 */ 050 public static void testKeyProvider(final Configuration conf) throws IOException { 051 String providerClassName = 052 conf.get(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, KeyStoreKeyProvider.class.getName()); 053 Boolean result = keyProviderResults.get(providerClassName); 054 if (result == null) { 055 try { 056 Encryption.getKeyProvider(conf); 057 keyProviderResults.put(providerClassName, true); 058 } catch (Exception e) { // most likely a RuntimeException 059 keyProviderResults.put(providerClassName, false); 060 throw new IOException( 061 "Key provider " + providerClassName + " failed test: " + e.getMessage(), e); 062 } 063 } else if (!result) { 064 throw new IOException("Key provider " + providerClassName + " previously failed test"); 065 } 066 } 067 068 /** 069 * Check that the configured cipher provider can be loaded and initialized, or throw an exception. 070 * nn 071 */ 072 public static void testCipherProvider(final Configuration conf) throws IOException { 073 String providerClassName = 074 conf.get(HConstants.CRYPTO_CIPHERPROVIDER_CONF_KEY, DefaultCipherProvider.class.getName()); 075 Boolean result = cipherProviderResults.get(providerClassName); 076 if (result == null) { 077 try { 078 Encryption.getCipherProvider(conf); 079 cipherProviderResults.put(providerClassName, true); 080 } catch (Exception e) { // most likely a RuntimeException 081 cipherProviderResults.put(providerClassName, false); 082 throw new IOException( 083 "Cipher provider " + providerClassName + " failed test: " + e.getMessage(), e); 084 } 085 } else if (!result) { 086 throw new IOException("Cipher provider " + providerClassName + " previously failed test"); 087 } 088 } 089 090 /** 091 * Check that the specified cipher can be loaded and initialized, or throw an exception. Verifies 092 * key and cipher provider configuration as a prerequisite for cipher verification. Also verifies 093 * if encryption is enabled globally. 094 * @param conf HBase configuration 095 * @param cipher chiper algorith to use for the column family 096 * @param key encryption key 097 * @throws IOException in case of encryption configuration error 098 */ 099 public static void testEncryption(final Configuration conf, final String cipher, byte[] key) 100 throws IOException { 101 if (cipher == null) { 102 return; 103 } 104 if (!Encryption.isEncryptionEnabled(conf)) { 105 String message = 106 String.format("Cipher %s failed test: encryption is disabled on the cluster", cipher); 107 throw new IOException(message); 108 } 109 testKeyProvider(conf); 110 testCipherProvider(conf); 111 Boolean result = cipherResults.get(cipher); 112 if (result == null) { 113 try { 114 Encryption.Context context = Encryption.newContext(conf); 115 context.setCipher(Encryption.getCipher(conf, cipher)); 116 if (key == null) { 117 // Make a random key since one was not provided 118 context.setKey(context.getCipher().getRandomKey()); 119 } else { 120 // This will be a wrapped key from schema 121 context.setKey(EncryptionUtil.unwrapKey(conf, 122 conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase"), key)); 123 } 124 byte[] iv = null; 125 if (context.getCipher().getIvLength() > 0) { 126 iv = new byte[context.getCipher().getIvLength()]; 127 Bytes.secureRandom(iv); 128 } 129 byte[] plaintext = new byte[1024]; 130 Bytes.random(plaintext); 131 ByteArrayOutputStream out = new ByteArrayOutputStream(); 132 Encryption.encrypt(out, new ByteArrayInputStream(plaintext), context, iv); 133 byte[] ciphertext = out.toByteArray(); 134 out.reset(); 135 Encryption.decrypt(out, new ByteArrayInputStream(ciphertext), plaintext.length, context, 136 iv); 137 byte[] test = out.toByteArray(); 138 if (!Bytes.equals(plaintext, test)) { 139 throw new IOException("Did not pass encrypt/decrypt test"); 140 } 141 cipherResults.put(cipher, true); 142 } catch (Exception e) { 143 cipherResults.put(cipher, false); 144 throw new IOException("Cipher " + cipher + " failed test: " + e.getMessage(), e); 145 } 146 } else if (!result) { 147 throw new IOException("Cipher " + cipher + " previously failed test"); 148 } 149 } 150}