You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ColdFusion中实现Apple Wallet Passkit优惠券签名与打包?

Implementing Apple Wallet Passkit Coupon Signing & Packaging in ColdFusion

I’ve been struggling to find clear guidance on how to sign and package Apple Wallet Passkit coupons in ColdFusion—most Google searches lead to dead ends or unanswered questions. I do have a fully functional PHP implementation for this workflow, and I’m hoping to adapt this logic to ColdFusion. Reusable Java libraries would be especially helpful here, so if anyone has experience with this, please share your approach!

My Working PHP Implementation Reference

//load .p12 certificate
$PKCS12 = file_get_contents($Certificates['Certificate']);
$certs = array();
if(openssl_pkcs12_read($PKCS12, $certs, $Certificates['CertPassword']) == true) {
    $certdata = openssl_x509_read($certs['cert']);
    $privkey = openssl_pkey_get_private($certs['pkey'], $Certificates['CertPassword']);
}
//sign file hashes with AppleWWDRCA certificate
openssl_pkcs7_sign($ManifestPath, $SignaturePath, $certdata, $privkey, array(), PKCS7_BINARY | PKCS7_DETACHED, $Certificates['AppleWWDRCA']);
$ManifestSignature = file_get_contents($SignaturePath);
$ManifestSignatureDER = PEM2DER($ManifestSignature);
//put files (and strings as files) in a zip archive
$ZIP = new ZipArchive();
$ZIP->open($PKPassPath, ZIPARCHIVE::CREATE);
$ZIP->addFromString('signature', $ManifestSignatureDER);
$ZIP->addFromString('manifest.json', $Manifest);
$ZIP->addFromString('pass.json', $JSON);
foreach($ImageFiles as $ImagePath) {
    $ImageName = basename($ImagePath);
    $ZIP->addFile($ImagePath, $ImageName);
}
$ZIP->close();
//load pass data und delete temp files (if debug mode is off)
$Pass['data'] = file_get_contents($PKPassPath);
$Pass['size'] = filesize($PKPassPath);
$Pass['name'] = $PassName;
function echoPass($Pass) {
    //send http headers and zip archive content to client
    header('Pragma: no-cache');
    header('Content-type: application/vnd.apple.pkpass');
    header('Content-length: '.$Pass['size']);
    header('Content-Disposition: attachment; filename="'.$Pass['name'].'.pkpass"');
    echo $Pass['data'];
}

ColdFusion Implementation Approach

Since ColdFusion runs on Java, we can leverage Java’s security and zip libraries to replicate the PHP workflow. Here’s a step-by-step breakdown with code examples:

1. Load PKCS12 Certificate & Apple WWDR CA Certificate

ColdFusion can use java.security.KeyStore to load the PKCS12 file, which contains your private key and developer certificate. You’ll also need the Apple WWDR CA certificate (in DER or PEM format).

<cfscript>
    // Paths to your certificates
    var pkcs12Path = "/path/to/your/certificate.p12";
    var pkcs12Password = "your-cert-password";
    var wwdrCertPath = "/path/to/AppleWWDRCA.cer";

    // Load PKCS12 keystore
    var keyStore = createObject("java", "java.security.KeyStore").getInstance("PKCS12");
    var fileInputStream = createObject("java", "java.io.FileInputStream").init(pkcs12Path);
    keyStore.load(fileInputStream, pkcs12Password.toCharArray());
    fileInputStream.close();

    // Get private key and developer certificate
    var alias = keyStore.aliases().nextElement(); // Assuming single entry in PKCS12
    var privateKey = keyStore.getKey(alias, pkcs12Password.toCharArray());
    var devCertificate = keyStore.getCertificate(alias);

    // Load Apple WWDR CA certificate
    var certFactory = createObject("java", "java.security.cert.CertificateFactory").getInstance("X.509");
    var wwdrInputStream = createObject("java", "java.io.FileInputStream").init(wwdrCertPath);
    var wwdrCertificate = certFactory.generateCertificate(wwdrInputStream);
    wwdrInputStream.close();
</cfscript>

2. Generate Manifest.json

First, create a manifest file that contains SHA1 hashes of all files in the pass (pass.json, images, etc.).

<cfscript>
    var manifest = {};
    var passJsonContent = '{"formatVersion":1, "passTypeIdentifier":"your-pass-type-id", "serialNumber":"unique-serial", ...}'; // Your pass JSON
    var imagePaths = ["/path/to/icon.png", "/path/to/logo.png"]; // List of pass images

    // Add hash for pass.json
    var md5 = createObject("java", "java.security.MessageDigest").getInstance("SHA1");
    var passJsonBytes = charsetDecode(passJsonContent, "UTF-8");
    md5.update(passJsonBytes);
    manifest["pass.json"] = lcase(binaryEncode(md5.digest(), "hex"));

    // Add hashes for images
    for (var imgPath in imagePaths) {
        var imgFile = createObject("java", "java.io.File").init(imgPath);
        var imgInputStream = createObject("java", "java.io.FileInputStream").init(imgFile);
        var buffer = createObject("java", "byte[]").init(8192);
        var bytesRead = 0;
        md5.reset();
        while ((bytesRead = imgInputStream.read(buffer)) != -1) {
            md5.update(buffer, 0, bytesRead);
        }
        imgInputStream.close();
        manifest[getFileFromPath(imgPath)] = lcase(binaryEncode(md5.digest(), "hex"));
    }

    // Convert manifest to JSON string
    var manifestJson = serializeJson(manifest);
</cfscript>

3. Sign the Manifest (PKCS7 Detached Signature)

To replicate openssl_pkcs7_sign, we recommend using the BouncyCastle library (simpler for PKCS7 handling). You’ll need to add the BouncyCastle JCE provider JAR to your ColdFusion WEB-INF/lib directory and restart the server.

<cfscript>
    // Initialize BouncyCastle provider
    var security = createObject("java", "java.security.Security");
    security.addProvider(createObject("java", "org.bouncycastle.jce.provider.BouncyCastleProvider").init());

    // Prepare manifest bytes
    var manifestBytes = charsetDecode(manifestJson, "UTF-8");

    // Create PKCS7 signed data
    var generator = createObject("java", "org.bouncycastle.cms.CMSSignedDataGenerator").init();
    var signerInfoGeneratorBuilder = createObject("java", "org.bouncycastle.cms.SignerInfoGeneratorBuilder").init(
        createObject("java", "org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder").init().build()
    );
    var signerGenerator = signerInfoGeneratorBuilder.build(
        createObject("java", "org.bouncycastle.operator.jcajce.JcaContentSignerBuilder").init("SHA1withRSA").build(privateKey),
        createObject("java", "org.bouncycastle.cert.jcajce.JcaX509CertificateHolder").init(devCertificate)
    );
    generator.addSignerInfoGenerator(signerGenerator);
    // Add Apple WWDR CA certificate as additional certificate
    generator.addCertificate(createObject("java", "org.bouncycastle.cert.jcajce.JcaX509CertificateHolder").init(wwdrCertificate));

    // Generate detached signature
    var cmsSignedData = generator.generate(
        createObject("java", "org.bouncycastle.cms.CMSProcessableByteArray").init(manifestBytes),
        true // Detached signature
    );

    // Get DER-encoded signature (matches PHP's PEM2DER output)
    var signatureDER = cmsSignedData.getEncoded();
</cfscript>

4. Package into PKPass Zip File

Use Java’s ZipOutputStream to create the .pkpass archive, adding all required files:

<cfscript>
    var pkpassPath = "/path/to/output.pkpass";
    var fileOutputStream = createObject("java", "java.io.FileOutputStream").init(pkpassPath);
    var zipOutputStream = createObject("java", "java.util.zip.ZipOutputStream").init(fileOutputStream);

    // Helper function to add bytes to zip
    function addToZip(zipStream, entryName, contentBytes) {
        var entry = createObject("java", "java.util.zip.ZipEntry").init(entryName);
        zipStream.putNextEntry(entry);
        zipStream.write(contentBytes);
        zipStream.closeEntry();
    }

    // Add signature file
    addToZip(zipOutputStream, "signature", signatureDER);

    // Add manifest.json
    addToZip(zipOutputStream, "manifest.json", charsetDecode(manifestJson, "UTF-8"));

    // Add pass.json
    addToZip(zipOutputStream, "pass.json", charsetDecode(passJsonContent, "UTF-8"));

    // Add images
    for (var imgPath in imagePaths) {
        var imgFile = createObject("java", "java.io.File").init(imgPath);
        var imgInputStream = createObject("java.io.FileInputStream").init(imgFile);
        var imgBytes = createObject("java", "org.apache.commons.io.IOUtils").toByteArray(imgInputStream); // Use Commons IO or read manually
        imgInputStream.close();
        addToZip(zipOutputStream, getFileFromPath(imgPath), imgBytes);
    }

    // Close zip stream
    zipOutputStream.close();
    fileOutputStream.close();
</cfscript>

5. Serve the PKPass to the Client

Finally, send the correct headers and output the PKPass file content to the user:

<cfscript>
    var passFile = createObject("java", "java.io.File").init(pkpassPath);
    var passSize = passFile.length();
    var passName = "MyCoupon";

    // Set response headers
    cfheader(name="Pragma", value="no-cache");
    cfheader(name="Content-Type", value="application/vnd.apple.pkpass");
    cfheader(name="Content-Length", value=passSize);
    cfheader(name="Content-Disposition", value='attachment; filename="#passName#.pkpass"');

    // Output the file
    cfcontent(file=pkpassPath, type="application/vnd.apple.pkpass", deletefile=false);
</cfscript>

Key Notes

  • BouncyCastle Dependency: The PKCS7 signing step is much simpler with BouncyCastle. Download the BouncyCastle JCE provider (bcprov-jdk15on.jar) and place it in your ColdFusion WEB-INF/lib directory, then restart the server.
  • Certificate Formats: Ensure your Apple WWDR CA certificate is in DER format (convert from PEM if needed using openssl x509 -in AppleWWDRCA.pem -out AppleWWDRCA.cer -outform DER).
  • Hash Algorithm: Apple requires SHA1 hashes for the manifest.json file (even though SHA1 is deprecated, this is still required for Passkit).

内容的提问来源于stack exchange,提问作者M.A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:11:37