プロファイルをインストールし、トンネルが上がり、すべて完了したように見えます。ところが端末が再起動すると、on-demandが前面にアプリがないまま接続を呼び起こし、トンネルは起動を拒否します。プロファイルは残っており、パスワードも正しいままです。だからこそこの障害は、人を間違った場所へ探しに行かせます。Network Extensionはプロファイルから秘密を読めません。自分で解決できるキーチェーンの参照が必要です。

以下の解説は、参照を組み立てるクエリから、それを試す再起動まで、この受け渡しを通る一回の接続試行を追います。

プロファイルが保存するのは取っ手であり、秘密ではない

NEVPNProtocolがpasswordReferenceとsharedSecretReferenceを公開しているのには理由があります。デーモンがトンネルを開始するとき、それはあなたのプロセスの中で動いているとは限らず、あなたのコードを呼んでパスワードを尋ねることはできません。永続的なキーチェーンの参照を読み、自分のコンテキストで解決し、項目そのものを取得します。

その参照は、キーチェーンに問い合わせて作ります。これはデータを求めるのとは別の要求です。

let query: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword,
  kSecAttrService as String: "vpn-credentials",
  kSecAttrAccount as String: "user",
  kSecReturnPersistentRef as String: true,
  kSecMatchLimit as String: kSecMatchLimitOne
]

返ってきたオブジェクトをプロトコルに渡し、項目のデータを渡してはいけません。実際にはプロファイルは秘密がどこに住むかを記録するのであり、構成のバックアップがパスワードをplistへ静かに複製しないのもそのためです。

APIが-50を返す理由

errSecParamは、要求または値がその操作に対して不正な形だったことを意味し、参照の要求とデータの要求が混ざったときにもっともよく見るエラーです。受け取る側のコードが参照を待っているのにデータを要求する、生のバイト列をpasswordReferenceに代入する、アプリが使えないアクセスグループで問い合わせる、いずれもこれを生みます。

ラッパーライブラリはこれを誘います。バイト列を返すヘルパーは参照を返すヘルパーと交換できません。したがって修正はたいてい、別のパスワードではなく別のクエリです。

同じ食い違いが、文句も出さずに保存され、その後に接続できないプロファイルを説明します。保存されたのは取っ手ではなく塊であり、デーモンには解決するものがありません。

保存は成功し、接続時に失敗する

保存はアプリのコンテキストで行われます。接続はデーモンのコンテキストで行われ、両者のアクセス権は同じではありません。だからプロファイルはシミュレータで走らせたすべてのテストに通り、最初のon-demand起動で失敗し得ます。

再起動の症状も同じ形です。アプリが開いている間は動き、システムが自分でトンネルを開始すると死にます。

出荷前にバックグラウンド経路を意識して試してください。アプリを閉じたままon-demand接続を起こし、アプリのログではなくデバイスのコンソールを読みます。失敗は参照を解決しようとしたプロセスが記録するからです。コンソールがデーモンによる項目の拒否を示すなら、参照は正常で、問題は項目のアクセシビリティです。

再起動が機能するかはアクセシビリティが決める

項目は、トンネルを起動するどのシステムプロセスからも読めなければならず、それはアプリが前面にあるときより広い相手です。その制約がアクセシビリティ属性を決めてくれます。最初のロック解除まで保護された項目は、端末が一度ロック解除された後のバックグラウンド起動に利用できます。端末がいまロック解除されていることを要求する項目は利用できません。

より厳しいクラスを選ぶのは魅力的で、まさにそれがロックされた端末でon-demandプロファイルを壊します。

クラスを変えたら、既存の項目の属性を編集するのではなく項目を作り直し、新しい参照でプロトコルを保存し直します。既存の項目への属性変更は、ドキュメントが示唆する挙動をいつも生むとは限らず、作り直した項目は疑いを消します。

ローテーションこそ保存済みプロファイルが腐る場所

キーチェーンのパスワードを変えても、保存済みプロファイルが指す先は自動では変わりません。項目を削除するのはもっと悪いことです。プロファイルはもう存在しないものへの参照を持ち続け、障害は認証の拒否のように見えます。

ローテーションは一つの順序で行います。項目を更新し、新しい永続参照を問い合わせ、プロトコルを保存し直し、アプリを閉じたまま接続してデーモンがまだ解決できることを示します。利用者のパスワードと共有シークレットは別の項目に保ち、それぞれが自分の予定で回るようにし、クエリの辞書や参照そのものを決してログに残さないでください。取っ手は秘密ではありませんが、秘密への地図です。

関連記事