『入る学科間違えた高専生』の日記

プログラミングのコードを書いたりする予定です。あとは日記等。あといつまで高専生やねん

ハードコードを許されない変数をどうしても Xcode プロジェクトの Info.plist に差し込みたい🔑

この記事は,フラー株式会社 Advent Calendar 2021 の 18 日目の記事です.
17 日目の記事は @kousuke_sumiyasu さんで「 CoreMLとCreateMLを使って犬の分類器を作ってみた! 」でした.

CoreML と CreateML ,結局リリースされてから一度も触れていないんですよね.ちなみに再学習とかってできるんでしょうか?わたし,気になります.
MobileNetV2.mlmodel を見て MobileNetV2 の論文を読みながら電車に乗っていた記憶が蘇りました.

はじめに

みなさんは, Xcode プロジェクトで例えば iOS アプリ開発をしているときに API キー等などの秘密情報を扱う機会はありませんか?
私はよくあります.例えば GoogleMap の API キーとか TwitterAPI キーとかシークレットとか GitHub のアクセストークンとか etc...
上記に上げたものであれば基本的に Info.plist に差し込むなどはない実装になるかと思います.
もしくはそもそも上記で挙げた例だと通信時にパケットキャプチャされてしまえば見られてしまう情報だからハードコードしてしまうなどあるかとも思います.

しかし,たまたまどうしても業務でどうしても絶対にプライベートであっても GitHubリポジトリやプロジェクトに関係ない人には知られてはいけないと言われてしまった文書を組み込まなくてはならなくなった場合どうしたらよいかということで今回考えてみた対応策です.*1 *2

開発環境

  • Xcode12 以上
  • Swift

ソースコードに埋め込めるようにするには

まず,今回のようなパターンで Swift や Objective-Cソースコードに乗せるためには cocoapods-keys というプラグインがあります.
ただ CocoaPods 依存ではないプロジェクトには新しく CocoaPods を依存させる必要があります.

github.com

gem "cocoapods-keys"

私は RubyGems を入れるときは bundler を使うのでこれを Gemfile に追加します.

qiita.com

今回は本題とそれるので参考記事だけで実際の実装は省略します.
Swift のソースコードなどには簡単に埋め込むことができます.データ自体は Base64エンコードされた String で定義されていたはずです.*3

本題

さて本題です.ハードコードができずに環境変数として Xcode プロジェクトに追加するのはどうしたらよいでしょう.
答えは,前章で書いた cocoapods-keys の仕組みから得られました.

みんな大好きキーチェーンを使った仕組みです.

キーチェーン

macOS にも iOS にも iPadOS にもキーチェーンってありますよね.あれの macOS 版を使います.

1. 追加

$ security add-generic-password -a ${ACCOUNT_NAME} -w ${PASSWORD} -s ${SERVICE_NAME}
  • サンプル
$ security add-generic-password -a NO_HARD_CODE -w very-totemo-secret-value -s App-xcodeproj

very-totemo-secret-value というハードコードが禁止されたとても大事な値をキーチェーンに保存してみました.

f:id:nanashinodonbee:20211217214300p:plain

という感じでキーチェーンに保存されていることが確認できました.

2. 取得

$ security find-generic-password -a ${ACCOUNT_NAME} -s ${SERVICE_NAME} -w
  • サンプル
$ security find-generic-password -a NO_HARD_CODE -s App-xcodeproj -w

とすることで very-totemo-secret-value が標準出力で得られます.

今回は,2パターンの方法を紹介します.

PlistBuddy を使うパターン

Info.plist を代表とする Property List ファイルの操作を可能とするコマンドラインツールです.
macOS を使っているということは新しく何かをインストールする必要なく使うことが可能です!

/usr/libexec/PlistBuddy で設置されています.

qiita.com

例えば BundleID を取得しようとした場合は

$ /usr/libexec/PlistBuddy -c "Print:CFBundleIdentifier" /path/to/Info.plist

とすることで標準出力が得られます.

組み合わせて Info.plist に打ち込む

1. 下準備

今回は,サンプルとして iOS プロジェクトを作成しました.
また,デモで作成しているプロジェクトなので Info.plist の LSEnvironment に差し込んでみようと思います.

f:id:nanashinodonbee:20211217223907p:plain

こんな感じで追加しました.
こうすることでプログラムから Info.plist に書き込めているかの確認ができますね.

func logNoHardCodeValue() {
    guard let environment = Bundle.main.object(forInfoDictionaryKey: "LSEnvironment") as? [String: String],
          let noHardCode = environment["NO_HARD_CODE"],
          !noHardCode.isEmpty else {
        print("empty")
        return
    }
    print(noHardCode)
}

取得できたら very-totemo-secret-value がコンソール上に表示されるはずです!

2. Info.plist の書き換え

PlistBuddy で Info.plist 書き換えします!
となると実態が書き換わってしまうので普通にハードコードになってしまいます.
なので XcodePreprocess Info.plist File というビルド設定を有効にします.

App.xcodeproj である場合

TARGETS > App > Build Settings > INFOPLIST_PREPROCESS の値を NO から YES に変更します.
もちろん, Configuration Settings File ( xcconfig )で置き換えしまっても大丈夫です.

f:id:nanashinodonbee:20211217224859p:plain

こうすると, Copy Bundle Resources の Phase ぐらいのときにいい感じに ${TEMP_DIR}/Preprocessed-Info.plist がバンドルされ Info.plist が置き換わる仕組みになっているらしいです.

なので Copy Bundle Resources より前に Run Script Phase で PlistBuddy を実行して置き換えをしてあげる必要があります.

export NO_HARD_CODE="$(security find-generic-password -a NO_HARD_CODE -s App-xcodeproj -w)"
/usr/libexec/PlistBuddy -c "Set:LSEnvironment:NO_HARD_CODE ${NO_HARD_CODE}" "${TEMP_DIR}/Preprocessed-Info.plist"

スクリプト自体はこんな感じにしました!
しかしながら, Xcode10 から New Build System の恩恵のおかげで今のところ値が変わらないことがあります.
なので Input Files$(TEMP_DIR)/Preprocessed-Info.plist を追加する必要があります.

これで実行してあげると

f:id:nanashinodonbee:20211217225434p:plain

うまくいきました\( ‘ω’)/
Info.plist の方にも反映書き換えた値は反映されずにバンドルのみ含まれています.

XcodeGen を使う

いろんなところで名前を聞いたことがある人はいるかと思いますが,一応紹介だけ書いておくと,
Xcode プロジェクトの人間が読むものじゃない代表である *.xcodeproj/project.pbxproj を含む *.xcodeproj を生成するコマンドラインツールです.

導入コストはちょっと高いですが, *.xcodeproj/project.pbxproj のコンフリクトを解消するコストを考えると導入したほうがよいのではという感じで導入する機会が個人的には多いです.

github.com

techlife.cookpad.com

また, iOSDC Japan 2021 では SwiftPM によって脱 XcodeGen という話も取り上げられました.

fortee.jp

1. project.yml を書いて

はい.頑張って project.yml を書いてください.

2. 環境変数を組み込む

XcodeGen には xcodegen コマンドを実行されたタイミングで環境変数を読み込んで *.xcodeproj/project.pbxproj に組み込ませる機能(順序は逆だと思う)が組み込まれています.( v2.25.0 現在)

You can also use environment variables in your configuration file, by using ${SOME_VARIABLE} in a string.

引用 : https://github.com/yonaskolb/XcodeGen/blob/2.25.0/Docs/ProjectSpec.md#general

targets:
  App:
    type: application
    platform: iOS
    sources:
      - path: App
        name: App
    settings:
      base:
        NO_HARD_CODE: ${NO_HARD_CODE}

こうしておくことで,

$ export NO_HARD_CODE=$(security find-generic-password -a NO_HARD_CODE -s App-xcodeproj -w)
$ xcodegen

とすると前節で紹介した PlistBuddy と同じ結果が得られるようになります.
もちろんここでも Info.plist に同様の追加が必要です.

f:id:nanashinodonbee:20211217223907p:plain

おまけ

macOS のキーチェーンとの対話に security コマンドを使いましたが,途中で使用した cocoapods-keys でも同様な処理が可能になっています.

また,せっかく AEXML の不具合に PR 出してコントリビュート *4 して tuist/XcodeProj -> yonaskolb/XcodeGen と PR 出して XcodeGen v2.25.0 で対応までこぎつけたので Scheme の pre-action で環境変数読み込ませられないかなって格闘してみたのですがちょっと時間的な制限的にもできなかったので機会があったら試してみようと思います.

最後に

今回は,偉い人から強い力(圧力ではない)によって絶対にハードコードをしてはいけない値を2パターンの実装方法を使って Info.plist に差し込む実装をしてみました.
実際には2パターンとも実際には Info.plist には値は差し込まない絶対にハードコードをしない実装を実現することができました.

しかしながら,どちらともやっぱりビルドされた ipa ファイルの中にある Info.plist を見てみると very-totemo-secret-value が見えてしまうオチがあります.

お後がよろしいようで

明日は id:namaninotiteti1026 さんで「『コーディングしない』という不安に対してマネージャーはどう向き合うか」です!お楽しみに〜ノシ


参考

yukidarake.hateblo.jp

*1:どんな情報だよそんなのとか野暮なことは聞かないでください

*2:どっちしてもバイナリを解剖したりリバースエンジニアリングしたらわかるやん.みたいなやつはなしでお願いします!

*3:もう覚えてない

*4:https://github.com/tadija/AEXML/pull/177