Random Client Certificate Passwords

Random Client Certificate Passwords
Photo by Jason Dent / Unsplash

If you're familiar with the certificate exchange within TAK you already know that soft certificate generation or certificate auto-enrollment methods use a commonly shared password. In my TAK Security Best Practice article, I mention changing the default certificate password since it's a very common password and is included in the data package for TAK Server configuration at the end user. When you think about security you may want to add obfuscation and complexity within your deployment but it comes at a cost to the availability to the customer or simply the administration.

So why did I create this script? 👀

The creation of this script spawned from some conversations regarding Public Key Infrastructure (PKI) and the sharing of the certificate password. What do you do if the password is shared outside the trusted circle or compromised? Well, things like insider threats and the human factor will always have a vote. One way we can mitigate things is to use a random password in our deployment. What really matters is the certificate(s) within this trusted file which establish our SSL Handshake and client-to-server communication. You may ask what's the risk of not doing this? Nothing other than adding an additional layer of security within your environment.

What's the bottom line up front: Don't share common certificates and passwords.

The Basics

With any other script I do, we add a comment/title block that provides some simple information. Since we are working with another bash/shell script we add the shell interpreter language #!/bin/bash to our first line. Under that add our environmental variables, this way they are all in one location.

dir=/opt/tak
certs=$dir/certs/files
curuser=$(printenv SUDO_USER)

The reason we have printenv SUDO_USER is that when we execute the command we need to invoke root as part of the process. In the end process, we are going to copy our compiled certificate to our home directory. However, since we invoked root that home directory will be /root and not /home/useraccount. This command will pull the user who invoked root and save it as our variable for later on.

Next, I want to verify the user is root or can invoke root and then display the script usage options as well if something is wrong or not values are entered. This can be referenced using the [ "$UID" -eq 0 ] || exec sudo "$0" "$@" command. I'm sure there is probably another way to do this but I've found this effective in my deployments.

# Determine if the user is root; else, use sudo
[ "$UID" -eq 0 ] || exec sudo "$0" "$@"

Common Tasks

Here instead of repeating the same commands I declare them as a function so that I can call them later on in the script. Here the display_validation is displayed at the end of each successful execution that displays the command to validate our new PKCS12 certificate file.

The second function generates our random password using OpenSSL rand option. Additionally, we want to only include values that are commonly used or accepted by passwords (special characters). For this, we are using the tr -dc command with our accepted values. This is annotated by 'a-zA-Z0-9!"#$%&'''()*+,-./:;<=>?@[]^_`{|}~'. If we only wanted to accept alphanumeric letters we would only include 'a-zA-Z0-9'.

# Display at the end of each command
display_validation(){
    printf "\nTo verify the new PKCS12 use the following command\n"
    echo "openssl pkcs12 -info -in $client.p12"
}

# Generate a random password each run
display_password(){
    randpwd=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~')
    echo "New Certificate Password is:  $randpwd"
}

Input Validation

For input validation, we give the option to declare a variable on execution or prompt for one. If we are given a variable on execution we declare that as our new client variable used within the script. Otherwise, the prompt input passed creates our client variable on entry. The second part of our input validation is executed prior to our certificate generation. This verification first checks if the client certificate exists and displays a message when the requested client certificate is not found.

# Determine if the first variable is declared
if [[ -n $1 ]]; then
    client=$1
else
    read -p 'Specify the client certificate to randomize: ' client
fi

OpenSSL Legacy Providers

This command checks if we have an older version of OpenSSL installed. More information regarding this check and why can be found here.

# Check for legacy providers REF makeCert
openssl list -providers 2>&1 | grep "\(invalid command\|unknown option\)" >/dev/null
if [[ $? -ne 0 ]] ; then
  echo "Using legacy provider"
  LEGACY_PROVIDER="-legacy"
fi

Certificate Generation

As mentioned in input validation, we want to ensure that our client certificate exists first. Upon successful verification, we want to generate our random password and display this using the display_password function referenced earlier. Our next command will apply our new password to our new PKCS12 certificate. The command syntax asks for the certificate format and determines if it is a legacy provider. This -export tells us that we want to create a new certificate based on our input values.

The first logical check is to determine if the input is for our Root certificate authority, by default this is our truststore-root file. Since this is our first certificate in our PKI environment the creation of this certificate is a bit different. Next, we inspect if the input is calling for our Intermediate CA. This is commonly referred to as the enterprise issuing CA since it typically is the one that signs and issues our certificate signing requests.

The format of our Intermediate CA is identified by truststore identification as well but is followed by the CA name. Since this file is changed throughout the process we need to strip the beginning of our input to only include the CA name only. To do this we create a new variable truststore and call the client variable with the numerical number 11 to strip the first 11 characters in our input. This leaves us with our CA name only to make things easier. This can be emulated like this:

client=truststore-myCA.p12
truststore="${client:11}"
echo $truststore
Example substring execution

After our Intermediate CA check is the client certificate check which can include our server certificate as well if we want. For each of our validation checks using OpenSSL, the syntax is again common. openssl pkcs12 -export -in <commonname>.pem -out <commonname>.p12 -passin pass:<password> -passsout pass:<password>. The -passin is required since we are including the private key of the certificate. Whereas the Root and Intermediate CA only include the public certificate(s) only. The -passout is what we use to password protect our certificate for both the public certificates only and then the certificate bundle that includes both our public certificate and private key.

# Check if the client input is equal to the Root CA
if [[ "$client" == "truststore-root" ]]; then
    display_password
    openssl pkcs12 ${LEGACY_PROVIDER} -export -in $certs/ca-trusted.pem -out /home/"$curuser"/truststore-root.p12 -nokeys -passout pass:${randpwd}
    display_validation
# Check if the client input is equal to the Intermediate CA
elif [[ "$client" == "truststore-"* ]]; then
# Remove truststore- from our client input
    truststore="${client:11}"
# Check if the Intermediate CA exists
    if [[ -f $certs/$truststore.pem ]]; then
        display_password
        openssl pkcs12 ${LEGACY_PROVIDER} -export -in $certs/"$truststore"-trusted.pem -out /home/"$curuser"/truststore-"$truststore".p12 -nokeys -passout pass:${randpwd}
        display_validation
    else
# Intermediate CA is not found
        echo "Client Certificate: $client not found."
        exit 0
    fi
else
# Check if client certificate exists
    if [[ -f $certs/$client.p12 ]]; then
        clientcert=$certs/$client
        display_password
        openssl pkcs12 ${LEGACY_PROVIDER} -export -in "${clientcert}".pem -inkey "${clientcert}".key -out /home/"$curuser"/"${client}".p12 -name "${client}" -CAfile ca.pem -passin pass:${PASS} -passout pass:${randpwd}
        display_validation
    else
# Client Certificate not found
        echo "Client Certificate: $client not found."
        exit 0
    fi
fi

Putting it all together

After we have compiled the various sections of our script we need to make our script executable. To do this we will use the chmod with the +x option to add the execute attribute to our file.

chmod +x genRandpwdCert.sh

The Code

#!/bin/bash
#Version 1.1
#JR @myTeckNet.com
#Global Variables
dir=/opt/tak
certs=$dir/certs/files
curuser=$(printenv SUDO_USER)

# Determine if the user is root; else, use sudo
[ "$UID" -eq 0 ] || exec sudo "$0" "$@"

# Read the cert-metadata.sh file
. $dir/certs/cert-metadata.sh

# Determine if the first variable is declared
if [[ -n $1 ]]; then
    client=$1
else
    read -p 'Specify the client certificate to randomize: ' client
fi

# Display at the end of each command
display_validation(){
    printf "\nTo verify the new PKCS12 use the following command\n"
    echo "openssl pkcs12 -info -in $client.p12"
}

# Generate a random password each run
display_password(){
    randpwd=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~')
    echo "New Certificate Password is:  $randpwd"
}

# Check for legacy providers REF makeCert
openssl list -providers 2>&1 | grep "\(invalid command\|unknown option\)" >/dev/null
if [[ $? -ne 0 ]] ; then
  echo "Using legacy provider"
  LEGACY_PROVIDER="-legacy"
fi

# Check if the client input is equal to the Root CA
if [[ "$client" == "truststore-root" ]]; then
    display_password
    openssl pkcs12 ${LEGACY_PROVIDER} -export -in $certs/ca-trusted.pem -out /home/"$curuser"/truststore-root.p12 -nokeys -passout pass:${randpwd}
    display_validation
# Check if the client input is equal to the Intermediate CA
elif [[ "$client" == "truststore-"* ]]; then
# Remove truststore- from our client input
    truststore="${client:11}"
# Check if the Intermediate CA exists
    if [[ -f $certs/$truststore.pem ]]; then
        display_password
        openssl pkcs12 ${LEGACY_PROVIDER} -export -in $certs/"$truststore"-trusted.pem -out /home/"$curuser"/truststore-"$truststore".p12 -nokeys -passout pass:${randpwd}
        display_validation
    else
# Intermediate CA is not found
        echo "Client Certificate: $client not found."
        exit 0
    fi
else
# Check if client certificate exists
    if [[ -f $certs/$client.p12 ]]; then
        clientcert=$certs/$client
        display_password
        openssl pkcs12 ${LEGACY_PROVIDER} -export -in "${clientcert}".pem -inkey "${clientcert}".key -out /home/"$curuser"/"${client}".p12 -name "${client}" -CAfile ca.pem -passin pass:${PASS} -passout pass:${randpwd}
        display_validation
    else
# Client Certificate not found
        echo "Client Certificate: $client not found."
        exit 0
    fi
fi
# Cleanup, reset ownership
chown -R "$curuser":"$curuser" /home/"$curuser"

Example Outputs

Example Outputs