Using Subresource Integrity

Using Subresource Integrity

·

2 min read

Subresource Integrity (SRI) is a security feature that enables browsers to verify that resources they fetch. So if we are worried that our resources may be changed by hackers, we could use this feature to let browsers check content integrity before exection.

Process

Let's try a simple example to see how browsers do the verifing process.

First, we have a script file script.js.

console.log(123);

Then, we compute the digest of this file using openssl.

$ cat ./script.js | \
  openssl dgst -sha384 -binary | \
  openssl base64 -A
sha384-klGFenr7N+3HfO7FQiTnGOgAELIhqR2AsX6jP8WCe3+5n7DPu5RRjJJYJSQ16ARL

Here, we compute the sha384 digest of this file. Next, we put the output string into the integrity attribute in script tag.

<script 
  src="./script.js" 
  integrity="sha384-klGFenr7N+3HfO7FQiTnGOgAELIhqR2AsX6jP8WCe3+5n7DPu5RRjJJYJSQ16ARL">
</script>

Now, if we open this html file, we should see logs are printed normally in the console.

Let's change this integrity value, so browsers should show error when verifing.

<script 
  src="./script.js" 
  integrity="sha384-anything-at-all">
</script>

134104497-cab06e78-704e-4f1f-9f69-5b06fa40ff1b.png

One more thing we need to know that if the file is from different origin, crossorigin attribute must be present to check the integrity of the file.

<script 
  src="https://yaox023.com/static/script.js" 
  integrity="sha384-25IrZaChHLASagnS7QeCdzYJCrohQku6KFvrr+x+NampgR0cQuVU03gjIe/o8oWN"
  crossorigin="anonymous"
  >
</script>

Automation

Generate digest values by hand is boring, we can automate this process in webpack.

First, we init a simple project and install some packages. html-webpack-plugin is used to generate html files and webpack-subresource-integrity is used to compute digest values and inject it into html files.

npm init -y

mkdir src
echo "console.log(123)" > src/index.js

npm install webpack webpack-cli --save-dev
npm install html-webpack-plugin --save-dev
npm install webpack-subresource-integrity --save-dev

Then comes our webpack.config.js file to tell webpack how to build the project.

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    crossOriginLoading: 'anonymous',
  },
  plugins: [
    new SubresourceIntegrityPlugin({
      hashFuncNames: ['sha512'],
      enabled: true, // auto is the default and means to enable the plugin when the Webpack mode is production or none and disable it when it is development
    }),
    new HtmlWebpackPlugin(),
  ],
  mode: 'production',
}

Then add build command in package.json file.

{
  "name": "integrity-ex",
  "version": "1.0.0",
  "description": "",
  "main": "script.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "^5.3.2",
    "webpack": "^5.53.0",
    "webpack-cli": "^4.8.0",
    "webpack-subresource-integrity": "^5.0.0"
  }
}

Finally, we build the project.

npm run build

Let's see the output html.

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title>Webpack App</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <script defer="defer" src="bundle.js"
    integrity="sha512-vcsgyuzomGf8NJCajNOw6poJxJAHkY4vBytmJs/1/HtU7Jjw4TtyyDD4ax5G9j8rKlRKpidl0eDwlXdJuoTiMw=="
    crossorigin="anonymous"></script>
</head>

<body></body>

</html>

As we expected, SRI is working, we can enjoy this security benefit easily.