94

In my script I have:

openssl req \
  -x509 \
  -new \
  -nodes \
  -key certs/ca/my-root-ca.key.pem \
  -days 3652 \
  -out certs/ca/my-root-ca.crt.pem \
  -subj "/C=GB/ST=someplace/L=Provo/O=Achme/CN=${FQDN}"

Running this on Windows in Git Bash 3.1 gives:

Subject does not start with '/'.

Tried escaping the subj like so: -subj \"/C=UK/ST=someplace/L=Provo/O=Achme/CN=${FQDN}\"

Still doesn't work. Any ideas?

iss42
  • 2,720
  • 3
  • 21
  • 37
  • 1
    Standard first question: does your script file have DOS/Windows-style line endings (carriage return + linefeed), or unix-style (just linefeed)? Try printing the script with `cat -vet /path/to/script`, and see if the lines end with '^M$' (Windows-style) or just '$' (unix-style). – Gordon Davisson Jul 19 '15 at 21:45
  • 1
    This is a bash script? Run under what environment? What does adding `set -vx` to the top of the script show is being run for this line? – Etan Reisner Jul 20 '15 at 03:04
  • @EtanReisner `set -vx` is useful thanks! Environment is Windows, Git bash 3.1. With **-vx**, I get `+ openssl req -x509 -new -nodes -key certs/ca/my-root-ca.key.pem -days 3652 -out certs/ca/my-root-ca.crt.pem -subj /C=GB/ST=someplace/L=Provo/O=Achme/CN=domain.com` which shows the un-quoted `-subj` string. But I can't work out how to get this into a quoted form from the script. – iss42 Jul 20 '15 at 21:47
  • @GordonDavisson thanks! script has '^M$' line endings – iss42 Jul 20 '15 at 21:49
  • 1
    An unquoted argument in the `-vx` output is not surprising or a problem. The quotes are for the shell parsing not the command execution itself. That output looks correct to me. DOS line endings are generally not a good idea but don't appear to have caused any problems here (unless removing them fixes the problem in which case I'm a bit confused by the error message). – Etan Reisner Jul 20 '15 at 22:17
  • @EtanReisner thanks, removed them and tried again, same problem. Problem is with Git Bash, if I run the same command on the command line it gives the same error. In DOS the same command works. – iss42 Jul 21 '15 at 12:24
  • You can copy and paste the command between git bash and the cmd.exe prompt and it will work in cmd.exe and fail in git bash? That's very interesting. – Etan Reisner Jul 21 '15 at 12:40
  • This problem occur even if you use acme.sh (client for managing Let's Encrypt certificates) via git-bash.exe. You have to change **-subj "/CN=$_csr_cn"** to **-subj "//CN=$_csr_cn"** in acme.sh script – mikep Feb 10 '17 at 08:22
  • When I had this problem, this answer was the solution: https://stackoverflow.com/a/34444840/770927 – zovits Aug 25 '22 at 13:44

2 Answers2

226

This issue is specific to MinGW/MSYS which is commonly used as part of the Git for Windows package.

The solution is to pass the -subj argument with leading // (double forward slashes) and then use \ (backslashes) to separate the key/value pairs. Like this:

"//O=Org\CN=Name"

This will then be magically passed to openssl in the expected form:

"/O=Org/CN=Name"

So to answer the specific question, you should change the -subj line in your script to the following.

-subj "//C=GB\ST=someplace\L=Provo\O=Achme\CN=${FQDN}"

That should be all you need.

What is this magic?

For those curious about exactly what is going on here, I can explain this mystery. The reason is that MSYS reasonably assumes that arguments containing slashes are actually paths. And when those arguments are passed to an executable that haven't been compiled specifically for MSYS (like openssl in this case) then it will convert POSIX paths to Win32 paths. The rules for this conversion are quite complex as MSYS tries its best to cover most common scenarios for interoperability. This also explains why using openssl from a windows command prompt (cmd.exe) works fine, because no magical conversions are made.

You can test the conversion like this.

$ cmd //c echo "/CN=Name"
"C:/Program Files (x86)/Git/CN=Name"

We can't use the echo executable that comes with MSYS since it was compiled for MSYS, instead we'll use the echo builtin in cmd. Notice that since cmd switches starts with / (common for windows commands) we need to handle that with double slashes. As we can see in the output the argument was expanded to a windows path and it becomes clear why openssl does indeed claim that Subject does not start with '/'..

Let's see some more conversions.

$ cmd //c echo "//CN=Name"
/CN=Name

Double slashes makes MSYS believe the argument is a windows style switch which results in stripping a / only (no path conversion). You would think that with this we could just use slashes to add more key/value pairs. Let's try that.

$ cmd //c echo "//O=Org/CN=Name"
//O=Org/CN=Name

Suddenly the double slashes in the start isn't stripped down. This is because now, with a slash following the initial double slashes, MSYS thinks we are referencing a UNC path (e.g. //server/path). If this was passed to openssl it would skip the first key/value saying Subject Attribute /O has no known NID, skipped.

Here is the relevant rule from the MinGW wiki explaining this behavior:

  • An argument starting with 2 or more / is considered an escaped Windows style switch and will be passed with the leading / removed and all \ changed to /.
    • Except that if there is a / following the leading block of /, the argument is considered to be a UNC path and the leading / is not removed.

In this rule we can see the method we could use to create the argument we want. Since all \ that follows in an argument starting with // will be converted to plain /. Let's try that out.

$ cmd //c echo "//O=Org\CN=Name"
/O=Org/CN=Name

And as we can see it does work.

Hope this demystifies the magic a little bit.

Korroz
  • 2,276
  • 1
  • 12
  • 6
  • 5
    What if I use the same `bash`-script to generate keys in linux environment? How would be interpreted that leading double slashes and backslashes in the middle of the line? – Tomilov Anatoliy Apr 11 '17 at 05:33
  • 3
    @Orient Linux needs the slashes in the other direction so you'll need to detect which type of system it is running on - here's an answer that uses a `case` statement and `uname -s` to detect the environment, which you can then use with an `if` to use the appropriate slashes - https://stackoverflow.com/questions/3466166/how-to-check-if-running-in-cygwin-mac-or-linux – Tim Lewis Oct 25 '17 at 08:01
  • Totally awesome. I got caught with the same issue and had totally forgotten about the POSIX to Win32 path conversion. Kept thinking I was quoting it wrong. – davewasthere Apr 17 '19 at 02:29
0

I personally found this to be specific to the OpenSSL binary in use. On my system using msys2/mingw64 I've noticed that two different OpenSSL binaries are present, for example:

$ whereis openssl; echo; which openssl
openssl: /usr/bin/openssl.exe /usr/lib/openssl /mingw64/bin/openssl.exe /usr/share/man/man1/openssl.1ssl.gz

/mingw64/bin/openssl

I believe it to be the use of /mingw64/bin/openssl that requires using a subject that begins with //, however I'm not sure if this is specific to the package/build or the version of OpenSSL so to be sure, the version of each binary is below:

$ while read -r _openSslBin; do printf "${_openSslBin}: "; ${_openSslBin} version; done < <(whereis openssl | egrep -o '[^ ]+?\.exe ')
/usr/bin/openssl.exe: OpenSSL 1.0.2p  14 Aug 2018
/mingw64/bin/openssl.exe: OpenSSL 1.1.1  11 Sep 2018

I've found the following example of bash code to select the correct binary based on the OpenSSL version when using msys/mingw to work on my machine:

# determine openssl binary to use based on OS
# -------------------------------------------
_os="$(uname -s | awk 'BEGIN{FS="_"} {print $1}' | egrep -o '[A-Za-z]+')"
if [ "${_os,,}" = "mingw" ] || [ "${_os,,}" == "msys" ]; then
  while read -r _currentOpenSslBin; do
    if [[ "$(${_currentOpenSslBin}  version | awk '{print $2}')" =~ ^(1\.0\.[0-9].*|0\.\9\.8.*)$ ]]; then
      _openSslBin="${_currentOpenSslBin}"
    fi
  done < <(whereis openssl | egrep -o '\/[^ ]+?\.exe ' | egrep -v 'mingw')
  if [ -n "${_openSslBin}" ]; then
    printf "OpenSSL Binary: ${_openSslBin} (v. $(${_openSslBin}  version | awk '{print $2}'))\n"
  else
    printf "Unable to find compatible version of OpenSSL for use with '${_os}' OS, now exiting...\n"
    exit 1
  fi
else
  _openSslBin="openssl"
fi

# display selected openssl binary and it's version
# ------------------------------------------------
printf "${_openSslBin}: "; ${_openSslBin} version

In addition to fixing issues with passing the subject string I also found this to resolve issues with the size of the DN (I passed a custom openssl.cnf with a policy that did not set a max_size for any of the fields and that still had problems when using /mingw64/bin/openssl.exe).

Rob Frey
  • 1
  • 3