レシート検証プログラミングガイド (TP40010573 0.0.0)

レシート検証
プログラミングガイド
目次
レシートの検証について 5
はじめに 5
レシートをローカルで検証する 5
App Storeを使用してレシートを検証する 5
レシートをローカルで検証する 6
レシートがある場所の特定と解析 6
GUIDのハッシュ値の計算 9
レシートの検証 9
レシート検証の失敗への対応 10
OS Xで検証が失敗したら終了する 10
レシート検証がiOSで失敗したら更新する 10
MacアプリケーションにMinimum System Versionを設定する 10
バージョン番号をローカライズしない 10
検証チェックのプロテクト 11
開発プロセス中のテスト 11
In-App Purchaseの検証 12
実装方法のヒント 13
OS XでのGUIDの取得 13
レシートの解析と署名の確認 15
App Storeを使用してレシートを検証する 19
レシートデータの読み込み 19
レシートデータをApp Storeに送信 19
応答のパース 21
レシートのフィールド 23
アプリケーションのレシートのフィールド 23
バンドルID 23
アプリケーションのバージョン 23
オペーク値 24
SHA-1ハッシュ 24
In-App Purchaseのレシート 24
アプリケーションのオリジナルバージョン 24
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
2
目次
レシートの有効期限 25
In-App Purchaseのレシートのフィールド 25
数量 25
プロダクトID 26
トランザクションID 26
オリジナルのトランザクションID 26
購入日 27
オリジナル購入日 27
購読の有効期限 28
キャンセル日 28
アプリケーション項目ID 28
外部バージョンID 29
Web注文明細行ID 29
書類の改訂履歴 30
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
3
図、表、リスト
レシートをローカルで検証する 6
図 1-1
リスト 1-1
リスト 1-2
リスト 1-3
リスト 1-4
リスト 1-5
リスト 1-6
リスト 1-7
レシートの後続 7
ペイロードフォーマットのASN.1による定義 8
In-App PurchaseレシートのフォーマットのASN.1による定義 12
コンピュータのGUIDの取得 13
OpenSSLを使用した署名の確認 15
asn1cを使用したペイロードの解析 16
レシート属性の抽出 17
GUIDのハッシュ値の計算 18
App Storeを使用してレシートを検証する 19
表 2-1
ステータスコード 22
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
4
レシートの検証について
注意: 本書は以前は『Validating Mac App Store Receipts 』というタイトルでした。
アプリケーションまたはIn-App Purchaseのレシートは、アプリケーションおよびアプリケーション内
で行われた任意のIn-App Purchaseの販売の記録です。アプリケーションの不正なコピーが実行されな
いようにするために、アプリケーションにレシート検証コードを追加できます。コピープロテクトを
実装するためにアプリケーションにできることと、アプリケーションでしてはならないことの詳細に
ついては、ライセンス契約を参照し、ガイドラインを確認してください。
レシートの検証では、暗号と各種の安全なコーディングテクニックについての理解が必要になりま
す。アプリケーションに特有のソリューションを採用することが重要です。
はじめに
レシートの検証には、ローカルで行う方法とApp Storeを使う方法の2つの方法があります。両方の方
法を比較して、ご自分のアプリケーションとインフラストラクチャにより適した方法を決定してくだ
さい。両方の方法を実装することもできます。
レシートをローカルで検証する
ローカルでの検証には、PKCS #7署名を読み込んで検証するコードと署名されたペイロードを解析し
て検証するコードが必要です。
関連する章: “レシートをローカルで検証する” (6 ページ)、“レシートのフィール
ド” (23 ページ)
App Storeを使用してレシートを検証する
App Storeを使用した検証では、アプリケーションとサーバ間に安全な接続があり、また、App Store
を使用してレシートを検証するコードがサーバ上にあることが必要です。
関連する章: “App Storeを使用してレシートを検証する” (19 ページ)、“レシートのフィー
ルド” (23 ページ)
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
5
レシートをローカルで検証する
レシートの検証は、アプリケーションが起動された直後、ユーザインターフェイスの表示や子プロセ
スの生成が行われる前に実行します。このチェックは、NSApplicationMain関数が呼び出される前
にmain関数内で実装します。セキュリティを強化するには、アプリケーションの実行中にこのチェッ
クを定期的に繰り返します。
レシートがある場所の特定と解析
アプリケーションがApp Storeからインストールされる場合、アプリケーションにはAppleだけが有効
なレシートを作成できることを保証するために署名が暗号化されているアプリケーションのレシート
が含まれています。レシートはアプリケーションバンドル内に保存されます。レシートの場所を特定
するには、NSBundleクラスのappStoreReceiptURLメソッドを呼び出します。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
6
レシートをローカルで検証する
レシートがある場所の特定と解析
注意: OS Xで(システムが古いために)appStoreReceiptURLメソッドが使用できない場合
は、ハードコーディングされたパスにフォールバックすることができます。レシートのパス
はアプリケーションバンドル内の/Contents/_MASReceipt/receiptです。
iOSで(システムが古いために)appStoreReceiptURLメソッドが使用できない場合は、App
Storeを使用してSKPaymentTransactionオブジェクトのtransactionReceiptプロパティを
検証するためにフォールバックすることができます。詳細については、“App Storeを使用し
てレシートを検証する” (19 ページ)を参照してください。
レシートは、図 1-1に示される構造を持つバイナリファイルです。
図 1-1
レシートの後続
Receipt
Payload
Attribute
...
Attribute
In-App Purchase Receipt
...
Attribute
...
Certificate chain
Signature
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
7
レシートをローカルで検証する
レシートがある場所の特定と解析
レシートの最も外側の部分(図でReceipt と名前が付いている部分)はPKCS #7コンテナであり、その
ペイロードはRFC 2315で定義されているように、ITU-T X.690で定義されるASN.1(Abstract Syntax Notation
One)を使用してエンコードされています。ペイロードは、レシート属性のセットで構成されていま
す。各レシート属性には、タイプとバージョン、そして値が含まれています。
ペイロードの構造は、リスト 1-1に示すASN.1記法を使用して定義されています。この定義は、コード
のこの部分を手作業で記述する代わりにasn1cツールを使用して、データ型宣言の生成、ペイロード
をデコードする関数を生成するために使用できます。最初にasn1cをインストールする必要がありま
す。これは、MacPortsおよびSourceForgeから入手可能です。
レシートに含まれるキーの詳細については、“レシートのフィールド” (23 ページ)を参照してくだ
さい。
コードを生成するには、リスト 1-1に示されるペイロードの記述をファイルに保存してから、「ター
ミナル(Terminal)」で次のコマンドを実行します。
asn1c -fnative-types filename
asn1cツールによる現在のディレクトリへのファイルの生成が完了したら、生成されたファイルを
Xcodeプロジェクトに追加します。
リスト 1-1
ペイロードフォーマットのASN.1による定義
ReceiptModule DEFINITIONS ::=
BEGIN
ReceiptAttribute ::= SEQUENCE {
type
INTEGER,
version INTEGER,
value
OCTET STRING
}
Payload ::= SET OF ReceiptAttribute
END
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
8
レシートをローカルで検証する
GUIDのハッシュ値の計算
GUIDのハッシュ値の計算
OS Xでは、“OS XでのGUIDの取得” (13 ページ)で説明された方法を使用してGUIDをフェッチします。
iOSでは、UIDeviceのidentifierForVendorプロパティによって返された値を使用してGUIDを計算
します。
ハッシュ値を計算するには、最初にGUID値をオペーク値(タイプ4の属性)とバンドルIDに連結しま
す。UTF-8文字列の解釈や正規化を一切行わずにレシートの生のバイトを使用します。次に連結され
たこの一連のバイトに対してSHA-1のハッシュ値を計算します。
レシートの検証
レシートを検証するには、次のテストを順に実行します。
1.
レシートの位置を特定します。
レシートが存在しない場合、検証は失敗します。
2.
レシートがAppleによって適切に署名されていることを確認します。
Appleによる署名がない場合、検証は失敗します。
3.
レシートにあるバンドルIDが、Info.plistファイルにあると予想されるハードコーディングされ
た定数CFBundleIdentifierの値と一致することを確認します。
一致しない場合、検証は失敗します。
4.
レシートにあるバージョンI文字列Dが、Info.plistファイルにあると予想されるハードコーディ
ングされたCFBundleShortVersionString定数の値と一致することを確認します。
一致しない場合、検証は失敗します。
5.
“GUIDのハッシュ値の計算” (9 ページ)の説明に従って、GUIDのハッシュ値を計算します。
計算結果がレシートにあるハッシュ値と一致しない場合、検証は失敗します。
すべてのテストに合格すると、検証は成功です。
注意: バンドルIDとバージョンID文字列は、単なるバイト列ではなく、UTF-8の文字列です。
比較ロジックはこのことを踏まえてコーディングしてください。
アプリケーションで「Volume Purchase Program」がサポートされている場合は、レシートが期限切れ
になる日付をチェックします。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
9
レシートをローカルで検証する
レシート検証の失敗への対応
レシート検証の失敗への対応
検証はさまざまな理由で失敗することがあります。たとえば、ユーザがアプリケーションをあるMac
から別のMacにコピーすると、GUIDが一致しなくなり、レシート検証が失敗することになります。
OS Xで検証が失敗したら終了する
OS Xで検証が失敗したら、ステータス173でexitを呼び出します。この終了ステータスは、レシート
は無効であるとアプリケーションで判断したことをシステムに通知するものです。この時点で、シス
テムは有効なレシートを取得することを試み、ユーザのiTunes証明書の提示を求めることがあります。
有効なレシートの取得に成功すると、システムはアプリケーションを再度起動します。そうでない場
合は、問題があることを説明するエラーメッセージをユーザに表示します。
検証に失敗した場合、ユーザにエラーメッセージを表示しないでください。有効なレシートの取得を
試みる、あるいはレシートが有効でないことをユーザに伝えるのは、システムの役割です。
レシート検証がiOSで失敗したら更新する
iOSで検証が失敗した場合は、SKReceiptRefreshRequestクラスを使用してレシートを更新します。
アプリケーションを終了しないでください。アプリケーション側の選択で、ユーザに猶予期間を与え
るか、またはアプリケーション内で機能を制限します。
MacアプリケーションにMinimum System Versionを設定する
アプリケーションのInfo.plistファイルに、10.6.6以上の値を設定したLSMinimumSystemVersion
キーを含めます。レシートの検証がバージョン10.6.6よりも前のOS Xで失敗した場合、アプリケーショ
ンはユーザに何の説明も行わずに、起動後すぐに終了します。以前のバージョンのOS Xでは終了ス
テータス173を解釈しないため、有効なレシートの取得を試みたり、ユーザにエラーメッセージを表
示したりすることはありません。
バージョン番号をローカライズしない
アプリケーションがローカライズされている場合は、アプリケーションのInfoPlist.stringsファ
イルのどこにもCFBundleShortVersionStringキーが含まれていてはなりません。レシートには、
ローカライズされていない値がInfo.plistファイルから保存されます。このキーの値をローカライ
ズしようとすると、レシートの検証に失敗することがあります。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
10
レシートをローカルで検証する
検証チェックのプロテクト
検証チェックのプロテクト
攻撃者は、アプリケーションのバイナリにパッチを当てる、または検証コードが依存しているオペ
レーティングシステムの基本的なルーチンを改変することによって、検証コードの回避を試みること
があります。このようなタイプの攻撃に対して耐性を得るには、次のものも含めたさまざまなコー
ディングテクニックが必要になります。
●
●
暗号チェック用のコードは、システムが提供するAPIを使用せずに、インラインで処理します。
アプリケーションのバイナリにパッチを当てるときのターゲットになりやすい、単純なコード構
築を避けます。
たとえば、次のようなコードを書かないようにします。
if (failedValidation) {
exit(173);
}
●
コードを堅牢にする、難読化(obfuscation)などのテクニックを実装します。
複数のアプリケーションで検証の実行に同じコードを使用している場合、このコードに共通する
シグネチャがアプリケーションにパッチを当てるツールのターゲットになる場合があります。
●
exit関数がアプリケーションを終了することに失敗した場合でも、アプリケーションが実行を停
止することを確認します。
開発プロセス中のテスト
開発プロセス中にメインアプリケーションをテストするには、アプリケーションを起動するために有
効なレシートが必要になります。これをセットアップするには、次の手順を実行します。
1.
Appleのサーバに接続するためのインターネットアクセスがあることを確認します。
2.
アプリケーションをダブルクリックして起動します(または何らかの方法でLaunch Serviceによっ
てアプリケーションを起動させます)。
アプリケーションの起動後、次のことが発生します。
●
レシートが存在していないため、アプリケーションはレシートの検証に失敗し、ステータス173
で終了します。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
11
レシートをローカルで検証する
In-App Purchaseの検証
●
●
この終了ステータスがシステムによって解釈されて、有効なレシートの取得が試みられます。証
明書に署名をするアプリケーションが有効であると仮定すると、アプリケーションにはシステム
によって有効なレシートがインストールされます。システムからiTunesの証明書の提示を求めら
れることがあります。
システムによってアプリケーションが再起動され、レシートの検証がアプリケーションで成功し
ます。
レシートがインストール済みのこの開発手法を使用すると、gdbやXcodeデバッガを使用する場合な
ど、どのような方法でもアプリケーションを起動できます。
In-App Purchaseの検証
In-App Purchaseを検証するには、アプリケーションで次のテストを順に実行します。
1.
前のセクションの説明に従って、アプリケーションのレシートを解析して検証します。
レシートが有効でない場合は、In-App Purchaseはすべて無効です。
2.
In-App Purchaseのレシートを検証します(タイプ17の属性の値)。
In-App Purchaseの各レシートには、アプリケーションのレシートと同様に、属性のセットから構
成されています。これらのレシートの構造は、リスト 1-2 (12 ページ)で定義されています。レ
シートの解析時に、asn1cツールを使用してASN.1の記述からコードの一部を生成できます。表に
表示されないタイプの属性はすべて無視します。これらはシステムによって使用されるために予
約されており、内容が変更される可能性があります。
レシートにあるフィールドの詳細については、“レシートのフィールド” (23 ページ)を参照し
てください。
3.
対象となっているプロダクトIDを各In-App PurchaseレシートのプロダクトIDと比較します。
一致するレシートが存在する場合は、検証は成功です。そうでない場合、検証は失敗です。
検証に成功すると、たとえばコンテンツのダウンロードや機能の追加によって、購入した機能がアプ
リケーションで有効になります。検証に失敗すると、アプリケーションで機能を有効にできなくなり
ます。
リスト 1-2
In-App PurchaseレシートのフォーマットのASN.1による定義
InAppAttribute ::= SEQUENCE {
type
INTEGER,
version
INTEGER,
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
12
レシートをローカルで検証する
実装方法のヒント
value
OCTET STRING
}
InAppReceipt ::= SET OF InAppAttribute
元のトランザクションIDと元のトランザクション日付の属性は、購入したものを再ダウンロードする
場合に使用します。購入の再ダウンロードでは、新しいトランザクションIDが与えられますが、元の
購入におけるIDと日付が含まれています。
消耗型プロダクトと非更新購読: 消耗型プロダクトまたは非更新購読のIn-App Purchareのレ
シートは、購入が行われた時点でレシートに追加されます。これは、アプリケーションでト
ランザクションが終了するまでの間、レシートに保持されます。その後、このIn-App Purchare
レシートは、レシートが次回更新されると(たとえばユーザが別の購入を行ったり、アプリ
ケーションで明示的なレシートの更新が行われると)削除されます。
実装方法のヒント
このセクションには、レシートの検証を実装する際に参考になるコードのリストが掲載されていま
す。
OS XでのGUIDの取得
OS Xでは検証コードでGUIDを取得するときに使用される方法とアプリケーションのレシートが作成さ
れるときに使用される方法が正確に同じになるように、リスト 1-3のモデルに従います(またはこの
コードをそのまま使用します)。
リスト 1-3
コンピュータのGUIDの取得
#import <IOKit/IOKitLib.h>
#import <Foundation/Foundation.h>
// コンピュータのGUIDを格納したCFDataオブジェクトの取得
CFDataRef copy_mac_address(void)
{
kern_return_t
kernResult;
mach_port_t
master_port;
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
13
レシートをローカルで検証する
実装方法のヒント
CFMutableDictionaryRef
matchingDict;
io_iterator_t
iterator;
io_object_t
service;
CFDataRef
macAddress = nil;
kernResult = IOMasterPort(MACH_PORT_NULL, &master_port);
if (kernResult != KERN_SUCCESS) {
printf("IOMasterPort returned %d\n", kernResult);
return nil;
}
matchingDict = IOBSDNameMatching(master_port, 0, "en0");
if (!matchingDict) {
printf("IOBSDNameMatching returned empty dictionary\n");
return nil;
}
kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator);
if (kernResult != KERN_SUCCESS) {
printf("IOServiceGetMatchingServices returned %d\n", kernResult);
return nil;
}
while((service = IOIteratorNext(iterator)) != 0) {
io_object_t parentService;
kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane,
&parentService);
if (kernResult == KERN_SUCCESS) {
if (macAddress) CFRelease(macAddress);
macAddress = (CFDataRef) IORegistryEntryCreateCFProperty(parentService,
CFSTR("IOMACAddress"), kCFAllocatorDefault, 0);
IOObjectRelease(parentService);
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
14
レシートをローカルで検証する
実装方法のヒント
} else {
printf("IORegistryEntryGetParentEntry returned %d\n", kernResult);
}
IOObjectRelease(service);
}
IOObjectRelease(iterator);
return macAddress;
}
レシートの解析と署名の確認
次のコードのリストをOpenSSLとasn1cを使用したレシート検証の可能な実装の1つの概要として使用
してください。これらのリストは、コピーアンドペーストするための解決法ではなく、関連するAPI
とデータ構造体に注目することで、ユーザ独自のコードを作成するときのガイドとなるために提供さ
れています。
OpenSSLを使用する場合は、作成したバイナリを静的にリンクします。OpenSSLに対する動的なリン
クは非推奨であり、ビルド時に警告されます。
コードがリストで示した次の概要に従っていることを確認してください。
1.
署名を確認します(リスト 1-4 (15 ページ))。
2.
ペイロードを解析します(リスト 1-5 (16 ページ))。
3.
レシート属性を抽出します(リスト 1-6 (17 ページ))。
4.
GUIDのハッシュ値を計算します(リスト 1-7 (18 ページ))。
リスト 1-4
OpenSSLを使用した署名の確認
/* PKCS #7コンテナ(レシート)と確認の出力*/
BIO *b_p7;
PKCS7 *p7;
/* 生データとしてのAppleのルート証明書と、そのOpenSSLによる表現*/
BIO *b_x509;
X509 *Apple;
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
15
レシートをローカルで検証する
実装方法のヒント
/* 信頼チェーン検証用のルート証明書*/
X509_STORE *store = X509_STORE_new();
/* ...BIO_new_mem_buf()を使用して両方のBIO変数のバッファとサイズを初期化... */
/* 署名の確認時に抽出したレシートのペイロードを保持するためにb_outを出力BIOとして初期化*/
BIO *b_out = BIO_new(BIO_s_mem());
/* レシートファイルのコンテンツをキャプチャし、p7変数にPKCS #7コンテナを代入*/
p7 = d2i_PKCS7_bio(b_p7, NULL);
/* ...Appleルート証明書をb_X509へロード... */
/* Appleルート証明書の値でb_x509を入力BIOとして初期化してX509データ構造体へロード。次にApple
ルート証明書をこの構造体に追加*/
Apple = d2i_X509_bio(b_x509, NULL);
X509_STORE_add_cert(store, Apple);
/* 署名の確認。確認が正しければ、b_ouにはPKCS #7のペイロードが格納され、rcは1になります。*/
int rc = PKCS7_verify(p7, NULL, store, NULL, b_out, 0);
/* セキュリティを追加するために、ルート証明書のフィンガプリントの確認、および中間証明書と署名証明
書のOIDの確認が行われる場合があります。中間証明書の証明書ポリシー拡張にあるOIDは(1 2 840 113635
100 5 6 1)であり、署名証明書のマーカOIDは(1 2 840 113635 100 6 11 1)です。*/
リスト 1-5
asn1cを使用したペイロードの解析
#include "Payload.h" /* このヘッダファイルはasn1cによって作成されました。*/
/* レシートのペイロードとそのサイズ*/
void *pld = NULL;
size_t pld_sz;
/* ペイロードの解析で使用される変数。データ型はどちらもPayload.hで宣言されています。*/
Payload_t *payload = NULL;
asn_dec_rval_t rval;
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
16
レシートをローカルで検証する
実装方法のヒント
/* ...レシートファイルからペイロードをpldにロードし、pld_szにペイロードのサイズを設定します...
*/
/* asn1cによって生成されたデコード関数を使用してバッファを解析します。ペイロード変数にはレシート
属性が含まれています。*/
rval = asn_DEF_Payload.ber_decoder(NULL, &asn_DEF_Payload, (void **)&payload, pld,
pld_sz, 0);
リスト 1-6
レシート属性の抽出
/* レシート属性を保存する変数*/
OCTET_STRING_t *bundle_id = NULL;
OCTET_STRING_t *bundle_version = NULL;
OCTET_STRING_t *opaque = NULL;
OCTET_STRING_t *hash = NULL;
/* GUIDのハッシュ値の計算に必要な値を保存しながらレシート属性ごとに繰り返す*/
size_t i;
for (i = 0; i < payload->list.count; i++) {
ReceiptAttribute_t *entry;
entry = payload->list.array[i];
switch (entry->type) {
case 2:
bundle_id = &entry->value;
break;
case 3:
bundle_version = &entry->value;
break;
case 4:
opaque = &entry->value;
break;
case 5:
hash = &entry->value;
break;
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
17
レシートをローカルで検証する
実装方法のヒント
}
}
リスト 1-7
GUIDのハッシュ値の計算
/* copy_mac_address()から返されるGUIDはCFDataRefです。CFDataGetBytePtr()と
CFDataGetLength()を使用してGUIDを構成しているバイトへのポインタを取得し、その長さを取得します。
*/
UInt8 *guid = NULL;
size_t guid_sz;
/* OpenSSL用にEVPコンテキストを宣言して初期化*/
EVP_MD_CTX evp_ctx;
EVP_MD_CTX_init(&evp_ctx);
/* ハッシュ値の計算結果用のバッファ*/
UInt8 digest[20];
/* SHA-1ダイジェストを計算するためのEVPコンテキストを設定*/
EVP_DigestInit_ex(&evp_ctx, EVP_sha1(), NULL);
/* ハッシュ値を計算する各部分を連結。この順序で連結する必要があります。*/
EVP_DigestUpdate(&evp_ctx, guid, guid_sz);
EVP_DigestUpdate(&evp_ctx, opaque->buf, opaque->size);
EVP_DigestUpdate(&evp_ctx, bundle_id->buf, bundle_id->size);
/* ハッシュ値を計算して、結果をダイジェスト変数に保存 */
EVP_DigestFinal_ex(&evp_ctx, digest, NULL);
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
18
App Storeを使用してレシートを検証する
注意: iOS 5.1以前には、サーバを使用せずにデバイスから直接App Storeを使用するレシート
の検証に関係する脆弱性があります。詳細と対処方法については、『In-App Purchase Receipt
Validation for iOS 5.1 and Earlier 』を参照してください。
App Storeとの通信には信頼できるサーバを使用してください。独自のサーバを使用する場合は、アプ
リケーションがこのサーバのみを認識して信頼するようにデザインし、サーバがApp Storeのサーバに
接続していることを確認するようにします。接続のどちらの終端もコントロールすることができない
ため、ユーザのデバイスとApp Storeの間で信頼できる接続を直接構築することはできません。
App Storeとの通信は、RFC 4627で定義されているJSON辞書によって構造化されています。 バイナリ
データにはRFC 4648で定義されているbase64エンコードを施します。
レシートデータの読み込み
レシートデータを取得するためには、NSBundleのappStoreReceiptURLメソッドでアプリケーション
のレシートがある場所を特定した後、実際に読み込むことになります。appStoreReceiptURLメソッ
ドを使用できない場合は、下位互換性のためにトランザクションのtransactionReceiptプロパティ
の値にフォールバックできます。次にこのデータをサーバに送信します。サーバとのやり取りの実装
詳細は、他の場合と同様、開発者に任されています。
// アプリケーションバンドルからレシートをロードする。
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) { /* ローカルのレシートがない -- エラー処理。*/ }
/* ... レシートデータをサーバに送る ... */
レシートデータをApp Storeに送信
サーバ上で、次のキーを指定してJSONオブジェクトを生成します。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
19
App Storeを使用してレシートを検証する
レシートデータをApp Storeに送信
キー
値
receipt-data
base64エンコードを施したレシートデータ。
パスワード
自動更新型の購読に用いる、iOS 6型のトランザクションレシートの場合の
み。 アプリケーションの共有鍵(16進文字列)。
このJSONオブジェクトを、HTTP POSTリクエストのペイロードとして送信します。テスト環境では、
URLとして「https://sandbox.itunes.apple.com/verifyReceipt」を指定してください。実稼働
環境では「https://buy.itunes.apple.com/verifyReceipt」を指定します。
NSData *receipt; // デバイスからサーバに送信
// リクエスト内容を記述したJSONオブジェクトを生成する
NSError *error;
NSDictionary *requestContents = @{
@"receipt-data": [receipt base64EncodedStringWithOptions:0]
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData) { /* ... エラー処理 ... */ }
// レシートデータを指定してPOSTリクエストを生成する。
NSURL *storeURL = [NSURL
URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
// iTunes Storeとの接続処理をバックグラウンドキューに登録する。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError
*connectionError) {
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
20
App Storeを使用してレシートを検証する
応答のパース
if (connectionError) {
/* ... エラー処理 ... */
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data
options:0 error:&error];
if (!jsonResponse) { /* ... エラー処理 ...*/ }
/* ... デバイスに応答する ... */
}
}];
応答のパース
応答のペイロードはJSONオブジェクトで、次のキーと値が収容されています。
キー
値
status
レシートが有効であれば0、そうでなければ表 2-1 (22 ページ)に示
すいずれかのエラーコード。
iOS 6のトランザクションレコードであれば、該当するトランザクショ
ンに関するレシートの状態を表します。
iOS 7のアプリケーションレシートならば、当該レシート全体の状態を
表します。たとえば、有効なアプリケーションレシートを送信するけ
れども、その内容(購読物)は期限切れである場合、レシートそのも
のは有効なので、応答は0となります。
receipt
検証用に送信されたレシートをJSON形式で表したもの。レシートに含
まれるキーの詳細については、“レシートのフィールド” (23 ページ)
を参照してください。
latest_receipt
自動更新型の購読に用いる、iOS 6型のトランザクションレシートの
場合のみ。 直近の更新に対応するトランザクションレシートにbase-64
エンコードを施したもの。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
21
App Storeを使用してレシートを検証する
応答のパース
キー
値
latest_receipt_info
自動更新型の購読に用いる、iOS 6型のトランザクションレシートの
場合のみ。 直近の更新に対応するトランザクションレシートをJSON
形式で表したもの。
表 2-1
ステータスコード
ステータスコー
ド
説明
21000
App Storeは、提供したJSONオブジェクトを読むことができません。
21002
receipt-dataプロパティのデータが不正であるか、または欠落しています。
21003
レシートを認証できません。
21004
この共有秘密鍵は、アカウントのファイルに保存された共有秘密鍵と一致し
ません。
自動更新型の購読に用いる、iOS 6型のトランザクションレシートの場合の
み。
21005
レシートサーバは現在利用できません。
21006
このレシートは有効ですが、定期購読の期限が切れています。ステータス
コードがサーバに返される際、レシートデータもデコードされ、応答の一部
として返されます。
自動更新型の購読に用いる、iOS 6型のトランザクションレシートの場合の
み。
21007
テスト環境のレシートを、実稼働環境に送信して検証しようとしました。こ
れはテスト環境に送信してください。
21008
実稼働環境のレシートを、テスト環境に送信して検証しようとしました。こ
れは実稼働環境に送信してください。
latest_receiptキーおよびlatest_receipt_infoキーの値は、自動更新型の購読が現時点でアク
ティブかどうか判断するために有用です。購読に係るトランザクションレシートを渡してこの値を確
認することにより、現在アクティブな購読期間に関する情報を取得できます。検証したレシートが直
近の更新に関するものであれば、latest_receiptの値は(リクエストにおける)receipt-dataと同
じ、latest_receipt_infoの値はreceiptと同じになります。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
22
レシートのフィールド
レシートは多数のフィールドから構成されています。フィールドの中にはASN.1形式のレシートでの
み、またはJSON形式のレシートをApp Storeで検証したときにのみ、ローカルで使用できるものがあ
ります。以下に記載されていないキーは、Appleで使用するために予約されており、アプリケーショ
ンでは無視する必要があります。
アプリケーションのレシートのフィールド
バンドルID
アプリケーションのバンドルIDです。
ASN.1フィールドタイプ:2
ASN.1フィールド値:UTF8STRING
JSONフィールド名:bundle_id
JSONフィールド値:文字列
このフィールドはInfo.plistファイルのCFBundleIdentifierの値に対応します。
アプリケーションのバージョン
アプリケーションのバージョン番号です。
ASN.1フィールドタイプ:3
ASN.1フィールド値:UTF8STRING
JSONフィールド名:application_version
JSONフィールド値:文字列
このフィールドは、Info.plistのCFBundleVersion(iOSの場合)または
CFBundleShortVersionString(OS Xの場合)の値に対応します。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
23
レシートのフィールド
アプリケーションのレシートのフィールド
オペーク値
他のデータとともに、検証の際、SHA-1ハッシュを計算するために使う値。
ASN.1フィールドタイプ:4
ASN.1フィールド値:バイト列
JSONフィールド名:(なし)
JSONフィールド値:(なし)
SHA-1ハッシュ
レシートの検証で使用されるSHA-1ハッシュです。
ASN.1フィールドタイプ:5
ASN.1フィールド値: 20バイトのSHA-1ダイジェスト
JSONフィールド名:(なし)
JSONフィールド値:(なし)
In-App Purchaseのレシート
In-App Purchaseのレシートです。
ASN.1フィールドタイプ:17
ASN.1フィールド値:In-App Purchaseのレシート属性のセット
JSONフィールド名:in_app
JSONフィールド値:In-App Purchaseレシートの配列
JSONファイルの場合は、このキーの値はIn-App Purchaseのレシートを格納している配列になります。
ASN.1ファイルの場合は、複数のフィールドがあり、そのタイプはすべて17です。各フィールドには
In-App Purchaseのレシートが1つ格納されています。
アプリケーションのオリジナルバージョン
アプリケーションが最初に購入されたときのバージョンです。
ASN.1フィールドタイプ:19
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
24
レシートのフィールド
In-App Purchaseのレシートのフィールド
ASN.1フィールド値:UTF8STRING
JSONフィールド名:original_application_version
JSONフィールド値:文字列
このフィールドは、最初の購入が行われたときのInfo.plistファイルのCFBundleVersion(iOSの場
合)、またはCFBundleShortVersionString(OS Xの場合)に対応します。
サンドボックス環境では、このフィールドの値は常に“1.0”です。
2013年6月20日までのレシートには、このフィールドはありません。OSの版によらず、新規レシート
にはすべて必要です。必要なフィールドが欠落している場合、SKReceiptRefreshRequestクラスを
用い、手動でレシートを更新してください。
レシートの有効期限
アプリケーションのレシートが期限切れになる日付です。
ASN.1フィールドタイプ:21
ASN.1フィールド値: IA5STRING(RFC 3339の日付として解釈されます)
JSONフィールド名:expiration_date
JSONフィールド値:IA5STRING(RFC 3339の日付として解釈されます)
このキーは、Volume Purchase Program経由で購入されたアプリケーションにのみ存在します。この
キーが存在していない場合は、レシートは有効期限切れになりません。
レシートの検証時に、この日付と現在の日付とを比較して、レシートを期限切れにするかどうかを判
別します。この日付は、有効期限までの残り時間など、他の情報を計算するためには使用しないよう
にしてください。
In-App Purchaseのレシートのフィールド
数量
購入したアイテム数です。
ASN.1フィールドタイプ:1701
ASN.1フィールド値:INTEGER
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
25
レシートのフィールド
In-App Purchaseのレシートのフィールド
JSONフィールド名:quantity
JSONフィールド値:整数として解釈できる文字列
この値は、トランザクションのpaymentプロパティに保存されているSKPaymentオブジェクトの
quantityプロパティに対応します。
プロダクトID
購入したアイテムのプロダクトIDです。
ASN.1フィールドタイプ:1702
ASN.1フィールド値:UTF8STRING
JSONフィールド名:product_id
JSONフィールド値:文字列
この値は、トランザクションのpaymentプロパティに保存されているSKPaymentオブジェクトの
productIdentifierプロパティに対応します。
トランザクションID
購入したアイテムのトランザクションIDです。
ASN.1フィールドタイプ:1703
ASN.1フィールド値:UTF8STRING
JSONフィールド名:transaction_id
JSONフィールド値:文字列
この値は、トランザクションのtransactionIdentifierプロパティに対応します。
オリジナルのトランザクションID
以前のトランザクションを復元したトランザクションの場合、元のトランザクションのトランザク
ションIDです。そうでない場合は、トランザクションIDと同一になります。
ASN.1フィールドタイプ:1705
ASN.1フィールド値:UTF8STRING
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
26
レシートのフィールド
In-App Purchaseのレシートのフィールド
JSONフィールド名:original_transaction_id
JSONフィールド値:文字列
この値は、元のトランザクションのtransactionIdentifierプロパティに対応します。
自動更新購読の更新チェーンにあるすべてのレシートでは、このフィールドが同じ値になります。
購入日
項目が購入された日時です。
ASN.1フィールドタイプ:1704
ASN.1フィールド値: IA5STRING(RFC 3339の日付として解釈されます)
JSONフィールド名:purchase_date
JSONフィールド値:RFC 3339の日付として解釈される文字列
この値は、トランザクションのtransactionDateプロパティに対応します。
以前のトランザクションを復元したトランザクションの場合、購入日付は復元が行われた日付になり
ます。元のトランザクションの日付を取得するには、“オリジナル購入日” (27 ページ)を使用しま
す。
自動更新購読のレシートでは、トランザクションが復元されたかどうかに関係なく、このフィールド
は常に購読を購入した、または更新した日付になります。
オリジナル購入日
以前のトランザクションを復元したトランザクションの場合、元のトランザクションの日付になりま
す。
ASN.1フィールドタイプ:1706
ASN.1フィールド値: IA5STRING(RFC 3339の日付として解釈されます)
JSONフィールド名: original_purchase_date
JSONフィールド値:RFC 3339の日付として解釈される文字列
この値は、元のトランザクションのtransactionDateプロパティに対応します。
自動更新購読のレシートでは、購読が更新されている場合であっても、これは購読期間が開始された
ときを示します。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
27
レシートのフィールド
In-App Purchaseのレシートのフィールド
購読の有効期限
定期購読の有効期限が、1970年1月1日 00:00:00 GMTからのミリ秒単位で表されています。
ASN.1フィールドタイプ:1708
ASN.1フィールド値: IA5STRING(RFC 3339の日付として解釈されます)
JSONフィールド名:expires_date
JSONフィールド値:数値
このキーは、自動更新購読のレシートにのみ存在します。
キャンセル日
Appleカスタマーサポートによってキャンセルされたトランザクションで、キャンセルが行われた日
時です。
ASN.1フィールドタイプ:1712
ASN.1フィールド値: IA5STRING(RFC 3339の日付として解釈されます)
JSONフィールド名:cancellation_date
JSONフィールド値:RFC 3339の日付として解釈される文字列
キャンセルされたレシートは、購入が行われなかったものとして扱います。
アプリケーション項目ID
トランザクションを作成したアプリケーションを一意に識別するために、App Storeが使用する文字列
です。
ASN.1フィールドタイプ:(なし)
ASN.1フィールド値:(なし)
JSONフィールド名:app_item_id
JSONフィールド値:文字列
サーバが複数のアプリケーションをサポートする場合、この値を使用してそれらを区別できます。
アプリケーションにIDが割り当てられるのは、本番環境のときに限られるので、テスト環境で作成さ
れたレシートにはこのキーは存在しません。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
28
レシートのフィールド
In-App Purchaseのレシートのフィールド
このフィールドはMac用のアプリケーションには存在しません。
“バンドルID” (23 ページ)も参照してください。
外部バージョンID
アプリケーションの改訂を一意に識別する任意の番号です。
ASN.1フィールドタイプ:(なし)
ASN.1フィールド値:(なし)
JSONフィールド名:version_external_identifier
JSONフィールド値:文字列
このキーはテスト環境で作成されたレシートには存在しません。
Web注文明細行ID
購読の購入を識別するための主キーです。
ASN.1フィールドタイプ:1711
ASN.1フィールド値:INTEGER
JSONフィールド名:web_order_line_item_id
JSONフィールド値:文字列
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
29
書類の改訂履歴
この表は「レシート検証プログラミングガイド 」の改訂履歴です。
日付
メモ
2014-02-11
サーバ側のレシート検証処理について、より詳しく解説するように
しました(“App Storeを使用してレシートを検証する” (19 ペー
ジ))。
2013-09-18
新しいレシートフィールドが追加されました。サーバ側検証につい
ての内容が組み込まれました。
以前は『Validating Mac App Store Receipts 』というタイトルでした。
『In-AppPurchaseProgrammingGuide 』の以前の版からの内容が組み
込まれました。
2012-01-09
OS X 10.7用の細かな改訂
2011-07-07
アプリケーションでレシートを検証する方法を説明した新しい文
書。
2014-02-11 | Copyright © 2014 Apple Inc. All Rights Reserved.
30
Apple Inc.
Copyright © 2014 Apple Inc.
All rights reserved.
本書の一部あるいは全部を Apple Inc. から書
面による事前の許諾を得ることなく複写複製
(コピー)することを禁じます。また、製品
に付属のソフトウェアは同梱のソフトウェア
使用許諾契約書に記載の条件のもとでお使い
ください。書類を個人で使用する場合に限り
1 台のコンピュータに保管すること、またそ
の書類にアップルの著作権表示が含まれる限
り、個人的な利用を目的に書類を複製するこ
とを認めます。
Apple ロゴは、米国その他の国で登録された
Apple Inc. の商標です。
キーボードから入力可能な Apple ロゴについ
ても、これを Apple Inc. からの書面による事
前の許諾なしに商業的な目的で使用すると、
連邦および州の商標法および不正競争防止法
違反となる場合があります。
本書に記載されているテクノロジーに関して
は、明示または黙示を問わず、使用を許諾し
ません。 本書に記載されているテクノロジー
に関するすべての知的財産権は、Apple Inc.
が保有しています。 本書は、Apple ブランド
のコンピュータ用のアプリケーション開発に
使用を限定します。
本書には正確な情報を記載するように努めま
した。 ただし、誤植や制作上の誤記がないこ
とを保証するものではありません。
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
U.S.A.
アップルジャパン株式会社
〒163-1450 東京都新宿区西新宿
3 丁目20 番2 号
東京オペラシティタワー
http://www.apple.com/jp/
Offline copy. Trademarks go here.
Apple Inc. は本書の内容を確認しておりますが、本
書に関して、明示的であるか黙示的であるかを問わ
ず、その品質、正確さ、市場性、または特定の目的
に対する適合性に関して何らかの保証または表明を
行うものではありません。その結果、本書は「現状
有姿のまま」提供され、本書の品質または正確さに
関連して発生するすべての損害は、購入者であるお
客様が負うものとします。
いかなる場合も、Apple Inc. は、本書の内容に含ま
れる瑕疵または不正確さによって生じる直接的、間
接的、特殊的、偶発的、または結果的損害に対する
賠償請求には一切応じません。そのような損害の可
能性があらかじめ指摘されている場合においても同
様です。
上記の損害に対する保証および救済は、口頭や書面
によるか、または明示的や黙示的であるかを問わ
ず、唯一のものであり、その他一切の保証にかわる
ものです。 Apple Inc. の販売店、代理店、または従
業員には、この保証に関する規定に何らかの変更、
拡張、または追加を加える権限は与えられていませ
ん。
一部の国や地域では、黙示あるいは偶発的または結
果的損害に対する賠償の免責または制限が認められ
ていないため、上記の制限や免責がお客様に適用さ
れない場合があります。 この保証はお客様に特定
の法的権利を与え、地域によってはその他の権利が
お客様に与えられる場合もあります。