diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1616c8e..1f33d4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,8 @@ jobs: run: yarn - name: Build artifacts run: yarn build:prod:zip:all + env: + BUSTER_SECRETS: ${{ secrets.BUSTER_SECRETS }} - name: Hash artifacts run: sha256sum artifacts/*/* if: startsWith(github.ref, 'refs/tags/v') diff --git a/.gitignore b/.gitignore index 8cec733..455996f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .assets/ +secrets.json node_modules/ artifacts/ diff --git a/gulpfile.js b/gulpfile.js index 5045e71..4903d6c 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,8 +11,9 @@ const jsonmin = require('gulp-jsonmin'); const htmlmin = require('gulp-htmlmin'); const imagemin = require('gulp-imagemin'); const del = require('del'); -const {ensureDirSync} = require('fs-extra'); +const {ensureDirSync, readJsonSync} = require('fs-extra'); const sharp = require('sharp'); +const CryptoJS = require('crypto-js'); const targetEnv = process.env.TARGET_ENV || 'firefox'; const isProduction = process.env.NODE_ENV === 'production'; @@ -193,6 +194,33 @@ See the LICENSE file for further information. return src(['LICENSE']).pipe(dest(distDir)); } +function secrets(done) { + try { + let data = process.env.BUSTER_SECRETS; + if (data) { + data = JSON.parse(data); + } else { + data = readJsonSync('secrets.json'); + } + data = JSON.stringify(data); + + const key = CryptoJS.SHA256( + readFileSync(path.join(distDir, 'src/background/script.js')).toString() + + readFileSync(path.join(distDir, 'src/solve/script.js')).toString() + ).toString(); + + const ciphertext = CryptoJS.AES.encrypt(data, key).toString(); + + writeFileSync(path.join(distDir, 'secrets.json.enc'), ciphertext); + } catch (err) { + console.log( + 'Secrets have not been set, secrets.json.enc will not be included in the extension package.' + ); + } + + done(); +} + function zip(done) { exec( `web-ext build -s dist/${targetEnv} -a artifacts/${targetEnv} -n '{name}-{version}-${targetEnv}.zip' --overwrite-dest`, @@ -217,7 +245,9 @@ function inspect(done) { exports.build = series( clean, - parallel(js, html, css, images, fonts, locale, manifest, license) + parallel(js, html, css, images, fonts, locale, manifest, license), + secrets ); exports.zip = zip; exports.inspect = inspect; +exports.secrets = secrets; diff --git a/package.json b/package.json index a70724c..9eb4b8a 100755 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "audiobuffer-to-wav": "^1.0.0", "bowser": "^2.11.0", "core-js": "^3.6.5", + "crypto-js": "^4.0.0", "ext-components": "dessant/ext-components#^0.4.0", "ext-contribute": "dessant/ext-contribute#^0.3.3", "fontsource-roboto": "^3.0.3", diff --git a/secrets.json.example b/secrets.json.example new file mode 100644 index 0000000..1272511 --- /dev/null +++ b/secrets.json.example @@ -0,0 +1,34 @@ +{ + "witApiKeys": { + "arabic": "", + "bengali": "", + "catalan": "", + "chinese": "", + "dutch": "", + "english": "", + "finnish": "", + "french": "", + "german": "", + "hindi": "", + "indonesian": "", + "italian": "", + "japanese": "", + "kannada": "", + "korean": "", + "malay": "", + "malayalam": "", + "marathi": "", + "polish": "", + "portuguese": "", + "russian": "", + "sinhala": "", + "spanish": "", + "swedish": "", + "tamil": "", + "telugu": "", + "thai": "", + "turkish": "", + "urdu": "", + "vietnamese": "" + } +} diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index eceb7c3..131042b 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -304,6 +304,11 @@ "description": "Value of the option." }, + "optionValue_witSpeechApiLang_marathi": { + "message": "Marathi", + "description": "Value of the option." + }, + "optionValue_witSpeechApiLang_polish": { "message": "Polish", "description": "Value of the option." diff --git a/src/background/main.js b/src/background/main.js index 4a40738..f6cbef1 100644 --- a/src/background/main.js +++ b/src/background/main.js @@ -1,5 +1,8 @@ import browser from 'webextension-polyfill'; import audioBufferToWav from 'audiobuffer-to-wav'; +import aes from 'crypto-js/aes'; +import sha256 from 'crypto-js/sha256'; +import utf8 from 'crypto-js/enc-utf8'; import {initStorage} from 'storage/init'; import storage from 'storage/storage'; @@ -29,9 +32,10 @@ import { ibmSpeechApiUrls, microsoftSpeechApiUrls } from 'utils/data'; -import {targetEnv, clientAppVersion, witApiKeys} from 'utils/config'; +import {targetEnv, clientAppVersion} from 'utils/config'; let nativePort; +let secrets; function getFrameClientPos(index) { let currentIndex = -1; @@ -221,12 +225,37 @@ async function prepareAudio(audio) { return audioBufferToWav(audioSlice); } +async function loadSecrets() { + try { + const ciphertext = await (await fetch('/secrets.json.enc')).text(); + + const key = sha256( + (await (await fetch('/src/background/script.js')).text()) + + (await (await fetch('/src/solve/script.js')).text()) + ).toString(); + + secrets = JSON.parse(aes.decrypt(ciphertext, key).toString(utf8)); + } catch (err) { + secrets = {}; + const {speechService} = await storage.get('speechService', 'sync'); + if (speechService === 'witSpeechApiDemo') { + await storage.set({speechService: 'witSpeechApi'}, 'sync'); + } + } +} + async function getWitSpeechApiKey(speechService, language) { if (speechService === 'witSpeechApiDemo') { - if (language === 'english') { - return witApiKeys[language][getRandomInt(1, 4) - 1]; - } else { - return witApiKeys[language]; + if (!secrets) { + await loadSecrets(); + } + const apiKeys = secrets.witApiKeys; + if (apiKeys) { + const apiKey = apiKeys[language]; + if (Array.isArray(apiKey)) { + return apiKey[getRandomInt(1, apiKey.length) - 1]; + } + return apiKey; } } else { const {witSpeechApiKeys: apiKeys} = await storage.get( @@ -453,7 +482,11 @@ async function transcribeAudio(audioUrl, lang) { } } - return solution; + if (!solution) { + showNotification({messageId: 'error_captchaNotSolved'}); + } else { + return solution; + } } async function onMessage(request, sender) { @@ -540,7 +573,7 @@ async function onInstall(details) { await browser.tabs.insertCSS(tabId, { frameId, runAt: 'document_idle', - file: 'src/solve/reset-button.css' + file: '/src/solve/reset-button.css' }); await browser.tabs.executeScript(tabId, { diff --git a/src/solve/main.js b/src/solve/main.js index 52bb775..743e422 100644 --- a/src/solve/main.js +++ b/src/solve/main.js @@ -341,10 +341,6 @@ async function solve(simulateUserInput, clickEvent) { }); if (!solution) { - browser.runtime.sendMessage({ - id: 'notification', - messageId: 'error_captchaNotSolved' - }); return; } diff --git a/src/utils/config.js b/src/utils/config.js index fe55eea..0bb1315 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -2,41 +2,4 @@ const targetEnv = process.env.TARGET_ENV; const clientAppVersion = '0.3.0'; -const witApiKeys = { - arabic: 'AD6RLFYBWRGGJD76SWKALZMUFVGMVCTB', - bengali: 'ACETQ4IVS5ITSUTSWBETY2QZKBJVIQAI', - catalan: 'YBAZZV6ITGFD3C2QX7CANYQPMGUOI7RK', - chinese: 'KBBALJMRKYDJJDMC4NJ32VWCBKFJFIIU', - dutch: 'T43TEUNW4HDQFNUTIA3EYTTD4A22AS4H', - english: [ - 'BQE4QJJNYC6JVTDULHYIZHQOQFWUVDCQ', - 'EQAXGAJVZIMI3YMILN2XQZY7IWGUPKGL', - 'AA3LFNYPDNXX4MUDQQJNLT74YFRQ3M6F', - 'DRTXENV66YE44PRYOJGWPX2BPGZNPYTQ' - ], - finnish: '3GOWLMYD7DUY72XTPJW6QTVZSK2QEAPT', - french: 'JLNITTO2D4KMEOGQ6MTSN634ADK62VZ7', - german: 'LPVVXWRBFTVBOOHZAEJC3QRM6E3UMD7I', - hindi: 'GZY4V5WN75QHO4Q5PMCU5AIDKQZLKP4L', - indonesian: 'NBKNPYO6ZTVSSTH2AT3H5DM7OUOIAN3F', - italian: 'JNSYW453QWIXNR3TOCE2K7NO5GGU3KYL', - japanese: 'S3IDY5JTJVJ6XBISPGZBZQHQXO23BXEB', - kannada: 'MZ7NKAQGQI3T4JH6YRIEL6K4AWDCDE3A', - korean: '5UWNE4YDBZTSNWWBYC4XZEWJGBUI24YL', - malay: 'YW73R7QWXQT23GE5CTM4R2IVNCH2KMS3', - malayalam: 'KIZY64QLOGZ7JWJWX2DKE247KV6VHAYT', - polish: 'G73FWND7N5O3ZCSBCHF6NDOV2QIXW2MF', - portuguese: 'N7D57ZCGWLRSMQNHQYKWC3OHVNTZI5UT', - russian: 'V2IMQUEC5M7TSYAY3VCKSGQ7HOCWFPFY', - sinhala: 'N4RYKKDHSXB6IID6JILOX5A27UJLBJJ7', - spanish: 'JU6JOEJBHRH7IILGSSUVEYO552JUNMG4', - swedish: '33RMFTS5OKLEWV2KGX2HMTP7M3VCKXC4', - tamil: 'MNIEHY7BQVMEU6BBJBL57E4QYNHOUIDB', - telugu: 'MXE6BFOLRMR72GLUFEK3Y7NOUBPV5W4G', - thai: 'MA3JUGAZNCCTBBTO2K7HR3RWJ3LILU6E', - turkish: 'HZJRJNL3C3KLBXZQ4BPIODYRKXXRKYCL', - urdu: 'T5FSZMLJ55LNPBIILM2A2SVEA3YIIYL3', - vietnamese: 'ULJN5SUWA3HJKLPKHOSTOH5AJSSWN5H3' -}; - -export {targetEnv, clientAppVersion, witApiKeys}; +export {targetEnv, clientAppVersion}; diff --git a/src/utils/data.js b/src/utils/data.js index 9e9dda5..df9e23c 100755 --- a/src/utils/data.js +++ b/src/utils/data.js @@ -290,7 +290,7 @@ const captchaWitSpeechApiLangCodes = { lt: '', // Lithuanian ms: 'malay', // Malay ml: 'malayalam', // Malayalam - mr: '', // Marathi + mr: 'marathi', // Marathi mn: '', // Mongolian no: '', // Norwegian fa: '', // Persian diff --git a/yarn.lock b/yarn.lock index da952ca..df3c35b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3730,6 +3730,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc" + integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"