org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandler.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.ambari.server.serveraction.kerberos;

import org.apache.ambari.server.utils.ShellCommandUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
import org.apache.directory.server.kerberos.shared.keytab.Keytab;
import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
import org.apache.directory.shared.kerberos.components.EncryptionKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * KerberosOperationHandler is an abstract class providing basic implementations of common Kerberos
 * operations (like generating secure passwords) and placeholders for KDC-specific operations
 * (such as creating principals).
 */
public abstract class KerberosOperationHandler {
    private final static Logger LOG = LoggerFactory.getLogger(KerberosOperationHandler.class);

    private final static SecureRandom SECURE_RANDOM = new SecureRandom();

    /**
     * The number of characters to generate for a secure password
     */
    protected final static int SECURE_PASSWORD_LENGTH = 18;

    /**
     * Kerberos-env configuration property name: ldap_url
     */
    public final static String KERBEROS_ENV_LDAP_URL = "ldap_url";

    /**
     * Kerberos-env configuration property name: container_dn
     */
    public final static String KERBEROS_ENV_PRINCIPAL_CONTAINER_DN = "container_dn";

    /**
     * Kerberos-env configuration property name: create_attributes_template
     */
    public final static String KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE = "create_attributes_template";

    /**
     * Kerberos-env configuration property name: encryption_types
     */
    public final static String KERBEROS_ENV_ENCRYPTION_TYPES = "encryption_types";

    /**
     * Kerberos-env configuration property name: kdc_host
     */
    public final static String KERBEROS_ENV_KDC_HOST = "kdc_host";

    /**
     * Kerberos-env configuration property name: admin_server_host
     */
    public final static String KERBEROS_ENV_ADMIN_SERVER_HOST = "admin_server_host";

    /**
     * The set of available characters to use when generating a secure password
     */
    private final static char[] SECURE_PASSWORD_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890?.!$%^*()-_+=~"
            .toCharArray();

    /**
     * A Map of MIT KDC Encryption types to EncryptionType values.
     * <p/>
     * See http://web.mit.edu/kerberos/krb5-devel/doc/admin/conf_files/kdc_conf.html#encryption-types
     */
    private static final Map<String, Set<EncryptionType>> ENCRYPTION_TYPE_TRANSLATION_MAP = Collections
            .unmodifiableMap(new HashMap<String, Set<EncryptionType>>() {
                {
                    // aes: The AES family: aes256-cts-hmac-sha1-96 and aes128-cts-hmac-sha1-96
                    put("aes", EnumSet.of(EncryptionType.AES256_CTS_HMAC_SHA1_96,
                            EncryptionType.AES128_CTS_HMAC_SHA1_96));

                    // aes256-cts-hmac-sha1-96 aes256-cts:  AES-256   CTS mode with 96-bit SHA-1 HMAC
                    put("aes256-cts-hmac-sha1-96", EnumSet.of(EncryptionType.AES256_CTS_HMAC_SHA1_96));
                    put("aes256-cts", EnumSet.of(EncryptionType.AES256_CTS_HMAC_SHA1_96));
                    put("aes-256", EnumSet.of(EncryptionType.AES256_CTS_HMAC_SHA1_96));

                    // aes128-cts-hmac-sha1-96 aes128-cts AES-128:   CTS mode with 96-bit SHA-1 HMAC
                    put("aes128-cts-hmac-sha1-96", EnumSet.of(EncryptionType.AES128_CTS_HMAC_SHA1_96));
                    put("aes128-cts", EnumSet.of(EncryptionType.AES128_CTS_HMAC_SHA1_96));
                    put("aes-128", EnumSet.of(EncryptionType.AES128_CTS_HMAC_SHA1_96));

                    // rc4   The RC4 family: arcfour-hmac
                    put("rc4", EnumSet.of(EncryptionType.RC4_HMAC));

                    // arcfour-hmac rc4-hmac arcfour-hmac-md5:   RC4 with HMAC/MD5
                    put("arcfour-hmac", EnumSet.of(EncryptionType.RC4_HMAC));
                    put("rc4-hmac", EnumSet.of(EncryptionType.RC4_HMAC));
                    put("arcfour-hmac-md5", EnumSet.of(EncryptionType.UNKNOWN));

                    // arcfour-hmac-exp rc4-hmac-exp arcfour-hmac-md5-exp:   Exportable RC4 with HMAC/MD5 (weak)
                    put("arcfour-hmac-exp", EnumSet.of(EncryptionType.RC4_HMAC_EXP));
                    put("rc4-hmac-exp", EnumSet.of(EncryptionType.RC4_HMAC_EXP));
                    put("arcfour-hmac-md5-exp", EnumSet.of(EncryptionType.UNKNOWN));

                    // camellia    The Camellia family: camellia256-cts-cmac and camellia128-cts-cmac
                    put("camellia", EnumSet.of(EncryptionType.UNKNOWN));

                    // camellia256-cts-cmac camellia256-cts:   Camellia-256 CTS mode with CMAC
                    put("camellia256-cts-cmac", EnumSet.of(EncryptionType.UNKNOWN));
                    put("camellia256-cts", EnumSet.of(EncryptionType.UNKNOWN));

                    // camellia128-cts-cmac camellia128-cts:   Camellia-128 CTS mode with CMAC
                    put("camellia128-cts-cmac", EnumSet.of(EncryptionType.UNKNOWN));
                    put("camellia128-cts", EnumSet.of(EncryptionType.UNKNOWN));

                    //des:   The DES family: des-cbc-crc, des-cbc-md5, and des-cbc-md4 (weak)
                    put("des", EnumSet.of(EncryptionType.DES_CBC_CRC, EncryptionType.DES_CBC_MD5,
                            EncryptionType.DES_CBC_MD4));

                    // des-cbc-md4: DES cbc mode with RSA-MD4 (weak)
                    put("des-cbc-md4", EnumSet.of(EncryptionType.DES_CBC_MD4));

                    // des-cbc-md5:   DES cbc mode with RSA-MD5 (weak)
                    put("des-cbc-md5", EnumSet.of(EncryptionType.DES_CBC_MD5));

                    // des-cbc-crc:   DES cbc mode with CRC-32 (weak)
                    put("des-cbc-crc", EnumSet.of(EncryptionType.DES_CBC_CRC));

                    // des-cbc-raw: DES cbc mode raw (weak)
                    put("des-cbc-raw", EnumSet.of(EncryptionType.UNKNOWN));

                    // des-hmac-sha1: DES with HMAC/sha1 (weak)
                    put("des-hmac-sha1", EnumSet.of(EncryptionType.UNKNOWN));

                    // des3:   The triple DES family: des3-cbc-sha1
                    put("des3", EnumSet.of(EncryptionType.DES3_CBC_SHA1_KD)); // Using DES3_CBC_SHA1_KD since DES3_CBC_SHA1 invalid key issues with KDC

                    // des3-cbc-raw:   Triple DES cbc mode raw (weak)
                    put("des3-cbc-raw", EnumSet.of(EncryptionType.UNKNOWN));

                    // des3-cbc-sha1 des3-hmac-sha1 des3-cbc-sha1-kd:   Triple DES cbc mode with HMAC/sha1
                    put("des3-cbc-sha1", EnumSet.of(EncryptionType.DES3_CBC_SHA1_KD)); // Using DES3_CBC_SHA1_KD since DES3_CBC_SHA1 invalid key issues with KDC
                    put("des3-hmac-sha1", EnumSet.of(EncryptionType.UNKNOWN));
                    put("des3-cbc-sha1-kd", EnumSet.of(EncryptionType.DES3_CBC_SHA1_KD));

                }
            });

    /**
     * The default set of ciphers to use for creating keytab entries
     */
    private static final Set<EncryptionType> DEFAULT_CIPHERS = Collections
            .unmodifiableSet(new HashSet<EncryptionType>() {
                {
                    add(EncryptionType.DES_CBC_MD5);
                    add(EncryptionType.DES3_CBC_SHA1_KD);
                    add(EncryptionType.RC4_HMAC);
                    add(EncryptionType.AES128_CTS_HMAC_SHA1_96);
                    add(EncryptionType.AES256_CTS_HMAC_SHA1_96);
                }
            });

    private KerberosCredential administratorCredentials = null;
    private String defaultRealm = null;
    private Set<EncryptionType> keyEncryptionTypes = new HashSet<EncryptionType>(DEFAULT_CIPHERS);
    private boolean open = false;

    /**
     * Create a secure (random) password using a secure random number generator and a set of (reasonable)
     * characters.
     *
     * @return a String containing the new password
     */
    public String createSecurePassword() {
        return createSecurePassword(SECURE_PASSWORD_LENGTH);
    }

    /**
     * Create a secure (random) password using a secure random number generator and a set of (reasonable)
     * characters.
     *
     * @param length an integer value declaring the length of the password to create,
     *               if <1, a default will be used.
     * @return a String containing the new password
     */
    public String createSecurePassword(int length) {
        StringBuilder passwordBuilder;

        // If the supplied length is less than 1 use the default value.
        if (length < 1) {
            length = SECURE_PASSWORD_LENGTH;
        }

        // Create a new StringBuilder and ensure its capacity is set for the length of the password to
        // be generated
        passwordBuilder = new StringBuilder(length);

        // For each character to be added to the password, (securely) generate a random number to pull
        // a random character from the character array
        for (int i = 0; i < length; i++) {
            passwordBuilder.append(SECURE_PASSWORD_CHARS[SECURE_RANDOM.nextInt(SECURE_PASSWORD_CHARS.length)]);
        }

        return passwordBuilder.toString();
    }

    /**
     * Prepares and creates resources to be used by this KerberosOperationHandler.
     * Implementation in this class is ignoring parameters ldapUrl and principalContainerDn and delegate to
     * <code>open(KerberosCredential administratorCredentials, String defaultRealm)</code>
     * Subclasses that want to use these parameters need to override this method.
     * <p/>
     * It is expected that this KerberosOperationHandler will not be used before this call.
     *
     * @param administratorCredentials a KerberosCredential containing the administrative credentials
     *                                 for the relevant KDC
     * @param defaultRealm             a String declaring the default Kerberos realm (or domain)
     * @param kerberosConfiguration    a Map of key/value pairs containing data from the kerberos-env configuration set
     */
    public abstract void open(KerberosCredential administratorCredentials, String defaultRealm,
            Map<String, String> kerberosConfiguration) throws KerberosOperationException;

    /**
     * Closes and cleans up any resources used by this KerberosOperationHandler
     * <p/>
     * It is expected that this KerberosOperationHandler will not be used after this call.
     */
    public abstract void close() throws KerberosOperationException;

    /**
     * Test to see if the specified principal exists in a previously configured KDC
     * <p/>
     * The implementation is specific to a particular type of KDC.
     *
     * @param principal a String containing the principal to test
     * @return true if the principal exists; false otherwise
     * @throws KerberosOperationException
     */
    public abstract boolean principalExists(String principal) throws KerberosOperationException;

    /**
     * Creates a new principal in a previously configured KDC
     * <p/>
     * The implementation is specific to a particular type of KDC.
     *
     * @param principal a String containing the principal to add
     * @param password  a String containing the password to use when creating the principal
     * @param service   a boolean value indicating whether the principal is to be created as a service principal or not
     * @return an Integer declaring the generated key number
     * @throws KerberosOperationException
     */
    public abstract Integer createPrincipal(String principal, String password, boolean service)
            throws KerberosOperationException;

    /**
     * Updates the password for an existing principal in a previously configured KDC
     * <p/>
     * The implementation is specific to a particular type of KDC.
     *
     * @param principal a String containing the principal to update
     * @param password  a String containing the password to set
     * @return an Integer declaring the new key number
     * @throws KerberosOperationException
     */
    public abstract Integer setPrincipalPassword(String principal, String password)
            throws KerberosOperationException;

    /**
     * Removes an existing principal in a previously configured KDC
     * <p/>
     * The implementation is specific to a particular type of KDC.
     *
     * @param principal a String containing the principal to remove
     * @return true if the principal was successfully removed; otherwise false
     * @throws KerberosOperationException
     */
    public abstract boolean removePrincipal(String principal) throws KerberosOperationException;

    /**
     * Tests to ensure the connection information and credentials allow for administrative
     * connectivity to the KDC
     *
     * @return true of successful; otherwise false
     * @throws KerberosOperationException if a failure occurs while testing the
     *                                    administrator credentials
     */
    public boolean testAdministratorCredentials() throws KerberosOperationException {
        if (!isOpen()) {
            throw new KerberosOperationException("This operation handler has not been opened");
        }

        KerberosCredential credentials = getAdministratorCredentials();
        if (credentials == null) {
            throw new KerberosOperationException("Missing KDC administrator credentials");
        } else {
            return principalExists(credentials.getPrincipal());
        }
    }

    /**
     * Create a keytab using the specified principal and password.
     *
     * @param principal a String containing the principal to test
     * @param password  a String containing the password to use when creating the principal
     * @param keyNumber a Integer indicating the key number for the keytab entries
     * @return the created Keytab
     * @throws KerberosOperationException
     */
    protected Keytab createKeytab(String principal, String password, Integer keyNumber)
            throws KerberosOperationException {

        if ((principal == null) || principal.isEmpty()) {
            throw new KerberosOperationException("Failed to create keytab file, missing principal");
        }

        if (password == null) {
            throw new KerberosOperationException(
                    String.format("Failed to create keytab file for %s, missing password", principal));
        }

        Set<EncryptionType> ciphers = new HashSet<EncryptionType>(keyEncryptionTypes);
        List<KeytabEntry> keytabEntries = new ArrayList<KeytabEntry>();
        Keytab keytab = new Keytab();

        if (!ciphers.isEmpty()) {
            // Create a set of keys and relevant keytab entries
            Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principal, password,
                    ciphers);

            if (keys != null) {
                byte keyVersion = (keyNumber == null) ? 0 : keyNumber.byteValue();
                KerberosTime timestamp = new KerberosTime();

                for (EncryptionKey encryptionKey : keys.values()) {
                    keytabEntries.add(new KeytabEntry(principal, 1, timestamp, keyVersion, encryptionKey));
                }

                keytab.setEntries(keytabEntries);
            }
        }

        return keytab;
    }

    /**
     * Create or append to a keytab file using keytab data from another keytab file.
     * <p/>
     * If the destination keytab file contains keytab data, that data will be merged with the new data
     * to create a composite set of keytab entries.
     *
     * @param sourceKeytabFile      a File containing the absolute path to the file with the keytab data to store
     * @param destinationKeytabFile a File containing the absolute path to where the keytab data is to be stored
     * @return true if the keytab file was successfully created; false otherwise
     * @throws KerberosOperationException
     * @see #createKeytabFile(org.apache.directory.server.kerberos.shared.keytab.Keytab, java.io.File)
     */
    protected boolean createKeytabFile(File sourceKeytabFile, File destinationKeytabFile)
            throws KerberosOperationException {
        return createKeytabFile(readKeytabFile(sourceKeytabFile), destinationKeytabFile);
    }

    /**
     * Create or append to a keytab file using the specified principal and password.
     * <p/>
     * If the destination keytab file contains keytab data, that data will be merged with the new data
     * to create a composite set of keytab entries.
     *
     * @param principal             a String containing the principal to test
     * @param password              a String containing the password to use when creating the principal
     * @param keyNumber             an Integer declaring the relevant key number to use for the keytabs entries
     * @param destinationKeytabFile a File containing the absolute path to where the keytab data is to be stored
     * @return true if the keytab file was successfully created; false otherwise
     * @throws KerberosOperationException
     * @see #createKeytabFile(org.apache.directory.server.kerberos.shared.keytab.Keytab, java.io.File)
     */
    protected boolean createKeytabFile(String principal, String password, Integer keyNumber,
            File destinationKeytabFile) throws KerberosOperationException {
        return createKeytabFile(createKeytab(principal, password, keyNumber), destinationKeytabFile);
    }

    /**
     * Create or append to a keytab file using the specified Keytab
     * <p/>
     * If the destination keytab file contains keytab data, that data will be merged with the new data
     * to create a composite set of keytab entries.
     *
     * @param keytab                the Keytab containing the data to add to the keytab file
     * @param destinationKeytabFile a File containing the absolute path to where the keytab data is to be stored
     * @return true if the keytab file was successfully created; false otherwise
     * @throws KerberosOperationException
     */
    protected boolean createKeytabFile(Keytab keytab, File destinationKeytabFile)
            throws KerberosOperationException {

        if (destinationKeytabFile == null) {
            throw new KerberosOperationException("The destination file path is null");
        }

        try {
            mergeKeytabs(readKeytabFile(destinationKeytabFile), keytab).write(destinationKeytabFile);
            return true;
        } catch (IOException e) {
            String message = "Failed to export keytab file";
            LOG.error(message, e);

            if (!destinationKeytabFile.delete()) {
                destinationKeytabFile.deleteOnExit();
            }

            throw new KerberosOperationException(message, e);
        }
    }

    /**
     * Merge the keytab data from one keytab with the keytab data from a different keytab.
     * <p/>
     * If similar key entries exist for the same principal, the updated values will be used
     *
     * @param keytab  a Keytab with the base keytab data
     * @param updates a Keytab containing the updated keytab data
     * @return a Keytab with the merged data
     */
    protected Keytab mergeKeytabs(Keytab keytab, Keytab updates) {
        List<KeytabEntry> keytabEntries = (keytab == null) ? Collections.<KeytabEntry>emptyList()
                : new ArrayList<KeytabEntry>(keytab.getEntries());
        List<KeytabEntry> updateEntries = (updates == null) ? Collections.<KeytabEntry>emptyList()
                : new ArrayList<KeytabEntry>(updates.getEntries());
        List<KeytabEntry> mergedEntries = new ArrayList<KeytabEntry>();

        if (keytabEntries.isEmpty()) {
            mergedEntries.addAll(updateEntries);
        } else if (updateEntries.isEmpty()) {
            mergedEntries.addAll(keytabEntries);
        } else {
            Iterator<KeytabEntry> iterator = keytabEntries.iterator();

            while (iterator.hasNext()) {
                KeytabEntry keytabEntry = iterator.next();

                for (KeytabEntry entry : updateEntries) {
                    if (entry.getPrincipalName().equals(keytabEntry.getPrincipalName())
                            && entry.getKey().getKeyType().equals(keytabEntry.getKey().getKeyType())) {
                        iterator.remove();
                        break;
                    }
                }
            }

            mergedEntries.addAll(keytabEntries);
            mergedEntries.addAll(updateEntries);
        }

        Keytab mergedKeytab = new Keytab();
        mergedKeytab.setEntries(mergedEntries);
        return mergedKeytab;
    }

    /**
     * Reads a file containing keytab data into a new Keytab
     *
     * @param file A File containing the path to the file from which to read keytab data
     * @return a Keytab or null if the file was not readable
     */
    protected Keytab readKeytabFile(File file) {
        Keytab keytab;

        if (file.exists() && file.canRead() && (file.length() > 0)) {
            try {
                keytab = Keytab.read(file);
            } catch (IOException e) {
                // There was an issue reading in the existing keytab file... quietly assume no data
                keytab = null;
            }
        } else {
            keytab = null;
        }

        return keytab;
    }

    public KerberosCredential getAdministratorCredentials() {
        return administratorCredentials;
    }

    /**
     * Sets the administrator credentials for this KerberosOperationHandler.
     * <p/>
     * If the supplied {@link KerberosCredential} is not <code>null</code>, validates that the administrator
     * principal is not <code>null</code> or empty and that either the password or the keytab value
     * is not <code>null</code> or empty. If the credential value does not validate, then a
     * {@link KerberosAdminAuthenticationException} will be thrown.
     *
     * @param administratorCredentials the relevant KerberosCredential
     * @throws KerberosAdminAuthenticationException if the non-null KerberosCredential fails contain
     *                                              a non-empty principal and a non-empty password or
     *                                              keytab value.
     */
    public void setAdministratorCredentials(KerberosCredential administratorCredentials)
            throws KerberosAdminAuthenticationException {

        // Ensure the KerberosCredential is not null
        if (administratorCredentials == null) {
            throw new KerberosAdminAuthenticationException("The administrator credential must not be null");
        }

        String value;

        // Ensure the principal is not null or empty
        value = administratorCredentials.getPrincipal();
        if ((value == null) || value.isEmpty()) {
            throw new KerberosAdminAuthenticationException("Must specify a principal but it is null or empty");
        }

        // Ensure either the password or the keytab value is not null or empty
        value = administratorCredentials.getPassword();
        if ((value == null) || value.isEmpty()) {
            value = administratorCredentials.getKeytab();

            if ((value == null) || value.isEmpty()) {
                throw new KerberosAdminAuthenticationException(
                        "Must specify either a password or a keytab but both are null or empty");
            }
        }

        this.administratorCredentials = administratorCredentials;
    }

    public String getDefaultRealm() {
        return defaultRealm;
    }

    public void setDefaultRealm(String defaultRealm) {
        this.defaultRealm = defaultRealm;
    }

    /**
     * Gets the encryption algorithms used to encrypt keys in keytab entries
     *
     * @return a Set of EncryptionKey values indicating which algorithms are to be used when
     * encrypting keys for keytab entries.
     */
    public Set<EncryptionType> getKeyEncryptionTypes() {
        return keyEncryptionTypes;
    }

    /**
     * Sets the encryption algorithms to use to encrypt keys in keytab entries
     * <p/>
     * If set to <code>null</code> the default set of ciphers will be used.  See {@link #DEFAULT_CIPHERS}
     *
     * @param keyEncryptionTypes a Set of EncryptionKey values or null to indicate the default set
     */
    public void setKeyEncryptionTypes(Set<EncryptionType> keyEncryptionTypes) {
        this.keyEncryptionTypes = new HashSet<EncryptionType>(
                (keyEncryptionTypes == null) ? DEFAULT_CIPHERS : keyEncryptionTypes);
    }

    /**
     * Test this KerberosOperationHandler to see whether is was previously open or not
     *
     * @return a boolean value indicating whether this KerberosOperationHandler was open (true) or not (false)
     */
    public boolean isOpen() {
        return open;
    }

    /**
     * Sets whether this KerberosOperationHandler is open or not.
     *
     * @param open a boolean value indicating whether this KerberosOperationHandler was open (true) or not (false)
     */
    public void setOpen(boolean open) {
        this.open = open;
    }

    /**
     * Given base64-encoded keytab data, decode the String to binary data and write it to a (temporary)
     * file.
     * <p/>
     * Upon success, a new file is created.  The caller is expected to clean up this file when done
     * with it.
     *
     * @param keytabData a String containing base64-encoded keytab data
     * @return a File pointing to the decoded keytab file or null if not successful
     * @throws KerberosOperationException
     */
    protected File createKeytabFile(String keytabData) throws KerberosOperationException {
        boolean success = false;
        File tempFile = null;

        // Create a temporary file
        try {
            tempFile = File.createTempFile("temp", ".dat");
        } catch (IOException e) {
            LOG.error(String.format("Failed to create temporary keytab file: %s", e.getLocalizedMessage()), e);
        }

        if ((tempFile != null) && (keytabData != null)) {
            OutputStream fos = null;

            // Decoded the base64-encoded String and write it to the temporary file
            try {
                fos = new FileOutputStream(tempFile);
                fos.write(Base64.decodeBase64(keytabData));
                success = true;
            } catch (FileNotFoundException e) {
                String message = String.format("Failed to write to temporary keytab file %s: %s",
                        tempFile.getAbsolutePath(), e.getLocalizedMessage());
                LOG.error(message, e);
                throw new KerberosOperationException(message, e);
            } catch (IOException e) {
                String message = String.format("Failed to write to temporary keytab file %s: %s",
                        tempFile.getAbsolutePath(), e.getLocalizedMessage());
                LOG.error(message, e);
                throw new KerberosOperationException(message, e);
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        // Ignore this...
                    }
                }

                // If there was an issue, clean up the file
                if (!success) {
                    if (!tempFile.delete()) {
                        tempFile.deleteOnExit();
                    }

                    tempFile = null;
                }
            }
        }

        return tempFile;
    }

    /**
     * Executes a shell command.
     * <p/>
     * See {@link org.apache.ambari.server.utils.ShellCommandUtil#runCommand(String[])}
     *
     * @param command an array of String value representing the command and its arguments
     * @return a ShellCommandUtil.Result declaring the result of the operation
     * @throws KerberosOperationException
     */
    protected ShellCommandUtil.Result executeCommand(String[] command) throws KerberosOperationException {

        if ((command == null) || (command.length == 0)) {
            return null;
        } else {
            try {
                return ShellCommandUtil.runCommand(command);
            } catch (IOException e) {
                String message = String.format("Failed to execute the command: %s", e.getLocalizedMessage());
                LOG.error(message, e);
                throw new KerberosOperationException(message, e);
            } catch (InterruptedException e) {
                String message = String.format("Failed to wait for the command to complete: %s",
                        e.getLocalizedMessage());
                LOG.error(message, e);
                throw new KerberosOperationException(message, e);
            }
        }
    }

    /**
     * Given a principal, attempt to create a new DeconstructedPrincipal
     *
     * @param principal a String containing the principal to deconstruct
     * @return a DeconstructedPrincipal
     * @throws KerberosOperationException
     */
    protected DeconstructedPrincipal createDeconstructPrincipal(String principal)
            throws KerberosOperationException {
        try {
            return DeconstructedPrincipal.valueOf(principal, getDefaultRealm());
        } catch (IllegalArgumentException e) {
            throw new KerberosOperationException(e.getMessage(), e);
        }
    }

    /**
     * Given a cipher (or algorithm) name, attempts to translate it into an EncryptionType value.
     * <p/>
     * If a translation is not able to be made, {@link org.apache.directory.shared.kerberos.codec.types.EncryptionType#UNKNOWN}
     * is returned.
     *
     * @param name a String containing the name of the cipher to translate
     * @return an EncryptionType
     */
    protected Set<EncryptionType> translateEncryptionType(String name) {
        Set<EncryptionType> encryptionTypes = null;

        if ((name != null) && !name.isEmpty()) {
            encryptionTypes = ENCRYPTION_TYPE_TRANSLATION_MAP.get(name.toLowerCase());
        }

        return (encryptionTypes == null) ? Collections.<EncryptionType>emptySet() : encryptionTypes;
    }

    /**
     * Given a delimited set of encryption type names, attempts to translate into a set of EncryptionType
     * values.
     *
     * @param names     a String containing a delimited list of encryption type names
     * @param delimiter a String declaring the delimiter to use to split names, if null, " " is used.
     * @return a Set of EncryptionType values
     */
    protected Set<EncryptionType> translateEncryptionTypes(String names, String delimiter) {
        Set<EncryptionType> encryptionTypes = new HashSet<EncryptionType>();

        if ((names != null) && !names.isEmpty()) {
            for (String name : names.split((delimiter == null) ? "\\s+" : delimiter)) {
                encryptionTypes.addAll(translateEncryptionType(name.trim()));
            }
        }

        return encryptionTypes;
    }

    /**
     * Iterates through the characters in a string to escape special characters
     *
     * @param string             the String to process
     * @param charactersToEscape a Set of characters declaring the special characters to escape
     * @param escapeCharacter    a character to use for escaping
     * @return the string with escaped characters
     */
    protected String escapeCharacters(String string, Set<Character> charactersToEscape, Character escapeCharacter) {
        if ((string == null) || string.isEmpty() || (charactersToEscape == null) || charactersToEscape.isEmpty()) {
            return string;
        } else {
            StringBuilder builder = new StringBuilder();

            for (char character : string.toCharArray()) {
                if (charactersToEscape.contains(character)) {
                    builder.append(escapeCharacter);
                }
                builder.append(character);
            }

            return builder.toString();
        }
    }
}