OpenVPNのセキュリティ周りの動作について気になった点をメモ
OpenVPN 2.3系、証明書認証を前提。だいたいmanpageから。 https://openvpn.net/index.php/open-source/documentation/manuals/65-openvpn-20x-manpage.html
OpenVPNプロトコル
- 独自プロトコルで仕様書は見当たらない。ソースを読め。
- 単一のUDPポート上で制御チャネル(認証など)とトラフィックチャネルを扱う(一応TCPでも動くが遅い)
- L2イーサネットまたはL3仮想化
- サーバー側からコントロールチャネルで、ルート設定とDHCP相当を配信可能
- IPv6ルート通知も可能だが、IPv6なDNSアドレスなどはまだ非対応
- コネクション毎に仮想ルータとサブネットを分けているため、IPv4アドレス消費はコネクション毎に4アドレス
- L3モードは内部IPルーティングもできるが、クライアント間の隔離以外にフィルタ機能はないので、必要なら内部ルーティングを無効化してiptablesなりで
セキュリティフロー
- セッション開始パケットを事前共有鍵で署名(オプション)
- 独自実装のTLS over UDPで、独自ルートCAで認証し、制御チャネルを暗号化
- 実際の通信は別の鍵を共有してパケット単位で暗号化・署名
各設定項目はデフォルトで安全よりに設定されている。ca, cert, keyを正しく設定すればしばらくは問題なさそう。
参考 https://openvpn.net/index.php/open-source/documentation/security-overview.html
開始パケットの署名
オプション。 通常OpenVPNサーバーはUDP上で構成され、最初のパケットを処理する際にとてもコストの高いTLS(over UDP)セッションの確立処理が必要になる。 偽装されたUDPパケットを送られるとCPUとメモリを浪費させるDoS攻撃ができてしまうので、最初のパケットには専用の共通鍵を用いたHMAC検証機構がある。 この鍵ファイルは tls-auth オプションで指定する。
ここで使う鍵はプロセス毎にしか指定できず、複数のクライアントがいるなら全てのクライアントで共通になる。この鍵を自動更新する機構はプロトコルには無い。 漏洩時に切り分けと鍵更新が面倒になるので多人数で利用するサーバーでの利用は難しいかもしれない。
TLS over UDP
OpenVPNがUDP上に独自実装したTLS variant(バックエンドはOpenSSLまたはGnuTLS)で、通常のTLS同様に、サーバー検証とクライアント検証を行う。
このTLSで使う鍵交換・暗号・署名アルゴリズムは tls-ciphers で設定する。対応するラベルはopensslのアルゴリズム指定書式に準じていて openvpn −show-tlsで分かる。ひどい命名規則だ。 サーバーは共通のサーバー証明書を(SNIは非対応)、クライアントは個別のクライアント証明書を利用する。それぞれ cert, keyで証明書と秘密鍵を指定。 証明書は ca オプションで指定したCAから認証されているか検証され、オプションで名前(CommonNameだけらしい)の検証と証明書タイプの検証もできる。
証明書の失効はCRL(失効リスト)のみ対応、crl-verifyで有効化。
基本的にクライアント識別やログに記録されるクライアント名はクライアント証明書のcommon nameで行われる。一応一部文字は除去されるようだが、 証明書発行処理を自動化する際はログインジェクションや偽装に注意。 また一つのクライアント証明書を使いまわした複数端末から同時接続も許可できるが、ログが面倒になるのでおすすめしない。
鍵交換はECDHEも指定できるが、曲線の指定方法がない。DHEはdhparamが dh オプションで指定できる。
トラフィック暗号化
トラフィックは制御チャネルで共有した鍵を使って暗号化と署名を行っている
デフォルトの暗号アルゴリズムは Blowfish。 設定ファイル上では cipher で設定する。 鍵長を指定するkeysizeは古いアルゴリズム用なので今は使わずcipherで指定する。 パケット署名はHMACで、デフォルトはSHA1。 auth オプションで指定する。
暗号モード設定は外向きにのみ適用されるので、クライアントとサーバーの両方で個別に設定が必要。
ややこしいことにcipherで署名つきのAES-128-CBC-HMAC-SHA1も指定できる。GCMをつかったAEAD署名はパッチはあるもののまだ取り込まれていない。 利用可能な暗号は openvpn −show-ciphersでわかる。
comp-lzoオプションでLZOパケット圧縮が利用でき、他にもビルドオプションでsnappy、LZ4に対応する。 パケット圧縮を有効にすると、攻撃者がトラフィックを生成することでCRIME攻撃が原理的には可能になる。 ただ圧縮はパケット単位なので、TLS上のCRIME同様同一セッションに攻撃トラフィックが乗る必要がある。ブラウザとVPNの出口側がセキュアであれば問題はないはず。
暗号化パケットはシーケンス番号とsliding window(デフォルトで64パケット/15秒)で管理している。つまり短時間の順序変化と重複は許容され、パケットのドロップや重複は上位レイヤの回復処理に依存している。
no-replayとno-ivオプションがありシーケンス番号とIVを省略できるが、リプレイと暗号文一致攻撃による平文回復(BEAST攻撃と同じ)が可能になるため基本的に有効にしてはいけない。
floatオプションを指定しない限り、各コネクションのリモートIPアドレス・ポートは固定され他のアドレスからのパケットはドロップされる。
トラフィックとクライアント管理
L3モードでは内部ルーティング機能があり、最低限のルート管理とクライアント間トラフィックの分離が可能。
ファイアウォール的な機能はないので、OS側のフィルタ機構を使う。 特にL2モードではARPフィルタなどイーサネットレベルのフィルタリングも忘れずに設定する。
クライアント毎のトラフィックは status オプションで定期的にダンプ可能。IPアドレス割り当ては ifconfig-pool-persist で固定割り当てが可能。
shaperオプションでコネクション当りの外向き速度制限が可能で、client-connectフックを使えばクライアント毎に異なる速度制限を生成することも出来るようだ。ただし内向きの速度制限は見当たらない。
基本的にクライアント別に細かい設定や認証をしたい場合は、フックスクリプト経由で設定ファイルを動的生成する。 どのオプション項目が動的生成に対応しているか記載がないので、個別に検証が必要。