はじめに
iOSでIKEv2が使えるようになっているのですが、パスワード認証の話が多くて証明書による認証について書いてあるところがあまりないので、試行錯誤の結果をまとめておきます。
目的
以下の環境での接続を目指します。
- サーバ
- Ubuntu 18.04
- strongSwan (swanctl)
- オレオレサーバ証明書
- クランアント
- iPhone (iOS 14)
- オレオレクライアント証明書
- 使用プロトコルと認証方法
- IKEv2
- クライアント証明書認証(パスワードなし)
凡例
この文章では以下のようになっているものとします。
- ドメイン名:
exmaple.com
- サーバホスト名:
servername.example.com
- サーバアドレス:
198.51.100.1
(DNS登録済み) - クライアント名:
iphone
- クライアントに割り当てる仮想IP:
203.0.113.0/24
- クライアント用DNSサーバ:
198.51.100.2
- オレオレ証明書の名前:
strongswan-ca.example.com
説明上ここではIPv4しか使っていませんが、IPv6も使えます。poolとlocal_tsに,
で区切ってIPv4アドレスとともに列挙すればOKです。
ただし、ping6は通るのですがtest-ipv6.comやaaaa onlyなwebサイトとの通信は失敗しますので事実上使い物になりません。
ハマりどころ
試行錯誤の結果、以下に気をつけると繋がりました。
証明書はRSAではなくECDSAにする
以下のバグのためstrongSwanはRSA証明書での検証ではSHA1しか使えないのですが、iOSはRSA証明書についてはSHA2を使うようです(というかIKEv2のRFCにはSHA2を使えと書いてある)。
そこで、クライアント側もサーバ側もECDSAを使うことでこの問題を回避します。
Issue #3343: signature validation failed, looking for another key - strongSwan
証明書のCN, SAN, EKU
iOSにいれるクライアント証明書は、
- CN, SANともに
iphone@example.com
やiphone.example.com
の形式ではなくiphone
にする - EKUは
clientAuth
strongSwanにいれるサーバ側の証明書は、
- CN, SANともに
servername.example.com
にする - EKUは
serverAuth
となります。
IKEv2 で接続できた各OSの認証方式とクライアント証明書 | KUSONEKOの見る世界
オレオレCAのルート証明書をいれて信頼する必要がある
後述のmobileconfigに同梱すればIKEv2接続時だけ勝手に信頼してくれるのかと思ったのですが、そうではありませんでした。
mobileconfigとは別にオレオレCAのルート証明書もいれて設定 - 一般 - 情報 - 証明書信頼設定
から全面的信頼をつける必要があります。
スマートフォン(iOS、Android)にオレオレ証明書がインストールできない? | 世界一わかりやすいセキュリティ
サーバ証明書をletsencryptにした場合、この作業は不要なようです (2022/05追記)。
mobileconfig
- 「証明書のタイプ」はECDSA521など正しい値を選択する(ECDSAなのにRSAのままにしない)
- 「EAPを使用する」はチェックしない
EAPを頑張ったのですがどうもうまくいきませんでした...
IKEv2 Configuration Profile for Apple iOS 8 and newer - IKEv2 Configuration Profile for Apple iOS 8 and newer - strongSwan
iOS (Apple iPhone/iPad...) and macOS Interoperability - iOS (Apple iPhone/iPad...) and macOS Interoperability - strongSwan
OpenSSL 3.0系のp12はiOS/macOSでインポートできない
OpenSSL 3.0の生成したp12の暗号化の形式にiOS/macOSが対応していないようです。
Ubuntu 22のOpenSSLは3.0ですので、頑張って1.1.1系を入れるか--certpbe NONE --keypbe NONE
と諦めてしまう必要があります。
SecPKCS12Import is failing to impo… | Apple Developer Forums
rdar://FB8988319: MacOS security framework fails to import RFC 7292 compliant PKCS #12 v1.1 file into keychain using modern cyphers
proposalsを指定する
strongswanのdefaultではだめでした。
証明書作成
こちらに掲載のスクリプトを修正して使いました。感謝。
strongSwanでVPN (IKEv2)を構築する - Qiita
CA生成
一回だけ実行します。
#!/bin/bash
set -x
set -e
#各種 key,crt,サーバードメインを埋めて実行します。
#ファイル名
readonly CA_NAME=strongswan-ca
readonly DOMAIN=example.com
readonly OUTPATH=$HOME/strongswan/pki/certs/
readonly CA_KEY=$CA_NAME.key
readonly CA_CRT=$CA_NAME.crt
#初期化
rm -fr $OUTPATH
mkdir $OUTPATH
LIFETIME="--lifetime 3650"
#ルート証明書作成
cakey=${OUTPATH}/${CA_KEY}
cacrt=${OUTPATH}/${CA_CRT}
ipsec pki --gen $OPTS > $cakey
ipsec pki --self --in $cakey $LIFETIME --ca --dn "C=JP, O=strongSwan, CN=${CA_NAME}.${DOMAIN}" --san "${CA_NAME}.${DOMAIN}" > $cacrt
#確認
openssl x509 -text -fingerprint -noout -inform der -in $cacrt
#pem
openssl x509 -inform der -outform pem -in $cacrt > ${OUTPATH}/${CA_NAME}.pem
etckey=/etc/swanctl/ecdsa/$CA_KEY
etccrt=/etc/swanctl/x509ca/$CA_CRT
sudo cp $cakey $etckey
sudo cp $cacrt $etccrt
sudo chmod 600 $etckey $etccrt
sudo chown root:root $etckey $etccrt
サーバ証明書生成
#!/bin/bash
set -x
set -e
readonly HOST_NAME=$1
[ -z "$HOST_NAME" ] && no_host_name
readonly CA_NAME=strongswan-ca
readonly DOMAIN=example.com
readonly P12PASS=opensesame
readonly OUTPATH=$HOME/strongswan/pki/certs/
readonly CA_KEY=$CA_NAME.key
readonly CA_CRT=$CA_NAME.crt
readonly SRV_DOMAIN=example.com
readonly VPN_KEY=$HOST_NAME.key
readonly VPN_CRT=$HOST_NAME.crt
cakey=${OUTPATH}/${CA_KEY}
cacrt=${OUTPATH}/${CA_CRT}
srvkey=${OUTPATH}/${VPN_KEY}
srvcrt=${OUTPATH}/${VPN_CRT}
OPTS="--type ecdsa --size 521"
TYPE=ecdsa
TYPE_O=ec
#OPTS="--type rsa --size 4096"
#TYPE=rsa
#TYPE_O=rsa
LIFETIME="--lifetime 3650"
[ -e $srvkey ] && already_exists
ipsec pki --gen $OPTS > $srvkey
if [[ $0 =~ server ]] ;then
echo server
FLAGS="--flag serverAuth"
SAN="${HOST_NAME}.${SRV_DOMAIN}"
elif [[ $0 =~ name ]] ;then
# ios用
FLAGS="--flag clientAuth"
SAN="${HOST_NAME}"
elif [[ $0 =~ client ]] ;then
# macos他普通のクライアント用
FLAGS="--flag clientAuth"
SAN="${HOST_NAME}@${SRV_DOMAIN}"
fi
ipsec pki --issue $LIFETIME $FLAGS \
--in $srvkey --type priv --cacert $cacrt --cakey $cakey \
--flag serverAuth \
--dn "C=JP, O=strongSwan, CN=${SAN}" \
--san ${SAN} > $srvcrt
#確認
openssl x509 -text -fingerprint -noout -inform der -in $srvcrt
etckey=/etc/swanctl/$TYPE/$VPN_KEY
etccrt=/etc/swanctl/x509/$VPN_CRT
sudo cp $srvkey $etckey
sudo cp $srvcrt $etccrt
sudo chmod 600 $etckey $etccrt
sudo chown root:root $etckey $etccrt
#p12
p12=${OUTPATH}/${HOST_NAME}.p12
ca_cert_pem=$(tempfile).pem
srv_cert_pem=$(tempfile).pem
srv_key_pem=$(tempfile).pem
openssl x509 -inform der -outform pem -in $cacrt -out $ca_cert_pem
openssl x509 -inform der -outform pem -in $srvcrt -out $srv_cert_pem
openssl $TYPE_O -inform der -outform pem -in $srvkey -out $srv_key_pem
openssl pkcs12 -export -passout pass:$P12PASS -inkey $srv_key_pem -in $srv_cert_pem -certfile $ca_cert_pem -out $p12
rm $ca_cert_pem $srv_cert_pem $srv_key_pem
これを実行します。
$ ./genserver.sh servername
なお、クライアント証明書はオレオレのままでサーバ証明書だけをletsencryptにすることもできます。
この場合、iOS/mac側でオレオレCAを強制的に信用しなくてもクライアント証明書が使えます。
Ubuntuで作るiOS/AndroidでIKEv2接続できるVPN環境のつくりかた - Qiita
クライアント証明書生成
上記スクリプトをgennamesh
にln -s
して実行します。
$ ln -s genserver.sh genname.sh
$ ./genname.sh iphone
StrongSwan (swanctl) の設定
connections {
iphone {
local_addrs = 198.51.100.1
proposals = aes256-sha256-modp2048
local {
auth = pubkey
certs = servername.crt
id = servername.exmaple.com
}
remote {
auth = pubkey
id = iphone
}
pools = iphone-pool
children {
iphone {
local_ts = 0.0.0.0/0,::/0
remote_ts = 203.0.113.0/24
esp_proposals = aes256-sha256
}
}
encap = yes
send_certreq = no
send_cert = always
keyingtries = 10
version = 2
mobike = yes
reauth_time = 0
}
}
pools {
iphone-pool {
addrs = 203.0.113.0/24
dns = 198.51.100.2
}
}
iPhoneの設定
Apple Configurator 2を使ってmobileconfigを生成します。
- 証明書
-
genname.sh
で生成したPKCS12をいれる
-
- VPN
- 接続のタイプ: IKEv2
- サーバ:
servername.exmaple.com
- リモート識別子:
servername.example.com
- ローカル識別子:
iphone
- コンピュータ認証: 証明書
- 固有名証明書: 上記の追加したPKCS12を選択
- 証明書のタイプ: ECDSA521
- EAPを有効にする: チェックしない
- 暗号化アルゴリズム: AES-256
- 整合性アルゴリズム: SHA2-256
作ったら安全にiPhoneに転送してプロファイルとして取り込みます。
それとは別に、initca.sh
が生成してくれたstrongswan-ca.pem
もいれて、設定 - 一般 - 情報 - 証明書信頼設定
から全面的信頼をつけます。
参考: macOSの場合
$ ln -s genserver.sh genclient.sh
$ ./genclient.sh mac
としてmac.p12
を生成してキーチェーンに取り込み、やはりルート証明書として強制的に信頼させたあと、環境設定 - ネットワーク
から以下の条件でVPNを設定します。
- VPNタイプ: IKEv2
- サーバアドレス:
servername.example.com
- リモートID:
servername.exmample.com
- ローカルID:
mac@example.com
- 認証設定: 証明書 (
mac@example.com
の証明書を選択)
ChaCha20-Poly1305
高速でNSAのバックドアが無いであろうchacha20-poly1305も使えます。
iPhone側はAESをハードウェアで処理できるそうですので、もしかしたらAESの方がバッテリーには優しいかもしれませんが...
strongSwan と iOS (13+) 端末間で IKEv2 に ChaCha20-Poly1305 を使って遅いラズパイ VPN サーバーを高速化 - Qiita
一つだけ問題がありまして、Ubuntu 18.04のstrongSwan 5.6.2にはchacah20poly1305回りにバグがあり、そのままではつながりません。
以下のチケットに記載のパッチをあてるか、5.6.3以上を入れる必要があります。
Issue #3195: chacha20poly1305 cipher name mismatch on proposals - strongSwan
謝辞
strongSwanの製作者、リンク先の記事の筆者の皆様、ありがとうございます。