Error executing openssl command from bash but works from command line

I deal a lot with SSL certificates, private keys, CSRs, etc. I have a number of small bash scripts that automate my tasks. One of these scripts generates a new private key and CSR for an SSL certificate, using openssl. The script is effectively just one line plus error checking – to save me typing:

#!/usr/bin/env bash

if [[ $# -ne 1 ]]; then
    echo "Format: $0 hostname"
    echo "   e.g. $0 www.example.com"
    exit 2
fi

openssl req -new -newkey rsa:2048 -nodes -keyout "$1.key" -out "$1.csr" 
            -subj "/C=GB/ST=County/L=City/O=My Company Ltd./CN=$1"

cat "$1.csr"

This has been working fine. However recently I need more and more certs with SANs added, so I modified the script like this:

#!/usr/bin/env bash

if [[ $# -eq 0 ]]; then
    echo "Format: $0 hostname"
        echo "   e.g. $0 www.example.com [SAN1 SAN2 ...]"
        exit 2
fi

SANS=""
if [[ $# -gt 1 ]]; then
    SANS=""
    for san in "${@:2}"; do
        SANS="${SANS}DNS:${san}, "
    done
    SANS="-addext "subjectAltName = ${SANS%,*}""
fi


openssl req -new -newkey rsa:2048 -nodes -keyout "$1.key" -out "$1.csr" 
            -subj "/C=GB/ST=County/L=City/O=My Company Ltd./CN=$1" 
            $SANS

cat "$1.csr"

Now the script doesn’t work any more, simply producing

req: Use -help for summary.

I modified the last bit of the script like so:

CMD="openssl req -new -newkey rsa:2048 -nodes -keyout "$1.key" -out "$1.csr" 
            -subj "/C=GB/ST=County/L=City/O=My Company Ltd./CN=$1" 
            $SANS"
echo $CMD
$CMD

This allowed me to see the command before it’s executed. The command is correct and when I copy/paste this command into the bash prompt, I do get the right results. So what causes this command to fail when executing from the script?

Asked By: Aleks G

||

Your issue is your SANS variable, which is a string. You use the variable unquoted, which means the shell will split it on whitespace (and apply filename globbing on all the split-up parts).

Instead of storing multiple separate strings in one string, use an array:

SANS=()
if [[ $# -gt 1 ]]; then
    subjAltNames=("${@:2}")
    IFS=,
    SANS=(-addext "subjectAltName = ${subjAltNames[*]}")
fi

If $# is greater than 1, this gives you SANS as an array containing the two elements -addext and the string subjectAltNames = followed by a comma-delimited list of strings from the command line arguments. The comma-delimited list is created from another array, subjAltNames, and by using it as ${subjAltNames[*]}, its elements are concatenated with the first character of $IFS as the delimiter (this is why we set IFS to a comma beforehand).

You would later use SANS as "${SANS[@]}" to correctly access the list of individually quoted elements:

openssl req -new -newkey rsa:2048 -nodes -keyout "$1.key" -out "$1.csr" 
            -subj "/C=GB/ST=County/L=City/O=My Company Ltd./CN=$1" 
            "${SANS[@]}"

If the SANS array is empty, it will not contribute an empty argument to the end of the list of arguments.

Answered By: Kusalananda
Categories: Answers Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.