Verifying Integrity of Files using NodeJS
Tags: node, javascript, cryptography
Published: 2023-04-01 19:22:14

While using third party JavaScript or CSS libraries from CDN, you've probably came across with `integrity` attribute on `script` or `link` tags.

```html
<script 
  src="https://code.jquery.com/jquery-3.6.4.min.js" 
  integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" 
  crossorigin="anonymous">
</script>
```

If you haven't researched about them till now, then you're in the right place.

# Man in the Middle Attacks

It's possible that the any data which travels through internet can be modified till it reaches to our machine. An attacker might use techniques such as [network eavesdropping](https://en.wikipedia.org/wiki/Network_eavesdropping) to intercept the requests and responses between you and the server and might manipulate the file before you even receive it.

We need a way to verify that the file we've received has not been tempered with.

# Subresource Integrity in Browsers

Browsers implement a security feature called [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) (SRI) to verify integrity of the resource they fetch and execute.

The garbage looking string of characters you see as the value of `integrity` attribute is Base64 decoded cryptographic digest formed by applying specific [hash algorithm](https://en.wikipedia.org/wiki/Cryptographic_hash_function) to the contents of the file.

If any characters inside the file changes, then the calculated hash will also change, thus integrity verification will fail, thus the browser will know that the file has been tempered with. 

If browsers cannot verify integrity of the file being requested, they'll show error message similar to this:

```
Failed to find a valid digest in the 'integrity' attribute 
for resource '<resource-name>' with computed SHA-256 integrity '<calculated-hash>'. 
The resource has been blocked.
```

# Performing Integrity Check with NodeJS

To show a little demo how this check might be implemented, I'll use [NodeJS's `crypto` module](https://nodejs.org/api/crypto.html). We'll use the same `jquery` file the you saw in the first paragraph. I'll download that file into my machine to be able to make modifications later.

Let's create an `index.js` file and import `fs` and `crypto` modules:

```js
import crypto from 'crypto';
import fs from 'fs';
```

We'll need `fs` module to read contents of the file that we want to verify integrity of. Let's declare resource name and expected integrity:

```js
const resource = 'jquery-3.6.4.min.js';
const expectedIntegrity = 'oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=';
```

Note that `sha256` in front of the value of `integrity` attribute is just the name of hashing algorithm that this hash has been calculated with, so we skip it when comparing.

Let's read the file contents and calculate the digest using `sha256` algorithm:

```js
const jquerySource = fs.readFileSync(resource);
const digest = crypto
  .createHash('sha256')
  .update(jquerySource, 'utf8')
  .digest();

const calculatedIntegrity = digest.toString('base64');
```

The value of `digest` variable is a type called `Buffer` which is just bunch of bytes, so we need to convert it to a string in `base64` encoding.

And finally, we just need to compare `calculatedIntegrity` with `expectedIntegrity`, if they are equal then the `jquery-3.6.4.min.js` has not been tempered with, if not, it means something has changed inside the file:

```js
if (calculatedIntegrity == expectedIntegrity) {
  console.log('✔️ Resource integrity verified!');
} else {
  console.log(`❌ Failed to find a valid digest in the 'integrity' attribute 
  for resource '${resource}' with computed SHA-256 integrity '${calculatedIntegrity}'.`);
}
```

If you run this code using `node index.js` then, you might see this output:

```sh
$ node index.js
✔️ Resource integrity verified!
```

Now, if you change the contents of `jquery-3.6.4.min.js` file, and run the code again, then you'll see output similar to this:

```sh
$ echo 'some malicious code' >> jquery-3.6.4.min.js
$ node index.js
❌ Failed to find a valid digest in the 'integrity' attribute 
  for resource 'jquery-3.6.4.min.js' with computed SHA-256 integrity '+FiqKbj0h12Db1PZj62G+h2BMtOueBg26YQOJFY+6wg='.
```

# Conclusion

I hope this article was helpful for you, I suggest you read more at [article at Smashing Magazine about Subresource Integrity](https://www.smashingmagazine.com/2019/04/understanding-subresource-integrity/) and share your thoughts.

You can see the full version of the code we wrote at [my GitLab repository](https://gitlab.com/orkhan-huseyn/node-integrity-check).