iPad 2、A5はディアルコアってことで、OpenCL搭載も近いっすな。
 でも「ブログでOpenCL取り上げると仕事が火を吹く伝説」が自分、内部的に確定しつつあるのでしばらく放置します。処理の連携とか、再帰ができない部分をどう克服するか、レイトレの反射演算や影の演算で検証していくつもりなんだけど…
 ま、iOSにOpenCLが搭載されるまでには、まだちょっと時間があっだろ。

 てなわけでKeychain。
 前回は、すでに登録済みの項目を
SecItemAdd

 すると
SecItemAdd error = -25299

 が返るってことを確認したんだけど、じゃ、いったいどこまでを一緒と見なすんだ?
 と。
 例えばkSecClass。

kSecClass
 kSecClassの種類には
kSecClassGenericPassword

 の他に
kSecClassInternetPassword

 なんてのもあって、どっちでもkSecAttrAccountを設定可能なんですが、これはどうなるんだと?

 実際試してみました。
NSMutableDictionary* item = [NSMutableDictionary dictionary];
[item setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[item setObject:@"xcc" forKey:(id)kSecAttrAccount];
OSStatus status = SecItemAdd((CFDictionaryRef)item, nil);
printf("kSecClassGenericPassword SecItemAdd error = %d\n", (int)status);
[item setObject:(id)kSecClassInternetPassword forKey:(id)kSecClass];
OSStatus status = SecItemAdd((CFDictionaryRef)item, nil);
printf("kSecClassInternetPassword SecItemAdd error = %d\n", (int)status);

 OKみたいっす。
 完全に別物としてどっちも登録できました。
 そもそも、検索ではkSecClass指定が必須なので、ぶつかる理由もないわけですな。
 保存する容器そのものが分かれてるのかもしれん。

$テン*シー*シー-1

 次に考えるのが、前回kSecAttrServiceだけ指定した時に、自動で追加されたkSecAttrAccountやkSecAttrAccessGroup、kSecAttrAccessibleとの組み合わせはどうなのか?

kSecAttrAccount
 これもOK。
[item setObject:@"konohana" forKey:(id)kSecAttrService];
[item setObject:@"xcc" forKey:(id)kSecAttrAccount];
status = SecItemAdd((CFDictionaryRef)item, nil);
printf("konohana & xcc SecItemAdd error = %d\n", (int)status);
[item setObject:@"reborn" forKey:(id)kSecAttrAccount];
status = SecItemAdd((CFDictionaryRef)item, nil);
printf("konohana & reborn SecItemAdd error = %d\n", (int)status);

 kSecAttrAccount、kSecAttrServiceの組み合わせが異なればぶつからないようっす。
$テン*シー*シー-2

 で、kSecAttrAccessGroupはちょっとおいといて先にkSecAttrAccessibleを。

kSecAttrAccessible
 こいつは駄目でした。
[item setObject:(id)kSecAttrAccessibleAlways forKey:(id)kSecAttrAccessible];
status = SecItemAdd((CFDictionaryRef)item, nil);
printf("konohana & reborn,kSecAttrAccessibleAlways SecItemAdd error = %d\n",
(int)status);

 ぶつかりますた。

$テン*シー*シー-3

 前の2つと違って登録項目を特定するために必要ってわけじゃなく、設定されてないと困るから省略時はkSecAttrAccessibleWhenUnlockedで設定するだけっぽい。
 というところで、最後のkSecAttrAccessGroup。

kSecAttrAccessGroup
 こいつはシミュレータで設定しようとするとエラーになるんですよ。
errSecNoAccessForItem = –25243

 というのもkSecAttrAccessGroupは適当な値を設定できず、プロビジョニングファイルで提供されるアプリケーション識別子が必要になるから。

$テン*シー*シー-4
オーガナイザで確認できるあれね

 で、こいつを組み込んだグループ名をEntitlements.plistのkeychain-access-groupsに登録してやって、初めて指定できるようになる。
 Entitlements.plistで使うアプリケーション識別子は、以下のリンクにも書かれてるように、ちゃんとしたものでないと実機にインストール自体できないので、シミュレータではどうしようもないんですな。

Why do I get an "Invalid application-identifier Entitlement" email after uploading my app to iTunes Connect?

 手順としては

$テン*シー*シー-5

 という感じ。
 具体的なステップは
 1、ファイル>新規ファイル…メニューのダイアログでCode Signingタブを選びEntitlementを選択して作成。

$テン*シー*シー-6

 2、プロジェクトにEntitlement.plistが組み込まれるので、これを選択。

テン*シー*シー-7

 3、すでに登録済みのkeychain-access-groupsのitem0を選び右端の + をクリック、item 1を作成。

テン*シー*シー-8

 4、item 1にはItem 0から
$(AppIdentifierPrefix)

 をコピペして、その後ろに自分が別アプリと共有するグループ名を設定。

$(AppIdentifierPrefix)jp.xcc.konohanaGroup


 となります。
 jp.xccってところは、当然、自分のアプリ識別子にするよーに。konohanaGroupの部分は任意の名称でOK。項目は複数追加できるので、必要な分グループ名を作ることが出来る。
 こんな風にEntitlements.plistでグループを提供しておいて、新しい別のアプリでもEntitlements.plistに同じグループを登録してやれば、新しい別のアプリでも先に作られたアプリのkeyChain項目にアクセスできるようになるわけですな。
 ちなみに上記の過程で、プロジェクト>アクティブなターゲット"keychain"を編集メニューで出るウィンドウのビルドタブ、Code Signingのコード署名権限のところにEntitlement.plistが自動的に設定されてます。

 こんなふうにkeychain-access-groupsに設定した項目がkSecAttrAccessGroupで指定可能になる(省略時は先頭の項目が利用される)わけですな。これでやってみると
[item setObject:@"ABCDEFG.jp.xcc.konohanaGroup" forKey:(id)kSecAttrAccessGroup];
status = SecItemAdd((CFDictionaryRef)item, nil);
printf("konohana & reborn,group SecItemAdd error = %d\n", (int)status);

 は(こっちはABCDEFG.jp.xcc.てふうにプロビジョニングのアプリケーション識別子をちゃんと書かないと駄目っすよ)
konohana & reborn,group SecItemAdd error = 0

 となって、kSecAttrAccount、kSecAttrService、kSecAttrAccessGroupの組み合わせが異なれば別々に登録可能ってのがわかりました。
 実際、その後にkSecAttrService=konohanaで検索してみると
NSMutableDictionary* queryDic = [NSMutableDictionary dictionary];
[queryDic setObject:@"konohana" forKey:(id)kSecAttrService];
printf("list up kSecAttrService=konohana\n");
[self seek:queryDic];

 以下のように3つの項目が見つかるわけです。
list up kSecAttrService=konohana
dic = {
acct = xcc;
agrp = "ABCDEFG.jp.xcc.keychain";
pdmn = ak;
svce = konohana;
}
dic = {
acct = reborn;
agrp = "ABCDEFG.jp.xcc.keychain";
pdmn = ak;
svce = konohana;
}
dic = {
acct = reborn;
agrp = "ABCDEFG.jp.xcc.konohanaGroup";
pdmn = dk;
svce = konohana;
}

 ま、SecItemAddはそんなところで、さんざん登録したので、そろそろ整理。
SecItemDelete

 で削除してみます。
 ここで、例えばkSecClassだけ指定して実行するとどうなるかというと
NSMutableDictionary* deleteDic = [NSMutableDictionary dictionary];
[deleteDic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
status = SecItemDelete((CFDictionaryRef)deleteDic);
printf("kSecClassGenericPassword SecItemDelete error = %d\n", (int)status);
// 検索
NSMutableDictionary* queryDic = [NSMutableDictionary dictionary];
[queryDic setObject:@"konohana" forKey:(id)kSecAttrService];
printf("list up kSecAttrService = konohana kSecAttrService=konohana\n");
[self seek:queryDic];
[queryDic removeObjectForKey:(id)kSecAttrService];
[queryDic setObject:@"xcc" forKey:(id)kSecAttrAccount];
printf("list up kSecAttrAccount = xcc kSecAttrService=konohana\n");
[self seek:queryDic];

 結果は
list up kSecAttrService = konohana kSecAttrService=konohana
SecItemCopyMatching error = -25300
list up kSecAttrAccount = xcc kSecAttrService=konohana
SecItemCopyMatching error = -25300

 てな感じで、kSecClass=kSecClassGenericPasswordに引っかかるやつすべてが削除されるみたいです。しかも最初に登録したkSecAttrAccount:xccだけ指定の項目まで。
 便利だけど、気をつけないと必要なものまで消してしまうところは注意。
 もちろん
[deleteDic setObject:@"konohana" forKey:(id)kSecAttrService];

 とかやっておけば、kSecAttrService=konohanaの項目だけ削除してくれます。

 次に項目の更新。
SecItemUpdate

 こいつは第1引数に、変更したい項目を探すための属性を指定して、第2引数で変更する項目を指定する。探すための属性にヒットした項目はすべて変更かかるみたいなんで、以下のような記述
NSMutableDictionary* updateDic = [NSMutableDictionary dictionary];
[updateDic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[updateDic setObject:@"xcc" forKey:(id)kSecAttrAccount];
NSMutableDictionary* newDic = [NSMutableDictionary dictionary];
[newDic setObject:(id)@"test comment" forKey:(id)kSecAttrComment];
status = SecItemUpdate((CFDictionaryRef)updateDic, (CFDictionaryRef)newDic);
printf("SecItemUpdate error = %d\n", (int)status);

 だと(ちなみに、ここではkSecAttrCommentに"test comment"を設定してます)
kSecAttrAccount = xcc、kSecAttrService = konohana
kSecAttrAccount = xcc、kSecAttrService = ""

 の両方に"test comment"が設定されることになる。
 属性はこんな感じで変更可能。ただしkSecAttrAccessibleは変えようとすると-4ってエラーコードが返ってきた。アクセス権源とかは変更したい場合、SecItemDeleteしてからSecItemAddしないと駄目なのかもね。

 最後にパスワード本体。こっちは暗号化される必要があるので、属性じゃなく
kSecValueData

 で指定します。属性はたぶん暗号化されない。あと、kSecValueDataは文字通りNSData型で渡す必要があるので、例えば以下のように"test-password"という文字列なら、dataUsingEncoding:メソッドでNSDataにして渡すようにします。文字列エンコードをNSUTF8StringEncodingと指定しているけど、あとでNSDataとして取り出してNSStringに変換する時に同じ文字列エンコードを指定するなら、NSJapaneseEUCStringEncodingあたりでも大丈夫じゃないかと思われ。ただ、わざわざ利用できる漢字を制限する必要もないだろうからNSUTF8StringEncoding。
NSString *passwordString = @"test-password";
[newDic setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding]
forKey:(id)kSecValueData];
status = SecItemUpdate((CFDictionaryRef)updateDic, (CFDictionaryRef)newDic);
printf("SecItemUpdate error = %d\n", (int)status);

 というわけで、取り出す時はseek:メソッドでやってるように
NSData* passwordData = [dic objectForKey:(id)kSecValueData];
if (passwordData) {
NSString *password = [[NSString alloc]
initWithBytes:[passwordData bytes]
length:[passwordData length]
encoding:NSUTF8StringEncoding];
printf("password = %s\n", [password UTF8String]);
[password release];
}

 としてNSDataからNSStringに変換してます。
 問い合わせ用の辞書に
[queryDic setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

 として、kSecReturnAttributes(属性を返す)だけじゃなくkSecReturnData(データを返す)も設定するのも忘れないように。

 ま、だいたいこんなところで、KeyChain Service APIのお勉強はおしまい。
 次回は実際にパスワード設定GUIを作って連携させてみる。

 サンプルは、keychain-Info.plistを
Bundle identifier:jp.xcc.${PRODUCT_NAME:rfc1034identifier}

 Entitlements.plistを
keychain-access-groups(item 1):$(AppIdentifierPrefix)jp.xcc.konohanaGroup

 keychainAppDelegate.mを
[item setObject:@"ABCDEFG.jp.xcc.konohanaGroup" forKey:(id)kSecAttrAccessGroup];

 としているので、実機で動かす時は各自適当に修正してから使ってください。
 ではでは。

------------
サンプルプロジェクト:keychain-02.zip