iOSのコード署名がなんのためにどうやって行われているかを理解する

## 背景

iOS 開発に入門する上でつまづきやすい概念にコード署名があります。アプリを実機で動かすまでのコード署名の手続き自体は実行できても、それぞれの手続きがなんのために行われているかをちゃんと理解するのはなかなか難しいと思います。この記事では、

- そもそもコード署名はなんのために、どうやって行われているのか
- Profile や `.p12` などいろいろなファイルが登場してくるけどそれぞれの役割はなんなのか

を調べたのでまとめます。

この記事にはこう思えば自分は理解しやすいなと思った内容を書いたので、有識者から見ると単純化しすぎていたりそもそも間違っているものが含まれているかもしれません。その場合はコメントで教えていただけると助かります。

## 参考リンク

- [Code Signing Guide](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005929-CH1-SW1)
- [Understanding code signing for iOS apps](https://www.nodesagency.com/understanding-code-signing-for-ios-apps/)
- [Xcodeと署名](https://scrapbox.io/tasuwo-ios/Xcode_%E3%81%A8%E7%BD%B2%E5%90%8D)
- [Inside Code Signing](https://www.objc.io/issues/17-security/inside-code-signing/)
- [iOSアプリのプロビジョニング周りを図にしてみる](https://qiita.com/fujisan3/items/d037e3c40a0acc46f618)
- [暗号技術入門 第3版　秘密の国のアリス](https://www.amazon.co.jp/dp/B015643CPE/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)

## コード署名がなぜ必要なのか

コード署名は、iOSが信頼できるアプリしかインストールしないようにするための仕組みです。ここで信頼できるアプリとは何かというと

- 開発者を特定できる
- 開発者が署名してからアプリが改変されていない

という２つの条件を満たすものを指しています。この仕組みがないと、その辺の適当な人が作った適当なアプリや、ちゃんとした開発者が作ったがその後別の誰かによって改変されたアプリをインストールすることが可能になります。これを防ぐため、iOSでは署名されたアプリしか実行できないようになっており、開発者はアプリに署名をする必要があるというわけです。

当然、悪意を持った開発者がAppleに開発者登録して正常にリリースしたアプリはインストールできてしまいますが、そのような問題はコード署名のスコープ外です。

## コード署名でやっていること

以上の目的を達するため、コード署名でどういうことをしているのかを大まかに書いておきます。ここで登場する用語に馴染みのない方は [暗号技術入門 第3版　秘密の国のアリス](https://www.amazon.co.jp/dp/B015643CPE/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1) を一読するのがおすすめです。

- アプリの開発者は、公開鍵/秘密鍵のペアを用意する
    - 公開鍵はデジタル証明書の形でインストール先のiOSに渡される
    - 秘密鍵は鍵ペアを生成したマシンに保持され、署名に使われる
- アプリをアーカイブする際、あらかじめ決められた手順でアプリからハッシュ値を計算し、これを秘密鍵で暗号化してアプリに同梱する。このプロセスが署名と呼ばれる
- アプリをインストールしたiOSは、同じ手順でアプリからハッシュ値を計算する。一方で、アーカイブ時に計算されて暗号化されたハッシュ値を証明書に含まれている開発者の公開鍵で復号する。これら２つの方法で得られたハッシュ値が一致することを確認する

ここで、まず暗号化されたハッシュ値が正しく復号できたことで、コード署名の１つ目の目的である「開発者の特定」ができます。ハッシュ値が公開鍵で復号できるということはそれを暗号化したのは開発者の秘密鍵であり、秘密鍵を持っているのは開発者本人しかいないからです。
次に、２つのハッシュ値が一致したということから、コード署名の２つ目の目的である「開発者が署名した後アプリが改変されていないことの確認」ができます。もし第三者がアプリの内容をいじっていたらiOSで計算されるハッシュ値が変わってしまい一致しなくなるためです。ハッシュ値の方も一緒に変えられたらまずいんですが、第三者は開発者の秘密鍵を持っておらずハッシュ値の暗号化ができないため、改変したハッシュ値をばれないように差し替えることができないことになります。もちろんその辺の適当な秘密鍵で暗号化することは可能ですが、そうすると開発者の公開鍵では復号できず、そこでiOSでの検証に引っかかることになります。

## コード署名の登場人物

大まか流れがわかったところで、コード署名に登場するいろいろな概念を、

- 何者なのか
- コード署名による役割は何か
- どうやって作るか

に注目してまとめます。

### 証明書署名要求（CSR）： `.certSigningRequest` ファイル

- 何：開発者の公開鍵＋開発者の情報
- 役割：Appleから証明書をもらうための引換券
- 作り方：ローカルのキーチェーンアプリで作成する

コード署名のプロセスの始まりがCSRの作成です。すごそうな名前なので作り方も難しいのかと思いきや、Macを使っている人なら馴染み深いであろうキーチェーンアプリで簡単に作ることができます。具体的な手順は検索すればすぐ出てきます。
CSRを作るとき、裏側では秘密鍵・公開鍵のペアが生成されます。公開鍵はCSRに含まれ、秘密鍵はローカルに保存されます。前の項に書いたように、コード署名というのは大雑把に言うとこのとき生成された秘密鍵で開発者がアプリに署名をし、それをインストール先のiOSが公開鍵で検証するというものなので、ここで生成される鍵ペアは非常に重要な役割を持ちます。
CSRは開発者の公開鍵に開発者情報などを付加したもので、これををAppleに渡すことで、Appleから証明書をもらうことができます。CSR作成にあたって一緒に作られる秘密鍵と公開鍵は重要ですが、CSR自体はその後のプロセスでは使われることはないので、コード署名の概要を理解する上では証明書をもらうための引換券くらいに思っておけば良さそうです。

### 証明書（Certificate）： `.cer`ファイル

- 何：「開発者の公開鍵＋開発者の情報」にAppleが署名したもの
- 役割：アプリのインストール先のiOSに信頼できる公開鍵を提供する
- 作り方：CSRをDeveloper Centerにアップロードすると生成され、ダウンロードできるようになる

アプリがインストールされたiOSは署名の検証のために公開鍵を使いますが、その公開鍵が第三者にすり替えられた信用できないものである可能性があるという問題があります。Apple先生のお墨付きがあれば信頼できるよね...ということで開発者の公開鍵にAppleが自身の秘密鍵で署名したのが証明書です。iOSはまずAppleの証明書の署名を検証することで、公開鍵が証明書に書いてある開発者自身のものであり改変もされていないことを確認できるというわけです。つまり、コード署名を検証するために必要な、信頼できる公開鍵をiOSに提供することが証明書の主要な役割だと思います。
証明書には有効期限がついており、定期的に作り直す必要があります。また、証明書の元となる公開鍵が１つであったとしても証明書は役割ごとに複数作ることが多く、アプリのコード署名についてだけでもXCodeでビルドしたアプリを実機で走らせるためのDevelopment用とアーカイブしたアプリをApp StoreやAd-hocで配布するためのDistribution用があります。

### Signing Identity： `.p12`ファイル

- 何：秘密鍵と証明書のペア
- 役割：実際にコード署名を行う。秘密鍵を作成したPC以外で署名したい場合にもこのファイルを受け渡す
- 作り方： キーチェーンアプリからエクスポートする

署名を実際に行うためには、秘密鍵を証明書とペアにしてSigning Identity（`.p12`ファイル）という形式にしておく必要があるようです。キーチェーンアプリは証明書に含まれる公開鍵がどの秘密鍵とペアかを認識してくれるため、キーチェーン上で証明書を右クリックしてp12ファイルをエクスポートすることができます。
例えばあるマシンで鍵ペアを作成してアプリ開発を始めたけど新しいマシンに買い換えたというとき、新しいマシンには秘密鍵がないので署名ができず、アプリを実機で走らせることもApp Storeへのリリースもできません。このような場合、古いマシンでp12ファイルをエクスポートして新しいマシンになんらかの形で渡すことで新しいマシンで署名を行うことが可能になります。当然、p12ファイルには秘密鍵が含まれるので扱いには注意する必要があるでしょう。

### Provisioning Profile: `.mobileprovision`ファイル

- 何：App ID+証明書+インストールを許可する端末のデバイスIDのリスト
- 役割：特定のデバイス上でアプリを実行して大丈夫か判断する
- 作り方：事前に作成した証明書やApp ID、デバイス情報を、Developer Center上で指定して作成

リリース前にアプリをテスト的に社内配布したい時など、App Store以外の手段でアプリをiOSにインストールさせたいときに必要なのがProvisioning Profileです。Profileはアプリ本体と一緒にiOS端末に渡され、コード署名を正当性を検証するために必要です。原理的には検証に使うのは公開鍵なのですが、公開鍵は証明書に含まれ、証明書はProfileに含まれるので結局ファイルとして何が必要かというとProfileということになります。

ここまではコード署名の話だけをしてきましたが、実際にアプリをiOS端末で実行するにはそのほかにも検証しなければいけない内容があり、Profileはそれらの検証にも使われます。具体的には、以下の３つの検証がパスしたときにアプリ時の実行が許可されます。

- Profileに含まれる証明書の公開鍵を使ってコード署名が正しいか
- Profileに含まれるApp IDがアプリのBundleIDと合致するか
- Profileに含まれるデバイスIDのリストの中にアプリを実行しようとしているデバイスのIDが含まれるか

## コード署名の流れ

登場人物それぞれ役割を踏まえて、アプリをAd-hoc配布する際にコード署名でやらなければいけないこと、起こることの流れを簡単にまとめます。

- キーチェーンアプリでCSRを作成。このときに裏で鍵ペアが生成される
- CSRをDeveloper CenterにアップロードしてDistribution用の証明書を取得する
- キーチェーンアプリで証明書からp12ファイルをエクスポートしておく
- Developer Center上でProvisioning Profileを取得する
    - App IDを登録
    - アプリをインストールしたいデバイスのIDを登録
    - 以上の情報からProvisioning Profileを作成し、ダウンロード
- XCodeにProvisioning Profileをimportする
- アプリをアーカイブする。ここで、XCodeがProvisioning Profileに含まれる証明書と結びつく秘密鍵を取得し、署名もしてくれていると思われる
- なんらかの手段でアプリを配布する。ダウンロードしたiOS端末ではProvisioning Profileを使ってアプリが検証され、OKならばインストール/実行される

## まとめ

- コード署名はiOSが信頼できるアプリのみをインストールするための仕組み
- 開発者の秘密鍵でアプリに署名し、アプリインストール先のiOS端末において公開鍵を使った署名の検証がなされる
    - 秘密鍵はSigning Identity（`.p12`ファイル）に、公開鍵はProvisioning Profileのなかの証明書に含まれている