org.apache.hadoop.crypto.key.KeyProviderCryptoExtension.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.crypto.key.KeyProviderCryptoExtension.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.crypto.key;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.List;
import java.util.ListIterator;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.google.common.base.Preconditions;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.crypto.CryptoCodec;
import org.apache.hadoop.crypto.Decryptor;
import org.apache.hadoop.crypto.Encryptor;

/**
 * A KeyProvider with Cryptographic Extensions specifically for generating
 * and decrypting encrypted encryption keys.
 * 
 */
@InterfaceAudience.Private
public class KeyProviderCryptoExtension extends KeyProviderExtension<KeyProviderCryptoExtension.CryptoExtension> {

    /**
     * Designates an encrypted encryption key, or EEK.
     */
    public static final String EEK = "EEK";
    /**
     * Designates a decrypted encrypted encryption key, that is, an encryption key
     * (EK).
     */
    public static final String EK = "EK";

    /**
     * An encrypted encryption key (EEK) and related information. An EEK must be
     * decrypted using the key's encryption key before it can be used.
     */
    public static class EncryptedKeyVersion {
        private String encryptionKeyName;
        private String encryptionKeyVersionName;
        private byte[] encryptedKeyIv;
        private KeyVersion encryptedKeyVersion;

        /**
         * Create a new EncryptedKeyVersion.
         *
         * @param keyName                  Name of the encryption key used to
         *                                 encrypt the encrypted key.
         * @param encryptionKeyVersionName Version name of the encryption key used
         *                                 to encrypt the encrypted key.
         * @param encryptedKeyIv           Initialization vector of the encrypted
         *                                 key. The IV of the encryption key used to
         *                                 encrypt the encrypted key is derived from
         *                                 this IV.
         * @param encryptedKeyVersion      The encrypted encryption key version.
         */
        protected EncryptedKeyVersion(String keyName, String encryptionKeyVersionName, byte[] encryptedKeyIv,
                KeyVersion encryptedKeyVersion) {
            this.encryptionKeyName = keyName == null ? null : keyName.intern();
            this.encryptionKeyVersionName = encryptionKeyVersionName == null ? null
                    : encryptionKeyVersionName.intern();
            this.encryptedKeyIv = encryptedKeyIv;
            this.encryptedKeyVersion = encryptedKeyVersion;
        }

        /**
         * Factory method to create a new EncryptedKeyVersion that can then be
         * passed into {@link #decryptEncryptedKey}. Note that the fields of the
         * returned EncryptedKeyVersion will only partially be populated; it is not
         * necessarily suitable for operations besides decryption.
         *
         * @param keyName Key name of the encryption key use to encrypt the
         *                encrypted key.
         * @param encryptionKeyVersionName Version name of the encryption key used
         *                                 to encrypt the encrypted key.
         * @param encryptedKeyIv           Initialization vector of the encrypted
         *                                 key. The IV of the encryption key used to
         *                                 encrypt the encrypted key is derived from
         *                                 this IV.
         * @param encryptedKeyMaterial     Key material of the encrypted key.
         * @return EncryptedKeyVersion suitable for decryption.
         */
        public static EncryptedKeyVersion createForDecryption(String keyName, String encryptionKeyVersionName,
                byte[] encryptedKeyIv, byte[] encryptedKeyMaterial) {
            KeyVersion encryptedKeyVersion = new KeyVersion(null, EEK, encryptedKeyMaterial);
            return new EncryptedKeyVersion(keyName, encryptionKeyVersionName, encryptedKeyIv, encryptedKeyVersion);
        }

        /**
         * @return Name of the encryption key used to encrypt the encrypted key.
         */
        public String getEncryptionKeyName() {
            return encryptionKeyName;
        }

        /**
         * @return Version name of the encryption key used to encrypt the encrypted
         * key.
         */
        public String getEncryptionKeyVersionName() {
            return encryptionKeyVersionName;
        }

        /**
         * @return Initialization vector of the encrypted key. The IV of the
         * encryption key used to encrypt the encrypted key is derived from this
         * IV.
         */
        public byte[] getEncryptedKeyIv() {
            return encryptedKeyIv;
        }

        /**
         * @return The encrypted encryption key version.
         */
        public KeyVersion getEncryptedKeyVersion() {
            return encryptedKeyVersion;
        }

        /**
         * Derive the initialization vector (IV) for the encryption key from the IV
         * of the encrypted key. This derived IV is used with the encryption key to
         * decrypt the encrypted key.
         * <p/>
         * The alternative to this is using the same IV for both the encryption key
         * and the encrypted key. Even a simple symmetric transformation like this
         * improves security by avoiding IV re-use. IVs will also be fairly unique
         * among different EEKs.
         *
         * @param encryptedKeyIV of the encrypted key (i.e. {@link
         * #getEncryptedKeyIv()})
         * @return IV for the encryption key
         */
        protected static byte[] deriveIV(byte[] encryptedKeyIV) {
            byte[] rIv = new byte[encryptedKeyIV.length];
            // Do a simple XOR transformation to flip all the bits
            for (int i = 0; i < encryptedKeyIV.length; i++) {
                rIv[i] = (byte) (encryptedKeyIV[i] ^ 0xff);
            }
            return rIv;
        }
    }

    /**
     * CryptoExtension is a type of Extension that exposes methods to generate
     * EncryptedKeys and to decrypt the same.
     */
    public interface CryptoExtension extends KeyProviderExtension.Extension {

        /**
         * Calls to this method allows the underlying KeyProvider to warm-up any
         * implementation specific caches used to store the Encrypted Keys.
         * @param keyNames Array of Key Names
         */
        public void warmUpEncryptedKeys(String... keyNames) throws IOException;

        /**
         * Drains the Queue for the provided key.
         *
         * @param keyName the key to drain the Queue for
         */
        public void drain(String keyName);

        /**
         * Generates a key material and encrypts it using the given key name.
         * The generated key material is of the same
         * length as the <code>KeyVersion</code> material of the latest key version
         * of the key and is encrypted using the same cipher.
         * <p/>
         * NOTE: The generated key is not stored by the <code>KeyProvider</code>
         * 
         * @param encryptionKeyName
         *          The latest KeyVersion of this key's material will be encrypted.
         * @return EncryptedKeyVersion with the generated key material, the version
         *         name is 'EEK' (for Encrypted Encryption Key)
         * @throws IOException
         *           thrown if the key material could not be generated
         * @throws GeneralSecurityException
         *           thrown if the key material could not be encrypted because of a
         *           cryptographic issue.
         */
        public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName)
                throws IOException, GeneralSecurityException;

        /**
         * Decrypts an encrypted byte[] key material using the given key version
         * name and initialization vector.
         * 
         * @param encryptedKeyVersion
         *          contains keyVersionName and IV to decrypt the encrypted key
         *          material
         * @return a KeyVersion with the decrypted key material, the version name is
         *         'EK' (For Encryption Key)
         * @throws IOException
         *           thrown if the key material could not be decrypted
         * @throws GeneralSecurityException
         *           thrown if the key material could not be decrypted because of a
         *           cryptographic issue.
         */
        public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKeyVersion)
                throws IOException, GeneralSecurityException;

        /**
         * Re-encrypts an encrypted key version, using its initialization vector
         * and key material, but with the latest key version name of its key name
         * in the key provider.
         * <p>
         * If the latest key version name in the provider is the
         * same as the one encrypted the passed-in encrypted key version, the same
         * encrypted key version is returned.
         * <p>
         * NOTE: The generated key is not stored by the <code>KeyProvider</code>
         *
         * @param  ekv The EncryptedKeyVersion containing keyVersionName and IV.
         * @return     The re-encrypted EncryptedKeyVersion.
         * @throws IOException If the key material could not be re-encrypted.
         * @throws GeneralSecurityException If the key material could not be
         *                            re-encrypted because of a cryptographic issue.
         */
        EncryptedKeyVersion reencryptEncryptedKey(EncryptedKeyVersion ekv)
                throws IOException, GeneralSecurityException;

        /**
         * Batched version of {@link #reencryptEncryptedKey(EncryptedKeyVersion)}.
         * <p>
         * For each encrypted key version, re-encrypts an encrypted key version,
         * using its initialization vector and key material, but with the latest
         * key version name of its key name. If the latest key version name in the
         * provider is the same as the one encrypted the passed-in encrypted key
         * version, the same encrypted key version is returned.
         * <p>
         * NOTE: The generated key is not stored by the <code>KeyProvider</code>
         *
         * @param  ekvs List containing the EncryptedKeyVersion's
         * @throws IOException If any EncryptedKeyVersion could not be re-encrypted
         * @throws GeneralSecurityException If any EncryptedKeyVersion could not be
         *                            re-encrypted because of a cryptographic issue.
         */
        void reencryptEncryptedKeys(List<EncryptedKeyVersion> ekvs) throws IOException, GeneralSecurityException;
    }

    private static class DefaultCryptoExtension implements CryptoExtension {

        private final KeyProvider keyProvider;
        private static final ThreadLocal<SecureRandom> RANDOM = new ThreadLocal<SecureRandom>() {
            @Override
            protected SecureRandom initialValue() {
                return new SecureRandom();
            }
        };

        private DefaultCryptoExtension(KeyProvider keyProvider) {
            this.keyProvider = keyProvider;
        }

        @Override
        public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName)
                throws IOException, GeneralSecurityException {
            // Fetch the encryption key
            KeyVersion encryptionKey = keyProvider.getCurrentKey(encryptionKeyName);
            Preconditions.checkNotNull(encryptionKey, "No KeyVersion exists for key '%s' ", encryptionKeyName);
            // Generate random bytes for new key and IV

            CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf());
            try {
                final byte[] newKey = new byte[encryptionKey.getMaterial().length];
                cc.generateSecureRandom(newKey);
                final byte[] iv = new byte[cc.getCipherSuite().getAlgorithmBlockSize()];
                cc.generateSecureRandom(iv);
                Encryptor encryptor = cc.createEncryptor();
                return generateEncryptedKey(encryptor, encryptionKey, newKey, iv);
            } finally {
                cc.close();
            }
        }

        private EncryptedKeyVersion generateEncryptedKey(final Encryptor encryptor, final KeyVersion encryptionKey,
                final byte[] key, final byte[] iv) throws IOException, GeneralSecurityException {
            // Encryption key IV is derived from new key's IV
            final byte[] encryptionIV = EncryptedKeyVersion.deriveIV(iv);
            encryptor.init(encryptionKey.getMaterial(), encryptionIV);
            final int keyLen = key.length;
            ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen);
            ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen);
            bbIn.put(key);
            bbIn.flip();
            encryptor.encrypt(bbIn, bbOut);
            bbOut.flip();
            byte[] encryptedKey = new byte[keyLen];
            bbOut.get(encryptedKey);
            return new EncryptedKeyVersion(encryptionKey.getName(), encryptionKey.getVersionName(), iv,
                    new KeyVersion(encryptionKey.getName(), EEK, encryptedKey));
        }

        @Override
        public EncryptedKeyVersion reencryptEncryptedKey(EncryptedKeyVersion ekv)
                throws IOException, GeneralSecurityException {
            final String ekName = ekv.getEncryptionKeyName();
            final KeyVersion ekNow = keyProvider.getCurrentKey(ekName);
            Preconditions.checkNotNull(ekNow, "KeyVersion name '%s' does not exist", ekName);
            Preconditions.checkArgument(
                    ekv.getEncryptedKeyVersion().getVersionName().equals(KeyProviderCryptoExtension.EEK),
                    "encryptedKey version name must be '%s', but found '%s'", KeyProviderCryptoExtension.EEK,
                    ekv.getEncryptedKeyVersion().getVersionName());

            if (ekv.getEncryptedKeyVersion().equals(ekNow)) {
                // no-op if same key version
                return ekv;
            }

            final KeyVersion dek = decryptEncryptedKey(ekv);
            final CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf());
            try {
                final Encryptor encryptor = cc.createEncryptor();
                return generateEncryptedKey(encryptor, ekNow, dek.getMaterial(), ekv.getEncryptedKeyIv());
            } finally {
                cc.close();
            }
        }

        @Override
        public void reencryptEncryptedKeys(List<EncryptedKeyVersion> ekvs)
                throws IOException, GeneralSecurityException {
            Preconditions.checkNotNull(ekvs, "Input list is null");
            KeyVersion ekNow = null;
            Decryptor decryptor = null;
            Encryptor encryptor = null;
            try (CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf())) {
                decryptor = cc.createDecryptor();
                encryptor = cc.createEncryptor();
                ListIterator<EncryptedKeyVersion> iter = ekvs.listIterator();
                while (iter.hasNext()) {
                    final EncryptedKeyVersion ekv = iter.next();
                    Preconditions.checkNotNull(ekv, "EncryptedKeyVersion is null");
                    final String ekName = ekv.getEncryptionKeyName();
                    Preconditions.checkNotNull(ekName, "Key name is null");
                    Preconditions.checkNotNull(ekv.getEncryptedKeyVersion(), "EncryptedKeyVersion is null");
                    Preconditions.checkArgument(
                            ekv.getEncryptedKeyVersion().getVersionName().equals(KeyProviderCryptoExtension.EEK),
                            "encryptedKey version name must be '%s', but found '%s'",
                            KeyProviderCryptoExtension.EEK, ekv.getEncryptedKeyVersion().getVersionName());

                    if (ekNow == null) {
                        ekNow = keyProvider.getCurrentKey(ekName);
                        Preconditions.checkNotNull(ekNow, "Key name '%s' does not exist", ekName);
                    } else {
                        Preconditions.checkArgument(ekNow.getName().equals(ekName),
                                "All keys must have the same key name. Expected '%s' " + "but found '%s'",
                                ekNow.getName(), ekName);
                    }

                    final String encryptionKeyVersionName = ekv.getEncryptionKeyVersionName();
                    final KeyVersion encryptionKey = keyProvider.getKeyVersion(encryptionKeyVersionName);
                    Preconditions.checkNotNull(encryptionKey, "KeyVersion name '%s' does not exist",
                            encryptionKeyVersionName);
                    if (encryptionKey.equals(ekNow)) {
                        // no-op if same key version
                        continue;
                    }

                    final KeyVersion ek = decryptEncryptedKey(decryptor, encryptionKey, ekv);
                    iter.set(generateEncryptedKey(encryptor, ekNow, ek.getMaterial(), ekv.getEncryptedKeyIv()));
                }
            }
        }

        private KeyVersion decryptEncryptedKey(final Decryptor decryptor, final KeyVersion encryptionKey,
                final EncryptedKeyVersion encryptedKeyVersion) throws IOException, GeneralSecurityException {
            // Encryption key IV is determined from encrypted key's IV
            final byte[] encryptionIV = EncryptedKeyVersion.deriveIV(encryptedKeyVersion.getEncryptedKeyIv());

            decryptor.init(encryptionKey.getMaterial(), encryptionIV);
            final KeyVersion encryptedKV = encryptedKeyVersion.getEncryptedKeyVersion();
            int keyLen = encryptedKV.getMaterial().length;
            ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen);
            ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen);
            bbIn.put(encryptedKV.getMaterial());
            bbIn.flip();
            decryptor.decrypt(bbIn, bbOut);
            bbOut.flip();
            byte[] decryptedKey = new byte[keyLen];
            bbOut.get(decryptedKey);
            return new KeyVersion(encryptionKey.getName(), EK, decryptedKey);
        }

        @Override
        public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKeyVersion)
                throws IOException, GeneralSecurityException {
            // Fetch the encryption key material
            final String encryptionKeyVersionName = encryptedKeyVersion.getEncryptionKeyVersionName();
            final KeyVersion encryptionKey = keyProvider.getKeyVersion(encryptionKeyVersionName);
            Preconditions.checkNotNull(encryptionKey, "KeyVersion name '%s' does not exist",
                    encryptionKeyVersionName);
            Preconditions.checkArgument(
                    encryptedKeyVersion.getEncryptedKeyVersion().getVersionName()
                            .equals(KeyProviderCryptoExtension.EEK),
                    "encryptedKey version name must be '%s', but found '%s'", KeyProviderCryptoExtension.EEK,
                    encryptedKeyVersion.getEncryptedKeyVersion().getVersionName());

            try (CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf())) {
                final Decryptor decryptor = cc.createDecryptor();
                return decryptEncryptedKey(decryptor, encryptionKey, encryptedKeyVersion);
            }
        }

        @Override
        public void warmUpEncryptedKeys(String... keyNames) throws IOException {
            // NO-OP since the default version does not cache any keys
        }

        @Override
        public void drain(String keyName) {
            // NO-OP since the default version does not cache any keys
        }
    }

    /**
     * This constructor is to be used by sub classes that provide
     * delegating/proxying functionality to the {@link KeyProviderCryptoExtension}
     * @param keyProvider
     * @param extension
     */
    protected KeyProviderCryptoExtension(KeyProvider keyProvider, CryptoExtension extension) {
        super(keyProvider, extension);
    }

    /**
     * Notifies the Underlying CryptoExtension implementation to warm up any
     * implementation specific caches for the specified KeyVersions
     * @param keyNames Arrays of key Names
     */
    public void warmUpEncryptedKeys(String... keyNames) throws IOException {
        getExtension().warmUpEncryptedKeys(keyNames);
    }

    /**
     * Generates a key material and encrypts it using the given key version name
     * and initialization vector. The generated key material is of the same
     * length as the <code>KeyVersion</code> material and is encrypted using the
     * same cipher.
     * <p/>
     * NOTE: The generated key is not stored by the <code>KeyProvider</code>
     *
     * @param encryptionKeyName The latest KeyVersion of this key's material will
     * be encrypted.
     * @return EncryptedKeyVersion with the generated key material, the version
     * name is 'EEK' (for Encrypted Encryption Key)
     * @throws IOException thrown if the key material could not be generated
     * @throws GeneralSecurityException thrown if the key material could not be 
     * encrypted because of a cryptographic issue.
     */
    public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName)
            throws IOException, GeneralSecurityException {
        return getExtension().generateEncryptedKey(encryptionKeyName);
    }

    /**
     * Decrypts an encrypted byte[] key material using the given a key version
     * name and initialization vector.
     *
     * @param encryptedKey contains keyVersionName and IV to decrypt the encrypted 
     * key material
     * @return a KeyVersion with the decrypted key material, the version name is
     * 'EK' (For Encryption Key)
     * @throws IOException thrown if the key material could not be decrypted
     * @throws GeneralSecurityException thrown if the key material could not be 
     * decrypted because of a cryptographic issue.
     */
    public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKey)
            throws IOException, GeneralSecurityException {
        return getExtension().decryptEncryptedKey(encryptedKey);
    }

    /**
     * Re-encrypts an encrypted key version, using its initialization vector
     * and key material, but with the latest key version name of its key name
     * in the key provider.
     * <p>
     * If the latest key version name in the provider is the
     * same as the one encrypted the passed-in encrypted key version, the same
     * encrypted key version is returned.
     * <p>
     * NOTE: The generated key is not stored by the <code>KeyProvider</code>
     *
     * @param  ekv The EncryptedKeyVersion containing keyVersionName and IV.
     * @return     The re-encrypted EncryptedKeyVersion.
     * @throws IOException If the key material could not be re-encrypted
     * @throws GeneralSecurityException If the key material could not be
     *                            re-encrypted because of a cryptographic issue.
     */
    public EncryptedKeyVersion reencryptEncryptedKey(EncryptedKeyVersion ekv)
            throws IOException, GeneralSecurityException {
        return getExtension().reencryptEncryptedKey(ekv);
    }

    /**
     * Calls {@link CryptoExtension#drain(String)} for the given key name on the
     * underlying {@link CryptoExtension}.
     *
     * @param keyName
     */
    public void drain(String keyName) {
        getExtension().drain(keyName);
    }

    /**
     * Batched version of {@link #reencryptEncryptedKey(EncryptedKeyVersion)}.
     * <p>
     * For each encrypted key version, re-encrypts an encrypted key version,
     * using its initialization vector and key material, but with the latest
     * key version name of its key name. If the latest key version name in the
     * provider is the same as the one encrypted the passed-in encrypted key
     * version, the same encrypted key version is returned.
     * <p>
     * NOTE: The generated key is not stored by the <code>KeyProvider</code>
     *
     * @param  ekvs List containing the EncryptedKeyVersion's
     * @return      The re-encrypted EncryptedKeyVersion's, in the same order.
     * @throws IOException If any EncryptedKeyVersion could not be re-encrypted
     * @throws GeneralSecurityException If any EncryptedKeyVersion could not be
     *                            re-encrypted because of a cryptographic issue.
     */
    public void reencryptEncryptedKeys(List<EncryptedKeyVersion> ekvs)
            throws IOException, GeneralSecurityException {
        getExtension().reencryptEncryptedKeys(ekvs);
    }

    /**
     * Creates a <code>KeyProviderCryptoExtension</code> using a given
     * {@link KeyProvider}.
     * <p/>
     * If the given <code>KeyProvider</code> implements the
     * {@link CryptoExtension} interface the <code>KeyProvider</code> itself
     * will provide the extension functionality.
     * If the given <code>KeyProvider</code> implements the
     * {@link KeyProviderExtension} interface and the KeyProvider being
     * extended by the <code>KeyProvider</code> implements the
     * {@link CryptoExtension} interface, the KeyProvider being extended will
     * provide the extension functionality. Otherwise, a default extension
     * implementation will be used.
     *
     * @param keyProvider <code>KeyProvider</code> to use to create the
     * <code>KeyProviderCryptoExtension</code> extension.
     * @return a <code>KeyProviderCryptoExtension</code> instance using the
     * given <code>KeyProvider</code>.
     */
    public static KeyProviderCryptoExtension createKeyProviderCryptoExtension(KeyProvider keyProvider) {
        CryptoExtension cryptoExtension = null;
        if (keyProvider instanceof CryptoExtension) {
            cryptoExtension = (CryptoExtension) keyProvider;
        } else if (keyProvider instanceof KeyProviderExtension && ((KeyProviderExtension) keyProvider)
                .getKeyProvider() instanceof KeyProviderCryptoExtension.CryptoExtension) {
            KeyProviderExtension keyProviderExtension = (KeyProviderExtension) keyProvider;
            cryptoExtension = (CryptoExtension) keyProviderExtension.getKeyProvider();
        } else {
            cryptoExtension = new DefaultCryptoExtension(keyProvider);
        }
        return new KeyProviderCryptoExtension(keyProvider, cryptoExtension);
    }

    @Override
    public void close() throws IOException {
        KeyProvider provider = getKeyProvider();
        if (provider != null && provider != this) {
            provider.close();
        }
    }

}