2 ポイント 投稿者 GN⁺ 2023-10-29 | 1件のコメント | WhatsAppで共有
  • Seattle Public LibraryとKing County Library Systemがプラスチックカード削減を目指していたことから、iPhoneのWalletパスのJSON・画像・署名の構造を直接扱うことになった
  • WalletはQR code、PDF417、Aztec Code、Code 128しかサポートしておらず、図書館カードのCodabarを標準バーコードとして入れられなかったため、実際にスキャンするバーコードは画像で回避した
  • パスはAppleが信頼する鍵で署名する必要があったが、年99ドルのApple Developerメンバーシップの代わりに、オフライン生成が可能なiOSパスアプリから証明書と鍵を見つけて使った
  • storeCardレイアウト、解像度ごとのロゴ、Codabarのstrip.png、空のPKBarcodeFormatCode128項目を組み合わせ、画面には画像バーコードを見せつつiOSの画面輝度上昇動作を生かした
  • 完成した.pkpassは、manifest生成、openssl smime署名、ZIPパッケージ化で作成でき、実際の図書館でのテスト前ではあるものの、別のスキャナーではスマホ画面上のバーコードを読み取れた

目標: プラスチックカードなしで図書館バーコードを使う

  • Seattle Public LibraryとKing County Library Systemは、貸出アカウントにひも付いたバーコード印刷済みプラスチックカードを発行している
  • 2023年時点でSeattleの図書館機能の大半はセルフサービスで、通常は図書館バーコードを手入力できる
  • それでもバーコードをすぐ見せられるほうがずっと便利なので、ほとんど使わないプラスチックカードを財布に入れず、iPhoneのWalletアプリに入れることが目標だった

WalletパスはJSON・画像・署名で構成される

  • Walletアプリは現在、決済カード、ID、鍵などを扱うが、2012年にPassbookが登場した当時は「パス」だけを管理していた
  • Appleのパス文書によれば、パスはメールやWebで配布できる自己完結型ZIPファイルであり、中にはJSONとPNGが入っている
  • .pkpassファイルの中核構成は単純
    • pass.json: 画像ではないパス内容を記述する
    • manifest.json: 他のファイルのSHA-1チェックサム一覧
    • signature: manifest.json内容に対するS/MIME署名
    • サポートされる画像ファイル群

Codabar非対応が最初の障壁

  • Walletパスがサポートするバーコードは4種類だけ
    • QR code
    • PDF417
    • Aztec Code
    • Code 128
  • 図書館カードは、図書館で広く使われるCodabar形式だった
  • 図書館のバーコードスキャナーが別の線形バーコードをサポートしている可能性はあるが、全拠点で確実に動くと見なせる形式はCodabarだけだった
  • 結局、Walletの標準バーコード機能の代わりに、スキャン可能なCodabar画像を作ってパスに入れる必要があった

パス署名証明書の問題

  • Walletパスには暗号署名が必要で、Appleの認証局のいずれかが信頼する鍵で署名されていなければならない
  • 航空券のゲート変更や座席変更のように、ベンダーがパスを自動更新する用途であれば、署名にはある程度の妥当性がある
  • Apple開発者ならパス署名鍵を比較的簡単に取得できるが、この作業のために年99ドルを払うつもりはなかった

既存のパス生成アプリが合わなかった理由

  • すでにApple開発者である人たちが作ったさまざまなパスデザインアプリはあったが、必要なレベルの制御を提供していなかった
  • ロゴサイズの制御

    • Appleはパス左上のロゴに最大50 device-independent pixelsの高さを許可している
    • テキスト右側に付く正方形ロゴは約40pxの高さが最適だった
    • 開発者はlogo.pnglogo@2x.pnglogo@3x.pngをそれぞれ用意する必要があるが、アプリは通常、単一ロゴの選択しか許さず、スケール制御を提供しない
  • 画面輝度の動作

    • 通常の対応バーコードがあるパスを開くと、スキャナー向けのコントラスト確保のため画面が明るくなる
    • 実際には動作しないバーコードを表示せずにiOSに画面を明るくさせるには、pass.jsonを直接扱う必要があった
    • 無料でダウンロードできるアプリでも、Walletに保存できるパス数を制限し、支払いを要求する場合があった

オフライン生成アプリから署名鍵を見つける

  • 複数の無料スターター向けパス生成アプリをダウンロードし、オフラインでもパス生成ができるか確認した
  • 機内モードかつWi-Fi無効の状態でテストした結果、少なくとも1つのアプリはオフラインでパスを作成できた
  • そのアプリの具体名は、鍵が失効させられるのを避けるため明かしていない
  • 手順は予想より単純だった
    • Apple silicon MacではiOSアプリを実行できるので、Macにアプリをダウンロードした
    • ラップされたiOSアプリバンドルの中から目立つ.p12ファイルを探した
    • PKCS#12ファイルにはインポート用パスワードが必要なので、メインバイナリにstringsを実行して、それらしい文字列を探した
  • openssl pkcs12で証明書情報を確認すると、issuerはApple Worldwide Developer Relations Certification Authorityと表示された
  • 証明書チェーンも必要で、中間証明書はアプリバンドル内にある可能性があり、X.509拡張フィールド内のURLからダウンロードすることもできる

パスレイアウトの構成

  • パススタイルboarding passcouponevent ticketgenericstore cardのいずれかを選ぶ必要がある
  • 大きな横長画像を入れるには、strip画像をサポートするレイアウトが必要だった
    • 候補はcouponevent ticketstore card
    • 物理的な図書館カードに最も近い形としてstore cardを選んだ
  • pass.jsonには次の値を入れる
    • passTypeIdentifier
    • teamIdentifier
    • formatVersion
    • serialNumber
    • organizationName
    • logoText
    • description
    • storeCard
    • backgroundColor
    • foregroundColor
    • sharingProhibited
  • passTypeIdentifierteamIdentifierは、Apple証明書subjectのUIDOUフィールドとそれぞれ一致していなければならない
  • 同じpassTypeIdentifierで作る各パスには一意のserialNumberが必要
  • 画像は用途ごとに用意した
    • icon.pngは必須だが、パス自体には表示されない
    • logo.pngは左上に表示されるロゴ
    • 40×40のlogo.png、80×80のlogo@2x.png、120×120のlogo@3x.pngを生成した
    • logo@3x.pngicon.pngにコピーした
    • 事前生成したバーコードはstrip.pngに入れた

Codabarバーコード画像を作る

  • iOSはstrip.pngを端末画面のボックスに合わせてスケール・クロップするため、strip.pngは解像度別に3種類作る必要がなかった
  • 2つの図書館カードはいずれも開始・終了シンボルとしてAとDを使っていた
  • 開始・終了シンボルは、バーコードスキャナーがあれば最も簡単に確認でき、なければWikipediaのCodabarエンコーディング表と目視比較できる
  • オンラインですぐ使えるCodabar生成器は多くなかったが、形式自体は自前で実装しやすかった
  • プロトタイプでは、Rust向けBarcodersライブラリでSVGを作り、SVGを調整してからPNGへ書き出した
  • 最終レイアウトは、スキャンのしやすさとiOSでの縮小表示を考慮して決めた
    • バーコードの高さは、開始・終了シンボルを含む全シンボル数の2倍単位に設定した
    • たとえば13桁のバーコード番号は15シンボルになるので、高さを30単位にした
    • 開始前と終了後にはそれぞれ15単位のquiet spaceを置いた
    • バーコードの上下にはそれぞれ50単位のパディングを置いた
    • 各単位は8ピクセルに拡大し、iOSが常に画像を縮小するようにした
    • 15シンボルのバーコード例では、最終画像の高さは1040ピクセル、バーコード自体の高さは240ピクセルになった
  • このレイアウトでCodabarのBMPを作り、sipsでPNGに変換する69行のシェルスクリプトを書いた
  • スクリプトの出力をstrip.pngとして保存すれば、パスに入れるバーコード画像の準備が整う

バーコード番号表示と画面輝度の回避策

  • バーコード番号はsecondaryFieldsでバーコードの下に表示する
    • key: number
    • label: CARD NUMBER
    • value: カード番号
  • iOSはバーコード付きのパスを選ぶと、スキャナーを助けるため画面を明るくする
  • 画像として入れたCodabarだけでは、iOSはバーコードがあると判断しない
  • pass.jsonの最上位に空のバーコード項目を指定してこの動作を回避した
    • messageは空文字列
    • formatPKBarcodeFormatCode128
    • messageEncodingiso-8859-1
  • この方法により、パス下部にバーコードを表示しないまま、iPhoneはバーコード付きパスのように画面を明るくした

署名とパッケージ化

  • すべてのファイルを用意したら、manifest.jsonを生成する必要がある
  • manifest.jsonは、ファイル名をキー、SHA-1チェックサムを値に持つオブジェクト
  • sha1sumjqを組み合わせて、PNGファイル群とpass.jsonのmanifestを生成した
  • manifestの署名はopenssl smimeコマンドで行う
    • signer証明書
    • private key
    • Apple WWDR中間証明書
    • 入力manifest.json
    • 出力signature
  • openssl smime-attimeオプションで、望む署名時刻を指定できる
    • オプション値はUNIX epoch
    • Appleから受け取った、または見つけた証明書が期限切れでも、その時刻で署名できる
  • 最後に次のファイルをZIPにまとめて.pkpassを作る
    • PNG画像群
    • pass.json
    • manifest.json
    • signature

テスト結果と惜しい点

  • macOSにはパスプレビュー用ツールがあり、パスが有効で概ね正しく表示されるか確認できる
  • 無効な場合はConsole.appでエラーを探せる
  • プレビューツールは100%正確ではないが、iCloud経由でiPhoneへ送るボタンがある
  • 完成したパスは実際の図書館ではまだテストしていない
  • 別のバーコードスキャナーでは、スマホ画面上のバーコードを実際のプラスチックカードと同様に読み取れた
    • ただし、画面輝度を最大まで上げたときによく読めた
    • 空バーコード回避策で上がる明るさよりもさらに明るく設定した
  • パス仕様が10年間ほとんど変わっていない点は良いが、PNGとJSONから成る無害なパスに署名するために年99ドルのApple Developerメンバーシップが必要なのは惜しい
  • 一部のパス機能に署名が必要なのは理解できるが、この作業でやったことに署名は不要であるべきだと感じる
  • AppleがWalletにCodabar対応を追加すれば、図書館システム全体のスキャナーがCode 128に対応しているか監査しなくても、デジタル図書館カードをサポートできる

1件のコメント

 
GN⁺ 2023-10-29
Hacker News の意見
  • 次は ORCA カードも取り上げてほしい。Seattle は米国の技術中心地の一つなのに、地下鉄・公共交通そのものもいまいちなだけでなく、私が使ったことのあるほぼすべての主要都市の公共交通より技術的にも遅れている
    欧州のどの都市でも、CDMX や Denver と比べても Seattle よりずっと進んでいて、個人的には Denver が一番よかった気がする
    ORCA を運営する機関の一つが、Android アプリに NFC 対応を追加するというブログ記事を出したこともあったが、その記事はもう消えていて、数年たった今もその機能はない

    • Google Wallet がまもなく対応する予定だと数日前に投稿されていた: https://blog.google/products/google-pay/commute-around-the-w...
    • うちの地域の交通システムは最近 タップ決済を追加し、どのクレジットカードでも、Apple Pay/Google Pay でも使えるようになった
      同じカードさえ使えば、乗り換えや数日分のパスのようなものもシームレスに処理し、一定期間内に一定回数以上乗るとそれ以上請求しない、といった形で正しい運賃が自動適用される
    • ほとんどの公共交通システムは 無料乗車に切り替えても失う財源は一部にすぎないのに、それに比べて運賃徴収システムを維持するコストがどれほどかかっているのか気になる
    • 関連して、root 化した Android で NFC カードを複製する方法はないのだろうか?
      オフィスのドアが NFC カードで開くので iOS で可能か調べたが、記憶では Apple は通常の PassKit より NFC ハードウェアを厳しく管理していて、一般アプリでは難しいと思う
    • https://info.myorca.com/news/can-i-use-my-phone-to-pay-for-a...
      ORCA に 2023 年中にタップ決済が来ると言っていたので、本当になるにはあと 2 か月ほどしかない
      Google が Google Wallet で ORCA をまもなくサポートすると書いていたので、楽観的に見ている
  • Android ユーザーで、Google Wallet が Apple Passbook のように任意のバーコードを入れられないのが嫌なら、F-Droid に Loyalty Card Keychain という素晴らしいアプリがある: https://f-droid.org/en/packages/protect.card_locker/
    数字を直接入力するか既存のバーコードをスキャンし、Codabar を含む複数のバーコード形式の中から一つを選んで保存できる。アプリのメイン画面で項目をタップすると生成されたバーコードを表示し、画面の明るさも上げてくれる
    バーコードを表示する以外はたいしたことをしないからか、アプリも非常に速く開く。機能は一つだけだが、私のお気に入りアプリかもしれない
    最近は Catima を使えと言われるようだが、少し使ってみたところ同じくらいシンプルで、同じコードベースを基にしているように見える

    • Apple では Pass4Wallet で「カード」を作成したあと Apple Wallet に取り込んで使っている
      不便ではあるが目的は達成できるし、プライバシーポリシーも良い
    • Google Wallet に好きなバーコードを入れられるアプリもある: https://play.google.com/store/apps/details?id=color.dev.com....
  • パスが暗号学的に署名されていて、Apple の認証局が知っている鍵で署名されていなければならない、というのがなぜ筋が通るのか分からない。こうした更新用途なら、すでに十分サポートされている HTTPS がある
    Apple はパスが更新時だけでなく、端末上でオフラインでも検証されることを望んでいるのかもしれないが、それでも変だ。悪意ある者はパスを更新する代わりに、丸ごと差し替えることもできる

    • 最初のパスが更新を許可する 公開鍵を指定するだけで十分だ
      Apple 開発者アカウントと紐づけることが更新問題の助けになるのかはまったく分からない
      Apple が承認した主体でなければならないという要件の理由として思いつくのは、偽チケット販売くらいだ。「コンサート X のチケット」という pkpass ファイルが本物かどうかは、その要件があってもなくても知る方法がない
      詐欺の通報が入ったらその開発者アカウントを停止して対応しようとしているのかもしれないが、これも解決策には思えない。開発者アカウントの費用は、摘発される前に詐欺で稼げる金額よりはるかに安い可能性が高い
  • 図書館カード番号が入った バーコード PNGを自分宛てにメールで送っておき、キオスクの前で Photos や Gmail アプリで開いて使っている

    • Wallet のパスは、そのパスが「関連あり」として表示される 場所と時間範囲を定義できる
      MakePass アプリで StarBucks の会員コードを入れたパスを作ったところ、よく行く店舗の近くに行くと、スマホがロック画面で StarBucks パスを自動提案してくれる
      イベントチケットも同様に場所と時間範囲を指定しておけば、現地に着いたときにパスを探し回る必要なく自動で提案される。イベントが終わるとそれ以上提案せず、「Expired Passes」セクションに移してメイン画面を散らかさない
      MakePass: https://pvieito.com
    • 記事をざっと読みながら、なぜ単に 写真を撮らなかったのかを探していた
  • 良いブログ記事だったが、最後が「まだ実際の図書館でこのパスをテストしてはいない」で終わっていた
    趣味のプロジェクトだというのは分かるが、成果物を共有する前に最終的な解決策をテストするのに必要な 10 分をなぜ使わなかったのか分からない

    • その部分のせいで記事全体の勢いが抜けた。「ランニング中の酸素損失に関する優れた科学理論があります。正直に言うと、まだジョギングを始めてはいませんが……」という感じだ
    • 結局はスキャンされるバーコードを表す ピクセルの集まりにすぎない。正しく表示されさえすれば、何が問題になり得るだろうか?
    • 図書館が土曜日は開いていないのかもしれない
  • バーコードを作るときは、個人的には PostScript製のバーコード生成ツールを好んで使っています
    https://bwipp.terryburton.co.uk/

  • この問題は、図書館カードの写真を撮ることで解決しました。本を借りるときに写真を開いてスキャナーにかざせばよいです

    • バーコード画像を位置情報に基づく通知に入れておいたところ、近所の図書館に行くと通知として表示されます
    • 静的なバーコードなら写真だけで十分です。Walletに入れるメリットはそれほど大きくありません
      携帯電話の写真アルバムにすべての身分証を保存しています
  • Androidで似たことをしたいなら、Google PlayとF-Droidにある Catima があります。複数種類のバーコードに対応しています
    https://catima.app/

    • Google PayやSamsung Payを使ってもよいです
  • 安価なレーザー式の一次元バーコードスキャナーは、画面上のバーコードを読み取れません。eInkなら可能かもしれません
    よく行くスーパーマーケットの会員カードがバーコード式で不便なのですが、幸いバーコードリーダーはキーボードをエミュレートするので、普通にキーボードでコードを入力できます

    • 私の理解では、古いシステムはデジタル画像センサーでスキャンするのではなく、回転するレーザービームと単純なフォトダイオードで、白黒のバーコード領域から反射される明るさの変化を読み取っているためです
      カメラベースのスキャナーは、バーコードの照明が周囲光であれ内蔵LEDであれバックライト画面であれ気にしませんが、レーザーベースのシステムは自分の光の反射に依存するため、アクティブなバックライト画面ではまったく動作しません
      e-inkやパッシブLCDディスプレイでは動作するのか気になります
    • Bluetooth MCUと E-inkディスプレイ で安価なバーコード表示器を作りましたが、一次元専用バーコードスキャナーではすべてうまく動作します
    • Costcoの携帯型スキャナーが、会員カードに非常に細く小さく印刷された会員バーコードをどれほど速く簡単に読み取るかを見るたびに感心します
      見た目は単純な赤い線の2Dタイプのように見えますが、やはりハードウェアが重要なようです
  • KCLSのアカウント番号は普通に暗記しました。覚えるのに30秒くらいで十分だと思いますし、人によるでしょうが、この方法のほうが速いと思います
    その後はバーコードをスキャンする代わりにアカウント番号を入力すればよく、携帯電話を取り出して準備する時間より短く済む可能性が高いです
    SPLも同じ方式かはわかりません。Seattleには住んでいないので確認はできません

    • 覚えたくないなら、好きなパスワードマネージャーに保存しておけばよいです