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    
019    package org.apache.hadoop.crypto.key;
020    
021    import java.io.IOException;
022    import java.nio.ByteBuffer;
023    import java.security.GeneralSecurityException;
024    import java.security.SecureRandom;
025    
026    import javax.crypto.Cipher;
027    import javax.crypto.spec.IvParameterSpec;
028    import javax.crypto.spec.SecretKeySpec;
029    
030    import com.google.common.base.Preconditions;
031    
032    import org.apache.hadoop.classification.InterfaceAudience;
033    import org.apache.hadoop.crypto.CryptoCodec;
034    import org.apache.hadoop.crypto.Decryptor;
035    import 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
043    public 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        if (getKeyProvider() != null) {
416          getKeyProvider().close();
417        }
418      }
419    
420    }