Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade jQuery in Heliosbooth and Heliosverifier #395

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

laoumh
Copy link
Contributor

@laoumh laoumh commented Oct 11, 2024

Context

The main aim of this PR is to upgrade jQuery to the latest version, in order to mitigate known vulnerabilities associated with older versions: CVE-2015-9251, CVE-2020-11022 and CVE-2020-11023.

Since each app (sub module) has it's own set of JS libraries, I only tackled heliosbooth and heliosverifier for now.
The Voting Booth was chosen first as it is the largest surface of interaction with the system (there are more people voting then administering election).

Documentation links were added to commit messages wherever pertinent.

Major changes

Removal of deprecated JS libraries

Since this PR is security-oriented, beyond upgrading jQuery I also tried to remove technical debt whenever possible, using current Browser API alternatives, that are now widely available.
Hence, the following jQuery plugins were removed:

  • jquery.query (replaced by the browser's URLSearchParams interface);
  • jquery.json (replaced by JSON.parse and JSON.stringify);
  • jtemplates (replaced by Underscore.js templates).

Switch from jTemplates to Underscore.js templates

By removing jTemplates, we need another way to render the booth templates. Instead of adding a new library to the project, I just used the template functionality available on Underscore.js, which is simple but sufficient for this use case. Underscore.js have three types of delimiters:

  • JavaScript code block: <% ...js commands... %>

We can use this to do logic control and loops. So, for instance, instead of jTemplates'

{#foreach $T.question.answers as answer}
...
{#/for}

we can substitute it by

<% for (const answer of question.answers) { %>
...
<% } %> // end for block

I believe that, in a sense, this is easier to maintain because it's just pure JS, instead of having its own syntax like jTemplates.

  • Value interpolation: <%= literal value %> and <%- HTML-escaped value %>, which are self-explanatory.

Another (better?) solution would have been use Django templates, but that would have been a much bigger change: adding routing and view, changing the client logic of how to show and hide each voting booth step etc.

Finally, Underscore.js was also updated to the latest version, with no breaking changes.

JS libraries compression

I could install but not run uglify. It seems this package is not maintained and it now breaks. But, inspecting the package code, it seems uglify uses grunt-contrib-uglify, which in turn uses uglifyjs. So, I changed heliosbooth build command to use uglifyjs with compression and mangle options (details in the commit message).

Tests

The following tests were performed:

  • Voting booth
    • All permutations of select min to max answers: min == 0, min > 0, max < num. questions, max == num. questions. (Note: the system allows to set min > max, and it breaks the booth).
    • Answers with URL
    • Randomized answer order
    • Election title, question title and answers containing HTML elements (e.g.: "
      answer 2 <\div>")
    • Comparison between the ballot hash computed in the booth and the one published in the Ballot Tracking Center
    • Ballot encrypted on the server (forced manually setting BigInteger.in_browser = false)
  • Single ballot verify
    • Correct ballot
    • Ballot with ill-formed JSON (e.g. missing a quote or a colon)
    • Ballot with altered value
  • Election Verifier
    • Verification of election with multiple questions and 100+ voters
    • Comparison between the ballot hash computed in the Verifier and the one published in the Ballot Tracking Center

These tests were done on latest versions of Firefox, Chromium and Microsoft Edge.

A Few Considerations

This is a sensitive PR. I tried to test the changes as thoroughly as possible, but edge cases might have escaped me.
For instance, could there be some portion of jscrypto that remained uncovered and might break?
Please let me know if additional tests are needed.

Here are a few considerations after this upgrade process.

Duplicate JS libraries

A major difficulty with this upgrade was duplication of libraries (is it intentional or a result of bringing each sub module into the main project?).
For example, there are five instances of jQuery through out the project:

./helios/media/js/jquery-1.4.2.min.js
./helios/media/helios/jquery-1.2.2.min.js
./heliosverifier/js/jquery-1.2.2.min.js
./server_ui/media/foundation/js/vendor/jquery.js
./heliosbooth/js/jquery-1.2.6.min.js

I believe it would have been possible to upgrade the whole project if there was a single source for statics.

Build

From the perspective of auditing the Voting Booth, wouldn't it be better to not mangle and compress JS libraries?
The difference between compressed and uncompressed JS is 45.6 kB.
Minifying (without mangling or compression) just jscrypto library reduces this difference to just 10.4 kB.

Also, boothworker-single.js loads jscrypto scripts anyway, so if we imported them directly on vote.html, we could leverage the browser's cache.

Small improvement loading templates

jTemplate's .setTemplateURL() method fetched templates in a synchronous fashion.
The current solution fetches them asynchronously, in parallel, reducing load time:

jTemplate

native fetch

This change also solved a browser warning: Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience.

Updates from version 1.1.6 to 1.13.7. Latest minified version was
downloaded from Underscore's GitHub repository:
https://raw.githubusercontent.com/jashkenas/underscore/refs/heads/master/underscore-min.js
As per the project author, there is no compeling reason to use
this plugin: https://github.com/alrusdi/jquery-plugin-query-object
jQuery JSON plugin is marked as deprecated:
https://www.npmjs.com/package/jquery-json

Author suggests using native JSON API - JSON.stringify() instead
of $.toJSON() and and JSON.parse() instead of $.secureEvalJSON().
This plugin website does not exist anymore, but can still be found
on the Web Archive:
http://web.archive.org/web/20080512013710/http://jtemplates.tpython.com:80/
Uses native browser APIs fetch() and Promise.all() to get the
templates instead of jTemplates' $().setTemplateURL().

Uses Underscore.js _.template() to compile and render templates
instead of jTemplates' $().processTemplate()
Latest jQuery version was downloaded from the official website:
https://code.jquery.com/jquery-3.7.1.min.js
From the docs: "in 1.4 JSON will yield a JavaScript object"
(see https://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings).

So, when the raw JSON is actually needed, set 'dataType' to "text".
Else, set it to "json" and just use the returned JS object
directly instead of manually parsing JSON into the object.
In some instances we should use $().prop() instead of $().attr(),
for exemple when disabling an element:

$('#my_button').prop('disable', true) instead of attr('disable',
'disable').

Docs: https://api.jquery.com/prop/#prop2
These methods were depreceted, should use $().trigger('select')
and $().trigger('submit') instead. Documentation:

https://api.jquery.com/select-shorthand/
https://api.jquery.com/submit-shorthand/
Could install but not run 'uglify' (doesn't seems to be maintained).
But looking at the source code, 'uglify' seems to call
'grunt-contrib-uglify' which in turn calls 'uglifyjs'. From grunt
docs: "will compress and mangle the input files using the default
[uglifyjs] options". So, we can use that last package directly, to
compress (-c) and mangle (-m):

$ uglifyjs <input files> -o <compressed.js> -c -m
reserved=['jQuery','$']

This last options tells it not to mangle jQuery.

Docs:
https://github.com/nanjingboy/uglify
https://www.npmjs.com/package/grunt-contrib-uglify
https://www.npmjs.com/package/uglify-js
By default, Python's 'json.dumps' method inserts a space after
comma an collon characters, which generates a different encrypted
ballot hash. So we specify separators without space:

json.dumps(d, sort_keys=True, separators=(',', ':'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant