既存のSSH秘密鍵をWindows証明書ストアに置く

SSHの秘密鍵をWindowsに管理させれば、Windowsのログインパスワードと秘密鍵の復号化を紐付けられて便利なんじゃないかと思った。

**注:** 単にWindowsログインパスワードと鍵の暗号化を関連付けたいなら、KeePass + KeeAgent の方がマシなので通常の人間はそちらを使うべき。

# 概要
![SnapCrab_NoName_2018-10-13_6-22-24_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/bbc1eb5a-30e4-9b01-8550-b5e44220d067.png)

- OpenSSHの `id_rsa` ファイルは生のRSA鍵ペアなので、これを `openssl` コマンドで PKCS#12 ファイルに変更することでWindowsにインポートできる。
- インポートした鍵をSSH鍵として使うには、PuTTY-CACのようなCAPIに対応したクライアントを使う

SSH2の鍵は基本的にRSA署名が扱える鍵ならなんでも良く、必ずしも`ssh-keygen`で作る必要性は無い。 `gpg-agent` に登録したPGP鍵を認証に使う( https://qiita.com/tsuyoshi_cho/items/79c09905ae3f192b3a0f )とか、yubikeyを認証に使う( https://qiita.com/dseg/items/77d77467970b1b510285 ) といった色々な方法が既に紹介されている。

今回は、GPGとかYubikeyの代わりに、Windows OS標準の公開鍵機構を使って、既存のOpenSSH鍵をインポートして使ってみる。

(この記事でやっている事は、 **ユーザ証明書を使って認証しているわけではなく** 、単にRSA鍵を証明書フォーマットに変換しているだけにしている。実際には、SSHプロトコル自体は真面目な証明書認証もサポートしている https://www.conoha.jp/guide/openssh.php この記事の手法は、サーバ側は単に `.ssh/authorized_keys` を使ったいわゆる普通のSSH公開鍵認証さえ設定されていれば良いのがポイントとなる。)

# OpenSSH鍵を証明書に変換する

`ssh-keygen` で作った鍵ファイルのうち、 `id_rsa` は生のRSA鍵データとなっている。このままでは有効期限とか名前といったデータが欠けていて証明書として使うことはできないが、幾つかのパラメタ(名前`CN`や有効期限`days`)を補ってやることで証明書に変換することができる。

この手の変換作業は `openssl` コマンドで実施するのが定番になっている。適当な環境にopensslをインストールし、適当にコンフィグファイル(ここでは`wrap.conf`)を用意し、

```:wrap.conf
distinguished_name=none
x509_extensions = usr_cert

[usr_cert]
keyUsage=digitalSignature

[none]
```

適当に自己署名証明書(`out.cer`)を生成し、

```:out.cerの生成
OPENSSL_CONF=wrap.conf openssl req -key ~/.ssh/id_rsa -days 7000 -x509 -subj /CN=my_ssh_key1 > out.cer
```

適当にPKCS#12形式でエクスポート(`out.p12`)すれば、

```:out.p12の生成
# エクスポート用のパスフレーズを要求されるが、空で良い
openssl pkcs12 -export -in out.cer -inkey ~/.ssh/id_rsa -name "My SSH Key1" > out.p12
```

Windowsにインポート可能なPKCS#12ファイルができる。

# Windowsに秘密鍵をインポートする

生成されたファイル `out.p12` をダブルクリックする("暗号化シェル拡張"で開く)と、証明書のインポートウィザードが始まる。(`.cer`には秘密鍵が入っていないため使えないのに注意)

![SnapCrab_NoName_2018-10-13_6-53-6_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/84a9ffdb-ef4d-db60-8156-8e0a29a97db0.png)

ここでは **現在のユーザー** を選択する。適当にウィザードを進めると鍵の強度設定になるが、ここは **秘密キーの保護を強力にする** を選択し、このキーをエクスポート可能にするをOFFにする。後者は、元にした秘密鍵(`id_rsa`に入っていたもの)を再度取り出せるようにするかという選択で、アプリケーションの挙動には影響しないが特に理由の無い限りエクスポート不可の方が良い。

![SnapCrab_NoName_2018-10-13_6-55-1_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/e3840bf2-dee7-26af-46de-07535bb24316.png)

あとは "自動的に証明書ストアを選択する" 等を適当に選択し、保護レベルを設定してウィザードを完了させると、証明書が証明書ストアに登録される。

"秘密キーの保護を強力にする" を選択することで、知らないソフトが勝手に証明書を使うのを防ぐことができる。これが選択されている場合、未知のアプリから証明書の利用要求があると、Windowsはダイアログを表示してユーザに確認を求める。保護レベルをデフォルトの"中"から"高"にすると、更にパスフレーズを設定することもできる。

## 登録内容の確認

"ファイル名を指定して実行" から `certmgr.msc` を開き、 "個人" → "証明書" と選ぶとインポートした証明書が確認できる。

![SnapCrab_NoName_2018-10-13_7-1-10_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/28735778-a151-6cb4-12b6-3ff66dc0293f.png)

証明書をダブルクリックすると内容を確認できる:

![SnapCrab_NoName_2018-10-13_7-2-13_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/0789444d-b412-e0b6-4077-0063ac370c43.png)

ここではRoot CAに信頼されていないといった警告が出るが、 **放置で良い** 。SSHの公開鍵認証では、特に鍵がCAによって承認されている必要は無い。

このプロパティダイアログは、コマンドラインから証明書を登録する際に使用するfingerprintの取得にも使用する。

# PuTTY-CACのPageantに鍵を置く

通常のPuTTYやMSのOpenSSH移植ではWindowsの鍵ストアにアクセスすることはできない。しかし、Yubikey界隈でよく使用されているPuTTY-CAC https://risacher.org/putty-cac/index-old.html を使うことで、Windowsの鍵ストアが認識している秘密鍵をSSH鍵として使用できる。

PuTTY-CACのリリースページ( https://github.com/NoMoreFood/putty-cac/releases )からpageant.exeをダウンロードして起動する。適切に鍵をインポートできていれば、"Add CAPI Cert"を選択した際にメニューで証明書を選択できる:

![SnapCrab_NoName_2018-10-13_6-37-24_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/3594d730-242c-087e-2595-d171003ad76a.png)

## コマンドラインからのロード

流石にGUIで毎回ロードするのはキツいものがあるので、コマンドラインからロードする。通常のPuTTYの場合、Pageantを起動する際に、コマンドラインで証明書ファイル(`.ppk`)を指定することでロードできるが、PuTTY-CACではこれが拡張され証明書のfingerprintを指定できるようになっている。

fingerprintを確認するには、証明書のプロパティを使う:

![SnapCrab_NoName_2018-10-13_6-41-13_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/3ec715c2-bf4d-6e19-6c26-62743b450c77.png)

(これはダミー証明書なのでキー使用法等が異なっている)

ここでは、拇印が `0309c343745244222037eb4b34415795836f4aa4` となっているので、コマンドラインには

```
pageant.exe CAPI:0309c343745244222037eb4b34415795836f4aa4
```

のように、 `CAPI:` を足して指定することでロードできる。

# 使う

後は適当にPuTTYのPageantを使えるクライアントで使用できる。

Cygwinで使用する場合は、Yubikeyのサイトにあるように https://developers.yubico.com/PGP/SSH_authentication/Windows.html 、 `ssh-pageant` を導入して

```:ssh-pageantの起動
eval $(/usr/bin/ssh-pageant -r -a "/tmp/.ssh-pageant-$USERNAME")
```

のようにすれば、Pageantにロードした秘密鍵情報をCygwinのSSHからも使用できる。

インポート時に"秘密キーの保護を強力にする"を選択した場合、初回の鍵使用時は

![SnapCrab_NoName_2018-10-13_7-9-35_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/a27e6a33-6ee1-efd0-af4c-35c7083e15ff.png)

のようなダイアログが現われ、 OK をクリックするまで処理が保留される。

# 秘密鍵の保護手法として適切か？

一応、MS的には、 [個人の証明書ストアに格納された秘密鍵はDPAPIで保護される](https://support.microsoft.com/ja-jp/help/309408/how-to-troubleshoot-the-data-protection-api-dpapi#2) ということになっている。

DPAPI(Data Protection API)で個人データを保護する場合は、 [ユーザに紐付けられた512bitsのマスターキーをパスワードで暗号化](https://msdn.microsoft.com/en-us/library/ms995355.aspx#windataprotection-dpapi_topic04) した上で使用しているので、Windowsのログオンパスワードで保護されているのと同様のレベルの保護があると言える。(ただし、この資料はちょっと古い)

ただ、UIとしてはあんまり強固にはなっていない。一応使用時点で承認を求めるように設定できるが、1回の起動で1度だけで、"N時間おきに確認を求める"といった設定ができない。せっかくWindows Helloとかが有ってEdgeのWebAuthn実装では適切に運用されているのに、証明書のような標準ベースのUIで使えないのはちょっと惜しい気がする。

Windows HelloをSSH秘密鍵の保護に使いたいなら、KeePass + KeeAgentのような外部のパスワードマネージャを使うしかないのが現状と言える。MSは [OpenSSH server/clientのWin32移植](https://github.com/PowerShell/openssh-portable) をメンテナンスして、これにはNTサービス化されたssh-agentまで含まれているが、今回のようにCAPIを使用した証明書の管理は一切サポートしていない。

もっとも、最近はMSも盛んにDevOpsフレンドリーを目指しているので、AppleとかUbuntuのようにOSのGUIレベルでssh-agentをサポートする時代がそのうち来るのかもしれない。