セキュリティ
ローカルアプリケーション
sshcエンジンはループバックアドレスでWeb UIとAPIを提供します。Web UI自体は、マシンごとに設定した固定ポートのURLから開けます。初めて使うブラウザを登録するときだけ、sshcコマンドが一度限りの登録URLを開きます。このURLに含まれる登録情報は、通常の起動ログには残しません。保存したポートを使用できず別のポートへ移った場合は、以前のブラウザ登録を失効させ、現在のsshcエンジンへの再登録を求めます。
保護されるもの
ブラウザ登録後の通常利用では、登録済みのブラウザだけがWeb UIとAPIを利用できます。ネットワーク上の別のマシンからは、ループバックアドレスへ直接接続できません。
Linuxでは、同じマシンの別のOSユーザーも、sshcコマンドがブラウザへ渡した登録URLを読み取れます。そのためsshcエンジンは、接続してきたプログラムが同じOSユーザー(またはroot)のものかを確かめてから登録を受け付けます。
- 登録URLを読み取られても、別のOSユーザーはそのURLでブラウザを登録できません。
- 登録済みのブラウザがsshcエンジンの再起動のあとに入り直すときも、同じように確かめます。
ポート変更時に古いブラウザ登録を失効させるため、以前のURLに保存された登録情報を新しいポートでは使えません。
WindowsのOpenSSHのssh-agent(\\.\pipe\openssh-ssh-agent)は、そのnamed pipeを作ったのがLocalSystem、Administrators、自分のユーザーのどれかのときだけ使います。ssh-agentサービスが止まっているあいだに同じマシンの別のユーザーが同じ名前のpipeを作っても、SSH Keysの画面からssh-agentへ登録する秘密鍵も、公開鍵認証の要求も、そこへは送りません。
保護されないもの
同じOSユーザーとして動くプロセスは、その利用者のSSHファイルやブラウザのデータへアクセスできる前提で扱っています。sshcは、同じ権限で動く悪意のあるプロセスからこれらを隔離しません。
利用者側で必要な対策
共有のマシンではOSのユーザーアカウントを分け、同じユーザー権限で信頼できないプログラムを実行しないでください。Web UIのポートが変わった場合は、sshcコマンドから現在のsshcエンジンへ登録し直します。
接続先のsshcエンジン
sshc open <alias>の中継と[リモートセッション]は、SSHの接続先で動いているsshcエンジンへ、SSH接続の中を通して接続します。
- 接続先へ接続を開くたびに、相手が接続先のsshcエンジンであることを、接続先の
~/.ssh/sshc/cliにあるシークレットで確かめてから、ブラウザの要求や認証情報を送ります。接続先のsshcエンジンが起動し直してポートが変わったあと、接続先の別のOSユーザーが前のポートで待ち受けていても、cookieやVaultのパスワードはそのプログラムへ送られません。 - 確かめられなかったときは、要求を送らずにエラーを表示します。[リモートセッション]では、接続先の
~/.ssh/sshc/cliを読み直して一度だけ接続し直します。別タブの中継では、sshc open <alias>をもう一度実行してください。 - [リモートセッション]では、接続先のsshcエンジンが返したcookieなどのヘッダーを、このマシンの画面へ渡しません。接続先のsshcエンジンが、このマシンの画面のサインインを書き換えたり消したりすることはできません。
- [リモートセッション]のターミナルの入出力は、このマシンの画面にサインインしていて、このマシンのVaultのロックを解除しているときだけ、接続先へ取り次ぎます。
Vault
Vaultの鍵で暗号化するものは次のとおりです。
- SSH接続に使うアカウントパスワード、鍵のパスフレーズ、TOTPのセットアップキー
- VPNプロファイルのシークレット(鍵を含むWireGuardとOpenVPNの設定ファイル、VPNのパスワード、IPsecの事前共有鍵、OpenConnectの二要素認証に使うTOTPのシークレット)
- スニペット、同期設定、バックアップ
パスワードを設定したVaultでは、この鍵をマスターパスワードから導出します。
- パスワードは4文字以上で、文字種の制限はありません。ファイルを持ち出された場合の保護はパスワードの強度に依存します。
- パスワードは引数や環境変数から受け取りません。
- 既定の自動ロックは12時間で、Settingsで変更できます。
前回同期したVaultとスニペットの内容はVaultの鍵を使ったハッシュで記録するため、同期の記録(sshc/sync-state.json)からその内容を照合することはできません。前のバージョンから更新した場合は、更新後に初めてVaultのロックを解除したとき(パスワードなしのVaultでは起動したとき)に、記録をこの形へ書き換えます。
パスワードなしのVaultでは、ロック解除用のランダムな鍵をこのマシン内に保存し、起動時に自動でロックを解除します。自動ロックは適用されません。この鍵と暗号化データの両方を読み取られた場合、内容は保護されません。この鍵は同期されません。同期データは、この鍵とは別の暗号化キーで暗号化されます。
VPN
接続ごとのVPNは、VPNプロファイルごとにDockerのコンテナを起動し、その中でVPNに接続します。使う前に次の点を確認してください。
- Dockerを操作できるユーザーは、一般にこのマシンに対して強い権限を持ちます。VPNを使うには、sshcを動かすOSユーザーがDockerを操作できる必要があります。
- コンテナへ渡すのは、VPNへ接続するための設定とシークレット、接続先のアドレスとポートだけです。シークレットはコマンドの引数や環境変数に置かず、標準入力からコンテナ内のメモリ上のファイル(tmpfs)へ渡します。コンテナを停止すると消えます。OpenConnectの二要素認証では、TOTPのシークレットではなく、生成したコードだけを渡します。
- 方式、VPNサーバー、VPN内のDNSサーバーなど、シークレットではない設定は、Vaultがロックされていても一覧に表示できるよう、Vaultの外に暗号化せずに保存します。
- SSHの秘密鍵、アカウントパスワード、Vaultの鍵はコンテナへ渡しません。SSHのハンドシェイク、認証、ホスト鍵の照合は、このマシンのsshcエンジンが行います。
--privilegedと--network hostは使いません。コンテナへ渡すデバイスと権限は方式によって違います。詳しくは接続ごとのVPNを参照してください。- 接続先への通信はfail closedです。トンネル以外から接続先へ出ようとする通信はコンテナ内で拒否するため、VPNが切断されているあいだ、接続先への通信がDockerの通常のネットワークへ流れることはありません。
- VPN画面のログと
sshc vpn logsは、保存済みのシークレットを[REDACTED]に置き換えて表示します。
変更の履歴とバックアップ
sshcで変更を保存すると、変更前のファイルをバックアップとして~/.ssh/sshc/backups/に、変更の記録を~/.ssh/sshc/history/に残します。[History]画面から、変更前の内容に戻せます。Vaultから削除したパスワードや変更前の秘密鍵も、バックアップの中に暗号化されたまま残ります。
バックアップと記録は、次の範囲にすべて収まる変更の分を保持します。
- 新しい方から200件まで
- 90日以内
- バックアップのファイルが、新しい変更から数えて合わせて512件まで。この上限に達するのは、同期の受信やフォルダの削除のように、1回で多くのファイルを書き換える変更が続いたときです。最も新しい変更の分は、512件を超えていても保持します。
範囲を超えた古い変更の分は、次に変更を保存したときか、sshcエンジンを起動したときに自動で削除されます。中断した変更のバックアップと記録は、完了させるか取り消すまで削除されません。90日より前に中断した変更を完了させた場合、その分は次に変更を保存したときか、sshcエンジンを起動したときに削除されます。
SSHホスト鍵
ホスト鍵は、OpenSSHと同じくUserKnownHostsFileとGlobalKnownHostsFileのすべてのファイルと照合しています。HostKeyAliasがある接続先では、その名前で照合しています。設定に書かれていない場合は、次のファイルを使います。
UserKnownHostsFile:~/.ssh/known_hostsと~/.ssh/known_hosts2GlobalKnownHostsFile: macOSとLinuxでは/etc/ssh/ssh_known_hostsと/etc/ssh/ssh_known_hosts2、Windowsでは%ProgramData%\ssh\ssh_known_hostsと%ProgramData%\ssh\ssh_known_hosts2
未知のホスト鍵の扱いは次のとおりです。
| 設定 | 動作 |
|---|---|
| 対話的なTerminal | 利用者が確認したあとに保存する |
StrictHostKeyCheckingがnoまたはaccept-new | OpenSSHと同じく確認なしで保存する |
StrictHostKeyCheckingがyes(true) | 保存せず、接続しない |
保存先はUserKnownHostsFileの最初のファイルです。sshcが保存するのは~/.sshの中のファイルだけで、/dev/nullなどそれ以外を指している場合は保存されません。
KnownHostsCommandは実行しないため、これを設定した接続先では、StrictHostKeyCheckingがnoやaccept-newでも確認が表示されます。
~/.ssh/known_hostsなどの保存先がシンボリックリンクの場合、sshcはリンク先に書き込まないため、新しいホストには接続できません。登録済みのホストには接続できます。新しいホストの鍵は、sshで一度接続すると登録できます。~/.sshそのものがシンボリックリンクの場合は、リンク先のフォルダに保存されます。
保存済みの鍵が変わっていた場合は、この設定に関係なく接続できません。非対話SSH、SFTP、公開鍵登録は、最終接続先とすべてのProxyJump踏み台のホスト鍵を登録してから実行してください。
Known Hostsで表示・削除できるのは、~/.ssh/known_hostsのエントリーだけです。~/.ssh/known_hosts2、UserKnownHostsFileで指定したほかのファイル、GlobalKnownHostsFileにある鍵は表示されません。これらのファイルにある鍵が変わって接続できない場合は、そのファイルを直接編集してください。~/.ssh/known_hostsがシンボリックリンクの場合、Known Hostsには一覧が表示されません。
ProxyCommand
SSH設定にProxyCommandがある場合、sshcは接続時にそのコマンドをローカルで実行します。SSH設定とInclude先は、実行可能な設定として扱ってください。内容を確認し、信頼できるファイルだけを使用してください。
Sync
同期はエンドツーエンド暗号化(E2EE)です。接続設定、Vault内の認証情報、スニペット、同期対象のSSH鍵は、同期専用の暗号化キーを使ってこのマシン上で暗号化してからアップロードします。復号できるのは、同じ暗号化キーを設定したマシンだけです。暗号化にはAES-256-GCMを使い、暗号化キーからの鍵の導出にはArgon2idを使います。ストレージ事業者やS3の認証情報を持つ第三者は、これらの内容を平文では取得できません。ただし、バケット名、S3オブジェクト名、オブジェクトのサイズと更新時刻は暗号化されません。
暗号文を取得した第三者は、オフラインで暗号化キーの総当たりを試せます。手入力した短い文字列ではなく、sshcが生成したランダムな暗号化キーを使用してください。
書き出した同期設定
[設定を書き出す]で作るファイルとQRコードには、エンドポイント、バケット、バケット内のパス、リージョン、S3のアクセスキーIDとシークレットアクセスキーが入ります。これらはスナップショットと同じ方式(AES-256-GCMとArgon2id)で、暗号化キーを使って暗号化します。暗号化キーは入りません。S3の認証情報がsshcエンジンの外へ出るのは、この暗号化された形だけです。
ファイルやQRコードを取得した第三者は、スナップショットの暗号文と同じように、オフラインで暗号化キーの総当たりを試せます。暗号化キーを知られると、S3の認証情報も読み取られます。暗号化キーを自分で決めている場合は、書き出しの画面に注意を表示します。書き出したファイルは、追加するマシンへ渡したあとに削除してください。
Androidのアプリがカメラを使うのは、QRコードを読み取っている間だけです。映像は保存も送信もしません。
同期キーを置き換えたあとに残るもの
同期キーを置き換えると、sshcは最新のスナップショットだけを新しいキーで暗号化し直します。バケットに残っている過去の履歴は、以前のキーで暗号化されたままです。sshcからは読み取れなくなりますが、以前のキーを知っている人は引き続き復号できます。置き換えたあとに送信するスナップショットは、新しいキーで暗号化されます。
同期キーやS3の認証情報の漏えいが疑われるとき
同期キーを置き換えるだけでは、過去の履歴は保護されません。次の順に対応してください。
- [同期設定を管理]で同期キーを置き換える
- S3のアクセスキーを発行し直して古いアクセスキーを無効にし、新しいアクセスキーを同期設定に保存する
- スナップショットに含まれていたSSH鍵とパスワードを、接続先のサーバーで変更する
- 必要に応じて、バケット内パスの
snapshots/にある過去の履歴を削除する。バケットのバージョニングが有効な場合は、古いバージョンも削除する
sshcには過去の履歴を削除する機能はありません。S3の管理画面やツールで削除してください。すでにダウンロードされた履歴は、バケットから削除しても保護できません。
Terminalのデータ
スクロールバックはメモリにだけ保持し、ディスクや同期先には保存しません。OSC 52によるクリップボード操作は設定で制御できます。OSC 8リンクの表示の文字が行き先と違うアドレスのときは、Ctrl(Macでは⌘)を押しながらクリックしても、行き先を表示してから開きます。画像は、解読や展開でメモリを使い切らないよう、ピクセル数と展開後の大きさを先に確かめ、上限を超えるものを表示しません。リモートシェルへ送ったシークレットは、シェルの履歴、TTYのエコー、Terminalの出力に残る可能性があります。
Telnet
Telnetは通信を暗号化せず、サーバー認証も行いません。sshcでは接続前に警告を表示していますが、プロトコル自体を安全にすることはできません。認証情報を送る場合は、信頼できる隔離ネットワークなど、別の保護が必要です。
SFTP
sshcは、SFTPサーバーが返すファイル名や属性をそのまま信用しません。悪意のあるサーバーや乗っ取られたサーバーに接続した場合に備えて、次のように扱います。
- このマシンへ保存するときは、サーバーが返した名前をすべて検査します。
\や..を含む名前など、保存先のフォルダの外を指す名前があれば、転送を始めずに失敗します。 - フォルダをZIPでダウンロードするときは、ファイルの読み取り・書き込み・実行の権限だけをZIPに書きます。setuid、setgid、stickyのビットは書きません。
- 1つのフォルダの一覧は10万項目まで読みます。サーバーが一覧を終わらせない場合も、上限で読み込みを止めてエラーにします。
更新
sshc updateとWeb画面からの更新は、install.shまたはinstall.ps1で導入したsshcを、最新の安定バージョンのタグにある同じインストーラーで更新します。インストーラーは、同じGitHub Releaseのchecksums.txtに記載されたSHA-256と一致したときだけ実行します。Releaseのワークフローを経ずにタグへ置かれたスクリプトは実行しません。
Web画面から更新するときは、インストーラーの標準出力と標準エラー、sshcエンジンを再起動する経過を~/.ssh/sshc/web-update.log(Windowsは%USERPROFILE%\.ssh\sshc\web-update.log)へ記録し、About sshcに表示します。
- 記録は1回の更新につき256 KiBまでです。超えた分は記録しません。
- 次の更新を始めると、前の記録は置き換わります。
- ファイルは本人だけが読める権限で作り、暗号化同期から除外します。
- 画面からは、サインイン済みのブラウザだけが読めます。更新後の再起動でVaultがロックされていても読めます。
- sshcはインストーラーにトークンやパスワードを渡しません。出力はそのまま記録し、伏せ字にはしません。