φ(.. )メモシテオコウ iOS、Androidバイナリに対してコード署名

ここ最近iOS、Androidバイナリに対してコード署名するというスクリプトを書いたのめも。なのでカーネルとか関係ない(´Д⊂グスン
ただ、これが必要になるケースはそうそう無いと思うのですけどね! まあ、そういうケースがあったから作った訳ですけども。
もの自体はsvnとJenkinsを使って自動でビルドできるようにしてますけど、その辺は特にメモるほどのことは無いのでコード署名部分だけ。
動作環境はmacでiOSもAndroidも処理はbashスクリプトで書いてます。

まずはどっちにも共通すること。
バイナリ(.ipa、.apk)はzip系式で圧縮されているので展開してごにょごにょする必要があります。
コード署名時のzipの作成、展開にはzip、unzipコマンドを使ってます。

ではiOS固有のこと。
iOSでコード署名を実行する時は.ipaファイルを展開した状態で実施します。なので、最初に.ipaファイルを適当に拡張子を.zipにしたファイル名にしてunzipしておきます。
.ipaファイルは展開するとPayloadというディレクトリができます。そしてさらにPayloadディレクトリにあるアプリ名のディレクトリをPayloadディレクトリから出し、Payloadディレクトリは削除ということをしてますがこれは作業用ってことです。

appName=`ls Payload | grep .app`
cd Payload
mv $appName ../$appName
cd ..
rm -fr Payload

次にResourceRules.plistがあるかどうかのチェックをして、ファイルが無ければテンプレートになるResourceRules.plistを${appName}の下に置きます。

if [ ! -f ${appName}/ResourceRules.plist ]; then
    cp /path/to/somewhere/template/ResourceRules.plist ${appName}/ResourceRules.plist
fi

Unreal等のサードパーティのライブラリを使っている場合このファイルが無い場合があるので、その場合のチェックです。
ここでコピーしているResourceRules.plistは以下のようなもので特にこれといった内容はありません。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>rules</key>
	<dict>
		<key>.*</key>
		<true/>
		<key>Info.plist</key>
		<dict>
			<key>omit</key>
			<true/>
			<key>weight</key>
			<real>10</real>
		</dict>
		<key>ResourceRules.plist</key>
		<dict>
			<key>omit</key>
			<true/>
			<key>weight</key>
			<real>100</real>
		</dict>
	</dict>
</dict>
</plist>

そしてコード署名の実施。コマンドラインはこんな感じですね。

/usr/bin/codesign -f -s "iPhone Distribution: foobar" --resource-rules=${appName}/ResourceRules.plist ${appName} 

codesignコマンドはxcode(コマンドラインツールだっけ?)が入っていれば存在していると思います。manもあります。
codesignコマンドの-fオプションで既に署名があっても強制的に再署名、-sは使用する証明書ですね。例えばAd Hoc用の証明書だとキーチェーンアクセスでみたときに「iPhone Distribution: ~」のようになっているかと思いますが、それです。--resource-rulesが先ほど見たResourceRules.plist、最後に対象のアプリのディレクトリです。

ここまでで署名が終わったので最後に.ipaファイルを作ります。
ここでPayloadディレクトリを作ってそこにコード署名をした$appNameディレクトリを置きます。そしてzipコマンドでPayloadディレクトリをzip圧縮して最後に名前を.zipから.ipaに変えれば終了です。

mkdir Payload
cp -rp $appName Payload/$appName
zip -r Payload.zip Payload 

次はAndroid
AndroidJava SDK付属のjarsignerコマンドを使用します。
Andoridの場合、署名は.apkファイルにしますが、その前にMETA-INFを削除するということをしてます。これはAndroid Debugとかで署名済みのバイナリに対して再署名しようとするとjarsignerコマンドの実行時にjava.util.zip.ZipExceptionが出るのでその対策です。

対象がfoobar.apkとして.apkをzipにリネームし、適当な作業ディレクトリの作成、そこにzipファイルの展開しMETA-INFを削除したらそのzipします。そしたら拡張子を.apkに戻して置きます。

mkdir foobar
cp /path/to/foobar.apk foobar.zip
cd  foobar
unzip -o ../foobar.zip
rm -fr META-INF
rm -f ../foobar.zip
zip -r ../foobar.zip .
mv ../foobar.zip ../foobar.apk 

ここまでで準備完了です。
自分はzipの作り方を間違えたせいでコード署名できたけどインストールに失敗するというとこでちょっとハマりました。
foobar.zipを展開したときにfoobar/ができるように作るのではなく、./fileA ./fileBというようにその場にファイルが作られるようにzipを作る必要があったんですね。。

コードサインの実行はjarsignerでできるのですが、このコマンドはパスワードをコマンドラインで渡せず別途聞いてくるのでshellスクリプトの中でexpectコマンドを使いました。
jarsignerの使い方自体はググると情報が見つかると思いますので割愛します。

expect -c "
set timeout 120
spawn jarsigner -verbose -keystore \"$keystore_path\" \"$app_path\" \"$sign_alias\"
expect \"Enter Passphrase for keystore:\" 
send \"$pass\r\"
expect $PS1
"

パスワードを入れたら処理が進むのですが、jarsignerはコマンドが終わったことを表すようなメッセージは出力してくれないので目印としてPS1を使っています。これはshellスクリプト内のexpectコマンド実行前に適当な文字列をセットしてます。

PS1="finish_android_recodesign"; export PS1

jarsignerは引数で渡した.apkファイルを直接弄るのでコマンドが完了したらそのバイナリはコード署名された状態になっています。
署名の確認は以下のコマンドで確認できます。

jarsigner -verify -verbose -certs アプリ.apk

とまあ、このような感じでiOS、Androidバイナリに対してコード署名を実行できます。