Commit 5b2ad82a authored by sajolida's avatar sajolida
Browse files

Integrate Mike's work (#16128)

parent 78b64472
......@@ -6,5 +6,6 @@
[[!meta stylesheet="install/inc/stylesheets/steps" rel="stylesheet" title=""]]
[[!meta stylesheet="install/inc/stylesheets/download-only-iso" rel="stylesheet" title=""]]
[[!meta script="install/inc/js/download"]]
[[!meta script="install/inc/js/forge.sha256"]]
[[!inline pages="install/inc/steps/download.inline" raw="yes" sort="age"]]
......@@ -5,5 +5,6 @@
[[!meta stylesheet="install/inc/stylesheets/steps" rel="stylesheet" title=""]]
[[!meta stylesheet="install/inc/stylesheets/download-only-img" rel="stylesheet" title=""]]
[[!meta script="install/inc/js/download"]]
[[!meta script="install/inc/js/forge.sha256"]]
[[!inline pages="install/inc/steps/download.inline" raw="yes" sort="age"]]
......@@ -6,5 +6,6 @@
[[!meta stylesheet="inc/stylesheets/steps" rel="stylesheet" title=""]]
[[!meta stylesheet="inc/stylesheets/dvd" rel="stylesheet" title=""]]
[[!meta script="install/inc/js/download"]]
[[!meta script="install/inc/js/forge.sha256"]]
[[!inline pages="install/inc/steps/download.inline" raw="yes" sort="age"]]
document.addEventListener("DOMContentLoaded", function() {
//specify url of json file containing valid checksums for ISO and USB images below:
//var URLofJsonFileContainingChecksums='https://tails.boum.org/install/v2/Tails/amd64/stable/latest.json'; //this URL will only work if download.html is served from served from https://tails.boum.org/ because of same origin because of same-origin policy.
var URLofJsonFileContainingChecksums='https://cors-anywhere.herokuapp.com/https://tails.boum.org/install/v2/Tails/amd64/stable/latest.json'; //use this URL to get around same-origin policy (SOP) if you are staging this locally. The https://cors-anywhere.herokuapp.com/ proxy includes the necessary CORS headers to relax SOP.
var sha256;
/* Generic functions */
function hide(elm) {
......@@ -247,16 +253,40 @@ document.addEventListener("DOMContentLoaded", function() {
// Trigger verification when file is chosen
document.getElementById("verify-file").onchange = function(e) { verifyFile(e, this); }
function verifyFile(e, elm) {
async function verifyFile(e, elm) {
file = elm.files[0]
/* Dummy code that simulates the various states of a verification
This is where the verification logic should go. */
showVerifyingDownload(file.name);
showVerificationProgress(50);
setTimeout(function(){ showVerificationResult("error"); }, 2500);
setTimeout(function(){ showVerificationResult("failed"); }, 5000);
setTimeout(function(){ showVerificationResult("failed-again"); }, 7500);
setTimeout(function(){ showVerificationResult("successful"); }, 10000);
try {
var response=await fetch(URLofJsonFileContainingChecksums);
var checksumjson=await response.text();
console.log('json file containing checksums for ISO and USB images downloaded from ' + URLofJsonFileContainingChecksums + '.');
//console.log(checksumjson);
} catch(err) {
console.log('json file containing checksums for ISO and USB images could not be downloaded from ' + URLofJsonFileContainingChecksums + '.');
console.error(err);
showVerificationResult("error");
return;
}
try {
showVerifyingDownload(file.name);
sha256=forge.md.sha256.create();
await readFile(file);
var fileactualchecksum = sha256.digest().toHex();
console.log('checksum of downloaded file: ' + fileactualchecksum);
} catch(err) {
console.log('error reading file and calculating checksum.');
console.error(err);
showVerificationResult("error");
return;
}
//If downloaded file is valid, then fileactualchecksum should be 64 hex characters in length, and should be contained within checksumjson. Otherwise, consider downloaded file to be invalid.
if(fileactualchecksum.length==64 && (checksumjson.includes(fileactualchecksum.toUpperCase()) || checksumjson.includes(fileactualchecksum.toLowerCase()))) {
showVerificationResult("successful");
} else {
showVerificationResult("failed");
}
}
/* Final initialization */
......@@ -272,6 +302,44 @@ document.addEventListener("DOMContentLoaded", function() {
showVerifyButton();
toggleContinueLink("skip-download");
async function readFile(file) {
var CHUNK_SIZE = 2 * 1024 *1024;
var offset = 0;
lastCalculatedPercentage=0;
while(true) {
var chunk = await readChunk(file, offset, CHUNK_SIZE);
sha256.update(chunk);
offset+=chunk.length;
var progressPercent = parseInt(offset * 100.0 / file.size);
if (progressPercent!=lastCalculatedPercentage) {
lastCalculatedPercentage = progressPercent;
showVerificationProgress(progressPercent);
}
if (chunk.length < CHUNK_SIZE) { return; }
}
}
function readChunk(file, chunk_offset, chunk_size) {
return new Promise(function(resolve, reject) {
let fr = new FileReader();
fr.onload = e => {
resolve(e.target.result);
};
// on error, reject the promise
fr.onerror = (e) => {
reject(e);
};
let slice = file.slice(chunk_offset, chunk_offset + chunk_size);
// This API is non-standard: https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString
// We use it for performance reasons, see #15059.
fr.readAsBinaryString(slice);
});
}
// To debug the display of the different states:
// showVerifyingDownload('test.img');
// showVerificationProgress('50');
......
/*! Forge v0.8.4 | (c) Digital Bazaar, Inc. */
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["forge"] = factory();
else
root["forge"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
/**
* Node.js module for Forge.
*
* @author Dave Longley
*
* Copyright 2011-2016 Digital Bazaar, Inc.
*/
module.exports = {
// default options
options: {
usePureJavaScript: false
}
};
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(2);
module.exports = __webpack_require__(0);
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
/**
* Secure Hash Algorithm with 256-bit digest (SHA-256) implementation.
*
* See FIPS 180-2 for details.
*
* @author Dave Longley
*
* Copyright (c) 2010-2015 Digital Bazaar, Inc.
*/
var forge = __webpack_require__(0);
__webpack_require__(3);
__webpack_require__(4);
var sha256 = module.exports = forge.sha256 = forge.sha256 || {};
forge.md.sha256 = forge.md.algorithms.sha256 = sha256;
/**
* Creates a SHA-256 message digest object.
*
* @return a message digest object.
*/
sha256.create = function() {
// do initialization as necessary
if(!_initialized) {
_init();
}
// SHA-256 state contains eight 32-bit integers
var _state = null;
// input buffer
var _input = forge.util.createBuffer();
// used for word storage
var _w = new Array(64);
// message digest object
var md = {
algorithm: 'sha256',
blockLength: 64,
digestLength: 32,
// 56-bit length of message so far (does not including padding)
messageLength: 0,
// true message length
fullMessageLength: null,
// size of message length in bytes
messageLengthSize: 8
};
/**
* Starts the digest.
*
* @return this digest object.
*/
md.start = function() {
// up to 56-bit message length for convenience
md.messageLength = 0;
// full message length (set md.messageLength64 for backwards-compatibility)
md.fullMessageLength = md.messageLength64 = [];
var int32s = md.messageLengthSize / 4;
for(var i = 0; i < int32s; ++i) {
md.fullMessageLength.push(0);
}
_input = forge.util.createBuffer();
_state = {
h0: 0x6A09E667,
h1: 0xBB67AE85,
h2: 0x3C6EF372,
h3: 0xA54FF53A,
h4: 0x510E527F,
h5: 0x9B05688C,
h6: 0x1F83D9AB,
h7: 0x5BE0CD19
};
return md;
};
// start digest automatically for first time
md.start();
/**
* Updates the digest with the given message input. The given input can
* treated as raw input (no encoding will be applied) or an encoding of
* 'utf8' maybe given to encode the input using UTF-8.
*
* @param msg the message input to update with.
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
*
* @return this digest object.
*/
md.update = function(msg, encoding) {
if(encoding === 'utf8') {
msg = forge.util.encodeUtf8(msg);
}
// update message length
var len = msg.length;
md.messageLength += len;
len = [(len / 0x100000000) >>> 0, len >>> 0];
for(var i = md.fullMessageLength.length - 1; i >= 0; --i) {
md.fullMessageLength[i] += len[1];
len[1] = len[0] + ((md.fullMessageLength[i] / 0x100000000) >>> 0);
md.fullMessageLength[i] = md.fullMessageLength[i] >>> 0;
len[0] = ((len[1] / 0x100000000) >>> 0);
}
// add bytes to input buffer
_input.putBytes(msg);
// process bytes
_update(_state, _w, _input);
// compact input buffer every 2K or if empty
if(_input.read > 2048 || _input.length() === 0) {
_input.compact();
}
return md;
};
/**
* Produces the digest.
*
* @return a byte buffer containing the digest value.
*/
md.digest = function() {
/* Note: Here we copy the remaining bytes in the input buffer and
add the appropriate SHA-256 padding. Then we do the final update
on a copy of the state so that if the user wants to get
intermediate digests they can do so. */
/* Determine the number of bytes that must be added to the message
to ensure its length is congruent to 448 mod 512. In other words,
the data to be digested must be a multiple of 512 bits (or 128 bytes).
This data includes the message, some padding, and the length of the
message. Since the length of the message will be encoded as 8 bytes (64
bits), that means that the last segment of the data must have 56 bytes
(448 bits) of message and padding. Therefore, the length of the message
plus the padding must be congruent to 448 mod 512 because
512 - 128 = 448.
In order to fill up the message length it must be filled with
padding that begins with 1 bit followed by all 0 bits. Padding
must *always* be present, so if the message length is already
congruent to 448 mod 512, then 512 padding bits must be added. */
var finalBlock = forge.util.createBuffer();
finalBlock.putBytes(_input.bytes());
// compute remaining size to be digested (include message length size)
var remaining = (
md.fullMessageLength[md.fullMessageLength.length - 1] +
md.messageLengthSize);
// add padding for overflow blockSize - overflow
// _padding starts with 1 byte with first bit is set (byte value 128), then
// there may be up to (blockSize - 1) other pad bytes
var overflow = remaining & (md.blockLength - 1);
finalBlock.putBytes(_padding.substr(0, md.blockLength - overflow));
// serialize message length in bits in big-endian order; since length
// is stored in bytes we multiply by 8 and add carry from next int
var next, carry;
var bits = md.fullMessageLength[0] * 8;
for(var i = 0; i < md.fullMessageLength.length - 1; ++i) {
next = md.fullMessageLength[i + 1] * 8;
carry = (next / 0x100000000) >>> 0;
bits += carry;
finalBlock.putInt32(bits >>> 0);
bits = next >>> 0;
}
finalBlock.putInt32(bits);
var s2 = {
h0: _state.h0,
h1: _state.h1,
h2: _state.h2,
h3: _state.h3,
h4: _state.h4,
h5: _state.h5,
h6: _state.h6,
h7: _state.h7
};
_update(s2, _w, finalBlock);
var rval = forge.util.createBuffer();
rval.putInt32(s2.h0);
rval.putInt32(s2.h1);
rval.putInt32(s2.h2);
rval.putInt32(s2.h3);
rval.putInt32(s2.h4);
rval.putInt32(s2.h5);
rval.putInt32(s2.h6);
rval.putInt32(s2.h7);
return rval;
};
return md;
};
// sha-256 padding bytes not initialized yet
var _padding = null;
var _initialized = false;
// table of constants
var _k = null;
/**
* Initializes the constant tables.
*/
function _init() {
// create padding
_padding = String.fromCharCode(128);
_padding += forge.util.fillString(String.fromCharCode(0x00), 64);
// create K table for SHA-256
_k = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
// now initialized
_initialized = true;
}
/**
* Updates a SHA-256 state with the given byte buffer.
*
* @param s the SHA-256 state to update.
* @param w the array to use to store words.
* @param bytes the byte buffer to update with.
*/
function _update(s, w, bytes) {
// consume 512 bit (64 byte) chunks
var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h;
var len = bytes.length();
while(len >= 64) {
// the w array will be populated with sixteen 32-bit big-endian words
// and then extended into 64 32-bit words according to SHA-256
for(i = 0; i < 16; ++i) {
w[i] = bytes.getInt32();
}
for(; i < 64; ++i) {
// XOR word 2 words ago rot right 17, rot right 19, shft right 10
t1 = w[i - 2];
t1 =
((t1 >>> 17) | (t1 << 15)) ^
((t1 >>> 19) | (t1 << 13)) ^
(t1 >>> 10);
// XOR word 15 words ago rot right 7, rot right 18, shft right 3
t2 = w[i - 15];
t2 =
((t2 >>> 7) | (t2 << 25)) ^
((t2 >>> 18) | (t2 << 14)) ^
(t2 >>> 3);
// sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32
w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) | 0;
}
// initialize hash value for this chunk
a = s.h0;
b = s.h1;
c = s.h2;
d = s.h3;
e = s.h4;
f = s.h5;
g = s.h6;
h = s.h7;
// round function
for(i = 0; i < 64; ++i) {
// Sum1(e)
s1 =
((e >>> 6) | (e << 26)) ^
((e >>> 11) | (e << 21)) ^
((e >>> 25) | (e << 7));
// Ch(e, f, g) (optimized the same way as SHA-1)
ch = g ^ (e & (f ^ g));
// Sum0(a)
s0 =
((a >>> 2) | (a << 30)) ^
((a >>> 13) | (a << 19)) ^
((a >>> 22) | (a << 10));
// Maj(a, b, c) (optimized the same way as SHA-1)
maj = (a & b) | (c & (a ^ b));
// main algorithm
t1 = h + s1 + ch + _k[i] + w[i];
t2 = s0 + maj;
h = g;
g = f;
f = e;
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
// can't truncate with `| 0`
e = (d + t1) >>> 0;
d = c;
c = b;
b = a;
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
// can't truncate with `| 0`
a = (t1 + t2) >>> 0;
}
// update hash state
s.h0 = (s.h0 + a) | 0;
s.h1 = (s.h1 + b) | 0;
s.h2 = (s.h2 + c) | 0;
s.h3 = (s.h3 + d) | 0;
s.h4 = (s.h4 + e) | 0;
s.h5 = (s.h5 + f) | 0;
s.h6 = (s.h6 + g) | 0;
s.h7 = (s.h7 + h) | 0;
len -= 64;
}
}
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
/**
* Node.js module for Forge message digests.
*
* @author Dave Longley
*
* Copyright 2011-2017 Digital Bazaar, Inc.
*/
var forge = __webpack_require__(0);
module.exports = forge.md = forge.md || {};
forge.md.algorithms = forge.md.algorithms || {};
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function(global) {/**
* Utility functions for web applications.
*
* @author Dave Longley
*
* Copyright (c) 2010-2018 Digital Bazaar, Inc.
*/
var forge = __webpack_require__(0);
var baseN = __webpack_require__(6);
/* Utilities API */
var util = module.exports = forge.util = forge.util || {};
// define setImmediate and nextTick