0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Apple Wallet用のパスを作るのはやたらと難しい

Last updated at Posted at 2025-04-29

はじめに

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 アカウント

発行までの流れ(理想)

  1. Pass Type ID証明書をApple Developer Programで取得
  2. .pkpass形式のパスをNode.jsで生成
  3. Cloud Storageにアップロード
  4. ユーザーに署名付きURLを提供してダウンロード

実際に出くわした「Invalid PEM formatted message」エラー

しかし、実際にはこんなエラーに悩まされました:

Error generating Apple Pass: Error: Invalid PEM formatted message.

このエラーの原因究明と解決までの過程を紹介します。

Pass Type IDの証明書取得手順

まず前提として、Apple Wallet用パスを発行するには、証明書の取得から始める必要があります:

  1. Apple Developer Programにログイン
  2. 「Certificates, Identifiers & Profiles」セクションに移動
  3. 「Identifiers」→「Pass Type IDs」を選択
  4. 「+」ボタンからPass Type IDを新規登録(例: pass.com.mycompany.myapp
  5. 「Certificates」→「+」で新規証明書作成
  6. 「Pass Type ID Certificate」を選択
  7. KeychainからCSR(証明書署名リクエスト)を作成・アップロード
  8. 発行された証明書をダウンロードして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」エラーが続くことがあります。考えられる追加の原因:

  1. Node.jsのバージョンによる互換性問題(18 vs 16)
  2. passkit-generatorライブラリの内部実装の問題
  3. OpenSSLのバージョンの違いによる暗号化/復号の非互換性
  4. 証明書や秘密鍵がKeychain Accessからうまくエクスポートされていない
  5. Cloud Functionsの一時ファイル操作関連の権限問題

ベストプラクティス

現時点でのベストプラクティスをまとめると:

  1. Triple DESまたはAESでp12をエクスポート

    • OpenSSLを使ってRC2からTriple DESに変換
    • -descertまたは-keypbe AES-256-CBC -certpbe AES-256-CBCオプションを使用
  2. wwdr.pemの正確な管理

    • 余分な空白行や改行を削除
    • 正確なPEM形式を維持
  3. Base64エンコードの注意点

    • 改行なしの1行の文字列にする
    • Secret Manager登録時にそのまま貼り付け
  4. パスワード管理

    • 特殊文字や空白を含まないシンプルなパスワードが安全

さいごに

Apple Wallet用パスの生成は、一見単純そうに見えて非常に複雑な要素が絡み合っています。証明書関連のエラーは根本原因の特定が難しく、解決に時間がかかることがあります。Appleのドキュメントには詳しい説明が少ないこの分野ですが、粘り強く取り組めばいつかApple Walletパスを提供できるでしょう。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?