ipsec

2011年11月22日

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プロファイル画面

保存後、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が切れなくなるわけではありませんが、多少の効果はあると思います。


#dSn
klab_gijutsu2 at 17:49|この記事のURLComments(9)
2006年05月17日

IPSec で暗号化されたパケットの,iptables 上での識別方法

はてなブックマークに登録

Linux 2.6 カーネルでは,2.4 カーネルの FreeS/WAN の IPSec の実装とは異なって,ESP でカプセル化されたパケットが届くネットワークインタフェースと,カプセル化が解かれたパケットが届くインタフェースは同じものにるので,IPSec のチャンネルを通ってきたパケット,を識別しづらくなっています.(2.4 カーネルの FreeS/WAN の場合,カプセル化が解かれたパケットは,専用のインタフェースから届く形になっています.) しかし,フィルタリングする上で,そのパケットが ESP 化されて届いたのかそれとも生の形で届いたのかを識別するのは,時としてとても重要になります.


続きを読む
klab_gijutsu2 at 22:53|この記事のURLComments(0)TrackBack(0)
2006年05月02日

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 パケットにカプセル化されるタイミング,に依るものです.

続きを読む
klab_gijutsu2 at 15:26|この記事のURLComments(0)TrackBack(0)
2006年04月07日

Linux 2.4 と 2.6 で IPsec 接続 -- FreeS/WAN と racoon

はてなブックマークに登録

KLab の事業所は何ヶ所かあって,当然ながらそれぞれに LAN が組まれています.ご多分に漏れず,弊社でもこれらの 事業所 LAN 同士を VPN 接続しています.事業所間が離れていることもあり,over Internet で IPsec を使って構成しています.
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 をやめて OpenswanstrongSwan を使えば 2.4 カーネルでも x509 証明書を扱えますが,今回は現在 FreeS/WAN が動いている VPN ゲートウェイサーバには手を加えたくありません.ということで,2.6 カーネルの IPsec の方で何とか FreeS/WAN にあわせることにしました.続きを読む

klab_gijutsu2 at 16:02|この記事のURLComments(0)TrackBack(0)
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント