Hey, I am trying to implement the apple pay process pay backend service, I have checked everything and somehow it fails. I only have 1 certificate for merchant and 1 for the apple pay process, I have the private keys and try to run this following code that fails -
import crypto from 'crypto'; import fs from 'fs'; import forge from 'node-forge';
const MERCHANT_ID_FIELD_OID = '1.2.840.113635.100.6.32';
function decryptedToken() { const token = "<token data here>"; const ephemeralPublicKey = "<encrypted ephemeral public key here>"; const encryptedData = "<encrypted data here>"; //=================================== // Import certs //=================================== const epk = Buffer.from(ephemeralPublicKey, 'base64'); const merchantCert = fs.readFileSync('merchant_full.pem', 'utf8') const paymentProcessorCert = fs.readFileSync("apple_pay_private.pem"); //===================================
let symmetricKey = '';
try {
symmetricKey = restoreSymmetricKey(epk, merchantCert, paymentProcessorCert);
} catch (err) {
throw new Error(`Restore symmetric key failed: ${err.message}`);
}
try {
//-----------------------------------
// Use the symmetric key to decrypt the value of the data key
//-----------------------------------
const decrypted = JSON.parse(decryptCiphertextFunc(symmetricKey, encryptedData));
console.log("Decrypted Token:", decrypted);
// const preppedToken = prepTabaPayToken(token, decrypted)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Send decrypted token back to frontend
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// res.send(preppedToken);
} catch (err) {
throw new Error(`Decrypt cipher data failed: ${err.message}`);
}
}
// extractMerchantID -
const extractMerchantID = (merchantCert) => {
//===================================
// Extract merchant identification from public key certificate
//===================================
try {
const info = forge.pki.certificateFromPem(merchantCert);
const result = info['extensions'].filter(d => d.id === MERCHANT_ID_FIELD_OID);
//-----------------------------------
// Return
//-----------------------------------
return result[0].value.toString().substring(2);
} catch (err) {
throw new Error(Unable to extract merchant ID from certificate: ${err}
);
}
}
// generateSharedSecret - const generateSharedSecret = (merchantPrivateKey, ephemeralPublicKey) => { //=================================== // Use private key from payment processing certificate and the ephemeral public key to generate // the shared secret using Elliptic Curve Diffie*Hellman (ECDH) //===================================
const privateKey = crypto.createPrivateKey({
key: merchantPrivateKey,
format: "pem",
type: "sec1", // because it's "EC PRIVATE KEY"
});
const publicKey = crypto.createPublicKey({
key: ephemeralPublicKey,
format: 'der',
type: 'spki'
});
//----------------------------------- // Return //----------------------------------- return crypto.diffieHellman({privateKey,publicKey: publicKey,}); //----------------------------------- }
// getSymmetricKey - const getSymmetricKey = (merchantId, sharedSecret) => { //=================================== // Get KDF_Info as defined from Apple Pay documentation //=================================== const KDF_ALGORITHM = '\x0didaes256GCM'; const KDF_PARTY_V = Buffer.from(merchantId, 'hex').toString('binary'); const KDF_PARTY_U = 'Apple'; const KDF_INFO = KDF_ALGORITHM + KDF_PARTY_U + KDF_PARTY_V; //----------------------------------- // Create hash //----------------------------------- const hash = crypto.createHash('sha256'); hash.update(Buffer.from('000000', 'hex')); hash.update(Buffer.from('01', 'hex')); hash.update(Buffer.from(sharedSecret, 'hex')); hash.update(KDF_INFO, 'binary'); //----------------------------------- // Return //----------------------------------- return hash.digest('hex'); //----------------------------------- }
// restoreSymmetricKey - const restoreSymmetricKey = (ephemeralPublicKey, merchantCert, paymentProcessorCert) => { //=================================== // 3.a Use the payment processor private key and the ephemeral public key, to generate the shared secret //=================================== const sharedSecret = generateSharedSecret(paymentProcessorCert, ephemeralPublicKey);
//----------------------------------- // 3.b Use the merchant identifier of the public key certificate and the shared secret, to derive the symmetric key //----------------------------------- const merchantId = extractMerchantID(merchantCert); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Return //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ console.log("Merchant ID:", merchantId); console.log("Shared Secret (hex):", sharedSecret); return getSymmetricKey(merchantId, sharedSecret); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ }
// decryptCiphertextFunc - const decryptCiphertextFunc = (symmetricKey, encryptedData) => { console.log("🔑 Decrypting Ciphertext with Symmetric Key:", symmetricKey); //=================================== // Get symmetric key and initialization vector //=================================== const buf = Buffer.from(encryptedData, 'base64'); const SYMMETRIC_KEY = Buffer.from(symmetricKey, 'hex'); const IV = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); // Initialization vector of 16 null bytes const CIPHERTEXT = buf.slice(0, -16); //----------------------------------- // Create and return a Decipher object that uses the given algorithm and password (key) //----------------------------------- const decipher = crypto.createDecipheriv("aes-256-gcm", SYMMETRIC_KEY, IV); const tag = buf.slice(-16, buf.length); decipher.setAuthTag(tag); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Load encrypted token into Decipher object //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ let decrypted = decipher.update(CIPHERTEXT); console.log("🔑 Decrypted Data"); decrypted += decipher.final(); //::::::::::::::::::::::::::::::::::: // Return //::::::::::::::::::::::::::::::::::: return decrypted; //::::::::::::::::::::::::::::::::::: } decryptedToken();