自分のスマホに図書館カードを苦労して登録するまでの過程
(iliana.fyi)- 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.png、logo@2x.png、logo@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 pass、coupon、event ticket、generic、store cardのいずれかを選ぶ必要がある - 大きな横長画像を入れるには、
strip画像をサポートするレイアウトが必要だった- 候補は
coupon、event ticket、store card - 物理的な図書館カードに最も近い形としてstore cardを選んだ
- 候補は
pass.jsonには次の値を入れるpassTypeIdentifierteamIdentifierformatVersionserialNumberorganizationNamelogoTextdescriptionstoreCardbackgroundColorforegroundColorsharingProhibited
passTypeIdentifierとteamIdentifierは、Apple証明書subjectのUID、OUフィールドとそれぞれ一致していなければならない- 同じ
passTypeIdentifierで作る各パスには一意のserialNumberが必要 - 画像は用途ごとに用意した
icon.pngは必須だが、パス自体には表示されないlogo.pngは左上に表示されるロゴ- 40×40の
logo.png、80×80のlogo@2x.png、120×120のlogo@3x.pngを生成した logo@3x.pngをicon.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:numberlabel:CARD NUMBERvalue: カード番号
- iOSはバーコード付きのパスを選ぶと、スキャナーを助けるため画面を明るくする
- 画像として入れたCodabarだけでは、iOSはバーコードがあると判断しない
pass.jsonの最上位に空のバーコード項目を指定してこの動作を回避したmessageは空文字列formatはPKBarcodeFormatCode128messageEncodingはiso-8859-1
- この方法により、パス下部にバーコードを表示しないまま、iPhoneはバーコード付きパスのように画面を明るくした
署名とパッケージ化
- すべてのファイルを用意したら、
manifest.jsonを生成する必要がある manifest.jsonは、ファイル名をキー、SHA-1チェックサムを値に持つオブジェクトsha1sumとjqを組み合わせて、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.jsonmanifest.jsonsignature
テスト結果と惜しい点
- macOSにはパスプレビュー用ツールがあり、パスが有効で概ね正しく表示されるか確認できる
- 無効な場合は
Console.appでエラーを探せる - プレビューツールは100%正確ではないが、iCloud経由でiPhoneへ送るボタンがある
- 完成したパスは実際の図書館ではまだテストしていない
- 別のバーコードスキャナーでは、スマホ画面上のバーコードを実際のプラスチックカードと同様に読み取れた
- ただし、画面輝度を最大まで上げたときによく読めた
- 空バーコード回避策で上がる明るさよりもさらに明るく設定した
- パス仕様が10年間ほとんど変わっていない点は良いが、PNGとJSONから成る無害なパスに署名するために年99ドルのApple Developerメンバーシップが必要なのは惜しい
- 一部のパス機能に署名が必要なのは理解できるが、この作業でやったことに署名は不要であるべきだと感じる
- AppleがWalletにCodabar対応を追加すれば、図書館システム全体のスキャナーがCode 128に対応しているか監査しなくても、デジタル図書館カードをサポートできる
1件のコメント
Hacker News の意見
次は ORCA カードも取り上げてほしい。Seattle は米国の技術中心地の一つなのに、地下鉄・公共交通そのものもいまいちなだけでなく、私が使ったことのあるほぼすべての主要都市の公共交通より技術的にも遅れている
欧州のどの都市でも、CDMX や Denver と比べても Seattle よりずっと進んでいて、個人的には Denver が一番よかった気がする
ORCA を運営する機関の一つが、Android アプリに NFC 対応を追加するというブログ記事を出したこともあったが、その記事はもう消えていて、数年たった今もその機能はない
同じカードさえ使えば、乗り換えや数日分のパスのようなものもシームレスに処理し、一定期間内に一定回数以上乗るとそれ以上請求しない、といった形で正しい運賃が自動適用される
オフィスのドアが NFC カードで開くので iOS で可能か調べたが、記憶では Apple は通常の PassKit より NFC ハードウェアを厳しく管理していて、一般アプリでは難しいと思う
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 の認証局が知っている鍵で署名されていなければならない、というのがなぜ筋が通るのか分からない。こうした更新用途なら、すでに十分サポートされている HTTPS がある
Apple はパスが更新時だけでなく、端末上でオフラインでも検証されることを望んでいるのかもしれないが、それでも変だ。悪意ある者はパスを更新する代わりに、丸ごと差し替えることもできる
Apple 開発者アカウントと紐づけることが更新問題の助けになるのかはまったく分からない
Apple が承認した主体でなければならないという要件の理由として思いつくのは、偽チケット販売くらいだ。「コンサート X のチケット」という pkpass ファイルが本物かどうかは、その要件があってもなくても知る方法がない
詐欺の通報が入ったらその開発者アカウントを停止して対応しようとしているのかもしれないが、これも解決策には思えない。開発者アカウントの費用は、摘発される前に詐欺で稼げる金額よりはるかに安い可能性が高い
図書館カード番号が入った バーコード PNGを自分宛てにメールで送っておき、キオスクの前で Photos や Gmail アプリで開いて使っている
MakePass アプリで StarBucks の会員コードを入れたパスを作ったところ、よく行く店舗の近くに行くと、スマホがロック画面で StarBucks パスを自動提案してくれる
イベントチケットも同様に場所と時間範囲を指定しておけば、現地に着いたときにパスを探し回る必要なく自動で提案される。イベントが終わるとそれ以上提案せず、「Expired Passes」セクションに移してメイン画面を散らかさない
MakePass: https://pvieito.com
良いブログ記事だったが、最後が「まだ実際の図書館でこのパスをテストしてはいない」で終わっていた
趣味のプロジェクトだというのは分かるが、成果物を共有する前に最終的な解決策をテストするのに必要な 10 分をなぜ使わなかったのか分からない
バーコードを作るときは、個人的には PostScript製のバーコード生成ツールを好んで使っています
https://bwipp.terryburton.co.uk/
この問題は、図書館カードの写真を撮ることで解決しました。本を借りるときに写真を開いてスキャナーにかざせばよいです
携帯電話の写真アルバムにすべての身分証を保存しています
Androidで似たことをしたいなら、Google PlayとF-Droidにある Catima があります。複数種類のバーコードに対応しています
https://catima.app/
安価なレーザー式の一次元バーコードスキャナーは、画面上のバーコードを読み取れません。eInkなら可能かもしれません
よく行くスーパーマーケットの会員カードがバーコード式で不便なのですが、幸いバーコードリーダーはキーボードをエミュレートするので、普通にキーボードでコードを入力できます
カメラベースのスキャナーは、バーコードの照明が周囲光であれ内蔵LEDであれバックライト画面であれ気にしませんが、レーザーベースのシステムは自分の光の反射に依存するため、アクティブなバックライト画面ではまったく動作しません
e-inkやパッシブLCDディスプレイでは動作するのか気になります
見た目は単純な赤い線の2Dタイプのように見えますが、やはりハードウェアが重要なようです
KCLSのアカウント番号は普通に暗記しました。覚えるのに30秒くらいで十分だと思いますし、人によるでしょうが、この方法のほうが速いと思います
その後はバーコードをスキャンする代わりにアカウント番号を入力すればよく、携帯電話を取り出して準備する時間より短く済む可能性が高いです
SPLも同じ方式かはわかりません。Seattleには住んでいないので確認はできません