Qithub-BOT / QiiCipher

✅ GitHub の SSH 公開鍵でファイルを暗号化およびローカルの秘密鍵で復号・署名・検証するスクリプトのリポジトリです。
https://qiita.com/KEINOS/items/2abce1e5b15d799ac6d7
Creative Commons Attribution Share Alike 4.0 International
4 stars 3 forks source link

コマンドの引数が間違えていた場合のメッセージ改善 #33

Closed KEINOS closed 3 years ago

KEINOS commented 3 years ago

下記は引数の順番を間違えているのに、公開鍵のアクセス権に問題があるように見えます。

$ verify ~/.ssh/github hello.txt emadurandal

/home/emadurandal/.ssh/github を電子署名 emadurandal で検証します

- hello.txt の GitHub 上の公開鍵を取得中 ... OK
- RSA 形式の公開鍵を PKCS8 形式に変換中 ... @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0664 for '/tmp/verify-/hello.txt.pub' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/tmp/verify-/hello.txt.pub": bad permissions
NG:RSA -> PKCS8 変換中にエラーが発生しました。

(Issue #31 より)

引数の順番の検知、もしくはエラー内容に引数の順番に間違いがないかなど、もう少しユーザに優しくてもいいと思います。

yoshi389111 commented 3 years ago

全体的に入力チェックを厳しくするという話なのかもしれませんが、とりあえず curl のエラーが取得できていないのも1つの原因だと思うので、それについて調査しました。

調査

現在の curl で公開鍵を取得する処理は以下のような感じになっています。

if ! curl -s "https://github.com/${USERNAME}.keys" | head -n 1 >"$PATHPUBKEY"; then
    echo "NG:公開鍵を取得・保存できませんでした。"
    exit 1
fi

上記処理の curl で 404 not found が起きても、どうやら続行してしまうようです(Ubuntu で確認)。 問題は2つありそう。

1つ目は、curl404 not found の場合にエラーメッセージを標準出力に出力して、正常終了をしていることです。 (以下の例では、エラーメッセージとして "Not Found" (改行無し)を出力している)

$ curl -s https://github.com/hello.txt.keys
Not Found$ echo $?
0

対策としては curl--fail を指定して、エラーメッセージを出力せず、終了コードを 0 以外にすることだと思います。

$ curl --fail -s https://github.com/hello.txt.keys
$ echo $?
22

2つ目は、curl の終了コードが、パイプのため握りつぶされていることです。

$ exit 1 | exit 0
$ echo $?
0

対策はパイプをやめてファイル経由で処理するか、出力した公開キーが空でないことをチェックするかでしょうか。 (以下は、公開キーが空の場合にエラーとしている例)

curl --fail -s "https://github.com/${USERNAME}.keys" | head -n 1 >"$PATHPUBKEY"
if [ ! -s "$PATHPUBKEY" ]; then
    echo "NG:公開鍵を取得・保存できませんでした。"
    exit 1
fi

影響範囲

curl で公開鍵を取得しているのは以下の4本 (今回の物も含めて、同様の処理をしているコマンド)

また、パイプでつないで終了コードを判定しているものは上記以外では、以下があります。

if ! (tar cz "$INPUTFILE" | openssl enc -e -aes-256-cbc -salt -k "$PASSWORD" -out "${TEMPDIR}${OUTPUTFILE}"); then
    echo "NG:ファイルを圧縮・暗号化できませんでした。"
    exit 1
fi
KEINOS commented 3 years ago

確かに POSIX 対応を考えた場合 -o pipefail も使えないので、パイプ渡しのネストは気をつけないといけないですね。

以下のように愚直に 1 ステップずつ確認していった方がいいかもしれませんね。(テストも作りやすくなるし)

list_keys="$(curl --fail -s "https://github.com/${USERNAME}.keys")" || {
    echo "NG:公開鍵を取得できませんでした。"
    exit 1
}

# 公開鍵の保存
echo "$list_keys" | head -n 1 >"$PATHPUBKEY" || {
    echo "NG:公開鍵を保存できませんでした。"
    exit 1
}
テストの例 ```bash #shellcheck shell=sh getContents() { url_target="${1:?'URL missing'}" list_keys="$(curl --fail -s "$url_target" 2>&1)" || { echo >&2 "NG: ファイルの取得に失敗しました。(URL: ${url_target})" echo >&2 "$list_keys" return 1 } echo "$list_keys" } getKeyPublic() { name_user="${1:?'GitHub user name missing'}" url_github_pubkey="https://github.com/${name_user}.keys" key_user="$(getContents "$url_github_pubkey") 2>&1" || { echo >&2 "NG: 公開鍵の取得に失敗しました。(URL: ${url_github_pubkey})" echo >&2 "$list_keys" return 1 } echo "$key_user" } Describe "getKeyPublic" It 'should print the list of pub keys in GitHub' name_user='KEINOS' When call getKeyPublic "$name_user" The status should be success The stdout should include 'ssh-rsa' The stderr should be blank End It 'should print err with status 1 when GitHub user name is missing or empty' When run getKeyPublic "$UNKNOWN" The status should be failure # status is 1-255 The stdout should be blank The stderr should include "GitHub user name missing" End End Describe 'getContents' It 'should print err with status 1 when URL is 404' url_ng='https://github.com/KEINOS/unknown_repo/' When call getContents "$url_ng" The status should be failure # status is 1-255 The stdout should be blank The stderr should include "ファイルの取得に失敗しました" End It 'should print err with status 1 when URL is invalid' url_ng='https://gitogitohub.com/FOOBAR/unknown_repo/' When call getContents "$url_ng" The status should be failure # status is 1-255 The stdout should be blank The stderr should include "ファイルの取得に失敗しました" End End ```