View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.util;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.util.Map;
25  import java.util.concurrent.ConcurrentHashMap;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.classification.InterfaceStability;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.io.crypto.DefaultCipherProvider;
34  import org.apache.hadoop.hbase.io.crypto.Encryption;
35  import org.apache.hadoop.hbase.io.crypto.KeyStoreKeyProvider;
36  import org.apache.hadoop.hbase.security.EncryptionUtil;
37  
38  @InterfaceAudience.Public
39  @InterfaceStability.Evolving
40  public class EncryptionTest {
41    private static final Log LOG = LogFactory.getLog(EncryptionTest.class);
42  
43    static final Map<String, Boolean> keyProviderResults = new ConcurrentHashMap<String, Boolean>();
44    static final Map<String, Boolean> cipherProviderResults =
45      new ConcurrentHashMap<String, Boolean>();
46    static final Map<String, Boolean> cipherResults = new ConcurrentHashMap<String, Boolean>();
47  
48    private EncryptionTest() {
49    }
50  
51    /**
52     * Check that the configured key provider can be loaded and initialized, or
53     * throw an exception.
54     *
55     * @param conf
56     * @throws IOException
57     */
58    public static void testKeyProvider(final Configuration conf) throws IOException {
59      String providerClassName = conf.get(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY,
60        KeyStoreKeyProvider.class.getName());
61      Boolean result = keyProviderResults.get(providerClassName);
62      if (result == null) {
63        try {
64          Encryption.getKeyProvider(conf);
65          keyProviderResults.put(providerClassName, true);
66        } catch (Exception e) { // most likely a RuntimeException
67          keyProviderResults.put(providerClassName, false);
68          throw new IOException("Key provider " + providerClassName + " failed test: " +
69            e.getMessage(), e);
70        }
71      } else if (result.booleanValue() == false) {
72        throw new IOException("Key provider " + providerClassName + " previously failed test");
73      }
74    }
75  
76    /**
77     * Check that the configured cipher provider can be loaded and initialized, or
78     * throw an exception.
79     *
80     * @param conf
81     * @throws IOException
82     */
83    public static void testCipherProvider(final Configuration conf) throws IOException {
84      String providerClassName = conf.get(HConstants.CRYPTO_CIPHERPROVIDER_CONF_KEY,
85        DefaultCipherProvider.class.getName());
86      Boolean result = cipherProviderResults.get(providerClassName);
87      if (result == null) {
88        try {
89          Encryption.getCipherProvider(conf);
90          cipherProviderResults.put(providerClassName, true);
91        } catch (Exception e) { // most likely a RuntimeException
92          cipherProviderResults.put(providerClassName, false);
93          throw new IOException("Cipher provider " + providerClassName + " failed test: " +
94            e.getMessage(), e);
95        }
96      } else if (result.booleanValue() == false) {
97        throw new IOException("Cipher provider " + providerClassName + " previously failed test");
98      }
99    }
100 
101   /**
102    * Check that the specified cipher can be loaded and initialized, or throw
103    * an exception. Verifies key and cipher provider configuration as a
104    * prerequisite for cipher verification.
105    *
106    * @param conf
107    * @param cipher
108    * @param key
109    * @throws IOException
110    */
111   public static void testEncryption(final Configuration conf, final String cipher,
112       byte[] key) throws IOException {
113     if (cipher == null) {
114       return;
115     }
116     testKeyProvider(conf);
117     testCipherProvider(conf);
118     Boolean result = cipherResults.get(cipher);
119     if (result == null) {
120       try {
121         Encryption.Context context = Encryption.newContext(conf);
122         context.setCipher(Encryption.getCipher(conf, cipher));
123         if (key == null) {
124           // Make a random key since one was not provided
125           context.setKey(context.getCipher().getRandomKey());
126         } else {
127           // This will be a wrapped key from schema
128           context.setKey(EncryptionUtil.unwrapKey(conf,
129             conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase"),
130             key));
131         }
132         byte[] iv = null;
133         if (context.getCipher().getIvLength() > 0) {
134           iv = new byte[context.getCipher().getIvLength()];
135           Bytes.random(iv);
136         }
137         byte[] plaintext = new byte[1024];
138         Bytes.random(plaintext);
139         ByteArrayOutputStream out = new ByteArrayOutputStream();
140         Encryption.encrypt(out, new ByteArrayInputStream(plaintext), context, iv);
141         byte[] ciphertext = out.toByteArray();
142         out.reset();
143         Encryption.decrypt(out, new ByteArrayInputStream(ciphertext), plaintext.length,
144           context, iv);
145         byte[] test = out.toByteArray();
146         if (!Bytes.equals(plaintext, test)) {
147           throw new IOException("Did not pass encrypt/decrypt test");
148         }
149         cipherResults.put(cipher, true);
150       } catch (Exception e) {
151         cipherResults.put(cipher, false);
152         throw new IOException("Cipher " + cipher + " failed test: " + e.getMessage(), e);
153       }
154     } else if (result.booleanValue() == false) {
155       throw new IOException("Cipher " + cipher + " previously failed test");
156     }
157   }
158 }