在 windows 上通过 bash 脚本运行 Opensl-Subject 不以’/’开头

在我的剧本里,我有:

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}"

在 Windows 上运行 Git Bash 3.1可以得到:

Subject does not start with '/'.

试着像这样逃避主题: - subj”/C = UK/ST = where/L = 普罗沃/O = Achme/CN = ${ FQDN }

还是没用,有办法吗?

45642 次浏览

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.

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).