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 = "";
const ephemeralPublicKey = "";
const encryptedData = "";
//===================================
// 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();