2022.01.12
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
OpenSSH 8.8 で SHA-1 の RSA 署名が デフォルトで無効化された
git を version2.33.1 にアップデートするタイミングで OpenSSH も version8.8 にアップデートされました。
OpenSSH 8.8 の変更点の中で個人的に問題が起きたのが、
This release disables RSA signatures using the SHA-1 hash algorithm by default. This change has been made as the SHA-1 hash algorithm is cryptographically broken, and it is possible to create chosen-prefix hash collisions for <USD$50K [1]
For most users, this change should be invisible and there is no need to replace ssh-rsa keys. OpenSSH has supported RFC8332 RSA/SHA-256/512 signatures since release 7.2 and existing ssh-rsa keys will automatically use the stronger algorithm where possible.
簡単に訳すと、SHA-1 ハッシュアルゴリズムは破ることが可能になったので、ハッシュ化された RSA 署名はデフォルトで無効化されました。
これによって、git でリモートとのやり取り(pull や push など)をするときに、
Unable to negotiate with [server IP] port 22: no matching host key type found. Their offer: ssh-rsa
fatal: Could not read from remote repository.
と ssh-rsa が認識できなく、リモートとやり取りができません。
ssh-rsa とは
SSH とはネットワークに接続されたリモートサーバーに安全に接続するためのプロトコルで、
RSA とは桁数が大きい合成数の素因数分解を解くのが非常に難しいことを利用した公開鍵暗号方式です。(詳しくはこちらのYoutube動画へ)
上での ssh-rsa は RSA 公開鍵暗号方式で作られた公開鍵と秘密鍵のフォーマットを指しますが、それと同時に SHA-1 ハッシュアルゴリズムを用いて署名することも決まります。
なぜ ssh-rsa が原因でエラーになるのかは SSH プロトコルの接続が確立するまでの流れを知る必要がある。
SSH プロトコルの接続が確立するまでの流れ
SSH サーバーは自らクライアントへ問いかけることはなく、常にクライアントからの問いかけに応答する形で接続を確立していきます。
クライアントから接続を開始し、以下の手順を通過します。
- クライアントとサーバーの SSH プロトコルバージョン交換
- 使用可能なアルゴリズムの相互確認
- Diffie-Hellman 鍵交換
- サービスリクエスト
- 公開鍵認証
クライアントとサーバーの SSH プロトコルバージョン交換
SSH のバージョンを交換して、クライアントとサーバー間で使用する SSH プロトコルのバージョンを決定します。要するに ssh1 と ssh2 どっちを使うかを決めます。
使用可能なアルゴリズムの相互確認
まずクライアント側からうちはこれらの鍵交換(KEX)アルゴリズム、ホスト鍵アルゴリズム、暗号化アルゴリズム、MACアルゴリズム、圧縮アルゴリズムが使えると提示します。
そしたらサーバー側からうちはこれらのアルゴリズムが使えると返して、お互いのリストから両方とも使えるアルゴリズムを決めます。なかったら接続を中断する。
以下例として、ssh -vvv を打て確認してみましょう。
git -c core.sshCommand="ssh -vvv -F /dev/null" fetch
ここで、冒頭で話した OpenSSH 8.8 では ssh-rsa をデフォルトで使えないようにしたが、サーバーの CodeCommit では
> host key algorithms: ssh-rsa
と ssh-rsa 以外のアルゴリズムを対応していないので、冒頭のエラーが出たわけです。
Diffie-Hellman 鍵交換
Diffie-Hellman 鍵交換(以下 DH 鍵交換)は、同じ暗号鍵で暗号化と復号をする共通鍵暗号鍵を、傍受されている可能性のある通信ネットワークでも安全に共有できるプロトコルです。この共有鍵は今後通信の暗号化に用いられるものです。
DH 鍵交換がいかにして鍵の受け渡しを安全に行っているかはこちらの記事でわかりやすく説明されています。
また、このプロトコルは同時にホスト認証も行います。一度 SSH サーバーへ接続した場合、サーバーのホスト公開鍵がクライアントの ~/.ssh/known_hosts に記録され、2回目以降はこの known_hosts と照合してホスト認証を行います。
ここでいったサーバーの公開鍵はサーバーを作ったときに作成されるもので、公開鍵認証で使うクライアントが生成した公開鍵とまた別のものです。
ホスト認証の役割は、known_hosts ファイルの名前の通り、クライアントがなりすましなどのサーバーではなく、正しいサーバーへ接続しようとしていることを確認するためです。
サービスリクエスト
鍵交換後に実施されるアクションはサービスと呼んで、クライアントからサーバーへリクエストして、承認を求める型となります。
今回の場合はユーザー認証と、SSH 接続をしたいですので、「ssh-userauth」、「ssh-connection」というサービス名でサーバーに要求します。
このリクエストが拒否されることもあるらしいですが、どのような場合かはよくわかりません。詳しい方いたら教えてください。
公開鍵認証
公開鍵暗号方式によるユーザー認証を公開鍵認証と呼びます。
まず現状を確認すると、クライアント(C)には公開鍵と秘密鍵を持っていて、サーバー(S)には公開鍵があります。
ここで注意してほしいのは、
- 秘密鍵には公開鍵の情報も含まれていること(皆さんもしPuTTY形式の秘密鍵があれば、Public-Lines と Private-Lines が記述されていると思います)。
- サーバーが持っている公開鍵は鍵交換で言っていたホスト公開鍵とは別物であること。この公開鍵は予めクライアントが鍵を生成したときに共有したものです。
で、以下のような手順で公開鍵認証が行われる:
- C が公開鍵を S に送って、S の持つ authorized_keys ファイルにこの公開鍵が登録されているかを確認。これは公開鍵が間違っている場合、無駄に署名という処理を走らせないためです。
- 登録がある場合、S から続行の承認を得て、C が公開鍵とハッシュ化して秘密鍵で暗号化されたメッセージ(このメッセージは具体的に何かが自分にはわかりません、どなたか教えてください)、つまり署名をサーバーに送り、公開鍵で署名検証を行う。
冒頭のエラーをどう回避するか
上で見たように、CodeCommit 側は ssh-rsa しか承認しないわけですので、.ssh/config の設定情報に
HostkeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsa
を追加して、クライアント側も ssh-rsa を使うようにすればよい。セキュリティに問題はありますが。
参考文献
ここら辺の知識は調べるとほとんど間違った情報しか出てきませんですので、勉強に大変時間がかかりました。自分の書いたものでも茶化したり、細かいところは間違っているかもしれん。