[Android] Host-based Card Emulation 公式リファレンス 超意訳 | アドカレ2013 : SP #7

2013.12.07

こんにちは。こむろです。Android 4.4 KitKatでHost-card Emulationが追加されました。NFCの機能拡張の一つですが、割とボリュームのある公式ドキュメントだったので、自分なりに解釈するために超意訳(+あまりよく分からなかったところは直訳)をしました。
読めば何となく分かる程度に噛み砕いてあると思いますが、余計な情報かなーと思われるところなどザクザク省いてますので、ご注意くださいませ。

Introduction

世の中にあるほとんどのAndroidデバイスはNFCの機能を持ってるよ。
大体の非接触カードの機能は、キャリアによって提供されたSIMカードとかに埋め込まれてるSecure Elementというナニかによって提供されてることが多いね
Android 4.4ではSecure Elementに関わらない機能を追加したんだよ。これを「Host-based Card Emulation」とわたしたちは名づけたのであるの。この機能によって、Secure Elementを通さずにアプリケーションが直接NFC Readerと会話をすることができるようになりました。
このドキュメントは、Host-based Card Emulation(通称HCE)の提供方法、実装方法などについて触れるよ

Card Emulation with a Secure Element

図1を見てみてください。
NFCコントローラーはReaderから受け取ったデータは、Secure Elementに渡してしまうのデス。
Androidアプリケーションは、Readerから受け取ったデータを処理するトランザクションには関われないの。トランザクション終了後に、トランザクションのステータスを見ることができるので、それを見てユーザーに通知したりするんだな。

図1へLink

Host-based Card Emulation

Host-based Card Emulationの時は、NFC Readerからのデータは、Secure Elementへ渡らずにNFCコントローラから直接アプリケーションへ渡されるよ。
図2にどのように動作しているかを描いておいたので見ておいてくれ!

図2へLink

Supported NFC Cards and Protocols

NFC標準はすごい多くのプロトコルと色んなカードタイプをサポートしろって言ってるんだけど、Android 4.4では市場でよく使われてる主要なものをいくつか用意しておいたよ。
今その辺にある非接触の決済カードとかポイントカードとかのプロトコルは、Androidに搭載されてるプロトコルに準じてるので使える種類も多いんだぜ(ふふん
当然、カードのエミュレーションだけじゃなくて、Android端末がReaderになることもできるのさ。その辺は、IsoDepクラスあたりに定義してあるので見ておいてねー
Androidを使ったNFCソリューションの準備はばっちりさ

特にAndroid 4.4では、NFC-Forum ISO-DEP(ISO/IEC1443-4ベース)のカードエミュレーションとISO/IEC 7816-4で定義されているAPDUsによる処理をサポートしてるよ。 Androidでは、Nfc-B(ISO/IEC 1443-4 Type B)技術は、オプションだよー。詳しくは図3のレイヤーを見てね☆ミ

図3へLink

HCE Services

HCEアーキテクチャはServiceコンポーネントをベースにしてるのデース。 サービス使うと、アプリを立ち上げていなくてもバックグラウンドで動くことができるのと、UIなしで動作できるのが強みの一つだよ。ポイントカードとか交通系カードのHCEアプリケーションを作るときには自然とこういう形になると思われます。だって、ポイントカードを使うために、わざわざユーザーがいちいちアプリケーションを立ち上げるとか、ないでしょ?常識的に考えて。
端末をReaderにかざすと、自動的にサービスが起動されて、バックグラウンドでトランザクションが実行されるのだ。当然、ユーザー通知とかUIを追加してもOKよ!

もし誰も知らない未知のNFC Readerインフラを使う場合は、AIDを自分で登録する必要があるよ。AIDの登録の仕方はISO/IEC 7816-5に定義されてるからそっちを参照してね。
グーグルは、他のアプリケーションとの競合を避けるために、登録するAIDは7816-5に則ってねと仰っております

Service selection

ユーザーがNFC Readerにタッチすると、AndroidシステムはどのHCEサービスと会話をしたいのかを知る必要があるよね?それはIOS/IEC 7816-4にあるのです Application ID(AID)を中心にアプリケーションを選択する方法が定義されているのだ。AIDは16バイトだよ。もし、既存のNFC Readerのインフラを使うなら、決められたAIDを使ってねー(VisaとかMasterCardとか定義済みらしいよー)

AID groups

何個かのAIDを必要とするサービスを実装したい場合どうしましょう。その場合は、デフォルトハンドラを欲しいAIDに対して必要とするよ。
AIDグループは、OSによってどのAIDが所属してるか決められてるんだなー。全てのAIDはAIDグループに属してるよ。Androidはこんなことを保証してるよ

  • グループに所属する全てのAIDは、HCEサービスへルーティングされるよ
  • グループに所属してないAIDはHCEサービスへはルーティングしないよ

言い換えるとHCEサービスとその他にルーティングされる以外の状態は存在しない(らしい)よ

AID groups and categories

AIDグループは、カテゴリーごとに構成されてるよ。AndroidはHCEサービスをカテゴリによって分類できるのさ。ユーザーはAIDレベルの代わりにカテゴリーレベルをデフォルトにセットすることもできるよ。
まあ、でも一般のユーザーには関係ないことだから、普通はあまり言及しないよ

Android 4.4は2つのカテゴリーをサポートしてるよ

  • categories.CATEGORY_PAYMENT(支払サービスが必要なアプリケーション)
  • categories.CATEGORY_OTHER(他の全HCEアプリケーション)

Implementing an HCE Service

Host-based Card Emulationを使ったHCEサービスの実装は、ServiceクラスとNFCトランザクションのハンドラを実装すればOK

Checking for HCE Support

HCEをサポートしてるかどうかをチェックするんだ!
FEATURE_NFC_HOST_CARD_EMULATION featureをManifestに書きましょう。これを書くと、そのアプリ家s-本はHCEを利用するっていう宣言になるよ。

Service Implementation

Android 4.4には新しく基本的なHCE Serviceを簡単に実装できるクラスを追加したよ。HostAdpuService クラスなのさ
こんな感じでHostAdpuServiceを継承しよう

public class MyHostApduService extends HostApduService {
    @Override
    public byte[] processCommandApdu(byte[] apdu, Bundle extras) {
       ...
    }
    @Override
    public void onDeactivated(int reason) {
       ...
    }   
}

HostAdpuServiceは2つのAbstractメソッドを宣言してあるので、オーバーライドして中身を実装してあげる必要があるよん。
processComanndApdu()は、NFC ReaderからAPDUをサービスが受け取った時に呼ばれるよ。APDUは、ISO/IEC 7816-4で定義されてるよ(再三出てきてるけど)
APDUは、アプリケーションレベルのパケットをNFC ReaderとHCEサービスの間で交換するんだよ。アプリケーションレベルのプロトコルは、半二重通信(双方向で通信できるんだけど、片方が通信してる間は通信経路がブロックされるよっていう感じの通信経路)なので、APDUを送ったら待ちが必要なんだよ。

ISO/IEC 7816-4には、複数の論理チャンネルが定義されてるので、分かれた論理チャンネルを使ってAPDUをパラレルにやり取りすることはできるんだけど、
AndroidのHCEの実装では、一つの論理チャンネルししかサポートしてないのです。そんなわけで、シングルスレッドのAPDUのやり取りになりますです。

前に書いた通り、Androidでは、AIDを使って、反応してるReaderがどのHCEサービスと会話したいかを決めるって話したよね。
普通一番初めにReaderから端末に送信されるAPDUは、「SELECT AID」のAPDUだよ。このAPDUは、Readerが誰と会話したいかを決めるAIDを持っているのだ。
Androidは、APDUの中からAIDを探してどのHCEサービスが必要なのかを解決するよ。その後、サービスにAPDUを転送するのだ。

processCommandApdu()でAPDUのレスポンスのバイト配列を受け取れるよ。このメソッドは普通ブロックしちゃいけないアプリケーションのメインスレッドで呼ばれるから注意な。
そんなわけで、なんか処理が出来なかったりAPDUのレスポンスがすぐに返せない場合は、nullが返ってくるよ。まあ、そのあと別のスレッドで必要な処理をやることはできるけどね。
んで、HostApduServiceに定義されてるsendResponseApdu()を使うと、全部処理を終えた後にレスポンスを返すことができるよ

Androidは次の状態になるまでは、APDUをサービスに送り続けるよー

  • NFC Readerから、OSが別サービスと判断した「SELECT AID」のAPDUが来た場合
  • NFC Readerとデバイスのリンクが切れた場合

上の二つのケースが発生した場合は、onDeactivated()を実装したメソッドが呼ばれるよ。どっちが発生したかを示したArgumentと共にね。
既存のReaderインフラで動かす場合は、HCEサービスの中で対象のReaderが要求する既存のアプリケーションレベルのプロトコルを実装しとかないとダメだよー

もし君が支配できる新しいReaderインフラを作る場合(!)、君はAPDUシーケンスとプロトコルを定義しなきゃいけないぞ。
普通は、APDUの量と、交換する時に必要なデータサイズとかを制限するかな。NFCリーダーと端末でデータのやりとりをするので、短い時間だけ接続を保持しておくことを確認するよ。
ちなみに普通の上限は300ミリ秒で1kb程度だよ

Service manifest declaration and AID registration

サービスを使う時はManifestに宣言を書かなきゃならないんだけど、そのほかにも追加でちょっと宣言を書く必要があるよ

まずは、HCEサービスがHostApduServiceインターフェースを実装してる場合、intent-filterに「SERVICE_INTERFACE」アクションを追加しなきゃならないのだよ

さらにサービスがAIDグループを必要とする場合は、meta-dataタグを書いてあげないとダメですよ。
中のデータは、HCEサービスの細かい情報をXMLリソースとして別に書いておきましょう。

最後に、android:exported属性をtrueに設定します。これは外部アプリケーションからサービスがバインドされることを示してますよ。
んで、android.permission.BIND_NFC_SERVICE パーミッションをサービスの宣言に追加します。
こっちは、android.permission.BIND_INFO_SERVICE を持っているアプリケーションからのみBind可能なことを強制しますよーということ。
android.permission.BIND_NFC_SERVICE はシステムのパーミッションであるため、Android OSからサービスにBind可能できることを強制するよ
HostApduService Manifestのサンプルはこちら

<serviceandroid:name=".MyHostApduService"android:exported="true"
         android:permission="android.permission.BIND_NFC_SERVICE">
    <intent-filter>
        <actionandroid:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
    </intent-filter>
    <meta-dataandroid:name="android.nfc.cardemulation.host_apdu_service"
               android:resource="@xml/apduservice"/>
</service>

メタデータタグに設定されてるadpuservice.xmlファイルはこんな感じで書こう。一つのAIDグループの下に二つのAIDを所有してるサンプルはこちら。

<host-apdu-servicexmlns:android="http://schemas.android.com/apk/res/android"
           android:description="@string/servicedesc" 
           android:requireDeviceUnlock="false">
    <aid-groupandroid:description="@string/aiddescription" 
               android:category="other">
        <aid-filterandroid:name="F0010203040506"/>
        <aid-filterandroid:name="F0394148148100"/>
    </aid-group>
</host-apdu-service>

host-apdu-serviceタグの中には、android:description 属性が必要で、ユーザーに分かりやすい説明を入れましょう。UIに表示するかもしれないし。
requireDeviceUnlock 属性はサービスがAPDUをゴニョゴニョする前にデバイスのロックをはずすことができるので、それを指定できますよっと。

最後に重要なこと。
HCEサービスを使うアプリケーションは NFC Permission を必要とするからね。

AID Conflict Resolution

複数の HostApduService を一つのデバイスにインストールした場合、同じAIDがいくつかのサービスに登録されてることもあるよ。
Androidは、AIDの競合が起きた時にどうするか?カテゴリには、それぞれAIDの競合を解決するポリシーがあるので、まずはAIDがどのカテゴリに属するかを判定するよ。競合の解決方法はカテゴリに依存するんだよ。

例えば、支払系のカテゴリの場合、ユーザーはデフォルトでどのサービスを起動するかをUIを通して、Androidに設定しておくことができるんだ。
他のカテゴリの場合は、競合が起きた時にユーザーに確認をお願いするポリシーになってるよ。競合解消ポリシーは、getSelectionModeForCategory() で取得できるのでReferenceを見てね

Checking if your services is the default

アプリケーションは、カテゴリごとにデフォルトで起動するサービスをチェックすることができるよ。 isDeafultServiceForCategory(ComponentName, String) APIを使って。
もし作ったサービスがデフォルトになってなかった場合、デフォルトにすることを要求することができるよ。ACTION_CHANGE_DEFAULT を見といてね

Payment Application

Androidは、支払系のカテゴリを持ったAIDグループをすでに宣言してあるHCEサービスを含むので、支払系のアプリケーションを作れるよ。Android 4.4のリリース時に、設定の一番上のリストに「Tap & Pay」が追加されてるっしょ。これ、全部の支払系アプリケーションの列挙になってるんよ
ここで、支払アプリケーションをデフォルトで起動するかを設定することができますよ

Required assets for payment applications

もっとUX的にビジュアルを提供したい場合は、HCE支払アプリケーションは、サービスバナーと呼ばれるアセットを追加することができるよ

このアセットは260x96 dpというサイズで作らないといかんのです。 指定する場所は host-apdu-serviceタグの中の android:adpuServiceBanner 属性です。ここにdrawableリソースを指定します。こんな感じ

<host-apdu-servicexmlns:android="http://schemas.android.com/apk/res/android"
        android:description="@string/servicedesc" 
        android:requireDeviceUnlock="false"
        android:apduServiceBanner="@drawable/my_banner">
    <aid-groupandroid:description="@string/aiddescription"
               android:category="payment">
        <aid-filterandroid:name="F0010203040506"/>
        <aid-filterandroid:name="F0394148148100"/>
    </aid-group>
</host-apdu-service>

Screen Off and Lock-screen Behavier

今のAndroidの実装だと、NFCコントローラとアプリケーション処理はスクリーンをOffにした時は、完全に止まっちゃうんです。
だからHCEサービスもスクリーンOff時には動作しないよ。

でもHCEサービスはスクリーンロックしてる状態からも機能を実行できるよ。これは、 host-adpu-service タグの android:requireDeviceUnlock 属性でコントロールできます。
これを設定すると端末がロックしてても動くことができます

android:requireDeviceUnlock 属性が true に設定されてる場合は、Androidは、NFCリーダーに端末をタップするとAIDによって解決されたサービスを選択し、ユーザーにUnlockするように促します。
アンロック後、再度タップしてねーというDialogが表示され、Transactionが完了します。
これはユーザーがロック解除した時に、NFCリーダーから離れてしまってる可能性があるので、この処理が必要です

Coexistence with Secure Element Cards

Secure Elementのカードエミュレーションに興味のある開発者さんのためのセクションですよー。AndroidのHCE実装は、複数のカードエミュレーション(Secure Elementを使ったものを含む)の実装を共存できるようになってます。

AndroidはSecure Element自身とと直接やりとりをするAPIを提供しませんよ

複数のカードエミュレーションの共存はAIDルーティングと呼ばれるものをベースに実現されてます。NFCコントローラーは、有限のルーティングリストのルーティングテーブルを持ってます。
それぞれのルーティングルールは、AIDと宛先に基づいてます。宛先はホストCPU(Androidアプリケーションが実行されてるやつね)か接続してるSecure Elementかのどっちになってます

NFC ReaderがAID選択のAPDUを送信すると、NFCコントローラはAPDUを解析して、ルーティングテーブルのFOOTNOTEを参考にもそもそ探します。もしビンゴだった場合、そのAPDUは全てのAPDUとそれに紐づくものを全部指定された宛先にお届けします。他のAID選択を受け取ると、NFCリンクは壊れるよ

:ISO/IEC 7816-4はAIDの部分一致も定義してるよ。でもAndroidは今はサポートしてないんだ、ごめーん

このアーキテクチャは図4に書いたから見てね

図4へLink

NFCコントローラは、一般的にAPDUのデフォルトのルートを持ってるよ。AIDがルーティングテーブルから見つからなかったときは、デフォルトのルートを使うんだ。
Android 4.4 で始めると、デフォルトのルートは、ホストCPUに設定しておく必要があるよ。これは、一般的にルーティングテーブルにはSecure Elementへの経路しか含まれてないからねー

HCEサービスを実装したり、Secure Elementを使ったAndroidアプリケーションは、ルーティングテーブルは気に掛けなくて大丈夫だよー。Androidが自動的にやってくれるから。
Androidは単にどのAIDがHCEサービスで処理することができるのか、どれがSecure Elementで処理することができるのかを知る必要があるだけなんだ。「どのサービスがインストールされ」て、「ユーザーがどれを設定しているか」から、自動的にルーティングテーブルは作られるよ。

HCEサービスの宣言の仕方については既に話したから、ここからはSecure Elementを使ったカードエミュレーションのやり方を教えるよ!

Secure element AID registration

Secure Elementを使ったカードエミュレーションは、off host service とか呼ばれてるものをManifestに宣言することで使えるよ。
この宣言は、HCEサービスと似たような宣言になってます。下の要素だけは例外ね

  • intent-filterに使用されるアクションは SERVICE_INTERFACE でなきゃダメだよ
  • meta-data の名前属性には SERVICE_META_DATA を指定する必要があるよ
  • meta-data XMLファイルには、 offhost-apdu-service のルートタグを使用しなきゃダメだよ
<serviceandroid:name=".MyOffHostApduService"android:exported="true"
         android:permission="android.permission.BIND_NFC_SERVICE">
    <intent-filter>
        <actionandroid:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/>
    </intent-filter>
    <meta-dataandroid:name="android.nfc.cardemulation.off_host_apdu_ervice"
               android:resource="@xml/apduservice"/>
</service>

二つのAIDを登録するのに対応した apduservice.xml サンプルはこちら

<offhost-apdu-servicexmlns:android="http://schemas.android.com/apk/res/android"
           android:description="@string/servicedesc">
    <aid-groupandroid:description="@string/subscription"android:category="other">
        <aid-filterandroid:name="F0010203040506"/>
        <aid-filterandroid:name="F0394148148100"/>
    </aid-group>
</offhost-apdu-service>

android:requireDeviceUnlock 属性は off host serviceでは適用されないよ。
というのも、ホストCPUはSecure Elementを利用する場合はトランザクションを関われないので。だから、デバイスがロックしてる時は、Secure Elementのトランザクションを妨害できないです
android:apduServiceBanner 属性は、デフォルト支払アプリケーションとして選択するために、off host Service では必ず必要だから設定してね

Off host service invocation

Androidは、サービスをスタートしたりBindしたりすることはできないよ。off host と宣言されてるやつはね。これはトランザクションが、AndroidのサービスではなくSecure Elementで実行されていることが関係してるよ。サービスの宣言は、Secure Element上にAIDを登録することをアプリケーションに許可するだけです

HCE and Security

HCEアーキテクチャは、セキュリティのコアの一つを提供するよ。なぜなら、BIND_NFC_SERVICE のシステムパーミッションによって守られてるからね。OSだけがサービスと会話できるよ。
これによって、サービスが受け取るどんなAPDUもNFCコントローラーからOSによって受信されたAPDUであることを保証するんだ

セキュリティのコアの残りは、データを取得する場所です。NFCリーダーに返送しているところから。
これらは意図的にHCEの設計で切り離されてます。データがどこから来たかは気にしないし、NFCコントローラーへの転送が安全に行われてるのを確認します

HCEサービスから送られてきたデータを安全に格納したり検索したりするために、例えば他のアプリから見えないように分離するための、Andoidのアプリケーションサンドボックスに頼ることができるよ。詳しくはSecurity Tipsを読んでな

Protocol parameters and details

このセクションでは、競合回避やNFCプロトコルのアクティベーションの段階で使うHCEデバイスのプロトコルパラメータを理解したい開発者向けですよ(要は興味がない人は読まなくてOK)
AndroidのHCEデバイスと互換性のあるReaderインフラを構築するのが許されてますよ

Nfc-A (ISO/IEC 14443 type A) protocol anti-collision and activation

NFC-Aをプロトコルを有効にする場合は、複数のフレームを交換しなきゃだめよ。

まず最初の交換はHCEデバイスの現在のUIDです。HCEデバイスはランダムなUIDを持ってると仮定するよ。
これはデバイスをタップするたびにReaderに通知されるUIDがランダムに作り替えられることを意味してるよ。これは、NFCリーダーはHCEデバイスのUIDに依存した認証や照会を使っちゃだめだよってこと。

NFCリーダーは"SEL_REQ" コマンドを送信することで、HCEデバイスを選択することができるよ。HCEデバイスの応答の"SEL_RES"には、少なくとも6ビット目に「0x20」がセットされてると、これはそのデバイスがISO-DEP をサポートしてるよってことを示してるよ。ちなみに、"SEL_RES"に他のビットが同じように6番目に設定されていた場合、例えばそれはNFC-DEP(p2p)をプロトコルをサポートしてるよ、ということを示してるかもね。
他のビットが設定されてることもあるかもしれないので、HCEデバイスと会話をしたい場合は6ビット目だけを明示的にチェックしておいた方が良いよ。"SEL_RES"の6ビット目が0x20と完全一致してるという比較は止めた方がいいかもね。

ISO-DEP activation

NFC-Aプロトコルを有効にした後、ISO-DEPプロトコル有効化の初期処理をNFCリーダーで行うよ。”RATS”(Request for Answer To Select)コマンドを送信します。
RATSのレスポンス(ATS)は、NFCコントローラによって生成され、HCEサービスは関係しないよ。でも、HCEの実装においてATSレスポンスはNFCフォーラムの要件を満たすために必要なんだ。だから、NFCリーダーはNFCの要件に従って、設定されたパラメータをカウントすることができるよ。例えどんなHCE端末でもね。

このセクションでは、HCEデバイスのNFCコントローラーが提供するATSレスポンスのそれぞれのバイトデータについて、もっと細かい詳細を解説するよぅ

  • TL: ATSレスポンスの長さを示すよ。20倍と以上の長さはNGさ
  • T0: ビット5, 6, 7はHCEデバイスには設定されなきゃダメ。TA(1), TB(1), TC(1)は、ATSレスポンスを含むことを示してるよ。1から4ビットはFSCIのこと。フレームサイズの最大値を示します。HCEデバイスのFSCIの値は0h~8hの間!
  • T(A)1: Readerとエミュレータとの間のビットレートを定義してるよ。これは非対称でもOKなのだ。HCEデバイスのためのビットレートの要件とか保証は特にないっすよ
  • T(B)1: ビット1から4は、Start-up Frame Guard time Integer(SFGI)を示すよ。HCEデバイスにおいて、SFGIは8h以下じゃないとダメです。5から8ビットは、Frame Waiting time Integer(FWI)を示してて、Frame Waiting Time(FWT)のコードが入ってるよ。HCEデバイスにおいて、FWIは8h以下じゃないとダメです。
  • T(C)1: ビット5が示すのは、"Advanced Protocol features"をサポートしてること。HCEデバイスは、"Advanced Protocol features"をサポートしてるし、してないかもしれないし。ビット2は、DIDをサポートしてることを示すよ。これもサポートしてるかもしれないし、してないかもしれない。ビット1が示すのは、NADのサポート状況。これもサポートしてるかどうか。ビットが1か0で設定されます
  • Historical bytes: HCEデバイスは、15のhistoricalバイトまで戻しても良いけど、ReaderはHCEサービスがhistoricalバイトを持ってることを前提にしない方がいいよ

ちなみに、多くのHCEデバイスはたいていEMVCoの決済ネットワークの要求してる「非接触通信プロトコル」の仕様に準じています。特に

  • T0のFSCIは、2hから8hの間
  • T(A)1 は必ず0x80が設定されなきゃいけないです。これは、106kbit/sのビットレートをサポートしてることを示してます。それとReaderとエミュレータ間の非対称なビットレートはサポートしてません!
  • T(B)1 のFWIは必ず7h以下であること

APDUデータ交換

前に言ったように論理チャンネルは一個しかサポートしてないから複数の論理チャンネルとか選択しようとしても動かないからね

おわりに

薄く分かった気がしますが、後半は専門的すぎてちんぷんかんぷんです。
正直用語も怪しいのでちゃんとNFC-Forumの仕様書を見た方が早いかもしれません。
ちなみに、この公式ドキュメント通りに実装しても動かないんだとか。ブライテクノBlog - Android 4.4 NFCホストカードエミュレーションへの道 (導入編)

Appleを見習って公式ドキュメントのローカライズしてください(涙)
はっ、これはまさか、お前ら英語できないんだから、これで勉強しろよというGoogle先生の厳しくも優しい試練なのでしょうか(錯乱)

それでは皆様ごきげんよう

参照