web-dev-qa-db-ja.com

WindowsでbashスクリプトからOpensslを実行する-件名が「/」で始まらない

私のスクリプトには次のものがあります:

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

Git Bash 3.1のWindowsでこれを実行すると、以下が得られます。

Subject does not start with '/'.

次のようにsubjをエスケープしようとしました:-subj\"/ C = UK/ST = someplace/L = Provo/O = Achme/CN = $ {FQDN} \"

それでも動作しません。何か案は?

68
iss42

この問題は、MinGW/MSYSに固有のもので、WindowsのGitの一部として一般的に使用されていますパッケージ。

解決策は、-subj引数を先頭に//(二重スラッシュ)を付けて渡し、\(バックスラッシュ)を使用してキー/値のペアを区切ることです。このような:

"//O=Org\CN=Name"

これは、予想される形式でopensslに魔法のように渡されます。

"/O=Org/CN=Name"

したがって、特定の質問に答えるには、スクリプトの-subj行を次のように変更する必要があります。

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

必要なのはこれだけです。

この魔法とは何ですか?

ここで何が起こっているのか正確に知りたい人のために、私はこの謎を説明することができます。その理由は、スラッシュを含む引数は実際にはパスであるとMSYSが合理的に想定しているためです。そして、これらの引数がMSYS専用にコンパイルされていない実行可能ファイル(この場合opensslなど)に渡されると、 POSIXパスをWin32パスに変換 になります。 MSYSは相互運用性の最も一般的なシナリオをカバーしようと最善を尽くしているため、この変換のルールは非常に複雑です。これは、魔法の変換が行われないため、Windowsコマンドプロンプト(cmd.exe)からopensslを使用することで問題なく動作する理由も説明しています。

このように変換をテストできます。

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

MSYS用にコンパイルされているため、MSYSに付属するecho実行可能ファイルは使用できません。代わりに、echoに組み込まれているcmdを使用します。 cmdスイッチは/(Windowsコマンドに共通)で始まるため、ダブルスラッシュで処理する必要があることに注意してください。出力からわかるように、引数はWindowsパスに展開され、opensslが実際にSubject does not start with '/'.であると主張する理由が明らかになります。

さらにコンバージョンを見てみましょう。

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

ダブルスラッシュにより、MSYSは、引数が/のみ(パス変換なし)を除去するWindowsスタイルスイッチであると見なします。これにより、スラッシュを使用してキー/値のペアを追加できると思うでしょう。それを試してみましょう。

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

突然、開始時のダブルスラッシュは削除されません。これは、最初のダブルスラッシュに続くスラッシュで、MSYSがUNCパス(// server/pathなど)を参照していると考えるためです。これがopensslに渡されると、Subject Attribute /O has no known NID, skippedという最初のキー/値がスキップされます。

この動作を説明する MinGW wiki の関連ルールは次のとおりです。

  • 2つ以上の/で始まる引数は、エスケープされたWindowsスタイルスイッチと見なされ、先頭の/が削除され、すべての\が/.に変更されて渡されます。
    • /の先頭ブロックの後に/がある場合を除き、引数はUNCパスと見なされ、先頭の/は削除されません。

このルールでは、必要な引数を作成するために使用できるメソッドを確認できます。 \で始まる引数に続くすべての//は、単純な/に変換されるためです。それを試してみましょう。

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

そして、見てわかるように、それは機能します。

これが魔法を少し分かりやすくすることを願っています。

157
Korroz

私は、これが使用中のOpenSSLバイナリに固有のものであると個人的に知りました。 msys2/mingw64を使用しているシステムでは、次の2つの異なるOpenSSLバイナリが存在することに気付きました。

$ 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

/mingw64/bin/opensslで始まるサブジェクトを使用する必要があるのは//の使用だと思いますが、これがパッケージ/ビルドまたはOpenSSLのバージョンに固有であるかどうかはわかりません確かに、各バイナリのバージョンは次のとおりです。

$ 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

私のマシンでmsys/mingwを使用して作業するときに、OpenSSLバージョンに基づいて正しいバイナリを選択する次のbashコードの例を見つけました。

# 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

件名文字列を渡す問題を修正することに加えて、DNのサイズに関する問題を解決するためにもこれを見つけました(どのフィールドにもmax_sizeを設定せず、まだ問題があったポリシーでカスタムopenssl.cnfを渡しました/mingw64/bin/openssl.exe)を使用する場合。

0
Rob Frey