Rewrite with service worker

This commit is contained in:
Bowen Ding
2020-04-06 23:42:56 +08:00
parent 9ebab43f07
commit a7332fc30e
7 changed files with 459 additions and 317 deletions

View File

@@ -196,8 +196,8 @@ def encrypt_gallery(gallery):
# gallery.encryptCache = cache
logger.info("starting encryption")
encrypt_files(settings, config, cache, albums, gallery.progressbar_target)
copy_assets(settings)
encrypt_files(settings, config, cache, albums, gallery.progressbar_target)
save_cache(settings, cache)
except Abort:
pass
@@ -209,6 +209,8 @@ def encrypt_files(settings, config, cache, albums, progressbar_target):
raise Abort
key = kdf_gen_key(config["password"].encode("utf-8"), config["kdf_salt"].encode("utf-8"), config["kdf_iters"])
gcm_tag = config["gcm_tag"].encode("utf-8")
medias = list(chain.from_iterable(albums.values()))
with progressbar(medias, label="%16s" % "Encrypting files", file=progressbar_target, show_eta=True) as medias:
for media in medias:
@@ -226,25 +228,38 @@ def encrypt_files(settings, config, cache, albums, progressbar_target):
continue
full_path = os.path.join(settings["destination"], f)
with BytesIO() as outBuffer:
try:
with open(full_path, "rb") as infile:
encrypt(key, infile, outBuffer, config["gcm_tag"].encode("utf-8"))
except Exception as e:
logger.error("Encryption failed for %s: %s", f, e)
else:
logger.info("Encrypting %s...", f)
try:
with open(full_path, "wb") as outfile:
outfile.write(outBuffer.getbuffer())
cacheEntry.add(f)
except Exception as e:
logger.error("Could not write to file %s: %s", f, e)
if encrypt_file(f, full_path, key, gcm_tag):
cacheEntry.add(f)
key_check_path = os.path.join(
os.path.join(settings["destination"], 'static'),
'keycheck.txt'
)
encrypt_file("keycheck.txt", key_check_path, key, gcm_tag)
def encrypt_file(filename, full_path, key, gcm_tag):
with BytesIO() as outBuffer:
try:
with open(full_path, "rb") as infile:
encrypt(key, infile, outBuffer, gcm_tag)
except Exception as e:
logger.error("Encryption failed for %s: %s", filename, e)
return False
else:
logger.info("Encrypting %s...", filename)
try:
with open(full_path, "wb") as outfile:
outfile.write(outBuffer.getbuffer())
except Exception as e:
logger.error("Could not write to file %s: %s", filename, e)
return False
return True
def copy_assets(settings):
theme_path = os.path.join(settings["destination"], 'static')
copy(ASSETS_PATH + "/decrypt.js", theme_path, symlink=False, rellink=False)
copy(ASSETS_PATH + "/decrypt-worker.js", theme_path, symlink=False, rellink=False)
copy(os.path.join(ASSETS_PATH, "decrypt.js"), theme_path, symlink=False, rellink=False)
copy(os.path.join(ASSETS_PATH, "keycheck.txt"), theme_path, symlink=False, rellink=False)
copy(os.path.join(ASSETS_PATH, "sw.js"), settings["destination"], symlink=False, rellink=False)
def inject_scripts(context):
try:

View File

@@ -32,6 +32,7 @@ from cryptography.exceptions import InvalidTag
from typing import BinaryIO
backend = default_backend()
MAGIC_STRING = "_e_n_c_r_y_p_t_e_d_"
def kdf_gen_key(password: bytes, salt:bytes, iters: int) -> bytes:
kdf = PBKDF2HMAC(
@@ -74,6 +75,7 @@ def encrypt(key: bytes, infile: BinaryIO, outfile: BinaryIO, tag: bytes):
ciphertext = outfile
rawbytes = plaintext.read()
encrypted = aesgcm.encrypt(iv, rawbytes, tag)
ciphertext.write(MAGIC_STRING.encode("utf-8"))
ciphertext.write(iv)
ciphertext.write(encrypted)
@@ -83,6 +85,9 @@ def decrypt(key: bytes, infile: BinaryIO, outfile: BinaryIO, tag: bytes):
aesgcm = AESGCM(key)
ciphertext = infile
plaintext = outfile
magicstring = ciphertext.read(len(MAGIC_STRING))
if magicstring != MAGIC_STRING.encode("utf-8"):
raise ValueError("Data is not encrypted")
iv = ciphertext.read(12)
rawbytes = ciphertext.read()
try:

View File

@@ -1,26 +0,0 @@
/*
* copyright (c) 2020 Bowen Ding
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
"use strict"
importScripts("decrypt.js");
onmessage = Decryptor.onWorkerMessage;

View File

@@ -23,51 +23,154 @@
"use strict"
class Decryptor {
constructor(config) {
const c = Decryptor._getCrypto();
if (Decryptor.isWorker()) {
this._role = "worker";
const encoder = new TextEncoder("utf-8");
const salt = encoder.encode(config.kdf_salt);
const iters = config.kdf_iters;
const shared_key = encoder.encode(config.password);
const gcm_tag = encoder.encode(config.gcm_tag);
this._jobCount = 0;
this._jobMap = new Map();
this._workerReady = false;
Decryptor
._initAesKey(c, salt, iters, shared_key)
.then((aes_key) => {
this._decrypt = (encrypted_blob_arraybuffer) =>
Decryptor.decrypt(c, encrypted_blob_arraybuffer,
aes_key, gcm_tag);
})
.then(() => {
Decryptor._sendEvent(self, "DecryptWorkerReady");
});
} else {
this._role = "main";
this._jobCount = 0;
this._numWorkers = Math.min(4, navigator.hardwareConcurrency);
this._jobMap = new Map();
this._workerReady = false;
this._galleryId = config.galleryId;
Decryptor._initPage();
if (!("password" in config && config.password)) {
this._askPassword()
.then((password) => {
config.password = password;
this._createWorkerPool(config);
})
} else {
this._createWorkerPool(config);
if (Decryptor.isServiceWorker()) {
this._role = "service_worker";
} else if (!Decryptor.isWorker()) {
if (!Decryptor.featureTest()) {
alert("This page cannot function properly because your browser does not support some critical features. Please update your browser.");
return;
}
this._role = "main";
this._galleryId = config.galleryId;
this._mSetupServiceWorker(config);
}
console.info("Decryptor initialized");
}
/* main thread only */
static init(config) {
if (Decryptor.isWorker()) return;
window.decryptor = new Decryptor(config);
if (Decryptor.isServiceWorker()) {
self.decryptor = new Decryptor(config);
} else {
window.decryptor = new Decryptor(config);
}
}
static featureTest() {
let features = [
typeof crypto,
typeof TextEncoder,
typeof navigator.serviceWorker,
typeof Proxy,
typeof fetch,
typeof Blob.prototype.arrayBuffer,
typeof Response.prototype.clone,
typeof caches
];
return features.every((e) => e !== "undefined");
}
async _swInitServiceWorker(config) {
const crypto = Decryptor._getCrypto();
const encoder = new TextEncoder("utf-8");
const salt = encoder.encode(config.kdf_salt);
const iters = config.kdf_iters;
const shared_key = encoder.encode(config.password);
const gcm_tag = encoder.encode(config.gcm_tag);
const aes_key = await Decryptor._initAesKey(crypto, salt, iters, shared_key);
if (await this._swCheckAesKey(aes_key, gcm_tag)) {
this.workerReady = true;
this._decrypt = (encrypted_blob_arraybuffer) =>
Decryptor.decrypt(crypto, encrypted_blob_arraybuffer, aes_key, gcm_tag);
this._swNotifyWorkerReady();
} else {
const array_clients = await self.clients.matchAll({includeUncontrolled: true});
for (let client of array_clients) {
this._proxyWrap(client)._mNotifyIncorrectPassword();
}
}
}
async _swCheckAesKey(aes_key, gcm_tag) {
let response;
try {
response = await fetch(Decryptor.keyCheckURL);
} catch (error) {
throw new Error("Fetched failed when checking encryption key");
}
try {
await Decryptor.decrypt(
Decryptor._getCrypto(),
await response.blob(),
aes_key,
gcm_tag,
true
);
} catch (error) {
console.warn("Password is incorrect!");
return false;
}
return true;
}
async _swNotifyWorkerReady() {
const array_clients = await self.clients.matchAll({includeUncontrolled: true});
for (let client of array_clients) {
this._proxyWrap(client)._mReload();
}
}
static isInitialized() {
if (Decryptor.isServiceWorker()) {
return 'decryptor' in self && self.decryptor.workerReady;
} else {
return 'decryptor' in window && window.decryptor.workerReady;
}
}
get workerReady() {
return this._workerReady;
}
set workerReady(val) {
this._workerReady = (val ? true : false);
if (this._workerReady) {
const eventTarget = (Decryptor.isWorker() ? self : document);
Decryptor._sendEvent(eventTarget, "DecryptWorkerReady");
}
}
_mReload() {
window.location.reload();
}
_mNotifyIncorrectPassword() {
localStorage.removeItem(this._galleryId);
}
async _mSetupServiceWorker(config) {
if (!('serviceWorker' in navigator)) {
console.error("Fatal: Your browser does not support service worker");
throw new Error("no service worker support");
}
if (navigator.serviceWorker.controller) {
this.serviceWorker = navigator.serviceWorker.controller;
} else {
navigator.serviceWorker.register(config.sw_script);
const registration = await navigator.serviceWorker.ready;
this.serviceWorker = registration.active;
}
navigator.serviceWorker.onmessage =
(e) => Decryptor.onMessage(this.serviceWorker, e);
this.serviceWorker = this._proxyWrap(this.serviceWorker);
if (!(await this.serviceWorker.Decryptor.isInitialized())) {
if (!('password' in config && config.password)) {
config.password = await this._mAskPassword();
}
this.serviceWorker._swInitServiceWorker(config);
}
}
static isServiceWorker() {
return ('undefined' !== typeof ServiceWorkerGlobalScope) && ("function" === typeof importScripts) && (navigator instanceof WorkerNavigator);
}
static isWorker() {
@@ -75,7 +178,7 @@ class Decryptor {
}
static _getCrypto() {
if(crypto && crypto.subtle) {
if('undefined' !== typeof crypto && crypto.subtle) {
return crypto.subtle;
} else {
throw new Error("Fatal: Browser does not support Web Crypto");
@@ -83,24 +186,16 @@ class Decryptor {
}
/* main thread only */
async _askPassword() {
let password = sessionStorage.getItem(this._galleryId);
async _mAskPassword() {
let password = localStorage.getItem(this._galleryId);
if (!password) {
return new Promise((s, e) => {
window.addEventListener(
"load",
s,
{ once: true, passive: true }
);
}).then((e) => {
const password = prompt("Input password to view this gallery:");
if (password) {
sessionStorage.setItem(this._galleryId, password);
return password;
} else {
return "__wrong_password__";
}
});
const password = prompt("Input password to view this gallery:");
if (password) {
localStorage.setItem(this._galleryId, password);
return password;
} else {
return "__wrong_password__";
}
} else {
return password;
}
@@ -129,78 +224,6 @@ class Decryptor {
);
}
async _doReload(url, img) {
const proceed = Decryptor._sendEvent(img, "DecryptImageBeforeLoad", {oldSrc: url});
if (proceed) {
let old_src = url;
try {
const blobUrl = await this.dispatchJob("reloadImage", [old_src, null]);
img.addEventListener(
"load",
(e) => Decryptor._sendEvent(e.target, "DecryptImageLoaded", {oldSrc: old_src}),
{once: true, passive: true}
);
img.src = blobUrl;
} catch (error) {
img.addEventListener(
"load",
(e) => Decryptor._sendEvent(e.target, "DecryptImageError", {oldSrc: old_src, error: error}),
{once: true, passive: true}
);
img.src = Decryptor.imagePlaceholderURL;
// password is incorrect
if (error.message.indexOf("decryption failed") >= 0) {
sessionStorage.removeItem(this._galleryId);
}
throw new Error(`Image reload failed: ${error.message}`);
}
}
}
async reloadImage(url, img) {
if (this._role === "main") {
const full_url = (new URL(url, window.location)).toString();
if (!this.isWorkerReady()) {
document.addEventListener(
"DecryptWorkerReady",
(e) => { this._doReload(full_url, img); },
{once: true, passive: true}
);
} else {
this._doReload(full_url, img);
}
} else if (this._role === "worker") {
let r;
try {
r = await fetch(url);
} catch (e) {
throw new Error("fetch failed");
}
if (r && r.ok) {
const encrypted_blob = await r.blob();
try {
const decrypted_blob = await this._decrypt(encrypted_blob);
return URL.createObjectURL(decrypted_blob);
} catch (e) {
throw new Error(`decryption failed: ${e.message}`);
}
} else {
throw new Error("fetch failed");
}
}
}
/* main thread only */
static onNewImageError(e) {
if (e.target.src.startsWith("blob")) return;
if (!window.decryptor) return;
window.decryptor.reloadImage(e.target.src, e.target);
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
static _sendEvent(target, type, detail = null) {
const eventInit = {
detail: detail,
@@ -210,186 +233,303 @@ class Decryptor {
return target.dispatchEvent(new CustomEvent(type, eventInit));
}
/* main thread only */
static _initPage() {
document.addEventListener(
"error",
e => {
if (e.target instanceof HTMLImageElement) {
Decryptor.onNewImageError(e);
}
},
{capture: true}
static async checkMagicString(arraybuffer) {
const sample = new DataView(
arraybuffer,
0,
Decryptor.MAGIC_STRING_ARRAYBUFFER.byteLength
);
Image = (function (oldImage) {
function Image(...args) {
let img = new oldImage(...args);
img.addEventListener(
"error",
Decryptor.onNewImageError
);
return img;
for (let i = 0; i < Decryptor.MAGIC_STRING_ARRAYBUFFER.byteLength; i++) {
if (Decryptor.MAGIC_STRING_ARRAYBUFFER[i] !== sample.getUint8(i)) {
return false;
}
Image.prototype = oldImage.prototype;
Image.prototype.constructor = Image;
return Image;
})(Image);
document.createElement = (function(create) {
return function() {
let ret = create.apply(this, arguments);
if (ret.tagName.toLowerCase() === "img") {
ret.addEventListener(
"error",
Decryptor.onNewImageError
);
}
return ret;
};
})(document.createElement);
}
return true;
}
static async decrypt(crypto, blob, aes_key, gcm_tag) {
const iv = await blob.slice(0, 12).arrayBuffer();
const ciphertext = await blob.slice(12).arrayBuffer();
static async decrypt(crypto, blob_or_arraybuffer, aes_key, gcm_tag, check_magic_string=false) {
let arraybuffer, return_blob;
if (blob_or_arraybuffer instanceof Blob) {
arraybuffer = await blob_or_arraybuffer.arrayBuffer();
return_blob = true;
} else if (blob_or_arraybuffer instanceof ArrayBuffer) {
arraybuffer = blob_or_arraybuffer
return_blob = false;
} else {
throw new TypeError("decrypt accepts either a Blob or an ArrayBuffer");
}
// make sure there is enough data to decrypt
// although 1 byte of data seems not acceptable for some browsers
// in which case crypto.decrypt will throw an error
// "The provided data is too small"
if (arraybuffer.byteLength <
Decryptor.MAGIC_STRING_ARRAYBUFFER.byteLength
+ Decryptor.IV_LENGTH
+ 1) {
throw new Error("not enough data to decrypt");
}
if (check_magic_string && !(await Decryptor.checkMagicString(arraybuffer))) {
// data is not encrypted
return blob;
}
const iv = new DataView(
arraybuffer,
Decryptor.MAGIC_STRING_ARRAYBUFFER.byteLength,
Decryptor.IV_LENGTH
);
const ciphertext = new DataView(
arraybuffer,
Decryptor.MAGIC_STRING_ARRAYBUFFER.byteLength + Decryptor.IV_LENGTH
);
const decrypted = await crypto.decrypt(
{
name: "AES-GCM",
iv: iv,
additionalData: gcm_tag
},
aes_key,
ciphertext
);
return new Blob([decrypted], {type: blob.type});
{
name: "AES-GCM",
iv: iv,
additionalData: gcm_tag
},
aes_key,
ciphertext
);
if (return_blob) {
return new Blob([decrypted], {type: blob_or_arraybuffer.type});
} else {
return decrypted;
}
}
isWorkerReady() {
return this._workerReady;
_proxyWrap(target) {
const decryptor = this;
const handler = {
get: (wrappedObj, prop) => {
if (prop in wrappedObj) {
if (wrappedObj[prop] instanceof Function) {
return (...args) => wrappedObj[prop].apply(wrappedObj, args);
} else {
return wrappedObj[prop];
}
}
if (prop === "Decryptor") {
return new Proxy(target, {
get: (wrappedObj, prop) => {
return decryptor._rpcCall(wrappedObj, prop, true);
}
});
}
return decryptor._rpcCall(wrappedObj, prop, false);
}
}
return new Proxy(target, handler);
}
_createWorkerPool(config) {
if (this._role !== "main") return;
if (this._workerReady) return;
_rpcCall(target, method, static_) {
const decryptor = this;
const dummyFunction = () => {};
const handler = {
apply: (wrappedFunc, thisArg, args) => {
return new Promise((success, error) => {
const jobId = decryptor._jobCount++;
decryptor._jobMap.set(jobId, {success: success, error: error});
Decryptor._rpcPostJob(jobId, target, method, args, static_);
});
}
};
return new Proxy(dummyFunction, handler);
}
let callback = (e) => {
const callbacks = this._jobMap.get(e.data.id);
static _rpcPostJob(jobId, messagePort, method, args, static_=false) {
const job = {
type: "job",
id: jobId,
method: method,
args: args,
static: static_
};
messagePort.postMessage(job);
}
static _asyncReturn(instance, method, args) {
if (!(instance instanceof Object)) {
return Promise.reject(new Error("calling method on a primitive"));
}
if (!(method in instance && instance[method] instanceof Function)) {
return Promise.reject(new Error(`no such method: ${method}`))
}
try {
let promise_or_value = instance[method].apply(instance, args);
if (promise_or_value instanceof Promise) {
return promise_or_value;
} else {
return Promise.resolve(promise_or_value);
}
} catch (e) {
return Promise.reject(e);
}
}
static onMessage(replyPort, e) {
const type = e.data.type;
const id = e.data.id;
const method = e.data.method;
const args = e.data.args;
const instance = e.data.static ? Decryptor : (Decryptor.isWorker() ? self : window).decryptor;
if (type === "job") {
Decryptor._asyncReturn(instance, method, args)
.then(
(result) => { return {type: "reply", success: true, result: result}; },
(error) => { return {type: "reply", success: false, result: error.message}; }
)
.then((reply) => {
reply.id = id;
replyPort.postMessage(reply);
});
} else if (type === "reply") {
const callbacks = decryptor._jobMap.get(e.data.id);
if (e.data.success) {
if (callbacks.success) callbacks.success(e.data.result);
} else {
if (callbacks.error) callbacks.error(new Error(e.data.result));
}
this._jobMap.delete(e.data.id);
};
let pool = Array();
for (let i = 0; i < this._numWorkers; i++) {
let worker = new Worker(config.worker_script);
worker.onmessage = callback;
pool.push(worker);
}
this._workerPool = pool;
let notReadyWorkers = this._numWorkers;
for (let i = 0; i < this._numWorkers; i++) {
this.dispatchJob("new", [config])
.then(() => {
if (--notReadyWorkers <= 0) {
this._workerReady = true;
Decryptor._sendEvent(document, "DecryptWorkerReady");
}
});
decryptor._jobMap.delete(e.data.id);
}
}
/*
* method: string
* args: Array
*/
dispatchJob(method, args) {
if (this._role === "main") {
return new Promise((success, error) => {
const jobId = this._jobCount++;
const worker = this._workerPool[jobId % this._numWorkers];
this._jobMap.set(jobId, {success: success, error: error});
Decryptor._postJobToWorker(jobId, worker, method, args);
});
} else if (this._role === "worker") {
return Decryptor._asyncReturn(this, method, args)
.then(
(result) => { return {success: true, result: result}; },
(error) => { return {success: false, result: error.message}; }
);
}
static async onServiceWorkerInstall(e) {
console.log("service worker on install: ", e);
e.waitUntil(self.skipWaiting());
}
static _asyncReturn(instance, method, args) {
if (method in instance && instance[method] instanceof Function) {
try {
let promise_or_value = instance[method].apply(instance, args);
if (promise_or_value instanceof Promise) {
return promise_or_value;
} else {
return Promise.resolve(promise_or_value);
}
} catch (e) {
return Promise.reject(e);
static onServiceWorkerActivate(e) {
console.log("service worker on activate: ", e);
e.waitUntil(self.clients.claim());
}
static onServiceWorkerMesssage(e) {
return Decryptor.onMessage(e.source, e);
}
static async _swHandleFetch(e) {
const request = e.request;
try {
const cached_response = await caches.match(request);
if (cached_response) {
// TODO: handle cache expiration
console.debug(`Found cached response for ${request.url}`);
return cached_response;
}
} else {
return Promise.reject(new Error(`no such method: ${method}`))
} catch (error) {
console.error("Caches.match error!");
}
}
static _postJobToWorker(jobId, worker, method, args) {
const job = {
id: jobId,
method: method,
args: args
};
worker.postMessage(job);
}
let response;
try {
response = await fetch(request);
} catch (error) {
console.debug(`Fetch failed when trying for ${request.url}: ${error}`);
throw error;
}
/* worker thread only */
static onWorkerMessage(e) {
const id = e.data.id;
const method = e.data.method;
const args = e.data.args;
if (!response.ok) {
console.debug(`Fetch succeeded but server returned non-2xx: ${request.url}`);
return response;
}
if (method === "new") {
self.decryptor = new Decryptor(...args);
self.addEventListener(
"DecryptWorkerReady",
(e) => self.postMessage({id: id, success: true, result: "worker ready"}),
{once: true, passive: true}
const is_image = [
request.destination === "image",
(() => {
const content_type = response.headers.get("content-type");
return content_type && content_type.startsWith("image");
})()
];
if (!is_image.some((e) => e)) {
console.debug(`Fetch succeeded but response is likely not an image ${request.url}`);
return response;
}
const response_clone = response.clone();
const encrypted_blob = await response.blob();
const encrypted_arraybuffer = await encrypted_blob.arrayBuffer();
if (!(await Decryptor.checkMagicString(encrypted_arraybuffer))) {
console.debug(`Response image is not encrypted: ${request.url}`);
return response_clone;
}
console.debug(`Fetch succeeded with encrypted image ${request.url}, trying to decrypt`);
if (!Decryptor.isInitialized()) {
console.debug(`Service worker not initialized on fetch event`);
return Decryptor.errorResponse.clone();
}
let decrypted_blob;
try {
decrypted_blob = new Blob(
[await self.decryptor._decrypt(encrypted_arraybuffer)],
{type: encrypted_blob.type}
);
} else {
self.decryptor
.dispatchJob(method, args)
.then((reply) => {
reply.id = id;
self.postMessage(reply);
});
} catch (error) {
console.debug(`Decryption failed for ${request.url}: ${error.message}`);
console.error("Corrupted data??? This shouldn't occur.");
return Decryptor.errorResponse.clone();
}
const decrypted_response = new Response(
decrypted_blob,
{
status: response.status,
statusText: response.statusText,
headers: response.headers
}
);
decrypted_response.headers.set("content-length", decrypted_blob.size);
const decrypted_response_clone = decrypted_response.clone();
const cache = await caches.open("v1");
cache.put(request, decrypted_response_clone);
console.debug(`Responding with decrypted response ${request.url}`);
return decrypted_response;
}
static onServiceWorkerFetch(e) {
e.respondWith(Decryptor._swHandleFetch(e));
}
}
Decryptor.imagePlaceholderURL = URL.createObjectURL(new Blob([
Decryptor.MAGIC_STRING = "_e_n_c_r_y_p_t_e_d_";
Decryptor.MAGIC_STRING_ARRAYBUFFER = (new TextEncoder("utf-8")).encode(Decryptor.MAGIC_STRING);
Decryptor.IV_LENGTH = 12;
Decryptor.keyCheckURL = "static/keycheck.txt";
Decryptor.imagePlaceholderBlob = new Blob([
`<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="#ffffff" id="canvas_background" height="202" width="202" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>
<title>background</title>
<rect fill="#ffffff" id="canvas_background" height="202" width="202" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>
</g>
<g>
<title>Layer 1</title>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="36" id="svg_1" y="61.949997" x="22.958336" stroke-width="0" stroke="#000" fill="#7f7f7f">Could not</text>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="36" id="svg_4" y="112.600002" x="65.974998" stroke-width="0" stroke="#000" fill="#7f7f7f">load</text>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="36" id="svg_5" y="162.949997" x="50.983334" stroke-width="0" stroke="#000" fill="#7f7f7f">image</text>
<title>Layer 1</title>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="36" id="svg_1" y="61.949997" x="22.958336" stroke-width="0" stroke="#000" fill="#7f7f7f">Could not</text>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="36" id="svg_4" y="112.600002" x="65.974998" stroke-width="0" stroke="#000" fill="#7f7f7f">load</text>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="36" id="svg_5" y="162.949997" x="50.983334" stroke-width="0" stroke="#000" fill="#7f7f7f">image</text>
</g>
</svg>`], {type: "image/svg+xml"}));
</svg>`], {type: "image/svg+xml"});
Decryptor.errorResponse = new Response(
Decryptor.imagePlaceholderBlob,
{
status: 200,
statusText: "OK",
headers: {
"content-type": "image/svg+xml"
}
}
);

View File

@@ -0,0 +1 @@
This file will be decrypted to test if the password supplied by the user is correct.

View File

@@ -0,0 +1,7 @@
"use strict"
importScripts("static/decrypt.js");
oninstall = Decryptor.onServiceWorkerInstall;
onactivate = Decryptor.onServiceWorkerActivate;
onfetch = Decryptor.onServiceWorkerFetch;
onmessage = Decryptor.onServiceWorkerMesssage;
Decryptor.init({});

View File

@@ -3,7 +3,7 @@
<script>
Decryptor.init({
password: "{{ encrypt_options.filtered_password }}",
worker_script: "{{ theme.url }}/decrypt-worker.js",
sw_script: "{{ theme.url }}/../sw.js",
galleryId: "{{ encrypt_options.galleryId }}",
gcm_tag: "{{ encrypt_options.escaped_gcm_tag }}",
kdf_salt: "{{ encrypt_options.escaped_kdf_salt }}",