Skip to content

Ecialo/erlang-apay-decrypt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

erlang_apay_decrypt

An Erlang library for verifying and decrypting Apple Pay payment tokens (EC_v1).

Implements the full Apple Pay Payment Token verification and decryption flow:

  • PKCS#7 signature extraction and validation
  • Leaf / intermediate certificate identification via Apple OIDs
  • Certificate chain verification against the Apple Root CA
  • CMS signing time and message digest checks (constant-time comparison)
  • ECDH shared secret derivation + Apple-specific KDF
  • AES-256-GCM decryption of the payment data

Requirements

  • Erlang/OTP 24+ (uses crypto:crypto_one_time_aead/6)
  • rebar3

Build

rebar3 compile

Usage

Quick start (Erlang)

%% Read your PEM-encoded merchant certificate, private key, and the Apple Root CA
{ok, RawCert}      = file:read_file("merchant_id.pem"),
{ok, RawPrivateKey} = file:read_file("private_key.pem"),
{ok, RawAppleRoot}  = file:read_file("AppleRootCA-G3.cer"),

%% Prepare a reusable environment (do this once at startup)
Env = erlang_apay_decrypt:prepare_env(RawCert, RawPrivateKey, RawAppleRoot, 180000),

%% Decode the token received from the client
APayMessage = jsx:decode(TokenJson, [return_maps]),

%% Verify and decrypt
case erlang_apay_decrypt:verify_and_decrypt_apay_message(APayMessage, [{env, Env}]) of
    {ok, DecryptedJson} ->
        %% DecryptedJson contains the payment data
        io:format("~s~n", [DecryptedJson]);
    {error, Reason} ->
        io:format("Failed: ~p~n", [Reason])
end.

Quick start (Elixir)

raw_cert       = File.read!("merchant_id.pem")
raw_private_key = File.read!("private_key.pem")
raw_apple_root  = File.read!("AppleRootCA-G3.cer")

env = :erlang_apay_decrypt.prepare_env(raw_cert, raw_private_key, raw_apple_root, 180_000)

token = Jason.decode!(token_json)

case :erlang_apay_decrypt.verify_and_decrypt_apay_message(token, env: env) do
  {:ok, decrypted} -> IO.puts(decrypted)
  {:error, reason} -> IO.inspect(reason)
end

See the examples/apay_decrypt_ex directory for a full Elixir example project.

API

prepare_env(RawCert, RawPrivateKey, RawAppleRoot, Threshold)

Prepares a reusable proplist containing the parsed merchant identity. Call once and pass the result via the env option.

Parameter Type Description
RawCert binary (PEM) Your Apple Pay merchant identity certificate
RawPrivateKey binary (PEM) The EC private key associated with the certificate
RawAppleRoot binary (PEM/DER) Apple Root CA – G3 certificate
Threshold integer Maximum allowed age of the signing time in milliseconds (e.g. 180000 for 3 minutes)

verify_and_decrypt_apay_message(APayMessage, Opts)

Verifies the signature and decrypts an Apple Pay payment token.

APayMessage is a map with binary keys as decoded from the JSON token (<<"data">>, <<"signature">>, <<"header">>, etc.).

Options (proplist):

Option Type Description
env proplist Pre-built environment from prepare_env/4
raw_merch_cert binary Merchant certificate PEM (alternative to env)
raw_private_key binary Private key PEM (alternative to env)
raw_apple_root binary Apple Root CA (alternative to env)
threshold integer Signing time threshold in ms (default 0)
skip_chain_check boolean Skip certificate chain validation (default false)
check_time {{Y,M,D},{H,Mi,S}} Override the current time used for signing time verification

Returns: decrypted payment data binary on success, or {error, Reason}.

Possible errors:

Error Meaning
{unexpected_num_of_certs, N} Signature does not contain exactly 2 certificates
no_required_certs Neither leaf nor intermediate certificate found
no_leaf_cert Leaf certificate missing from signature
no_intermediate_cert Intermediate certificate missing from signature
bad_signature_issuer Signer does not match the leaf certificate
bad_signing_time Signing time outside the allowed threshold
bad_message_digest Computed message digest does not match the signed digest

How it works

  1. Signature parsing — The base64-encoded PKCS#7 signature is decoded and the ContentInfo / SignedData structure is extracted.
  2. Certificate extraction — Leaf and intermediate certificates are identified by their Apple-specific OID extensions.
  3. Chain verification — The certificate chain [Intermediate, Leaf] is validated against the Apple Root CA using public_key:pkix_path_validation/3 (can be skipped with skip_chain_check).
  4. Signing time & digest — The CMS signing time is checked against the current time (± threshold), and the message digest over EphemeralPublicKey || Data || TransactionId || ApplicationData is verified.
  5. Decryption — An ECDH shared secret is computed from the ephemeral public key and the merchant private key. A symmetric AES-256-GCM key is derived using the Apple-specific KDF, and the encrypted data is decrypted.

License

MIT — see LICENSE.

About

An Erlang library for verifying and decrypting Apple Pay payment tokens (EC_v1)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages