ipsec
AndroidからL2TP/IPsec CRT VPNに接続する
Android端末には、標準でVPN接続機能が搭載されています。
その中でもL2TP/IPsec方式では、x509証明書による認証がサポートされていて、端末ごとに個別の証明書を発行するなどの方法でセキュリティの高いVPN接続を行うことができます。
KLabでは、社内用の認証局を運用していて、社員が自分のユーザ名の入ったクライアント証明書を持てるようになっています。
管理用サイトへのブラウザでのアクセスのほか、社内の無線アクセスポイントもWPA2 Enterprise EAP-TLS認証に対応させて、クライアント証明書による強力な認証を活用しています。
もちろん、万が一の秘密鍵流出の際には、認証局側でCRLにより証明書を失効させることができるよう整備しています。
本記事では、クライアント証明書を使ったAndroid端末からのL2TP/IPsec VPN接続を試してみたいと思います。
Android 2.2系までの端末では・・・
残念ながら、Android 2.2系までの端末では、組み込まれている racoon(ipsec-tools)に問題があり、x509証明書による認証が実質的に使えない状態です。理由は以下のとおりです。
IPsecでは、IKEの認証フェーズ1にて、お互いのIDを交換します。
このIDには、「ID_IPV4_ADDR」・「ID_FQDN」などいくつかの種類があり、x509証明書を認証に使う場合には、証明書の識別名を使う「ID_DER_ASN1_DN」というIDタイプを使うことができます。
本記事のように、接続元IPアドレスを固定せずx509証明書を使う環境では、ID_DER_ASN1_DNタイプのIDを使うのがベストですが、Android 2.2系のracoonでは、「L2TP/IPsec CRT VPN」を選んでいても常にID_IPV4_ADDRタイプのIDがサーバに通知されてしまいます。
x509証明書による認証で、ID_IPV4_ADDR等をIDに使うことは可能ですが、この場合は使用する証明書のsubjectAltName拡張等のフィールドに「IP:192.168.0.1」等の値を含めておく必要があります。
そしてモバイル端末では、キャリアから動的に割り当てられるIPアドレスを使いますから、事前にIPアドレス等を記載した証明書を用意することも難しいです。
このため、Android 2.2までのOSを搭載した端末では、証明書によるIPsecは実質使えないのです。
この問題は、バグレポートされていて、Android 2.3では改善されています。 そのため、以降はAndroid 2.3系のOSを搭載した端末を前提とします。
認証に使う証明書とCA
認証に使うクライアント証明書・サーバ証明書、発行するCAには、Android端末の実装上、いくつかの制約があるため注意が必要です。
- サーバ証明書のコモンネームには、サーバのFQDNを記載する
一般のWebサーバ用の証明書と同様に、コモンネームに記載されたFQDNと、端末の設定画面に入力した接続先サーバ名は一致する必要があります。
- サーバ証明書は、シングルルートCAから発行する
サーバ証明書やクライアント証明書の発行元は、いわゆるオレオレ認証局を使うことができますが、自己署名しているルートCAから直接発行する必要があります。
Android端末のIPsecの処理では、端末ビルトインの証明書ストアは使われず、設定画面で指定したクライアント証明書とCA証明書だけが認証に使われます。ですから、サーバ証明書の検証に必要なルート証明書は、CA証明書としてインポートしてあげれば良いことになります。
しかし、Android端末のCertInstallerでは、複数の証明書をまとめてインポートできない仕様らしく、PEMフォーマットの証明書を複数連結したファイルをインポートしようとしても、「CA証明書1件」と表示されて、中間証明書を含めてインポートすることができません。
IPsecの認証プロトコル中でやり取りできる証明書も各方向1枚だけのため、インポートしたルート証明書から直接サーバ証明書を発行するしか手がありません。
(IKEのプロコトル仕様では、複数の証明書ペイロードを送ることができます。
実際にstrongSwanに改造を加えて実験してみましたが、なんとracoon側が1つ目の証明書ペイロード以外を無視する実装になっていたため失敗しました・・・)
- クライアント証明書については、自由度は高い
クライアント証明書の検証は、サーバ側の仕事ですので、サーバに必要な中間証明書をインストールすればどのような証明書でも使用することができます。
openswanやstrongswanでは、クライアントのID_DER_ASN1_DNタイプのIDにワイルドカードを指定できるため、証明書の識別名についても自由度があります。
「rightid = "C=JP, ST=Tokyo, O=KLab Inc., CN=*」と設定すれば、各ユーザに個別に発行している「C=JP, ST=Tokyo, O=KLab Inc., CN=USERNAME」のような識別名の証明書をすべて使えるようになります。
端末への証明書・秘密鍵のインポート方法
インポートするクライアント証明書と秘密鍵は 「.p12」拡張子のPKCS#12形式、CA証明書は「.crt」拡張子のPEM形式もしくはDER形式にして、SDカードのルートかdownloadディレクトリに置きます。
Android端末のCertInstallerは「.p12」または「.crt」の拡張子の付いたファイルのみインポートしようとしますので、必ず所定の拡張子にする必要があります。
インポートは、設定の「現在地情報とセキュリティ」メニューの「SDカードからインストール」から行えます。
それぞれ読み込みできると証明書名を聞かれますが、これは後ほどVPN設定を行う際、証明書を選ぶメニューでの名前になりますので、わかりやすい名前を設定しておきます。
Android端末では、インポート済みの証明書の一覧表示や削除のインタフェースが充実していないので、証明書名には「シリアル番号+コモンネーム」等、証明書の個体を識別しやすい名前にするとよいでしょう。
サーバサイドの準備
Android端末からのVPN接続を受け入れるサーバ側の構築を行います。
使用するソフトウェアは、以下のとおりです。
- openswan-2.6.36
- xl2tpd-1.3.0 (後述のチューニングのためにパッチ適用あり)
- ppp-2.4.5
それぞれ、ビルドオプションに特別なものを指定する必要はありません。
カーネルのESPサポートは、linux-3.0.4の標準のものを使用しています。
また、LinuxでのIPsec実装は、openswanのほかにstrongswanもありますが、strongswanではVPN切断後の再接続が上手くいかないという問題が発生したため、openswanを使用します。
Android端末からのL2TP/IPsec接続では、VPN切断時にサーバ側のSAをうまく削除してくれないという問題があるようで、サーバ側にlifetime時間までSAが残ってしまいます。strongswanを使用するとSAが残っている間は再接続(2本目のSA)が出来ず通信不能になってしまいました。
openswanの設定ファイル(ipsec.conf)は以下のとおりです。
config setup interfaces = %defaultroute syslog = auth.error plutodebug = "control" uniqueids = no nat_traversal = yes conn Android-L2TP-IPsec auto = add type = transport keyexchange = ike auth = esp authby = rsasig pfs = no keyingtries = 1 ikelifetime = 8h keylife = 8h rekeymargin = 10m left = %defaultroute leftid = "C=JP, ST=Tokyo, O=KLab inc., (略)" leftrsasigkey = %cert leftcert = Android_IPsec_Server.pem leftupdown = "/bin/true" leftprotoport = 17/1701 right = %any rightca = "C=JP, ST=Tokyo, O=KLab Inc., (略)" rightid = "C=JP, ST=Tokyo, O=KLab Inc., (略)" rightrsasigkey = %cert rightprotoport = 17/%any
読み込む証明書の指定(leftcert)、各ID等は環境に合わせて調整する必要があります。
サーバ証明書を/etc/ipsec.d/certsに、秘密鍵を/etc/ipsec.d/private、クライアント証明書の検証に必要な中間CA証明書を/etc/ipsec.d/cacertsにそれぞれ設置します。
ファイル名は、OpenSSL流のハッシュ形式にしなくても自動的に読み込んでくれます。
また、秘密鍵の復号化に必要なパスワードは/etc/ipsec.secretsに以下のように記述します。
(leftcertに指定しているAndroid_IPsec_Server.pemに対応する秘密鍵が、/etc/ipsec.d/private以下にAndroid_IPsec_Server.keyの名前で保存されている前提です。)
: RSA Android_IPsec_Server.key "hogefuga"
xl2tpd側の設定ファイル(xl2tpd.conf)とpppオプション(ppp-options)は以下のとおりです。
トンネルに割り当てるIPアドレス(local ipやip range)もお好みで選んでください。
[global] listen-addr = 0.0.0.0 port = 1701 debug network = no debug state = no debug tunnel = no [lns default] pppoptfile = /etc/xl2tpd/ppp-options hostname = gw1 exclusive = No local ip = 10.100.254.1 ip range = 10.100.254.128 - 10.100.254.254 length bit = yes require chap = yes refuse pap = yes require authentication = yes
pppoptfileでは、認証可否の設定の他、Android端末に通知するDNSサーバを設定します。
auth refuse-pap require-chap ms-dns 10.100.254.1
上記設定では、CHAP認証を行う設定になっています。許可するユーザ名・パスワードは/etc/ppp/chap-secretsに記述します。
USERNAME * "PASSWORD" *
PAP/CHAPともにrefuseとすると、L2TP上では認証しない設定にすることができますが、Android端末ではユーザ名・パスワードの入力が必須になっているため、ダミーのパスワードを入力する必要があります。
Android側からのVPN接続を行うと、端末からのすべての通信がトンネルを経由してサーバへルーティングされます。
サーバ側では、トンネルごとにPPPインタフェースが作られますので、適宜インターネットや社内のシステムに対してルーティング・アクセス許可してあげれば通信可能になります。
名前解決に使われるDNSサーバはpppoptfileのms-dnsで指定したサーバが使用されます。
Android端末サイドのVPN設定
Android端末に、VPN接続のプロファイルを作ります。
設定の「無線とネットワーク」から「VPN設定」とたどります。
「VPNの追加」をタップして「L2TP/IPsec CRP VPNを追加」を選びます。
各項目を以下のように設定して保存すると、プロファイルが作られます。
- VPN名: 任意の名前を設定します
- VPNサーバの設定: 接続するサーバのFQDNを指定します
- L2TPセキュリティ保護を設定: チェックを外したままにします
- 証明書を設定する:インポートしたクライアント証明書を選びます
- CA証明書を設定する:インポートしたCA証明書を選びます
- DNS検索ドメイン: 社内のドメイン名等を任意で設定します
保存後、VPN欄に現れたプロファイルをタップして、サーバのchap-secretsに記述したユーザ名・パスワードを入力し接続します。
画面上部に「VPN [プロファイル名] が接続されました」と出ればVPN接続は完了です。
切断する場合には、プロファイルを再度タップすれば切断されます。
VPN接続中に通信できなくなる問題
筆者の環境では、長時間接続していると、突然通信不能になる症状が発生しました。
調査してみたところ、キャリアあるいは電波状況によっては、不定期的にパケロスする状況が発生するようで、L2TPサーバ側で定期的に送っているHelloメッセージがロスし最大再送回数に到達してしまったためにトンネルが閉じられているという状態でした。
Android端末側から、L2TPトンネルの切断を検知できるようなメッセージを送出できると良いのですが、端末標準の設定インタフェースでは実現できないため、サーバサイドで出来る限りの調整を行なってみます。
今回使用したL2TPサーバ(xl2tpd)では、HelloメッセージによるKeepaliveの間隔(60秒)、コントロールメッセージの再送間隔(1秒)・最大再送回数(5回)がソースコード中にハードコードされています。
この再送条件では、電波状況等により通信品質が変化するモバイル環境には厳しすぎますので、設定ファイルからパラメータを変更できるようにするパッチを書いてみました。
xl2tpd-1.3.0-add-ctrl-retrans-opt.patch(2012/02/17:コメントにて、1.3.1では上記パッチが当たらないという連絡をいただきましたのでdiffを取り直しました: xl2tpd-1.3.1-add-ctrl-retrans-opt.patch )
このパッチを適用すると、設定ファイルのglobalセクションに以下の3つのパラメータを設定できるようになります。
- hello delay (Helloメッセージの送信間隔)
- ctrl retrans max (最大再送回数)
- ctrl retrans delay (再送回数)
各パラメータをそれぞれ、Hello送信間隔を5分(hello delay = 300)、コントロールパケットの再送間隔を10秒(ctrl retrans delay = 10)で最大再送回数を18回(ctrl retrans max = 18)とすれば、最大3分程度通信不能な時間が発生してもトンネルを閉じられずにすみます。
長すぎる値を設定すると、本当に端末がオフラインになってしまっていても、長時間サーバ側にトンネルが残り続けてしまうため注意が必要です。
このパッチによるパラメータの変更は、あくまで切断される条件の緩和ですので、確実にVPNが切れなくなるわけではありませんが、多少の効果はあると思います。
IPSec で暗号化されたパケットの,iptables 上での識別方法
Linux 2.6 カーネルでは,2.4 カーネルの FreeS/WAN の IPSec の実装とは異なって,ESP でカプセル化されたパケットが届くネットワークインタフェースと,カプセル化が解かれたパケットが届くインタフェースは同じものにるので,IPSec のチャンネルを通ってきたパケット,を識別しづらくなっています.(2.4 カーネルの FreeS/WAN の場合,カプセル化が解かれたパケットは,専用のインタフェースから届く形になっています.) しかし,フィルタリングする上で,そのパケットが ESP 化されて届いたのかそれとも生の形で届いたのかを識別するのは,時としてとても重要になります.
続きを読む
Linux 2.6 kernel の IPsec と NAT -- 2.6.15 と 2.6.16 の IPsec の違い
先に結論から書くと,2.6 kernel の IPsec を NAT と絡めて使う場合,NAT の方向によっては 2.6.16 以後のカーネルを使う必要がある,というお話です.2.6.15 系までのカーネルでは,意図したとおりに NAT が適用され無いことがあります.これは,IPsec のチャンネルを通るパケットが ESP パケットにカプセル化されるタイミング,に依るものです.
続きを読むLinux 2.4 と 2.6 で IPsec 接続 -- FreeS/WAN と racoon
VPN ゲートウェイは Linux マシンです.この VPN は 2年以上前から動いているのですが,その当時の Linux のカーネルの主流は 2.4 で,これに FreeS/WAN のパッチを当てて IPsec を動かしていました.そしてそれは今も動いています.
最近,事業所の一つが移転したのですが,それに併せて VPN ゲートウェイを新調しました.その際,カーネルを 2.6 系に移行しました.この事業所でも当然ながら VPN 接続する必要があります.
2.6 系のカーネルでは IPsec のコードが初めから取り込まれています.このコードは kame の流れを汲む usagi のコードベースです.つまり,FreeS/WAN とは系統が異なる実装になります.系統が異なっていても,IPsec は RFC に規格化されたプロトコルですから基本的に相互接続には問題ないのですが,ただ相互の認証に公開鍵暗号系を用いようとすると少々面倒でした.
IPsec で peer 同士の認証をするのに公開鍵を使う場合,x509 証明書を用いた PKI を使うのが一般的ですが,patch(http://www.strongsec.com/freeswan/ など) を適用していない FreeS/WAN では,x509 証明書を扱うことができません.そして残念ながら,KLab の VPN ゲートウェイサーバの FreeS/WAN にはパッチはあたっていません.新たに patch を当ててカーネルを作り直すか,或いは(既に開発が終了している) FreeS/WAN をやめて Openswan や strongSwan を使えば 2.4 カーネルでも x509 証明書を扱えますが,今回は現在 FreeS/WAN が動いている VPN ゲートウェイサーバには手を加えたくありません.ということで,2.6 カーネルの IPsec の方で何とか FreeS/WAN にあわせることにしました.続きを読む