From ec6529d133b4222c625c811412125fd379efe8a4 Mon Sep 17 00:00:00 2001 From: Martii Date: Tue, 20 Jul 2021 20:33:02 -0600 Subject: [PATCH] Adopt a portion of SRI for our implementation * This was already implemented pre W3C recommendation in our form but normalizing to their syntax. * UI and DB remaining non-base64 encoded... semver limitation with extra characters that violate that spec. * Change caching mechanism... unfortunately traffic for a while will be increased while syncing with browsers. Also because spec doesn't use hex, which it probably should, the eTag header value will be bigger. Hashes, so far, are always "hex-able" by design of SHA but that could change in the future... who knows. * Base62 being dropped in favor of Base64 for cache mechanism. Should be okay with extra `+/` in base64 since that falls within ASCII limitations. * Any .user.js utilizing the .meta.json, or other language, will need to modify to check for the `sha512-` prefix and decode the value appropriately. * If .meta.json shows empty `hash` clear browser cache *(weird Fx issue perhaps)* * Bugfix on local copy of metadata script access... non-fatal atm just incorrect live copy referenced. Post #1076 and applies to #432 #249 Ref(s): * https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag * https://developer.mozilla.org/docs/Web/Security/Subresource_Integrity * https://w3c.github.io/webappsec-subresource-integrity/ * https://www.srihash.org/ --- README.md | 6 ------ controllers/scriptStorage.js | 34 ++++++++++++++++++++-------------- libs/modelParser.js | 9 ++++++++- package.json | 1 - views/pages/scriptPage.html | 2 +- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index e578451c3..ad39c8489 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ Repository | Reference | Recent Version [ansi-colors][ansi-colorsGHUrl] | [Documentation][ansi-colorsDOCUrl] | [![NPM version][ansi-colorsNPMVersionImage]][ansi-colorsNPMUrl] [async][asyncGHUrl] | [Documentation][asyncDOCUrl] | [![NPM version][asyncNPMVersionImage]][asyncNPMUrl] [aws-sdk][aws-sdkGHUrl] | [Documentation][aws-sdkDOCUrl] | [![NPM version][aws-sdkNPMVersionImage]][aws-sdkNPMUrl] -[base62][base62GHUrl] | [Documentation][base62DOCUrl] | [![NPM version][base62NPMVersionImage]][base62NPMUrl] [body-parser][body-parserGHUrl] | [Documentation][body-parserDOCUrl] | [![NPM version][body-parserNPMVersionImage]][body-parserNPMUrl] [bootstrap][bootstrapGHUrl] | [Documentation][bootstrapDOCUrl] | [![NPM version][bootstrapNPMVersionImage]][bootstrapNPMUrl] [bootstrap-markdown][bootstrap-markdownGHUrl] | [Documentation][bootstrap-markdownDOCUrl] | [![NPM version][bootstrap-markdownNPMVersionImage]][bootstrap-markdownNPMUrl] @@ -147,11 +146,6 @@ Outdated dependencies list can also be achieved with `$ npm outdated` [aws-sdkNPMUrl]: https://www.npmjs.com/package/aws-sdk [aws-sdkNPMVersionImage]: https://img.shields.io/npm/v/aws-sdk.svg?style=flat -[base62GHUrl]: https://github.com/base62/base62.js -[base62DOCUrl]: https://github.com/base62/base62.js/blob/master/Readme.md -[base62NPMUrl]: https://www.npmjs.com/package/base62 -[base62NPMVersionImage]: https://img.shields.io/npm/v/base62.svg?style=flat - [body-parserGHUrl]: https://github.com/expressjs/body-parser [body-parserDOCUrl]: https://github.com/expressjs/body-parser/blob/master/README.md [body-parserNPMUrl]: https://www.npmjs.com/package/body-parser diff --git a/controllers/scriptStorage.js b/controllers/scriptStorage.js index d0183811f..de74ffa39 100644 --- a/controllers/scriptStorage.js +++ b/controllers/scriptStorage.js @@ -27,7 +27,6 @@ var mediaType = require('media-type'); var mediaDB = require('mime-db'); var async = require('async'); var moment = require('moment'); -var Base62 = require('base62/lib/ascii'); var SPDX = require('spdx-license-ids'); var sizeOf = require('image-size'); var ipRangeCheck = require("ip-range-check"); @@ -570,6 +569,7 @@ exports.sendScript = function (aReq, aRes, aNext) { var lastModified = null; var eTag = null; + var hashSRI = null; var maxAge = 7 * 60 * 60 * 24; // nth day(s) in seconds var now = null; var continuation = true; @@ -628,6 +628,10 @@ exports.sendScript = function (aReq, aRes, aNext) { } } + hashSRI = aScript.hash + ? 'sha512-' + Buffer.from(aScript.hash).toString('base64') + : 'undefined'; + // HTTP/1.1 Caching aRes.set('Cache-Control', 'public, max-age=' + maxAge + ', no-cache, no-transform, must-revalidate'); @@ -639,8 +643,8 @@ exports.sendScript = function (aReq, aRes, aNext) { lastModified = moment(aScript.updated) .utc().format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT'; - // Convert a based representation of the hex sha512sum - eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .user.js"'; + // Use SRI of the stored sha512sum + eTag = '"' + hashSRI + ' .user.js"'; // If already client-side... HTTP/1.1 Caching if (aReq.get('if-none-match') === eTag || aReq.get('if-modified-since') === lastModified) { @@ -796,10 +800,12 @@ exports.sendScript = function (aReq, aRes, aNext) { } else { source = result.code; - // Calculate a based representation of the hex sha512sum - eTag = '"' + Base62.encode( - parseInt('0x' + crypto.createHash('sha512').update(source).digest('hex'), 16)) + - ' .min.user.js"'; + // Calculate SRI of the source sha512sum + eTag = '"sha512-' + + Buffer.from( + crypto.createHash('sha512').update(source).digest('hex') + ).toString('base64') + ' .min.user.js"'; + } } catch (aE) { // On any failure default to unminified if (isDev) { @@ -827,8 +833,8 @@ exports.sendScript = function (aReq, aRes, aNext) { lastModified = moment(aScript.updated) .utc().format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT'; - // Reset to convert a based representation of the hex sha512sum - eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .user.js"'; + // Reset SRI of the stored sha512sum + eTag = '"' + hashSRI + ' .user.js"'; } // If already client-side... partial HTTP/1.1 Caching @@ -928,8 +934,8 @@ exports.sendMeta = function (aReq, aRes, aNext) { meta = script.meta; // NOTE: Watchpoint if (/\.json$/.test(aReq.params.scriptname)) { - // Create a based representation of the hex sha512sum - eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .meta.json"'; + // Use SRI of the stored sha512sum + eTag = '"' + script.hashSRI + ' .meta.json"'; // If already client-side... HTTP/1.1 Caching if (aReq.get('if-none-match') === eTag) { @@ -955,7 +961,7 @@ exports.sendMeta = function (aReq, aRes, aNext) { // Overwrite any keys found with the following... meta.OpenUserJS.installs = [{ value: script.installs }]; meta.OpenUserJS.issues = [{ value: 'n/a' }]; - meta.OpenUserJS.hash = aScript.hash ? [{ value: aScript.hash }] : undefined; + meta.OpenUserJS.hash = script.hash ? [{ value: script.hashSRI }] : 'undefined'; // Get the number of open issues scriptOpenIssueCountQuery = Discussion.find({ category: exports @@ -966,8 +972,8 @@ exports.sendMeta = function (aReq, aRes, aNext) { async.parallel(tasks, asyncComplete); } else { - // Create a based representation of the hex sha512sum - eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .meta.js"'; + // Use SRI of the stored sha512sum + eTag = '"' + script.hashSRI + ' .meta.js"'; // If already client-side... HTTP/1.1 Caching if (aReq.get('if-none-match') === eTag) { diff --git a/libs/modelParser.js b/libs/modelParser.js index 27cf6e04a..9767751c5 100644 --- a/libs/modelParser.js +++ b/libs/modelParser.js @@ -489,7 +489,14 @@ var parseScript = function (aScript) { parseDateProperty(script, 'updated'); // Hash - script.hashShort = script.hash ? script.hash.substr(0, 7) : 'undefined'; + script.hashShort = 'undefined'; + script.hashSRI = 'undefined'; + + if (script.hash) { + // NOTE: May be absent in dev DB but should not be in pro DB + script.hashShort = script.hash.substr(0, 7); + script.hashSRI = 'sha512-' + Buffer.from(script.hash).toString('base64'); + } if (script.created && script.updated && script.created.toString() !== script.updated.toString()) { script.isUpdated = true; diff --git a/package.json b/package.json index 4def7cb12..e52d2843d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "async": "3.2.0", "@octokit/auth-oauth-app": "4.3.0", "aws-sdk": "2.944.0", - "base62": "2.0.1", "body-parser": "1.19.0", "bootstrap": "3.4.1", "bootstrap-markdown": "2.10.0", diff --git a/views/pages/scriptPage.html b/views/pages/scriptPage.html index cce6bc0a6..2938a9ea5 100644 --- a/views/pages/scriptPage.html +++ b/views/pages/scriptPage.html @@ -27,7 +27,7 @@

Published:

-

Version: {{script.meta.UserScript.version.0.value}}+{{script.hashShort}}{{#script.isUpdated}} updated {{/script.isUpdated}}

+

Version: {{script.meta.UserScript.version.0.value}}+{{script.hashShort}}{{#script.isUpdated}} updated {{/script.isUpdated}}

{{#script.description}}

Summary: {{script.description}}

{{/script.description}} {{#script.hasGroups}}