Subresource Integrity using PowerShell

When using external javascript files, like those from a CDN, there could be a risk that changes to, or replacement of the script file on the external resource could harm your site. To mitigate this issue, something called Subresource Integrity (SRI) is developed. It is not a standard yet, and might be subject to change, as stated on the Mozilla Developer Network (MDN). Also it is not yet supported on every browser.

I recently discovered this feature when looking at different javascript technologies and also looking into OWASP and javascript. Just a few days before I discovered that this option was available, I also discussed this potential risk of using a CDN with a colleague of mine.

I now tried to implement this on scripts in my own site. As the example on MDN uses openssl to create the hash, and I didn't want to download a third party openssl binary distribution, I tried looking for a way to do this with PowerShell. As I could not find a ready made solution, I created one myself, with some trial and error, and comparing the output with the one on https://www.srihash.org/.

Here is the script I came up with, that works with local files on your file system.

param(
    [Parameter(Mandatory=$true)]
    [string] 
    $filename, 

    [ValidateSet('sha256', 'sha384', 'sha512')]
    [Parameter(Mandatory=$true)]
    [string] 
    $hashType,

    [string] 
    $scriptPath
)

function GetSha($type) {
    switch($type) {
        "sha256" { return [System.Security.Cryptography.SHA256]::Create() }
        "sha384" { return [System.Security.Cryptography.SHA384]::Create() }
        "sha512" { return [System.Security.Cryptography.SHA512]::Create() }
        default { Write-Host "Unsupported SHA hashing algorithm." }
    }
}

$scriptText = '<script type="text/javascript" src="{3}{0}" integrity="{1}-{2}"></script>'

$file = Get-Content $filename -Raw
$sha = GetSha $hashType
try {
    $bytesHash = $sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($file))
    $base64Hash = [System.Convert]::ToBase64String($bytesHash)

    if ($filename.IndexOf("\") -ge 0) {
        $filename = $filename.Substring($filename.LastIndexOf("\") + 1)
    }

    $tag = [string]::format($scriptText, $filename, $hashType, $base64Hash, $scriptPath)
    Write-Host $tag
}
finally {
    if ($sha -ne $null) {
        $sha.Dispose()
    }
}

The important thing was to use the Get-Content with the -Raw switch, with this I got the same hash from my local JQuery as from the CDN using srihash.org. Also now my own script was allowed in Firefox when testing. You can call the script using for example:

.\GetScriptTag.ps1 .\booden.net.js sha384 /script/

You can select the available sha types by using the Tab key after your filename. You can also optionally add the path of your script as a parameter, this will then be included in the script tag that is being outputted. Note that I do not output the crossorigin attribute that was used in the MDN output sample, as this serves a different purpose, and I like to separate these things.

So not all browsers support this feature yet, but I do think this is a really good feature that should be supported by all browsers. You can even think of using this for your own scripts, especially when scripts could be in locations where they could be compromised. Though this all would be useless if the hash could also be compromised, still it requires more effort to do so. You could even think about automatically adding this attribute in a grunt or gulp task. Please notice that this attribute is also available for link tags.