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 */
018
019package org.apache.hadoop.crypto.key;
020
021import java.io.IOException;
022import java.nio.ByteBuffer;
023import java.security.GeneralSecurityException;
024import java.security.SecureRandom;
025
026import javax.crypto.Cipher;
027import javax.crypto.spec.IvParameterSpec;
028import javax.crypto.spec.SecretKeySpec;
029
030import com.google.common.base.Preconditions;
031
032import org.apache.hadoop.classification.InterfaceAudience;
033import org.apache.hadoop.crypto.CryptoCodec;
034import org.apache.hadoop.crypto.Decryptor;
035import org.apache.hadoop.crypto.Encryptor;
036
037/**
038 * A KeyProvider with Cryptographic Extensions specifically for generating
039 * and decrypting encrypted encryption keys.
040 *
041 */
042@InterfaceAudience.Private
043public class KeyProviderCryptoExtension extends
044    KeyProviderExtension<KeyProviderCryptoExtension.CryptoExtension> {
045
046  /**
047   * Designates an encrypted encryption key, or EEK.
048   */
049  public static final String EEK = "EEK";
050  /**
051   * Designates a decrypted encrypted encryption key, that is, an encryption key
052   * (EK).
053   */
054  public static final String EK = "EK";
055
056  /**
057   * An encrypted encryption key (EEK) and related information. An EEK must be
058   * decrypted using the key's encryption key before it can be used.
059   */
060  public static class EncryptedKeyVersion {
061    private String encryptionKeyName;
062    private String encryptionKeyVersionName;
063    private byte[] encryptedKeyIv;
064    private KeyVersion encryptedKeyVersion;
065
066    /**
067     * Create a new EncryptedKeyVersion.
068     *
069     * @param keyName                  Name of the encryption key used to
070     *                                 encrypt the encrypted key.
071     * @param encryptionKeyVersionName Version name of the encryption key used
072     *                                 to encrypt the encrypted key.
073     * @param encryptedKeyIv           Initialization vector of the encrypted
074     *                                 key. The IV of the encryption key used to
075     *                                 encrypt the encrypted key is derived from
076     *                                 this IV.
077     * @param encryptedKeyVersion      The encrypted encryption key version.
078     */
079    protected EncryptedKeyVersion(String keyName,
080        String encryptionKeyVersionName, byte[] encryptedKeyIv,
081        KeyVersion encryptedKeyVersion) {
082      this.encryptionKeyName = keyName;
083      this.encryptionKeyVersionName = encryptionKeyVersionName;
084      this.encryptedKeyIv = encryptedKeyIv;
085      this.encryptedKeyVersion = encryptedKeyVersion;
086    }
087
088    /**
089     * Factory method to create a new EncryptedKeyVersion that can then be
090     * passed into {@link #decryptEncryptedKey}. Note that the fields of the
091     * returned EncryptedKeyVersion will only partially be populated; it is not
092     * necessarily suitable for operations besides decryption.
093     *
094     * @param keyName Key name of the encryption key use to encrypt the
095     *                encrypted key.
096     * @param encryptionKeyVersionName Version name of the encryption key used
097     *                                 to encrypt the encrypted key.
098     * @param encryptedKeyIv           Initialization vector of the encrypted
099     *                                 key. The IV of the encryption key used to
100     *                                 encrypt the encrypted key is derived from
101     *                                 this IV.
102     * @param encryptedKeyMaterial     Key material of the encrypted key.
103     * @return EncryptedKeyVersion suitable for decryption.
104     */
105    public static EncryptedKeyVersion createForDecryption(String keyName,
106        String encryptionKeyVersionName, byte[] encryptedKeyIv,
107        byte[] encryptedKeyMaterial) {
108      KeyVersion encryptedKeyVersion = new KeyVersion(null, EEK,
109          encryptedKeyMaterial);
110      return new EncryptedKeyVersion(keyName, encryptionKeyVersionName,
111          encryptedKeyIv, encryptedKeyVersion);
112    }
113
114    /**
115     * @return Name of the encryption key used to encrypt the encrypted key.
116     */
117    public String getEncryptionKeyName() {
118      return encryptionKeyName;
119    }
120
121    /**
122     * @return Version name of the encryption key used to encrypt the encrypted
123     * key.
124     */
125    public String getEncryptionKeyVersionName() {
126      return encryptionKeyVersionName;
127    }
128
129    /**
130     * @return Initialization vector of the encrypted key. The IV of the
131     * encryption key used to encrypt the encrypted key is derived from this
132     * IV.
133     */
134    public byte[] getEncryptedKeyIv() {
135      return encryptedKeyIv;
136    }
137
138    /**
139     * @return The encrypted encryption key version.
140     */
141    public KeyVersion getEncryptedKeyVersion() {
142      return encryptedKeyVersion;
143    }
144
145    /**
146     * Derive the initialization vector (IV) for the encryption key from the IV
147     * of the encrypted key. This derived IV is used with the encryption key to
148     * decrypt the encrypted key.
149     * <p/>
150     * The alternative to this is using the same IV for both the encryption key
151     * and the encrypted key. Even a simple symmetric transformation like this
152     * improves security by avoiding IV re-use. IVs will also be fairly unique
153     * among different EEKs.
154     *
155     * @param encryptedKeyIV of the encrypted key (i.e. {@link
156     * #getEncryptedKeyIv()})
157     * @return IV for the encryption key
158     */
159    protected static byte[] deriveIV(byte[] encryptedKeyIV) {
160      byte[] rIv = new byte[encryptedKeyIV.length];
161      // Do a simple XOR transformation to flip all the bits
162      for (int i = 0; i < encryptedKeyIV.length; i++) {
163        rIv[i] = (byte) (encryptedKeyIV[i] ^ 0xff);
164      }
165      return rIv;
166    }
167  }
168
169  /**
170   * CryptoExtension is a type of Extension that exposes methods to generate
171   * EncryptedKeys and to decrypt the same.
172   */
173  public interface CryptoExtension extends KeyProviderExtension.Extension {
174
175    /**
176     * Calls to this method allows the underlying KeyProvider to warm-up any
177     * implementation specific caches used to store the Encrypted Keys.
178     * @param keyNames Array of Key Names
179     */
180    public void warmUpEncryptedKeys(String... keyNames)
181        throws IOException;
182
183    /**
184     * Drains the Queue for the provided key.
185     *
186     * @param keyName the key to drain the Queue for
187     */
188    public void drain(String keyName);
189
190    /**
191     * Generates a key material and encrypts it using the given key version name
192     * and initialization vector. The generated key material is of the same
193     * length as the <code>KeyVersion</code> material of the latest key version
194     * of the key and is encrypted using the same cipher.
195     * <p/>
196     * NOTE: The generated key is not stored by the <code>KeyProvider</code>
197     *
198     * @param encryptionKeyName
199     *          The latest KeyVersion of this key's material will be encrypted.
200     * @return EncryptedKeyVersion with the generated key material, the version
201     *         name is 'EEK' (for Encrypted Encryption Key)
202     * @throws IOException
203     *           thrown if the key material could not be generated
204     * @throws GeneralSecurityException
205     *           thrown if the key material could not be encrypted because of a
206     *           cryptographic issue.
207     */
208    public EncryptedKeyVersion generateEncryptedKey(
209        String encryptionKeyName) throws IOException,
210        GeneralSecurityException;
211
212    /**
213     * Decrypts an encrypted byte[] key material using the given a key version
214     * name and initialization vector.
215     *
216     * @param encryptedKeyVersion
217     *          contains keyVersionName and IV to decrypt the encrypted key
218     *          material
219     * @return a KeyVersion with the decrypted key material, the version name is
220     *         'EK' (For Encryption Key)
221     * @throws IOException
222     *           thrown if the key material could not be decrypted
223     * @throws GeneralSecurityException
224     *           thrown if the key material could not be decrypted because of a
225     *           cryptographic issue.
226     */
227    public KeyVersion decryptEncryptedKey(
228        EncryptedKeyVersion encryptedKeyVersion) throws IOException,
229        GeneralSecurityException;
230  }
231
232  private static class DefaultCryptoExtension implements CryptoExtension {
233
234    private final KeyProvider keyProvider;
235    private static final ThreadLocal<SecureRandom> RANDOM =
236        new ThreadLocal<SecureRandom>() {
237      @Override
238      protected SecureRandom initialValue() {
239        return new SecureRandom();
240      }
241    };
242
243    private DefaultCryptoExtension(KeyProvider keyProvider) {
244      this.keyProvider = keyProvider;
245    }
246
247    @Override
248    public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName)
249        throws IOException, GeneralSecurityException {
250      // Fetch the encryption key
251      KeyVersion encryptionKey = keyProvider.getCurrentKey(encryptionKeyName);
252      Preconditions.checkNotNull(encryptionKey,
253          "No KeyVersion exists for key '%s' ", encryptionKeyName);
254      // Generate random bytes for new key and IV
255
256      CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf());
257      final byte[] newKey = new byte[encryptionKey.getMaterial().length];
258      cc.generateSecureRandom(newKey);
259      final byte[] iv = new byte[cc.getCipherSuite().getAlgorithmBlockSize()];
260      cc.generateSecureRandom(iv);
261      // Encryption key IV is derived from new key's IV
262      final byte[] encryptionIV = EncryptedKeyVersion.deriveIV(iv);
263      Encryptor encryptor = cc.createEncryptor();
264      encryptor.init(encryptionKey.getMaterial(), encryptionIV);
265      int keyLen = newKey.length;
266      ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen);
267      ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen);
268      bbIn.put(newKey);
269      bbIn.flip();
270      encryptor.encrypt(bbIn, bbOut);
271      bbOut.flip();
272      byte[] encryptedKey = new byte[keyLen];
273      bbOut.get(encryptedKey);    
274      return new EncryptedKeyVersion(encryptionKeyName,
275          encryptionKey.getVersionName(), iv,
276          new KeyVersion(encryptionKey.getName(), EEK, encryptedKey));
277    }
278
279    @Override
280    public KeyVersion decryptEncryptedKey(
281        EncryptedKeyVersion encryptedKeyVersion) throws IOException,
282        GeneralSecurityException {
283      // Fetch the encryption key material
284      final String encryptionKeyVersionName =
285          encryptedKeyVersion.getEncryptionKeyVersionName();
286      final KeyVersion encryptionKey =
287          keyProvider.getKeyVersion(encryptionKeyVersionName);
288      Preconditions.checkNotNull(encryptionKey,
289          "KeyVersion name '%s' does not exist", encryptionKeyVersionName);
290      Preconditions.checkArgument(
291              encryptedKeyVersion.getEncryptedKeyVersion().getVersionName()
292                    .equals(KeyProviderCryptoExtension.EEK),
293                "encryptedKey version name must be '%s', is '%s'",
294                KeyProviderCryptoExtension.EEK,
295                encryptedKeyVersion.getEncryptedKeyVersion().getVersionName()
296            );
297
298      // Encryption key IV is determined from encrypted key's IV
299      final byte[] encryptionIV =
300          EncryptedKeyVersion.deriveIV(encryptedKeyVersion.getEncryptedKeyIv());
301
302      CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf());
303      Decryptor decryptor = cc.createDecryptor();
304      decryptor.init(encryptionKey.getMaterial(), encryptionIV);
305      final KeyVersion encryptedKV =
306          encryptedKeyVersion.getEncryptedKeyVersion();
307      int keyLen = encryptedKV.getMaterial().length;
308      ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen);
309      ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen);
310      bbIn.put(encryptedKV.getMaterial());
311      bbIn.flip();
312      decryptor.decrypt(bbIn, bbOut);
313      bbOut.flip();
314      byte[] decryptedKey = new byte[keyLen];
315      bbOut.get(decryptedKey);
316      return new KeyVersion(encryptionKey.getName(), EK, decryptedKey);
317    }
318
319    @Override
320    public void warmUpEncryptedKeys(String... keyNames)
321        throws IOException {
322      // NO-OP since the default version does not cache any keys
323    }
324
325    @Override
326    public void drain(String keyName) {
327      // NO-OP since the default version does not cache any keys
328    }
329  }
330
331  /**
332   * This constructor is to be used by sub classes that provide
333   * delegating/proxying functionality to the {@link KeyProviderCryptoExtension}
334   * @param keyProvider
335   * @param extension
336   */
337  protected KeyProviderCryptoExtension(KeyProvider keyProvider,
338      CryptoExtension extension) {
339    super(keyProvider, extension);
340  }
341
342  /**
343   * Notifies the Underlying CryptoExtension implementation to warm up any
344   * implementation specific caches for the specified KeyVersions
345   * @param keyNames Arrays of key Names
346   */
347  public void warmUpEncryptedKeys(String... keyNames)
348      throws IOException {
349    getExtension().warmUpEncryptedKeys(keyNames);
350  }
351
352  /**
353   * Generates a key material and encrypts it using the given key version name
354   * and initialization vector. The generated key material is of the same
355   * length as the <code>KeyVersion</code> material and is encrypted using the
356   * same cipher.
357   * <p/>
358   * NOTE: The generated key is not stored by the <code>KeyProvider</code>
359   *
360   * @param encryptionKeyName The latest KeyVersion of this key's material will
361   * be encrypted.
362   * @return EncryptedKeyVersion with the generated key material, the version
363   * name is 'EEK' (for Encrypted Encryption Key)
364   * @throws IOException thrown if the key material could not be generated
365   * @throws GeneralSecurityException thrown if the key material could not be
366   * encrypted because of a cryptographic issue.
367   */
368  public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName)
369      throws IOException,
370                                           GeneralSecurityException {
371    return getExtension().generateEncryptedKey(encryptionKeyName);
372  }
373
374  /**
375   * Decrypts an encrypted byte[] key material using the given a key version
376   * name and initialization vector.
377   *
378   * @param encryptedKey contains keyVersionName and IV to decrypt the encrypted
379   * key material
380   * @return a KeyVersion with the decrypted key material, the version name is
381   * 'EK' (For Encryption Key)
382   * @throws IOException thrown if the key material could not be decrypted
383   * @throws GeneralSecurityException thrown if the key material could not be
384   * decrypted because of a cryptographic issue.
385   */
386  public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKey)
387      throws IOException, GeneralSecurityException {
388    return getExtension().decryptEncryptedKey(encryptedKey);
389  }
390
391  /**
392   * Creates a <code>KeyProviderCryptoExtension</code> using a given
393   * {@link KeyProvider}.
394   * <p/>
395   * If the given <code>KeyProvider</code> implements the
396   * {@link CryptoExtension} interface the <code>KeyProvider</code> itself
397   * will provide the extension functionality, otherwise a default extension
398   * implementation will be used.
399   *
400   * @param keyProvider <code>KeyProvider</code> to use to create the
401   * <code>KeyProviderCryptoExtension</code> extension.
402   * @return a <code>KeyProviderCryptoExtension</code> instance using the
403   * given <code>KeyProvider</code>.
404   */
405  public static KeyProviderCryptoExtension createKeyProviderCryptoExtension(
406      KeyProvider keyProvider) {
407    CryptoExtension cryptoExtension = (keyProvider instanceof CryptoExtension)
408                         ? (CryptoExtension) keyProvider
409                         : new DefaultCryptoExtension(keyProvider);
410    return new KeyProviderCryptoExtension(keyProvider, cryptoExtension);
411  }
412
413  @Override
414  public void close() throws IOException {
415    KeyProvider provider = getKeyProvider();
416    if (provider != null && provider != this) {
417      provider.close();
418    }
419  }
420
421}