xcrun simctl keychainは、シミュレータのキーチェーンに証明書を追加したり、キーチェーンをリセットしたりするサブコマンドである。 自己署名の証明書を使ったローカル開発用サーバーへの接続を、シミュレータに信頼させたい場合などに使う。

基本的な使い方

keychain <device> <action> [arguments]の形式で、add-root-certadd-certresetのいずれかのアクションを指定する。

$ xcrun simctl keychain "TIL Keychain iPhone" add-root-cert ./cert.pem

add-root-certで証明書をルート証明書ストアに追加する

add-root-certは、指定した証明書をデバイスの信頼済みルート証明書ストアに追加する。 デバイスのデータディレクトリ配下にあるtrustdの実データベース(private/var/protected/trustd/private/TrustStore.sqlite3)を直接見ると、証明書のSHA-256ハッシュが実際に追加されていることを確認できる。

$ sqlite3 <UDID>/data/private/var/protected/trustd/private/TrustStore.sqlite3 "SELECT count(*) FROM tsettings;"
0

$ xcrun simctl keychain "TIL Keychain iPhone" add-root-cert ./cert.pem

$ sqlite3 <UDID>/data/private/var/protected/trustd/private/TrustStore.sqlite3 "SELECT count(*) FROM tsettings;"
1

$ openssl x509 -in ./cert.pem -outform DER | shasum -a 256
5a89edc0278d5893bd27d23955fb5177f719e41196f417090e9af46672a62f52

$ sqlite3 <UDID>/data/private/var/protected/trustd/private/TrustStore.sqlite3 "SELECT hex(sha256) FROM tsettings;"
5A89EDC0278D5893BD27D23955FB5177F719E41196F417090E9AF46672A62F52

実際に効果があることは、自己署名の証明書を使ったローカルHTTPSサーバーをシミュレータ内のSafariで開いて確認できる。 add-root-certを実行する前は証明書エラーで接続が拒否されるが、実行後は同じサーバーへ警告なしで接続できるようになる。

トラスト設定の検証はsimctl spawnではなく実際のアプリで行う方が確実

xcrun simctl spawncurlnscurlを実行してadd-root-certの効果を確認しようとしても、実行前後で結果は変わらず、常に証明書エラーになる。

$ xcrun simctl spawn "TIL Keychain iPhone" /usr/bin/nscurl https://127.0.0.1:18443/
Load failed with error: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. ..."

spawnで起動したプロセスはホスト(Mac本体)のプロセスとして動くため、デバイス固有のtrustdを正しく参照できていないとみられる。 実際、curl -vで同じリクエストを送るとCAfile: /etc/ssl/cert.pemというホスト側の証明書ストアのパスが出力されており、この推測を裏付けている。

xcrun simctl openurlでiOSシミュレータ内のURLを開く でSafariに同じURLを実際に開かせると、add-root-cert前後で挙動がはっきり変わることを確認できる。 証明書の信頼設定のようにOS全体のセキュリティコンポーネントが関わる項目を検証する際は、spawnで外部コマンドを実行するよりも、実際のアプリ経由で動作を確認する方が確実である。

resetはルート証明書の信頼設定を削除しない

ヘルプには「キーチェーンをリセットする」とだけ書かれているが、実際にadd-root-certで追加した証明書はresetを実行しても削除されない。

$ sqlite3 <UDID>/data/private/var/protected/trustd/private/TrustStore.sqlite3 "SELECT count(*) FROM tsettings;"
1

$ xcrun simctl keychain "TIL Keychain iPhone" reset

$ sqlite3 <UDID>/data/private/var/protected/trustd/private/TrustStore.sqlite3 "SELECT count(*) FROM tsettings;"
1

reset実行後も、Safariから同じ自己署名の証明書のサーバーへ警告なしで接続できる状態が続くことも確認できる。 add-root-certで追加した信頼設定を取り消したい場合は、resetではなくデバイス自体をxcrun simctl eraseでiOSシミュレータデバイスの内容を初期化する する必要がある。

add-certはルート証明書ストアに影響しない

add-certは証明書をキーチェーンに追加するだけで、add-root-certとは異なりルート証明書ストアには影響しない。

$ sqlite3 <UDID>/data/private/var/protected/trustd/private/TrustStore.sqlite3 "SELECT count(*) FROM tsettings;"
0

$ xcrun simctl keychain "TIL Keychain iPhone" add-cert ./cert.pem

$ sqlite3 <UDID>/data/private/var/protected/trustd/private/TrustStore.sqlite3 "SELECT count(*) FROM tsettings;"
0

サーバー証明書として信頼させたい場合はadd-root-certを使う必要がある。

存在しないファイルを指定するとエラーになる

$ xcrun simctl keychain "TIL Keychain iPhone" add-root-cert /nonexistent/cert.pem
An error was encountered processing the command (domain=NSCocoaErrorDomain, code=260):
Simulator device failed to complete the requested operation.
The file doesn't exist.