Protecting Data from Tampering

In this example, an SHA-256 signature is calculated for a request body and compared with the signature that is generated by the origin server. If the signatures are the same, the response content is not tampered with. Otherwise, a 416 status code is returned, indicating that the response content is tampered with. This example demonstrates how to use an Edge Function to verify whether the response content sent from the origin server is tampered with.

Note:

  1. Make sure that the origin server uses the same signature algorithm and tamper-proofing rules that are used in this example.
  2. To use the tamper-proofing rules described in this example in the production environment, perform sorting on the calculated signature to prevent the signature from being cracked by attackers.

Sample Code

// The supported text file formats.
const textFileTypes = [
  'application/javascript',
  'text/html; charset=utf-8',
  'text/css; charset=utf-8',
  'text/xml; charset=utf-8'
];

// The supported image file format.
const imageFileTypes = [
  'image/jpeg'
];

function uint8ArrayToHex(arr) {
  return Array.prototype.map
    .call(arr, (x) => (('0' + x.toString(16)).slice(-2)))
    .join('');
}

/**
 * The following algorithms are supported: MD5, SHA-1, SHA-256, SHA-384, and SHA-512. The algorithm name is not case-sensitive.
 * Note: When you calculate a signature on the origin server, do not directly sign the data in the source file. Instead, apply code obfuscation during signature calculation to prevent the signature from being cracked by attackers.
 * Use the same method in the sample code to calculate the signature for comparison. This way, you can prevent the signature from being tampered with.
 **/
async function checkAndResponse(response, hash, algorithm) {
  const headers = response.headers;

  let checkHash = 'sorry! not match';
  let data = null;
  const contentType = headers.get('Content-Type');
  if (textFileTypes.includes(contentType) || imageFileTypes.includes(contentType)) {
    data = await response.arrayBuffer();
  }
  let ret = await crypto.subtle.digest({name: algorithm}, data);
  checkHash = uint8ArrayToHex(new Uint8Array(ret));

  headers.append(`X-Content-${algorithm}-Check`, checkHash);
  // Compare the signature that is calculated in real time with the signature that is provided by the origin server. If the signatures are different, a 416 status code is returned, indicating that the request cannot be met.
  if (checkHash !== hash) {
    return new Response(null, {
      headers,
      status: 416
    });
  }

  return new Response(data, {
    headers,
    status: 200
  });
}

async function handleEvent(event) {
  // Obtain the content that is returned by the origin server. If the content is cached on the EdgeOne edge node, do not fetch the content from the origin server.
  const response = await fetch(event.request);
  if (response.status === 200) {
    const headers = response.headers;
    // The signature header of the content that is returned by the origin server.
    const hash = headers.get('X-Content-Sha256');
    if (hash) {
      // Check whether the calculated signature is the same as the signature that is provided by the origin server. The following algorithms are supported: MD5, SHA-1, SHA-256, SHA-384, and SHA-512. The algorithm name is not case-sensitive.
      return checkAndResponse(response, hash, 'Sha-256');
    }
  }

  return response;
}

addEventListener('fetch', event => {
  event.respondWith(handleEvent(event));
});

Sample Preview

In the address bar of the browser, enter a URL that matches a trigger rule of the Edge Function to preview the effect of the sample code.

  1. The signature that is calculated by the Edge Function is the same as the signature that is provided by the origin server.

14.1.png

  1. The signature that is calculated by the Edge Function is different from the signature that is provided by the origin server, and a 416 status code is returned.

14.2.png

References

  1. Runtime APIs: Fetch
  2. Runtime APIs: Web Crypto
  3. Runtime APIs: Headers
  4. Runtime APIs: Response