node.jsapple-business-chat

NodeJS Apple Business Chat REST API, Downloading and Decrypting Large interactive message


I am reading the apple business chat api docs and I'm at the section "Receiving Large Interactive Data Payloads". The last step is to decipher an attachment then send to the Business Chat Api.

The Download & data step Documentation

--- And the decipher instructions DOCUMENTATION --

Then, using a cypher library, decrypt the file by using the AES/CTR/NoPadding algorithm with an all-zero, 16-byte initialization vector (IV) with the key value in the Attachment dictionary for the downloaded attachment.

So here is my interpretation of this documentation as they leave me little to work with.


// The single-use, 256-bit AES key represented as a hex-encoded string.
const algorithm = 'aes-256-ctr';

// remove the 00 prefix from the hex-encoded string, 
// then decode the string into its original value. 
const key = Buffer.from(decryptKey.substr(2), 'hex');

// Use the decoded key value to decrypt the downloaded attachment file. 

// THE FULL IMPLEMENTATION

const iv = Buffer.alloc(16, 0);
const key = Buffer.from(decryptKey.substr(2), 'hex');
const decipher = crypto.createDecipheriv(algorithm, key, iv);
decipher.setAutoPadding(false)
let decrypted = decipher.update(data, '', 'hex');
decrypted += decipher.final('hex');
console.log("decrypted:", decrypted);

// Finally send to Apple Business Chat Api

    POST https://mspgw.push.apple.com/v1/decodePayload
accept: */*
accept-encoding: gzip, deflate
authorization: Bearer signed-web-token
source-id: business-id
bid: some-bid
 
{ attachment data }

// Here is a piece of the incoming data

��F�ڼ���/��G����+���)�\M���x�tk��Y(���-�-G�ȍ$t��� )

// After decipher

d3ffade249263d1252ee0dcfa6accd0beff31c607889ff0d31d893adde5063616a15591e181fb698350fb955f

Im not sure if I am doing deciphering correctly as when I send the decrypted code to Apples API

POST https://mspgw.push.apple.com/v1/decodePayload

it is always code response 400

I have contacted Apple for assistance on this issue. I will update this doc as soon as I get a response back from them.

Below is a diagram of the steps needed to take. I stuck at the last 2 steps.

Diagram of the steps


Solution

  • Here is the update for solving the deciphering issues using the apple business chat api with NodeJS. Main issue was converting deciphered data to a buffer before sending to Apple to be decoded.

    const decryptKeyFromInteractiveRef = "03f30ff3d3d03dc3".toUpperCase()
    
    async function main(decryptKeyFromInteractiveRef) {
    
    const url = await preDownloadUrl();
    
    const data = await downloadPayload(url);
    
    const decipheredData = await decipherInteractiveRef(data);
    
    const decodedData = await appleDecode(decipheredData);
    console.log("Finally your data", decodedData);
    
    async function appleDecode(decipheredData) {
    
        var config = {
            method: 'post',
            url: 'https://mspgw.push.apple.com/v1/decodePayload',
            headers: {
                "Authorization": Authorization,
                "bid": "com.apple.messages.MSMessageExtensionBalloonPlugin:0000000000:com.apple.icloud.apps.messages.business.extension",
                "source-id": BIZ_ID,
                "accept": "*/*",
                "accept-encoding": "gzip, deflate",
                'Content-Type': 'application/octet-stream'
            },
            data: decipheredData
        };
    
        const { data } = await axios(config);
        const path = Path.resolve(__dirname, 'images', 'data.json')
        fs.writeFileSync(path, JSON.stringify(data))
    }
    
    
    async function decipherInteractiveRef() {
    
        const iv = Buffer.alloc(16); // buffer alloc fills with zeros
        const key = Buffer.from(decryptKey.slice(2), 'hex',);
        const decipher = crypto.createDecipheriv("aes-256-ctr", key, iv);
        decipher.setAutoPadding(false); // No Padding
        let decrypted = decipher.update(data); // if input is a buffer dont choose a encoding
    
        return decrypted;
    }
    
    
    async function preDownloadUrl() {
        //Using the fields in the received interactiveDataRef key, 
        // retrieve the URL to the payload by calling the /preDownload endpoint.
    
        //interactiveDataRef key
        const signatureHex = "81101cc048b6b588c895f01c12715421f9d0a25329".toUpperCase()
        const signature = Buffer.from(signatureHex, 'hex').toString('base64')
    
        var configInteractiveRef = {
            method: 'get',
            url: 'https://mspgw.push.apple.com/v1/preDownload',
            headers: {
                'Authorization': Authorization,
                'source-id': BIZ_ID,
                'MMCS-Url': 'https://p56-content.icloud.com/MZ02db38070edccb2ce8c972efdcdd25437439745cad6f15473bb7880d436377702752e134be8bd3b4d695567a5d574142.C01USN00',
                'MMCS-Signature': signature,
                'MMCS-Owner': 'MZ02db38070edccb2ce8c972efdcdd25437439745cad6f15473bb7880d436377702752e134be8bd3b4d695567a5d574142.C01USN00'
            }
        };
    
        const response = await axios(configInteractiveRef)
        return response.data["download-url"];
    }
    
    // download big payload from apple
    async function downloadPayload(url) {
        const { data } = await axios.get(url, { responseType: 'arraybuffer' });
        return data
    }}