SSLandSSH - uuuta/Technical-Notes GitHub Wiki

Secure通信

概要

Secureな通信を実現する為には以下の三つを防止する必要がある。

  1. 盗聴 ⇒ 暗号化で防止
  2. 改竄 ⇒ ダイジェスト(ハッシュ化)で防止
  3. 成り済まし ⇒ 電子署名と電子証明書で防止

SSH

概要

SSH1とSSH2がある。SSH1もSSH2も共通鍵を使ってクライアント⇔サーバー間通信を暗号化する。
SSH1とSSH2の違いの一つは共通鍵の作り方(共有の仕方)である。
SSH1はサーバーキー方式、SSH2はDiffie-Hellman鍵交換方式(公開鍵交換方式)である。

SSHの流れ

  • ホスト認証(サーバー認証)

    1. サーバーは、事前に自分が誰かを示すホストキーとなる秘密鍵&公開鍵ペアを保持する(ホストキーは一時的なものではない)
    2. クライアントは、「~/.ssh/known_hosts」(OpenSSHの場合)等の公開鍵登録先ファイルに、IPアドレス(ホスト名)と公開鍵のペアを登録しておく
    3. クライアントがサーバーへSSH接続を行うと、サーバーはクライアントへ公開鍵を送信する
    4. クライアントは、SSHの接続先が意図したサーバーであるかを確認する為、IPアドレス(ホスト名)及び受信した公開鍵が公開鍵登録先ファイルに存在するかを確認する
    5. ファイルに登録されていない場合、または登録内容と異なる場合にユーザーへ警告を出す
      ※警告では、公開鍵のフィンガープリント(指紋)を表示し、ユーザーに許容するかどうかを確認する
      フィンガープリントとは、公開鍵をハッシュ化したダイジェストメッセージのこと
      Teratermで始めて接続するサーバー場合に、警告を見たことあるはず。
       C                        S
                                  公開鍵(PK:Public Key) 秘密鍵(SK:Secret Key) ★ホストキー
                   PK
       <-------------------------
    「~/.ssh/known_hosts」等の公開鍵登録先ファイルを確認する
    PKとファイル内の公開鍵を照会する
    存在しない場合、異なる場合はユーザーに警告を出す
    
  • 共通鍵交換 ホストとクライアントが通信データの暗号化で使う共通鍵の受け渡しを行う。以下の2つの方法がある。

    1. サーバーキー利用
      • サーバー側が一時的な秘密鍵&公開鍵ペアを作成し、クライアントに公開鍵を渡す。
      • クライアントは一時的な共通鍵を作成し、受け取った公開鍵で暗号化して、サーバーへ返す。
      • サーバーは秘密鍵を使って、暗号化されている共通鍵を復号し、入手する。
         C                        S
                                   公開鍵(PK) 秘密鍵(SK) ★サーバーキー
                 接続要求
         ------------------------->
                    PK
         <------------------------
     共通鍵作成(CK) ★セッションキー
                   PK(CK)
         ------------------------->
                                   (CK)PKをSKで復号
    
    1. Diffie-Hellman鍵交換(公開鍵交換方式)
      双方が秘密鍵を作り、公開鍵とパラメータを共有して、自身の秘密鍵と組み合わせて、双方で同じ共通鍵を作成する。
  • ユーザー認証(クライアント認証) 主に以下の方式がある。

    • パスワード認証

      1. [C] ユーザ名とパスワードをサーバに送信
      2. [S] ユーザ名とパスワードの組み合わせが正しければ、ログインを許可
    • 公開鍵認証

      1. [C] クライアントからサーバへ、ユーザ名(+公開鍵)を送信
      2. [S] サーバ側で、ランダムなデータを作成
      3. [S] 2) のデータをユーザの公開鍵で暗号化し、クライアントに送信 ⇒ユーザーの公開鍵は「~/.ssh/authorized_keys(OpenSSHの場合)」に登録しておく
      4. [C] 送られたデータをユーザの秘密鍵で復号化し、ハッシュ値を計算
      5. [C] ハッシュ値をサーバに返送
      6. [S] 2) から求めたハッシュ値と、返送された値が一致すれば、ログインを許可 ※ ちなみに、パスフレーズは、「秘密鍵」を復号するときに使われる「鍵」なので、ネットワークには流れません。
    • チャレンジ・レスポンス認証

      1. [C] クライアントからサーバへ、ユーザ名を送信
      2. [S] サーバ側で、ランダムなデータを作成
      3. [S] 2) のデータ(チャレンジ)を、クライアントに送信
      4. [C] 送られたデータを、ユーザのパスワードを使って暗号化
      5. [C] 暗号化したデータ(レスポンス)をサーバに返送
      6. [S] 2) をユーザのパスワードで暗号化したものと、返送されたデータが一致すれば、ログインを許可
    • ホストベース認証、 ユーザー単位ではなく、端末(おそらくOS単位?)に紐付く公開鍵認証のバリエーションのこと。
      ユーザー単位で認証するユースケースが多いため、あまり使われない。

認証エージェント

SSHサーバーとの認証を代理で行ってくれるエージェント
具体的には、ユーザーの秘密鍵のパスフレーズをエージェントがキャッシュしておいて、 ユーザーがサーバーにログインする都度、パスフレーズを入力するのを省略してくれる。(通常は毎回入力) パスフレーズとは、秘密鍵へのアクセスキー(zipのパスワードみたいなもの)。

エージェント転送

認証エージェントの機能は、リモートサーバーと連動することができる。これをエージェント転送と言う。
エージェント転送を使うことで、踏み台サーバー経由でもクライアントの秘密鍵を使える。 この時、ユーザー固有の情報がネットワークに流れないメリットがある。
「パスフレーズ及び秘密鍵はクライアント内でのみ使用される」

以下、概略図(S1とS2にユーザーアカウント、PKの登録が必要)

C(ssh) --------------------> S1 -------------------->S2
SK,PassPhrase                PK                      PK
*Agent                       *Agent

S1はArgentとしてC <--> S2間を転送するだけ。

S1にSK(秘密鍵)をおかなくて良い。
S1へパスフレーズを送らなくて良い(ターミナルで入力しなくて良い=通信路に流れない)

ポートフォワーディング(トンネリング ~トンネルの掘り方は複数ある~)

クライアントとサーバ間における任意のTCP接続を暗号化する機能
このような機能は暗号化された通信路の中にもうひとつ別の通信路を確立するため、一般にトンネリング (tunneling) とも呼ばれている。
設定方法は複数あるが、いずれも踏み台サーバー(Proxyとも言う)が必要。

-Lオプション

アクセス元となるローカル側(多くは自PC)でポートを開き(トンネルの入り口)、 接続を待ち受ける。
アクセス元と踏み台となるSSHサーバー間はSSHで接続する(入り口~中継点間のトンネル)。
SSHサーバーが中継となり通信データをリモート側のポート(トンネルの出口)に流し込む。

崩れやすいトンネル(-f、-Nオプション)

ローカルの10022 に対する通信をSSH経由で S1:8080 へ転送する。
ローカル(L0)⇔踏み台間で常時SSH接続を行い、トンネルを維持する必要がある。SSH接続が切れた場合はトンネルが崩れる。
基本的に、ローカルで待ち受けているので、(ポートを外部に解放する等しない限り)トンネルを作ったマシンからしか通ることはできない。

  • 概略図
    SSH ProxyとR1、R2はファイアーウォール内にあり、SSH Proxyは外部からアクセスできることが前提。

    L0(プロセス)とProxyがSSHで接続する。
    この時、10022と10023をListenして、localhost:10022に対する通信はR1に、localhost:10023に対する通信はR2へ転送する。
    
     L1 ------->10022                                +--> R1(10.10.10.1):8080
                 ↓                        SSH        |
                 L0 ------- ssh -------> proxy ------|
                 ↑                                   |
     L2 ------->10023                                +--> R2(10.10.10.2):22
    
     <- Local PC ->                    <踏み台>          <remote server>
                 〇================ トンネル ==============〇
    
     ※Teraterm設定例)DefaultForwarding=L10022:10.10.10.1:8080;L10023:10.10.10.2:22
       ローカルからトンネルを掘るので、Teraterm側に設定しておくことができる。
       OpenSSHでは、転送設定を ~/.ssh/config に記述できる。
    
  • コマンド

    • ssh (SSHサーバのホスト名orアドレス) -L (ローカルで待ち受けるポート):(リモートサーバのアドレス):(リモートサーバで待ち受けるポート)
      ssh example.proxy -L 10022:10.10.10.1:8080 -f -N
      ローカルの10022を10.10.10.1:8080へ転送する設定でsample.proxyへSSH接続する。ローカルの10022にアクセスすると、10.10.10.1:8080に繋がる。
      -f -Nオプションはトンネルを作ったあと、sshのプロセスをコマンドを実行せずにバックグラウンドに移す為に付与したオプション。
      付与しなくても動作するが、トンネル用のプロンプトを開いたままとなる。
      トンネルを停止する際は、psコマンドを叩き、該当するプロセスIDを調べ、killすれば良い。

崩れないトンネル(-gオプション)

「崩れやすいトンネル(f、Nオプション)」のL0の役割をサーバに移して、-gオプションを付与した構成。 サーバ(S0)⇔踏み台間で常時SSH接続を行い、トンネルを維持する必要がある。SSH接続が切れた場合はトンネルが崩れる。

  • 概略図
    SSH ProxyとR1、R2はファイアーウォール内にあり、SSH Proxyは外部からアクセスできることが前提

    S0(代表してSSH接続するサーバー)とproxyがSSHで接続する。
    SSH ProxyはR1へ転送する。
    L1、L2(R1にアクセスしたいマシン)はS0の2233ポートを介してR1に接続する。
    
     L1 ------------+  
                    ↓                  SSH                     
              2233 S0 -------------->  proxy -----------------> R1(10.10.10.1):22
                    ↑
     L2 ------------+
    
     <----- LAN ---->                <踏み台>                   <remote server>
                    〇================= トンネル ================〇
    
  • コマンド

    • ssh example.proxy -L 2233:10.10.10.1:22 -g -f -N
      -gを付与して、他のマシンからアクセスできるようにする。

リモートから掘るトンネル(-Rオプション)

-Lオプションは、ローカル側でポートを開き、接続を待ち受け、それをリモート側のポートへ転送するコマンド。
-Rオプションはそれの逆で、リモート側でポートを開いて接続を待ち受け、そこに来た接続をローカル側(リモートにとっての)のポートへ転送する。
リモート側から踏み台となるサーバへSSH接続を行い、事前にトンネルを掘っておく。
リモートサーバ(R1)⇔踏み台間で常時SSH接続を行い、トンネルを維持する必要がある。SSH接続が切れた場合はトンネルが崩れる。

  • 概略図
    SSH ProxyはL1及びR1の両方(つまり外部から)からアクセスできることが前提
    R1(アクセスしたリモートサーバ)とproxyがSSHで接続してトンネルを掘る。
    
                                  SSH   <------ ssh -----                  
     L1 ----------------------->  proxy -----------------> R1(10.10.10.1):22
    
    
                                  <踏み台>                  <remote server>
                                  〇======= トンネル =======〇
      1. L1からSSH Proxyにログイン
      2. SSH Proxy上で10022にアクセス(ssh localhost -p 10022)
      3. ポート転送されて、R1にアクセスできる
    
  • コマンド
    • ssh (SSHサーバのホスト名orアドレス) -R (SSHサーバーで待ち受けるポート):(リモートサーバのアドレス):(リモートサーバで待ち受けるポート番号)
      ssh example.com -R 10022:10.10.10.1:22

踏み台設定(ProxyCommand)

proxy serverを経由(踏み台にして)してtarget serverにアクセスするのを簡略化する手順

  • 概要図

                1.2.3.4           5.6.7.8
    client ---> proxy_server ---> target_server
    
  • 設定方法
    図中のclientの~/.ssh/configに以下の設定をする。
    ProxyCommandで、ssh -W %h:%p proxy_serverでproxy_serverに接続したあとproxy_serverに接続する。

    Host proxy_server
      HostName 1.2.3.4
    
    Host target_server
    	HostName 5.6.7.8
    	Port 22
    	ProxyCommand ssh -W %h:%p proxy_server
    
  • メリット

    • コマンドを2回実行しなくて良い
    • 踏み台サーバー上に秘密鍵を置いたりしなくて良い

主要コマンド一覧

ssh

ssh [user@]hostname [command]

  • ユーザー名を省略するとクライアントの現在のユーザーが使用される。
  • コマンドを指定するとリモートホストに接続したあと指定のコマンド(RPCみないな使い方)だけ実行してログアウトする。
  • コマンドを省略した場合はリモートホストにログインした状態でコマンドプロンプトが表示されるので、任意のコマンドを実行できる。ログアウトしたい時はexit

主要なオプション

  • -i identity_file
    公開鍵認証で使用する秘密鍵ファイルを指定する。OpenSSHのデフォルトでは~/.ssh/id_rsaなどが使用される。
  • -F configfile
    設定ファイルを指定する。OpenSSHのデフォルトでは~/.ssh/configが使用される。
  • -p ポート番号
    ポート番号を指定する。
  • -L 転送元ポート:転送先ホスト:転送先ホストのポート
    ポート転送機能を利用する。
    ローカル・ホストの指定した「ポート番号」へのTCP接続を指定した「ホスト」の指定した「ホスト側ポート番号」へ接続先ホストとの間で確立したSSHのトンネルを介して転送する。
  • -N
    リモート・ホストでコマンドを実行しない。ポート転送を行う際に指定する。
  • -f
    ssh 先でコマンドを実行したあとに, バックグラウンドへと潜る。
  • -R 転送元ポート:転送先ホスト:転送先ホストのポート
    ポート転送機能を利用する。

scp

scp file1 file2 [user@]hostname[:dir1] (クライアントのfile1とfile2をリモートホストのdir1へコピーする)

scp [user@]hostname:file1 [user@]hostname:file2 dir1(リモートホストのfile1とfile2をクライアントのdir1へコピーする)

ssh-keygen

公開鍵認証方式で使用するキーペアを生成する。

ssh-copy-id

公開鍵をリモートホストに登録するコマンド。環境によってはインストールされていない場合がある。

公開鍵認証で使うキーペア作成(OpenSSH)

鍵はログインする側、される側のどちらでつくってもよい。
以下はログインする側で鍵を作って、公開鍵を相手に渡して登録してもらう流れ。

  1. 鍵作成コマンドを実行
    $ ssh-keygen -t rsa -C ""<br>
    -t オプション:鍵の種類 rsa1, dsa, ecdsa, ed25319, rsa(省略時のデフォルト)
    -C オプション:コメントを設定する(デフォルトusername@hostnameが付与されるので、空文字指定しておく方が気持ちが良い)
  2. 鍵の保存名を設定
    Enter file in which to save the key (/home/useraccount/.ssh/id_rsa):
    Enterを押せば、()の中のパスに保存される。任意の保存先ファイル名を続けて入力すればそこに保存される。
  3. パスフレーズを設定(設定しなくてもよい) Enter passphrase (empty for no passphrase):
    空エンターでパスフレーズ無しの鍵となる。
  4. id_rsa(秘密鍵)、id_rsa.pub(公開鍵)という2つの鍵ファイルができる。
    Your identification has been saved in /home/useraccount/.ssh/id_rsa.
    Your public key has been saved in /home/useraccount/.ssh/id_rsa.pub.
    
  5. 鍵を設定する
    1. ログイン先のリモートマシン(サーバー)に公開鍵を渡す。
    2. リモートマシン(サーバー)のユーザーのホームディレクトリの.sshディレクトリ以下にauthorized_keysを置
      chmod 600 id_rsa.pub
      mv id_rsa.pub authorized_keys
    3. すでにauthorized_keysがある場合は、公開鍵を追記する。(既存の情報を消してしまわないように追記モード)
      cat id_rsa.pub >> authorized_keys
    4. 2,3を一度に行うには以下(sshのcommandが実行できる機能を利用する)
      cat ~/.ssh/id_rsa.pub | ssh remote.server 'cat >> ~/.ssh/authorized_keys; chmod 600 ~/.ssh/authorized_keys'
  6. ログインする
    # ssh ユーザー名@ホスト名 -i 使用する秘密鍵のパス (省略時のデフォルトは~/.ssh/id_rsa)
    ssh [email protected] -i ./keys/id_rsa 

関連便利ツール

  • autossh
    sshが切断しても再接続してくれるツール。トンネル用のSSH接続が切断された際に便利。
  • tmux
    仮想端末を使用するための、端末多重化ソフトウェア。
    トンネル用のSSH接続をバックグラウンドで実施する(フォアグラウンドだとPCスリープで切断される為)際に便利。

参考サイト

SSL

概要

SSLは安全に通信するための暗号化通信方法である。
SSHはシェル実行に最適化されたプロトコルではあるが、技術的には似ている。

  • 電子証明書を使ってサーバーの正当性を証明する ★ホスト認証1
  • 電子署名とダイジェストを使って改竄されていなことを確認する ★ホスト認証2+改竄防止
  • クライアントが生成した共通鍵をサーバーの公開鍵を使って共有する
  • 以降のデータ通信は共通鍵暗号化したデータ+ダイジェストで通信する ★盗聴防止+改竄防止

SSLの流れ

  1. 暗号化仕様交渉
    クライアントとサーバー間で通信に使う「暗号化方式、ハッシュ方式」等を決定する
  2. サーバ証明書送付
    成りすまし防止
  3. 共通鍵交換
    暗号化通信用の共通鍵を安全に交換する
  4. 共通鍵(+ダイジェスト)で暗号化通信
    盗聴防止、改竄防止

電子証明書(公開鍵証明書、SSLサーバー証明書)

認証局の秘密鍵で電子署名されたもの。認証局が付与する。
公開鍵の他に、公開鍵の所有者や電子証明書を発行した認証局の情報などが含まれる。
また、認証局自身の公開鍵は、認証局自身の電子証明書で確認することになります。
認証局自身の電子証明書は、認証局の秘密鍵で署名されているので「自己署名証明書」、あるいは、信頼性の拠り所となるため「ルート証明書」と呼ばれる。
電子証明書の中身は、認証を依頼した本人の公開鍵、暗号化手法や登録者情報などが記載されています。

自己署名証明書(オレオレ証明書)とは

自分で勝手に認証局を構築して署名したものをオレオレ証明書と言う。
実は、ルート証明書も自己署名証明書ということになる。
電子証明書はSSL通信時にクライアントに送付する為、サーバーにインストールする必要がある。

また、クライアントは認証局の「ルート証明書」を事前にインストールする必要がある。(onlineで認証局に問い合わせに行かなくて済む)
大抵は、ブラウザがWindowsUpdate等でインストールしてあるが、
自分で作成したルート証明書(自己署名証明書=オレオレ証明書)はインストールしておく必要がある。

電子署名(デジタル署名)

以下2点を保障するもの。

  • 電子文書の作成者を示すために行われたものであること

  • 作成された電子文書に対する改ざんが行われていないことを確認できるものであること

    送信するデータのダイジェストを作成して、秘密鍵で暗号化したもの
                                        ~~~~~~
                    送信データ
                        ↓
               +-----------------+
               |  ダイジェスト値   | 秘密鍵で暗号化
               +-----------------+
               署名
               つまり、電子署名(デジタル署名)はデータ送信毎に別ものになる
    

SSLの送信データ

          <   電子生データ   >
          (((   ハッシュ   )))  ★送信者の電子署名
          +------------------+ ★電子証明書(認証局の電子署名)
          | (((   公開鍵  ))) |
          | (((アルゴリズム))) |
          | ((( 所有者情報 ))) |
          |     認証局情報     |
          +-------------------+

SSLのユーザー認証方式

  • パスワード認証
    不特定多数のユーザーを扱う多くのWebサイトが採用している一般的な認証方式
  • クライアント証明書
    ユーザー側もサーバーと同様、電子証明書で認証を行う。
    この時、ユーザー側の電子証明書は大抵、オレオレ証明書である。
    企業のイントラやVPNで使われるのが一般的。
    ※SSHの公開鍵認証と同様の実現方式

SSHとSSLの違い

SSHにおいて、もし相手の公開鍵を事前に入手していなかった場合は、アクセスした時点で相手が送ってきた公開鍵を受け入れるかどうか判断する必要がある。 SSHで初めてアクセスするときに相手の公開鍵を受け入れるかどうか確認するためのメッセージが表示されますよね? もし受け入れなかった場合はその通信できません。 逆に受け入れる場合も注意が必要です。もし受け入れた鍵が偽物だった場合にどうなるかは容易に想像できますね? 他方、SSLでは初めてhttpsサイトにアクセスしても、公開鍵を受け入れるかどうか確認するためのメッセージは表示されません。 これは、証明書のチェーンの仕組みによって成り立っています。

SSLは任意のプロトコルをトンネリングできるが(HTTP over SSL、SMTP over SSL、POP3 over SSL等)、SSHは特定のプロトコルだけを通すことができない。 そもそもSSHを許可することで相手サーバへのリモートシェルも許可することになってしまう。 ところで、OpenSSHはOpenSSLのライブラリを使用していますが、使用しているのは暗号化の部分であり、SSH通信に関わる部分は全て独自の実装となっています。

鍵ファイル(.ppk .pem .crt .key .csr)

拡張子

  • ppk:PuTTYの秘密鍵格納ファイル
  • pem:単に書式が決まった格納ファイル 証明書or鍵をBASE64で保存
  • crt:Certificate、つまり証明書
  • key:公開鍵、あるいは秘密鍵
  • csr(Certificate Signing Request): 証明書署名要求 公開鍵とその所有者情報,申請者の署名を含む

参考
RSA鍵、証明書のファイルフォーマットについて

コマンド指定

scp -i xxx.pem ~/.ssh/[ファイル名].pub ec2-user@[ホスト名]:/home/ec2-user/

自己署名証明書(オレオレ証明書)作成手順

以下の手順では自己認証局は使っていない。

サーバー秘密鍵作成

$ openssl genrsa 2048 > server.key

証明書要求作成

Common Nameを正しく設定しないとSSLクライアント側でエラーになる。

$ openssl req -new -key server.key > server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP★任意
State or Province Name (full name) []:Tokyo★任意
Locality Name (eg, city) [Default City]:Minato-ku★任意
Organization Name (eg, company) [Default Company Ltd]:★任意 MyCompany
Organizational Unit Name (eg, section) []:★任意 MyCompany
Common Name (eg, your name or your server's hostname) []:192.168.1.1★DNS名 or IP
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:★Skip Enter
An optional company name []:★Skip Enter

自己署名

Common Nameが将来depulicateになるとのことで、subjectAltNameの設定をする必要がある。 クライアントによって、エラー、ワーニング等、扱いのレベルが違うので、設定しておくと安心。

$ openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 365 -extensions SAN -extfile <(printf "
[SAN]
subjectAltName=@alt_names
[alt_names]
DNS.1=localhost
IP.1=192.168.1.1
IP.2=127.0.0.1")

証明書の内容を確認

openssl x509 -in server.crt -text -noout
⚠️ **GitHub.com Fallback** ⚠️