はじめに(背景)
こんにちは。LINEでAIR ARMORの開発を担当しているSIM MINYOUNGです。AIR ARMORは、LINE GAME PLATFORMの一つであるAIRを構成する一つのセキュリティソリューションです。AIRについてはこのURLで紹介されました。前回のブログでは、Androidの署名の仕組みであるAPK Signingについて紹介されましたが、今回のブログでは、iOSのコード署名について紹介したいと思います。
コード署名は、ファイルの完全性を保証する、署名者(開発者)を確認する役割があります。Mach-O形式で表現されるiOSのバイナリーに対する完全性や署名者の検証は、後ほど説明するCode signature構造によって実現されています。
コード署名について
iOSでは、App Store登録用あるいは、テスト用としてビルドされたアプリは、コード署名を行った後、デバイスで実行することができます。また、コード署名は、必ずAppleから発行した証明書に対応した秘密鍵で署名されている必要があります。
App Storeからインストールしたアプリは、以下のようにAppleの証明書に対応した秘密鍵で署名されています。
開発者がテストもしくは配布するアプリは、以下のようにAppleが発行した開発者の証明書に対応した秘密鍵で署名されています。
ただし、Appleの証明書ではなく、Appleが発行した開発者の証明書でコード署名されたアプリをデバイスにインストールする際には、Provisioning profileが必要になります。Provisioning profileに明示したデバイスにインストールすることで、Appleの証明書に対応した秘密鍵で署名されていなくても実行が可能です。Provisioning profileについては次で説明いたします。
Provisioning profileとは
Provisioning profile(プロビジョニングプロファイル)は、iOSデバイスやアプリを識別するためのファイルであり、iOSアプリを実機で実行するために必要なファイルです。アプリをどの端末で、どのサービス機能を利用するかが記述されています。Provisioning profileは、Xcodeから自動生成するか"Apple Developer Program"から生成することが可能です。
以下の画面は、サンプルアプリをAd Hocで配布する際の画面です。
この画面で、"Automatically manage signing"を選択するとProvisioning profileファイルが自動生成されます。生成されたファイルは"~/Library/MobileDevice/Provisioning Profiles"の下に保存されます。
$ ls ~ /Library/MobileDevice/Provisioning Profiles
1axxxx49-xxxx-xxxx-xxxx-c2xxxxxxxxff.mobileprovision
a8xxxxec-xxxx-xxxx-xxxx-5cxxxxxxxx22.mobileprovision
上記の画面で"Manually manage signing"を選択する場合は、profileを選択する必要があります。
この場合は、手動で生成したprofileを使用します。
Provisioning profileの中身
Provisioning profileは、ビルドされるアプリ(.ipa)の内部に格納されるため、ipaファイルを解凍することで確認することができます。
(ex. Payload/sample.app/embedded.mobileprovision)
Payloadのフォルダ→アプリの名前のフォルダ→embedded.mobileprovisionファイルは、以下のコマンドで確認します。(テキストエディターなどでも開きます。)
$ security cms -D -i embedded.mobileprovision
Provisioning profileには、証明証、デバイスリスト、Entitlements 項目、有効期間などの情報があります。実行環境が、Provisioning profileに明示されている情報と一致しない場合はアプリを実行できません。
証明書
以下のようにDeveloperCertificates項目にbase64でエンコードされている文字列が証明書情報です。
上記の文字列をコピーして先端と末端に'-----BEGIN CERTIFICATE-----'と'-----END CERTIFICATE-----'をつけて(PEM形式として保存するため)textファイルとして保存するとopensslコマンドで証明証の情報を確認できます。
$ openssl x509 - in sample.pem -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: XXXXXX… (XXXXXX… )
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Apple Inc., OU = Apple Worldwide Developer Relations, CN = Apple Worldwide Developer Relations Certification Authority
Validity
Not Before: Oct 26 09:45:00 2018 GMT
Not After : Oct 26 09:45:00 2019 GMT
Subject: UID = XXXXXXXX, CN = iPhone Developer: XXXXXX (XXXXXXXX), OU = XXXXXXXX, O = XXXXXX, C = US
Subject Public Key Info:
.
.
.
上記では、開発者用で発行した証明書が使われたことがわかります。
Entitlements
以下のようにEntitlements 項目が存在します。
Entitlements 項目では、どのサービス機能を使用できるかの資格が明示されています。(上記では、デバック用のビルドであるため、プロセスデバックができるようにget-task-allowがtrueになっています) この項目は、Xcode build settingの"Capabilities"設定によって決まります。
もし以下のように、"Access WiFi Information" サービス機能と "Apple Pay" サービス機能にチェックする場合
以下のようにそれぞれkeyとvalueがEntitlements項目に追加されます。
有効期間
以下のようにExpirationDate 項目が存在します。有効期間がすぎるとアプリは実行できません。
デバイスリスト
以下のように実行可能なデバイスは、ProvisionedDevices 項目に存在します。アプリは、ここで明示したデバイス上のみ実行できます。
Ad Hoc などすべてのデバイスで実行可能なアプリのprovisioning profileには"ProvisionsAllDevices" 項目がtrueになっています。
バイナリーファイルの完全性の検証
署名されたアプリのMach-Oバイナリーには、コード署名に関連する構造体があります。これを利用してMach-Oバイナリーファイルの完全性を検証することができます。
Mach-O バイナリー
まず、Mach-Oのバイナリーファイルについて簡単に説明したいと思います。Mach-O形式は、iOSやMac OS系で使用する実行ファイルのフォマットです。
$ file sample
sample: Mach-O 64-bit executable x86_64
$ ./sample
sample!
iOSで動作するアプリ(.ipa)の中には、Mach-Oのバイナリーファイルがあります。iOSは、このMach-Oのバイナリーファイルを実行してアプリを起動します。
Mach-O バイナリーを得る
ipaファイルを解凍するとPayloadディレクトリの下にInfo.plistファイルがあります。
(ex. Payload/sample.app/Info.plist)
Info.plistファイルを開くと"Executable file"項目がりますが、これがアプリが起動する際に最初に実行されるファイル(Mach-Oファイル)になります。
以下は、Mach-Oのバイナリーファイルの構成を簡単にしたものです。
Mach-O header
まず、Mach-O headerですが、この情報は、Mach-O形式であるかを識別します。
Mach-O headerの詳細は以下のとおりです。
項目 | 説明 | 例 | 参考- url |
---|---|---|---|
magic | Mach-O形式を識別します。byte orderも決めます。 | 0xfeedface : 32bitのMach-Oファイルを意味byte orderは、ホストPCのendianに従います | <mach-o/loader.h> |
cpu_type | cpuのタイプを示します。 | 0xc : arm | <mach/machine.h> |
cpu_subtype | cpuの下位タイプを示します。 | 0x9 : arm v7 | <mach/machine.h> |
file_type | ファイルの種類を示します。 | 0x2 : 実行ファイルの形式を意味 | <mach-o/loader.h> |
num_load_commands | load_commandの数を示します。 | 34 : 総34個のload commandを意味 | |
size_of_load_commands | load_commandの全体サイズを示します。 | 3792 : 総34個のload commandを合わせたサイズが3792 byteです。 | |
flags | Mach-O ファイルフォーマットのoptional機能を示します。 | <mach-o/loader.h> |
[表1 Mach-O header の構造体の詳細]
load commands
load commandsは、Mach-O file headerの次に位置します。
Mach-O headerがロードされた後、load commands部分をロードします。
load commands は以下のような情報が含まれていますが
- segmentの位置
- 動的ライブラリの名前
- シンボルテーブルの位置
...
この記事では、コード署名と関連するload commandであるCODE_SIGNATUREセグメントを見ていきます。(図14のCODE_SIGNATUREセグメント)
Code signature構造体について
それでは、冒頭でも説明したようにiOSのバイナリーに対する完全性の確認や署名者の検証を実現するCode signature構造(図14のCODE_SIGNATUREセグメント)について見ていきます。
以下は、上記で説明しましたload commandsの中でCode signature構造体に関する情報が記述されています。
$ otool -l Payload/iOSSample-mobile.app /iOSSample-mobile | grep LC_CODE_SIGNATURE -A3
cmd LC_CODE_SIGNATURE
cmdsize 16
dataoff 3188480
datasize 59808
上記のコマンドを使っても情報を確認できますが、dataoffは、Code Signature構造体(コード署名情報に関連した構造体)のオフセットを意味します。この情報を追うことでCode Signature構造体を把握することができますが追う方法についてこの記事では割愛します。
Code Signature構造体は以下のようになっています。主にCode directoryとBlob wrapper部分を見ていきます。
Code directory
Code signature構造体にはCode directoryが存在し、実行バイナリーや特定ファイルのhash値が格納されています。
サンプルアプリのCode directoryを確認してみると、このようになります。
この中には、code hashとspecial hashのリストを確認できますがそれぞれ見ていきましょう
code hash slot
code hash slotは、実行バイナリーファイルをpageSize(0x1000)つず分割して、そのhash値を保存したものです。
図で表現すると以下のようになります。
もしも実行バイナリーの一部が改ざんされたら、このhash値が異なってしまうことになるため、これを確認することで改ざんされたかがわかります。
special hash slot
special hash slotは、簡単に整理すると以下のようになります。
Index | Contains | Hash (sample) |
---|---|---|
0 | Entitlement (bound in code signature) | 4b255acb014ab5dc8cd63f5120baeef19309e340 |
1 | Application Specific (largely unused) | 0000000000000000000000000000000000000000 |
2 | Resource Directory (_CodeResources) | 35b65bb61f6b617e9a944cdada31cb78b47ab393 |
3 | Internal requirements | cdbf07382a5a26998e34dc9d80070fc5db8c9230 |
4 | Bound Info.plist (Manifest) | 5108d83c00eb7f294fb73914abdb0a1c977b92f2 |
[表2 special hash slotの説明や例]
それぞれの領域について確認していきましょう。
Entitlementインデックス
special hash slotのEntitlementには、上記で説明しましたprovision profileのEntitlementのhash値を格納しています。
上記のようにEntitlementsは、Code signature構造体の中にありますので、コピー(magic, length, data)して以下のようにファイルに保存します。
$ shasum entitlement.txt
4b255acb014ab5dc8cd63f5120baeef19309e340 entitlement.txt
この方法で確認したhash値がspecialSlot[0] の値と同じであることが確認できます。
Application Specificインデックス
以下のリンクを確認するとまだ使用されていません。
https://opensource.apple.com/source/libsecurity_codesigning/libsecurity_codesigning-55032/lib/signer.cpp.auto.html (about application-specific)
Resource Directoryインデックス
ipaファイルの中にある_CodeSignature/CodeResources ファイルを意味します。
(ex. Payload/sample.app/_CodeSignature/CodeResources)
CodeResourcesファイルには、アプリ内に存在する全てのリソースファイルに対するcheck sumが保存されています。
$ shasum Payload/iOSSample-mobile.app/_CodeSignature/CodeResources
35b65bb61f6b617e9a944cdada31cb78b47ab393 Payload/iOSSample-mobile.app/_CodeSignature/CodeResources
この方法で確認したhash値がspecialSlot[2] の値と同じであることが確認できます。
Internal requirementsインデックス
Requirementは、コード署名を検証する規則を意味します。規則は複数存在することも可能です。
Requirementの数は、Requirements項目に記載されています。
Requirementsに関する情報は、以下のようにcodesignコマンドで確認できます。
$ codesign --display -r- Payload /iOSSample-mobile.app/iOSSample-mobile
Executable=Payload /iOSSample-mobile.app/iOSSample-mobile
designated => identifier "armor.sdk.sample.cocos2dx.ios" and anchor apple generic and certificate leaf[subject.CN] = "iPhone Distribution: XXXXXXX (XXXXXXX)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
この中からRequirements部分とRequirement部分を保存してhash値を確認します。
$ shasum requirements.txt
cdbf07382a5a26998e34dc9d80070fc5db8c9230 requirements.txt
この方法で確認したhash値がspecialSlot[3] の値と同じであることが確認できます。
Bound Info.plistインデックス
ipaファイルの中にあるInfo.plistファイルを意味します。
(ex. Payload/sample.app/Info.plist)
Info.plistファイルはアプリの名前、バージョン、iconのパスなどアプリに対する基本情報がふくまれています。
$ shasum Payload /iOSSample-mobile.app/Info.plist
5108d83c00eb7f294fb73914abdb0a1c977b92f2 Payload /iOSSample-mobile .app/Info.plist
この方法で確認したhash値がspecialSlot[4] の値と同じであることが確認できます。
special hash slotでは、上記の5つのインデックスを通じて、完全性を検証することがわかりました。
Blob wrapper
Blob wrapperには、上記で説明したCode directoryを署名したデータと署名に使用した証明書の情報がCMS(Cryptographic Message Syntax)形式で保存されています。
Blob wrapperの情報は、jtoolコマンドを利用して確認することが可能です。
$ jtool --sig - v Payload /iOSSample-mobile.app/iOSSample-mobile
…
Blob 4: Type: 10000 @41788: Blob Wrapper (4802 bytes) (0x10000 is CMS (RFC3852) signature)
この中からdata部分のみ保存するとopensslコマンドで中身を確認できます。
$ openssl pkcs7 -inform der -in blobwrapper.txt -print -noout
PKCS7:
type : pkcs7-signedData (1.2.840.113549.1.7.2)
d.sign:
version: 1
md_algs:
algorithm: sha256 (2.16.840.1.101.3.4.2.1)
parameter: NULL
contents:
type : pkcs7-data (1.2.840.113549.1.7.1)
d.data: <ABSENT>
cert:
cert_info:
version: 2
serialNumber: 134752589830791184
signature:
algorithm: sha1WithRSAEncryption (1.2.840.113549.1.1.5)
parameter: NULL
issuer: C=US, O=Apple Inc., OU=Apple Certification Authority, CN=Apple Root CA
validity:
notBefore: Feb 7 21:48:47 2013 GMT
notAfter: Feb 7 21:48:47 2023 GMT
subject: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority
key:
algor:
algorithm: rsaEncryption (1.2.840.113549.1.1.1)
parameter: NULL
public_key: (0 unused bits)
0000 - 30 82 01 0a 02 82 01 01-00 ca 38 54 a6 cb 0………8T.. . . .
以下、上記で説明したものを図で表現しました。
もしもバイナリーの一部が改ざんされたら、Code directory の中身も変更されます。署名に使われる鍵が異なり、Code directoryを署名したデータであるBlob wrapperのCMS形式の情報も変わるため、これを照らし合わせることで改ざんを確認することができます。
まとめ
以上、長い説明になりましたが、iOSのバイナリーに対する完全性の確認や署名者の検証は、Code signature構造体によって実現されていることがわかりました。
Code signature構造体は、主に以下の2つ領域が重要な役割を果たしており
- Code directory
- Blob wrapper
Code directoryには、code hash slotとspecial hash slotが存在していて code hash slotには、バイナリー全体を一定のサイズで分割して、そのhash値が保存され special hash slotには、entitlement情報のhash値と、リソースとinfo.plistなどのhash値が保存されます。また、Blob wrapperには、Code directoryのhash値と、証明書情報のhash値がCMS形式で保存されます。
今回のブログでは、この情報を照らし合せることでiOSのバイナリーに対する完全性や署名者を確認する仕組みを紹介しました。