Skip to content

Commit

Permalink
Adopt a portion of SRI for our implementation (#1826)
Browse files Browse the repository at this point in the history
* 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/

Auto-merge
  • Loading branch information
Martii committed Jul 21, 2021
1 parent ab7379e commit dfddc4c
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 23 deletions.
6 changes: 0 additions & 6 deletions README.md
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
34 changes: 20 additions & 14 deletions controllers/scriptStorage.js
Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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) {
Expand Down
9 changes: 8 additions & 1 deletion libs/modelParser.js
Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion views/pages/scriptPage.html
Expand Up @@ -27,7 +27,7 @@

<div class="script-meta">
<p><i class="fa fa-fw fa-clock-o"></i> <b>Published:</b> <time datetime="{{script.createdISOFormat}}" title="{{script.created}}">{{script.createdHumanized}}</time></p>
<p><i class="fa fa-fw fa-history"></i> <b>Version:</b> <code>{{script.meta.UserScript.version.0.value}}<span title="SHA-512 {{script.hash}}">+{{script.hashShort}}</span></code>{{#script.isUpdated}} updated <time class="script-updated" datetime="{{script.updatedISOFormat}}" title="{{script.updated}}">{{script.updatedHumanized}}</time>{{/script.isUpdated}}</p>
<p><i class="fa fa-fw fa-history"></i> <b>Version:</b> <code>{{script.meta.UserScript.version.0.value}}<span title="{{script.hashSRI}}">+{{script.hashShort}}</span></code>{{#script.isUpdated}} updated <time class="script-updated" datetime="{{script.updatedISOFormat}}" title="{{script.updated}}">{{script.updatedHumanized}}</time>{{/script.isUpdated}}</p>
{{#script.description}}<p><i class="fa fa-fw fa-info"></i> <b>Summary:</b> {{script.description}}</p>{{/script.description}}
{{#script.hasGroups}}
<span>
Expand Down

0 comments on commit dfddc4c

Please sign in to comment.