[iOS] アカウント情報をアプリ間で安全に共有する方法

keychain500x500

はじめに

google-642x309

Google Drive, Docs, SheetsのiOS版では、アプリ間でGoogleアカウント情報を共有しています。
いずれかのアプリで一度認証すれば、他のアプリでは面倒な認証手順が不要になります。
これはどういう仕組みで行われているのでしょう?
アプリはどのようにして、ユーザの入力無しにユーザを識別しているのでしょう。

Keychain:安全に機密情報を取り扱う仕組み

実は、先に述べたGoogleのアプリ達は、KeychainというOSの仕組みを使っています。
Keychainは、もしあなたがMac OSXユーザであればなじみ深いと思います。実はiOSにも備わっています。
これは、パスワードや秘密鍵、証明書などの機密情報を安全に管理するための仕組みです。
Keychainで保管されるデータは暗号化されます。

アカウント情報の取り扱いに最適

Keychainと同じようにユーザ情報を保存するための仕組みとして、UserDefaultsというものがあります。
UserDefaultsは、主にプリファレンスなどを保存するための仕組みです。
UserDefaultsと比べて、Keychainが機密情報の取り扱いに優れている点は以下です:

  • アプリが削除されてもデータが残る
  • 同じKeychain Access Groupに属するアプリ同士でアクセスできる (Keychain Access Groupは後ほど詳しく説明します)
  • データが暗号化される

Keychain Servicesとは

Keychain Servicesは、Keychainを使うためのAPI群です。
以下からAppleのドキュメントを参照できます:

Mac OSXとiOSではAPIが異なるので注意です。
ここでは、iOSでの使用を前提として説明します。
日本語の資料では以下が参考になります。

しかしながら上記ページは古くて、XCode上の手順が有効でない部分があります。
本ページでは、XCode 5で設定する場合を説明します。

Keychain Servicesの基本的な使い方

Framework

利用するには、Security.frameworkをプロジェクトに追加してください。

便利なラッパモジュールを使おう

Appleが用意したAPIをそのまま使うのもいいですが、我々には時間がありません。
オススメは LUKeychainAccess です。
LUKeychainAccessを使えば、NSUserDefaultsと同じ感覚でKeychainのアイテムを管理できます。
以降は、このラッパを使う前提で説明します。

基本的な使い方

NSUserDefaultsと全く同じです。

[[LUKeychainAccess standardKeychainAccess] setBool:NO forKey:@"authorized"];
BOOL authorized = [[LUKeychainAccess standardKeychainAccess] boolForKey:@"authorized"];

Keychainでのアクセス制御

制御の軸はアイテムに「いつ」アクセスできるのかと、「どれが」アクセスできるのか、の2つがあります。

Accessible属性

アプリケーションが「いつ」アクセスできるのかを制御します。詳しくはこちら
LUKeychainAccessでは、それぞれ以下の定数で指定できます。

  • LUKeychainAccessAttrAccessibleAfterFirstUnlock
    • 再起動後最初のアンロック以降/次の再起動まで
  • LUKeychainAccessAttrAccessibleAfterFirstUnlockThisDeviceOnly
    • 再起動後最初のアンロック以降/次の再起動まで
  • LUKeychainAccessAttrAccessibleAlways
    • 常にアクセス可
  • LUKeychainAccessAttrAccessibleAlwaysThisDeviceOnly
    • 常にアクセス可
  • LUKeychainAccessAttrAccessibleWhenUnlocked
    • デバイスがアンロックされた状態
  • LUKeychainAccessAttrAccessibleWhenUnlockedThisDeviceOnly
    • デバイスがアンロックされた状態

以下のように指定します。

LUKeychainAccess* keychainAccess = [LUKeychainAccess standardKeychainAccess];
keychainAccess.accessibilityState = LUKeychainAccessAttrAccessibleWhenUnlocked;

Keychain Access Group

「どの」アプリケーションがアイテムにアクセスできるのかを制御します。
デフォルトでは、アクセスできるアプリケーションはそのアイテムを格納したアプリケーション自身だけです。
グループの指定によって、同じグループに属するアプリケーションからのアクセスを許可出来ます。
このグループは、アイテムごとに指定できます。

例えば、あなたのapplication-identifiercom.foobar.applicationだとします。
あなたのアプリケーションが読み書き可能なグループは、そのグループ識別子がapplication-identifier前方一致する場合です。
グループによる権限の違いは以下の通りです。

  • アクセス可なアイテムのグループ
    • com.foobar.application
    • com.foobar
  • アクセス出来ないアイテムのグループ
    • com.apple.calendar
    • com.foobar.mailer

つまり、com.foobarというグループに属するアイテムを作成すると、アクセス出来るアプリケーションはapplication-identifiercom.foobar.*に相当するものになります。

また、注意点ですが、同じグループに属していてもアクセスできない場合があります。
それは、Code Signing Identityがアプリケーション間で異なる場合です。
つまり、アイテムを共有するアプリケーションは同一ベンダーのものでなければなりません

Keychain Access Groupの変更

Keychain Access Groupをデフォルトのapplication-identifierから変更するには、Entitlementsを作成します。
XCode 5ではこの作成手順が簡単になっています。

まず、ビルドターゲットのCapabilitiesタブを開きます。

Screen Shot 2014-07-10 at 12.02.58 AM

Keychain Sharingという項目を見つけたら、スイッチをONにしましょう。
Keychain Groupsというリストが現れたはずです。
ここに、指定したいグループの識別子を入力します。
グループは複数指定できます。
一番上に記述したものが、デフォルトで使用されるグループになります。

シミュレータにおけるグループの取り扱い

シミュレータでも動作しますが、テストは実機でやる事を強くおすすめします。
というのは、シミュレータだと常にグループがtestとなるからです。
シミュレータ上では、グループ指定に関わらず全てのアプリケーションから全てのアイテムにアクセス出来ます。

TestFlight用ビルドとデバッグ用ビルド間ではアイテムを共有出来ない

ここで、TestFlight経由でインストールしたビルドと、USB経由でインストールしたビルドのapplication-identifierを別々にすればテストできるかもしれないと思いますが、これも無理です。
理由は、Code Signing Identityが異なるからです。
共通のCode Signing Identityで、異なるapplication-identifierのアプリを用意しましょう。

アイテムのキー

基本的な使い方のセクションで説明した通り、アイテムのキーは単純な文字列で指定できます。

[[LUKeychainAccess standardKeychainAccess] setBool:NO forKey:@"authorized"];

これは以下のような単純なkey/valueストアです。

key = value

Keychain Servicesでは、この単純なキーに加えて、オプションでサービス名も付けられます。
以下のようにserviceディレクトリにkey/valueを格納していくイメージです。

service.key = value

サービス名の指定は、TwitterやFacebookの認証情報をセットにして格納したい場合に便利です。

サービス名つきアイテムへのアクセス方法

残念ながらLUKeychainAccessは、サービス名の指定にまだ対応していません。。
そこで、このラッパをフォークして対応させてみました。

サービス名対応版なら、以下のようにサービス名を指定できます。

[[LUKeychainAccess standardKeychainAccess] setBool:NO forKey:@"authorized" service:@"Twitter"];
BOOL authorized = [[LUKeychainAccess standardKeychainAccess] boolForKey:@"authorized" service:@"Twitter"];

簡単ですね!
本家にはこの拡張をプルリクエストしておきました。

自前ホストのHubotとSlackを連携させる方法

images hubot-featured

最近、チャットツールをHipChatからSlackに乗り換えました!

Slackとは?

いわゆるチャットツールです。技術者向け。
SlackはHipChatと違ってUIがリッチで分かりやすいです。オシャレ!
HipChatと同じくAPIを備えており、GitHubやAsanaなど様々なサービスと連携させる事ができます。
そして、タイトルにもあるHubotとも連携させる事ができます。

Hubotとは?

チャット上で使えるボットです。Node.jsで書かれています。GitHub社が開発しました。
ボットというとTwitterのボットを想像するかもしれませんが、基本は同じです。
ボットに向けて、決まった書式でメッセージを送ると、そのメッセージに応じた処理を行います。
例えば、Jenkinsのビルドを実行したり、アプリケーションをデプロイしたり、ネコ画像を拾ってきたりします。

Hubotを導入するメリット

従来は個別のターミナル内で行っていたタスク指示が、チャットウインドウに移ります。
この事のメリットは主に二つあります。

  1. 作業ログが残る
  2. チームとのコミュニケーションと作業が統合される

ビルドやデプロイなどは、いつ誰がやったのか残っている事が望ましいです。
また、チームワークにおいてはそのタスクが属人的にならないようにする事が重要です。
タスク実行をチャットウインドウから行う事で、直接的な作業が会話にシームレスに混ざります。
それによって、デプロイなどの手順や気をつけるべき点が自然と共有される形になります。

このようにチャットを使ったサーバ運用をChatOpsと呼びます。

Herokuではなく自前サーバのHubotと連携させたい

Slack用Hubotプラグインがあるので、書かれている手順に従えば簡単に設定できます。
日本語の情報もnanapiなどに掲載されています。
しかし、どれもHubotをHerokuにデプロイする場合の手順です。
いやいや、俺は自前サーバにHubotを稼働させる場合の手順が知りたいんだ
という訳で、このページでは自前ホストのHubotとSlackを連携させる手順について説明します。

Hubotのインストール

既に自前サーバで動作している方は読み飛ばして下さい。

  • Hubotをインストール
    $ npm install -g hubot coffee-script
    
  • botの作成
    $ hubot --create [path_name]
    $ cd [path_name]
    
  • Slack用アダプタのインストール
    $ npm install hubot-slack --save
    
  • 試しにローカルで起動
    $ ./bin./hubot
    

Slackとの連携設定

Slackの以下のページから、環境変数に設定する値を取得します。

Screen Shot 2014-07-08 at 11.15.43 PM

それぞれ、環境変数に設定します。

$ export HUBOT_SLACK_TOKEN=<token>
$ export HUBOT_SLACK_TEAM=<team>
$ export HUBOT_SLACK_BOTNAME=<bot name>

Hubotは、HTTPインターフェースも備えています。これはデフォルトで8080ですが、PORTの環境変数を指定することで変更できます。

$ export PORT=9999

ファイヤーウォールの中にいる場合は、このポートで外からhubotにアクセスできるようにしておきましょう。
EC2の場合はセキュリティグループを確認しておきましょう。
そして、あなたのHubot HTTPインターフェースへのURLをSlackのHubot URLの欄に記入します。

Screen Shot 2014-07-08 at 11.23.14 PM

これで、SlackはHubotへのメッセージをこのHTTPインターフェースに送信するようになります。
さあ、Hubotを起動してみましょう。

$ ./bin/hubot --adapter slack

Screen Shot 2014-07-08 at 11.26.54 PM

成功です!
Enjoy ChatOps!