はじめに
Apple Walletのパス(チケットやポイントカード等)を作成するためのシステムを構築していると、思わぬところで「なぜこんなに複雑なのか」と頭を抱えることがあります。特に証明書周りのトラブルは、一見単純なのに解決に何時間もかかることも。
この記事では、Firebase Cloud Functionsを使ってApple Wallet用パス(.pkpass)を生成する際に直面した問題と、その原因究明・解決までの道のりを共有します。
実現したかったこと
- Firebase上で構築したWebアプリから、Apple WalletとGoogle Walletの両方のパスを発行
- フロントエンドでパスのデザインを設定し、サーバー側でパスを動的に生成
- ユーザーにパスのダウンロードURLを提供
環境
- Firebase Cloud Functions (Node.js 18)
- passkit-generator ライブラリ
- macOS (Sonoma/Ventura)
- Apple Developer Program アカウント
発行までの流れ(理想)
- Pass Type ID証明書をApple Developer Programで取得
- .pkpass形式のパスをNode.jsで生成
- Cloud Storageにアップロード
- ユーザーに署名付きURLを提供してダウンロード
実際に出くわした「Invalid PEM formatted message」エラー
しかし、実際にはこんなエラーに悩まされました:
Error generating Apple Pass: Error: Invalid PEM formatted message.
このエラーの原因究明と解決までの過程を紹介します。
Pass Type IDの証明書取得手順
まず前提として、Apple Wallet用パスを発行するには、証明書の取得から始める必要があります:
- Apple Developer Programにログイン
- 「Certificates, Identifiers & Profiles」セクションに移動
- 「Identifiers」→「Pass Type IDs」を選択
- 「+」ボタンからPass Type IDを新規登録(例:
pass.com.mycompany.myapp
) - 「Certificates」→「+」で新規証明書作成
- 「Pass Type ID Certificate」を選択
- KeychainからCSR(証明書署名リクエスト)を作成・アップロード
- 発行された証明書をダウンロードしてKeychain Accessにインストール
ここまでは比較的直感的ですが、実際のパス生成時に必要なものは:
- Pass Type ID証明書(.p12形式に変換)
- Apple WWDR中間証明書(.pem形式)
- Pass Type ID証明書のパスワード
証明書周りの複雑さ
1. .p12ファイルのエクスポート
Keychain AccessからPass Type ID証明書をエクスポートする際、次のような問題が潜んでいます:
- 証明書だけでなく秘密鍵も一緒に選択してエクスポートする必要がある
- 暗号化方式がデフォルトでRC2(40bit)という古い形式になる
- この暗号化形式はNode.js環境やOpenSSL 3系では扱えない
2. WWDR中間証明書
Apple Worldwide Developer Relations Certificate(WWDR証明書)も必要で:
- Appleのサイトからダウンロード
- G4証明書(最新)を使用する
- 空白行や余計な文字が混じると「Invalid PEM」エラーの原因になる
3. 証明書の形式と暗号化
最初に出会ったエラー「Invalid PEM formatted message」の原因として、次のようなことがありました:
- RC2暗号化されたp12ファイルがNode.jsで処理できない
- PEMファイル内の余計な空白行や改行が問題
- 証明書や秘密鍵が正しくエクスポートされていない
エラー解決への道のり
問題1: p12ファイルの暗号化形式
キーチェーンアクセスからエクスポートしたp12ファイルを調べるとRC2暗号化でした:
$ openssl pkcs12 -in PassMint.p12 -info -nodes
Enter Import Password:
MAC: sha1, Iteration 1
MAC length: 20, salt length: 8
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
Error outputting keys and certificates
800CAE0E02000000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()
エラーの内容:Algorithm (RC2-40-CBC : 0)
はサポート外とのこと。
解決策1: Triple DESでp12を作り直す
OpenSSLを使って証明書を一度PEM形式で抽出し、Triple DES暗号化でp12を再作成:
# 1. PEM形式で証明書と秘密鍵を抽出(OpenSSL 1.1系が必要)
openssl pkcs12 -in original.p12 -out cert.pem -clcerts -nokeys
openssl pkcs12 -in original.p12 -out key.pem -nocerts -nodes
# 2. Triple DES暗号化でp12を再作成
openssl pkcs12 -export -in cert.pem -inkey key.pem -out signer_3des.p12 -certfile wwdr.pem -descert
問題2: wwdr.pemの内容
wwdr.pem内の空白行や余計な改行も「Invalid PEM」エラーの原因になります。
正しいPEM形式は:
-----BEGIN CERTIFICATE-----
(Base64エンコードされた証明書データ)
-----END CERTIFICATE-----
余計な空白行や改行があると、Node.js環境でパースエラーになります。
問題3: Base64エンコード
p12ファイルをSecret Managerに保存するためにBase64エンコードする際も注意点があります:
# 改行を含まないBase64文字列にする必要がある
base64 < signer_3des.p12 | tr -d '\n' > signer_3des.p12.b64
# またはOpenSSLで
openssl base64 -in signer_3des.p12 -A -out signer_3des.p12.b64
実装上の注意点
passkit-generatorライブラリを使う際の実装例:
// Secret Managerから証明書とパスワードを取得
const appleP12Base64 = await getSecret('APPLE_P12');
const p12Pass = await getSecret('P12_PASS');
const p12 = Buffer.from(appleP12Base64, "base64");
// 一時ファイルに保存(Cloud Functionsでは必要なケースが多い)
const p12TempPath = path.join(tempDir, `signerKey-${passId}.p12`);
fs.writeFileSync(p12TempPath, p12);
// PKPassオブジェクト生成
const pass = await PKPass.from({
model: templatePath,
certificates: {
wwdr: wwdrPath, // PEMファイルのパス
signerCert: p12TempPath, // P12ファイルのパス
signerKey: p12TempPath, // 同じP12ファイルのパス
signerKeyPassphrase: p12Pass // P12のパスワード
}
});
// パスをバッファとして取得
const passBuffer = await pass.getAsBuffer();
未解決の問題・疑問点
残念ながら、上記の対応をしてもNode.js環境によっては「Invalid PEM」エラーが続くことがあります。考えられる追加の原因:
- Node.jsのバージョンによる互換性問題(18 vs 16)
- passkit-generatorライブラリの内部実装の問題
- OpenSSLのバージョンの違いによる暗号化/復号の非互換性
- 証明書や秘密鍵がKeychain Accessからうまくエクスポートされていない
- Cloud Functionsの一時ファイル操作関連の権限問題
ベストプラクティス
現時点でのベストプラクティスをまとめると:
-
Triple DESまたはAESでp12をエクスポート
- OpenSSLを使ってRC2からTriple DESに変換
-
-descert
または-keypbe AES-256-CBC -certpbe AES-256-CBC
オプションを使用
-
wwdr.pemの正確な管理
- 余分な空白行や改行を削除
- 正確なPEM形式を維持
-
Base64エンコードの注意点
- 改行なしの1行の文字列にする
- Secret Manager登録時にそのまま貼り付け
-
パスワード管理
- 特殊文字や空白を含まないシンプルなパスワードが安全
さいごに
Apple Wallet用パスの生成は、一見単純そうに見えて非常に複雑な要素が絡み合っています。証明書関連のエラーは根本原因の特定が難しく、解決に時間がかかることがあります。Appleのドキュメントには詳しい説明が少ないこの分野ですが、粘り強く取り組めばいつかApple Walletパスを提供できるでしょう。