I have a multipart/related request that contains a SOAP part with security headers and message data, and a binary part.

The SOAP message contains the key used during the encryption of the binary part, which I can retrieve.

The binary part is a gzipped string encrypted with aes-128-gcm . The returned tag is appended to the encrypted part, and the iv used during the encryption is prepended to the encrypted part. So we have a value => [iv][encryptedPayload][tag].

When I control everything myself and make sure that all the values I work with are binary, I can encrypt and decrypt without issues, but this does not seem to be the way the binary part is generated according to the Peppol AS4 specification.

Can anyone help with guiding how to encode the binary part and properly split it?

Here is a full example of how I encrypt a string, add it to a mocked body and encode it in base64, and then go all the way back decoding and decrypting to get to the original string.

import crypto from 'crypto';

const plaintext = 'hello aes-128-gcm'; // string to be encrypted

const algorithm = 'aes-128-gcm';
const encryptionKey = crypto.randomBytes(16);
const nonce = crypto.randomBytes(12); // generate an iv to prepend to the payload
const cipher = crypto.createCipheriv(algorithm, encryptionKey, nonce); // init cipher to encrypt the string

let payloadEncrypted = cipher.update(Buffer.from(plaintext).toString('base64'), 'base64', 'binary');
payloadEncrypted += cipher.final('binary'); // encrypted string
const tag = cipher.getAuthTag(); // get the mac tag to be appended to the payload

const payload = nonce.toString('binary') + payloadEncrypted + tag.toString('binary');
console.log(payload); // final payload to be submitted

// mocking request payload. Building an example multipart request and base64 encoding it
const body = `
--uuid:4535f302-c49e-4a8e-b32f-49c3da3577fc
Content-Type: application/soap+xml; charset=UTF-8
Content-Transfer-Encoding: binary
Content-ID: <[email protected]>

<env:Envelope</env:Envelope>
--uuid:4535f302-c49e-4a8e-b32f-49c3da3577fc
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <0a7f6252-3854-4376-920d-5a01ae89a6e9@ip-10-10-131-116.eu-west-1.compute.internal>
CompressionType: application/gzip
MimeType: application/xml

${payload}
--uuid:4535f302-c49e-4a8e-b32f-49c3da3577fc--
`;
const b64Payload = Buffer.from(body).toString('base64');

// decoding the base64 request and decrypting the payload in the binary part
const request = Buffer.from(b64Payload, 'base64').toString();
const bData = Buffer.from(request.split('\n')[14], 'binary').toString('binary'); // the payload

const splitNonce = bData.substring(0, 12); // the prepended nonce
const splitEncryptedPayload = bData.substring(12, bData.length - 16); // the encrypted payload
const splitTag = bData.substring(bData.length - 16); // the appended tag

const decrypt = (content, tag, iv, password) => {
  const decipher = crypto.createDecipheriv(algorithm, Buffer.from(password, 'binary'), Buffer.from(iv, 'binary'));
  decipher.setAuthTag(Buffer.from(tag, 'binary'));
  let dec = decipher.update(content, 'binary', 'utf8');
  dec += decipher.final('utf8');
  return dec;
}

// I'm reusing the encryptionKey from above, in reality this would have been encrypted and provided in the SOAP message
const decrypted = decrypt(splitEncryptedPayload, splitTag, splitNonce, encryptionKey);
console.log(decrypted); // the decrypted message

When I try the above with an actual request (starting from the "decoding the base64 request and decrypting the payload in the binary part") I get an [Error: Unsupported state or unable to authenticate data] in the decrypt function.

1

There are 1 best solutions below

1
On

I've found my problem. I have not read the request binary data as binary in my actual code. There is no special way to concatenate or split the encrypted data, nonce and tag. It's just important to triple-check the encodings.

The example I provided in the question is a correct way to concatenate and split.