diff --git a/INSTALLATION.md b/INSTALLATION.md index 1569b65..436299c 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -19,6 +19,7 @@ NOTE: Use either HCAPTCHA_ or RECAPTHCA_, not both. - ARGON_TIME - argon2 iterations - ARGON_KB - argon2 memory usage in KB - POW_DIFFICULTY - pow difficulty +- POW_TYPE - type of ahsh algorithm for pow "argon2" or "sha256" #### Run in docker (for testing/development) diff --git a/docker-compose.yml b/docker-compose.yml index b5db76d..768642d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,7 @@ services: - ARGON_TIME=2 - ARGON_KB=512 - POW_DIFFICULTY=24 + - POW_TYPE=argon2 - TOR_CONTROL_PORT_PASSWORD=changeme nginx: diff --git a/src/js/challenge.js b/src/js/challenge.js index a678b34..4dcd4de 100644 --- a/src/js/challenge.js +++ b/src/js/challenge.js @@ -81,7 +81,7 @@ const powFinished = new Promise(resolve => { window.addEventListener('DOMContentLoaded', async () => { - const { time, kb, pow, diff } = document.querySelector('[data-pow]').dataset; + const { time, kb, pow, diff, mode } = document.querySelector('[data-pow]').dataset; window.addEventListener('storage', event => { if (event.key === 'basedflare-pow-response' && !finished) { console.log('Got answer', event.newValue, 'from storage event'); @@ -94,14 +94,15 @@ const powFinished = new Promise(resolve => { }); if (!wasmSupported) { - return insertError('browser does not support WebAssembly.'); + return insertError('Browser does not support WebAssembly.'); } - const argonOpts = { + const powOpts = { time: time, mem: kb, hashLen: 32, parallelism: 1, - type: argon2.ArgonType.Argon2id, + type: argon2 ? argon2.ArgonType.Argon2id : null, + mode: mode, }; console.log('Got pow', pow, 'with difficulty', diff); const eHashes = Math.pow(16, Math.floor(diff/8)) * ((diff%8)*2); @@ -128,34 +129,16 @@ const powFinished = new Promise(resolve => { submitPow(`${pow}#${answer}`); } for (let i = 0; i < workerThreads; i++) { - const argonWorker = new Worker('/.basedflare/js/worker.js'); - argonWorker.onmessage = messageHandler; - workers.push(argonWorker); + const powWorker = new Worker('/.basedflare/js/worker.js'); + powWorker.onmessage = messageHandler; + workers.push(powWorker); } for (let i = 0; i < workerThreads; i++) { await new Promise(res => setTimeout(res, 10)); - workers[i].postMessage([userkey, challenge, diff, diffString, argonOpts, i, workerThreads]); + workers[i].postMessage([userkey, challenge, diff, diffString, powOpts, i, workerThreads]); } } else { - //TODO: remove this section, it _will_ cause problems - console.warn('No webworker support, running in main/UI thread!'); - let i = 0; - const start = Date.now(); - while(true) { - const hash = await argon2.hash({ - pass: challenge + i.toString(), - salt: userkey, - ...argonOpts, - }); - if (hash.hashHex.startsWith(diffString) - && ((parseInt(hash.hashHex[diffString.length],16) & - 0xff >> (((diffString.length+1)*8)-diff)) === 0)) { - console.log('Main thread found solution:', hash.hashHex, 'in', (Date.now()-start)+'ms'); - break; - } - ++i; - } - submitPow(`${pow}#${i}`); + return insertError('Browser does not support Web Workers.'); } }); }).then((powResponse) => { diff --git a/src/js/worker.js b/src/js/worker.js index b401b49..9a120f0 100644 --- a/src/js/worker.js +++ b/src/js/worker.js @@ -1,7 +1,15 @@ -importScripts('/.basedflare/js/argon2.js'); +async function nativeHash(data, method) { + const buffer = new TextEncoder('utf-8').encode(data); + const hashBuffer = await crypto.subtle.digest(method, buffer) + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); +} onmessage = async function(e) { - const [userkey, challenge, diff, diffString, argonOpts, id, threads] = e.data; + const [userkey, challenge, diff, diffString, powOpts, id, threads] = e.data; + if (powOpts.mode === "argon2") { + importScripts('/.basedflare/js/argon2.js'); + } console.log('Worker thread', id, 'started'); let i = id; if (id === 0) { @@ -10,15 +18,21 @@ onmessage = async function(e) { }, 500); } while(true) { - const hash = await argon2.hash({ - pass: challenge + i.toString(), - salt: userkey, - ...argonOpts, - }); + let hash; + if (powOpts.mode === "argon2") { + const argonHash = await argon2.hash({ + pass: challenge + i.toString(), + salt: userkey, + ...powOpts, + }); + hash = argonHash.hashHex; + } else { + hash = await nativeHash(userkey + challenge + i.toString(), 'sha-256'); + } // This throttle seems to really help some browsers not stop the workers abruptly i % 10 === 0 && await new Promise(res => setTimeout(res, 10)); - if (hash.hashHex.startsWith(diffString) - && ((parseInt(hash.hashHex[diffString.length],16) & + if (hash.toString().startsWith(diffString) + && ((parseInt(hash[diffString.length],16) & 0xff >> (((diffString.length+1)*8)-diff)) === 0)) { console.log('Worker', id, 'found solution'); postMessage([id, i]); diff --git a/src/lua/libs/utils.lua b/src/lua/libs/utils.lua index eea98ff..00cd71a 100644 --- a/src/lua/libs/utils.lua +++ b/src/lua/libs/utils.lua @@ -43,6 +43,9 @@ end -- return true if hash passes difficulty function _M.checkdiff(hash, diff) + if #hash == 0 then + return false + end local i = 1 for j = 0, (diff-8), 8 do if hash:sub(i, i) ~= "0" then diff --git a/src/lua/scripts/bot-check.lua b/src/lua/scripts/bot-check.lua index c8e5236..f42b28a 100644 --- a/src/lua/scripts/bot-check.lua +++ b/src/lua/scripts/bot-check.lua @@ -9,13 +9,12 @@ local url = require("url") local utils = require("utils") local cookie = require("cookie") local json = require("json") -local sha = require("sha") local randbytes = require("randbytes") local templates = require("templates") -- POW +local pow_type = os.getenv("POW_TYPE") or "argon2" local pow_difficulty = tonumber(os.getenv("POW_DIFFICULTY") or 18) - -- argon2 local argon2 = require("argon2") local argon_kb = tonumber(os.getenv("ARGON_KB") or 6000) @@ -25,9 +24,8 @@ argon2.m_cost(argon_kb) argon2.parallelism(1) argon2.hash_len(32) argon2.variant(argon2.variants.argon2_id) - -- sha2 --- TODO +local sha = require("sha") -- environment variables local captcha_secret = os.getenv("HCAPTCHA_SECRET") or os.getenv("RECAPTCHA_SECRET") @@ -144,14 +142,20 @@ function _M.view(applet) captcha_sitekey, captcha_script_src) else pow_body = templates.pow_section - noscript_extra_body = string.format(templates.noscript_extra, user_key, + local noscript_extra + if pow_type == "argon2" then + noscript_extra = templates.noscript_extra_argon2 + else + noscript_extra = templates.noscript_extra_sha256 + end + noscript_extra_body = string.format(noscript_extra, user_key, challenge_hash, expiry, signature, math.ceil(pow_difficulty/8), argon_time, argon_kb) end -- sub in the body sections response_body = string.format(templates.body, combined_challenge, - pow_difficulty, argon_time, argon_kb, + pow_difficulty, argon_time, argon_kb, pow_type, site_name_body, pow_body, captcha_body, noscript_extra_body, ray_id) response_status_code = 403 @@ -200,11 +204,14 @@ function _M.view(applet) if given_signature == generated_signature then -- do the work with their given answer - local full_hash = argon2.hash_encoded(given_challenge_hash .. given_answer, given_user_key) - - -- check the output is correct - local hash_output = utils.split(full_hash, '$')[6]:sub(0, 43) -- https://github.com/thibaultcha/lua-argon2/issues/37 - local hex_hash_output = sha.bin_to_hex(sha.base64_to_bin(hash_output)); + local hex_hash_output = "" + if pow_type == "argon2" then + local encoded_argon_hash = argon2.hash_encoded(given_challenge_hash .. given_answer, given_user_key) + local trimmed_argon_hash = utils.split(encoded_argon_hash, '$')[6]:sub(0, 43) -- https://github.com/thibaultcha/lua-argon2/issues/37 + hex_hash_output = sha.bin_to_hex(sha.base64_to_bin(trimmed_argon_hash)); + else + hex_hash_output = sha.sha256(given_user_key .. given_challenge_hash .. given_answer) + end if utils.checkdiff(hex_hash_output, pow_difficulty) then diff --git a/src/lua/scripts/templates.lua b/src/lua/scripts/templates.lua index 02fadb5..93e1b46 100644 --- a/src/lua/scripts/templates.lua +++ b/src/lua/scripts/templates.lua @@ -32,7 +32,7 @@ _M.body = [[ - + %s %s %s @@ -50,7 +50,7 @@ _M.body = [[ ]] -_M.noscript_extra = [[ +_M.noscript_extra_argon2 = [[
No JavaScript?
    @@ -68,6 +68,24 @@ _M.noscript_extra = [[
]] +_M.noscript_extra_sha256 = [[ +
+ No JavaScript? +
    +
  1. +

    Run this in a linux terminal (requires perl):

    + + echo "dXNlIHN0cmljdDt1c2UgRGlnZXN0OjpTSEEgcXcoc2hhMjU2X2hleCk7cHJpbnQgIldvcmtpbmcuLi4iO215JGM9IiRBUkdWWzBdIi4iJEFSR1ZbMV0iO215JGlkPSRBUkdWWzRdKzA7bXkkZD0iMCJ4JGlkO215JGk9MDt3aGlsZSgxKXtsYXN0IGlmICRkIGVxIHN1YnN0ciBzaGEyNTZfaGV4KCRjLCRpKSwwLCRpZDskaSsrfXByaW50IlxuT3V0cHV0OlxuJEFSR1ZbMF0jJEFSR1ZbMV0jJEFSR1ZbMl0jJEFSR1ZbM10jJGlcbiI=" | base64 -d | perl -w - %s %s %s %s %s %s %s + +
  2. Paste the script output into the box and submit: +
    + +
    +
    +
+
+]] + -- title with favicon and hostname _M.site_name_section = [[