Compare commits

...

9 Commits

Author SHA1 Message Date
kev e39251cafc Update readme to reflect whitelist ability 2022-02-20 00:53:05 -06:00
Kevin F 866777686c Finished popup approach 2022-02-20 00:50:15 -06:00
Kevin F b3c0097270 Work on toolbar buffer solution 2022-02-19 18:45:20 -06:00
Kevin F d70732d12b Work on toolbar buffer solution 2022-02-19 12:34:08 -06:00
Kevin F a63066edbd Work on toolbar buffer solution 2022-02-19 01:04:13 -06:00
Kevin F 9dc7131f81 Fix and clarify readme 2022-02-18 17:17:27 -06:00
Kevin F bd81c3c582 work on prompt() solution 2021-10-12 18:36:09 +00:00
Kevin F b84a28f062 work on prompt() solution 2021-10-12 07:34:19 +00:00
Kevin F 9afe3ac2ab work on prompt() solution 2021-10-11 23:56:11 +00:00
8 changed files with 216 additions and 9 deletions

View File

@ -12,13 +12,13 @@ There are spy libraries that can determine how long keys are held down and the t
This addon was created to skew the key press timing by limiting the speed of presses. The speed is changed randomly on each page load, with a min of 150ms and a max of 300ms.
It uses a less than ideal thread locking solution, as simply buffering text does not stop event listeners from spies.
It uses a less than ideal thread locking solution, as simply buffering text does not stop event listeners from spies. **Now it also has a non-default setting to use prompt() on non-password single line input elements. This reduces CPU usage and increase typing speed while offering better protection than the delay approach.**
You can whitelist domains that you trust on the addon settings page, but it was decided not to support changing the key speed manually as that would add another fingerprinting avenue.
Development Roadmap:
* Menu button to add whitelisted domain more quickly
* ~~Menu button to add whitelisted domain more quickly~~
* Buffered window/menu/popup option to type quickly but prevent page surveillance
* More throrough testing against different avenues of keyboard surveillance
* Stylometry protection
@ -29,6 +29,7 @@ Development Roadmap:
* **This addon may not defeat all types of keyboard biometric surveillance**, however it was tested against the Keytrac and TypingDNA demos and it worked well.
* **This addon does not yet deal with stylometry**
* Some websites override all key events in which case it is not (yet) possible to protect against keyboarding analysis there
* You may see higher CPU usage while typing. This is due do the unideal locking solution described above
* Do not confuse this with spying keyboard apps on mobile devices, this cannot address that.
* Not tested on Firefox Android
@ -36,9 +37,9 @@ Development Roadmap:
Sources:
https://www.keytrac.net/en/
https://www.typingdna.com/
www dot keytrac dot net/en/
www dot typingdna dot com/
https://www.whonix.org/wiki/Stylometry
[https://www.whonix.org/wiki/Keystroke_Deanonymization](https://www.whonix.org/wiki/Keystroke_Deanonymization)
(Spy companies not linked to avoid helping their SEO)

View File

@ -16,13 +16,70 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
let lastActive = null
const defaultHosts = "<all_urls>";
let appCode = function (){
let lastActive = document.createElement("p")
let popupEnabled = false
let popupGetter = browser.storage.sync.get("keyboardprivacyprompt")
popupGetter.then(function(val){
popupEnabled = val['keyboardprivacyprompt']
console.debug(popupEnabled)
}, function(){})
let clearSelect = function()
{
if (window.getSelection) {window.getSelection().removeAllRanges()}
else if
(document.selection) {
document.selection.empty()
}
}
let minValue = 75
let maxValue = 150
let time = 0
let last = null
let doPopup = function(e) {
let text = ""
let inputType = ""
if (e.target.tagName === "INPUT"){
inputType = e.target.getAttribute("type")
if (Date.now() - time < 1000 && e.target === last){
return
}
if (! inputType){
text = prompt("[PrivateKeyboard]\n\nEnter text for the text field:", e.target.value)
}
else if (! ["text", "search", "email", "number"].includes(inputType.toLowerCase())){
return
}
else{
text = prompt("[PrivateKeyboard]\n\nEnter text for the " + inputType + " field:", e.target.value)
}
if (text !== null) {
e.target.value = text
}
last = e.target
clearSelect()
time = Date.now()
}
}
let popupMode = function(){
if (popupEnabled){
document.addEventListener('focusin', doPopup, true)
}
}
let mainKeyboardPrivacy = function(){
popupMode()
function getRandNum(){
let buf = new Uint8Array(1)
@ -63,9 +120,24 @@ let appCode = function (){
}, )
document.addEventListener('keydown', function(e){
if (e.key.startsWith('Arrow') || e.key.startsWith('Page')){
if (e.key.startsWith('Arrow') || e.key.startsWith('Page') ||
e.key == "Enter" || e.key == "Control" || e.key == "Tab"
){
return true;
}
else if (e.key.length > 1 && e.key.startsWith("F")){
// Ignore function keys (unicode can also be >1 length so check for f)
return true;
}
else{
}
if (popupEnabled){
setTimeout(
function(){doPopup(e)}, 10)
}
pausecomp(down);
return true;
})
@ -126,6 +198,38 @@ let appCode = function (){
let whitelist = browser.storage.sync.get("keyboardprivacywhitelist");
whitelist.then(shouldRunKeyboardPrivacy, noKeyboardPrivacySettings)
document.addEventListener('focus', function(e){
let active = document.activeElement;
if (active.tagName == "INPUT" || active.tagName == "TEXTAREA") {
lastActive = active
}
})
browser.runtime.onMessage.addListener(request => {
let active = document.activeElement
if (request.getCurrent){
if (active.tagName != "INPUT" && active.tagName != "TEXTAREA") {
if (lastActive.tagName != "INPUT" && lastActive.tagName != "TEXTAREA"){
console.debug("no current active or last active")
return Promise.resolve({response: false});
}
return Promise.resolve({response: lastActive.value});
}
return Promise.resolve({response: active.value});
}
if (active.tagName != "INPUT" && active.tagName != "TEXTAREA"){
lastActive.value = request.keys
}
else{
document.activeElement.value = request.keys
lastActive = document.activeElement
}
return Promise.resolve({response: "ack"});
});
}
const dummyStr = ''

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "Private Keyboard",
"version": "1.8",
"version": "2.0",
"description": "Protect against keyboard biometrics",
@ -32,6 +32,15 @@
"default_popup": "settings/button.html"
},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Ctrl+1"
}
}
},
"browser_specific_settings": {
"gecko": {

View File

@ -5,10 +5,14 @@
<title>Private Keyboard</title>
<link rel="stylesheet" href="./button.css">
<script src="./button.js" defer></script>
<script src="./keybuffer2.js" defer></script>
</head>
<body>
<header id="siteDomain"></header>
<button id="toggleSite">Disable Keyboard Privacy</button>
<p id="reloadPage">Refresh the page to apply</p>
<br>
<textarea tabindex="0" id="keyBuffer" cols="20" rows="10" placeholder="Text typed here will be sent to active input/textarea elements. Breaks on many sites, but is faster than typing directly on the page"></textarea>
</body>
</html>

View File

@ -116,4 +116,4 @@ document.getElementById('toggleSite').onclick = async function(){
document.getElementById('toggleSite').removeAttribute('disabled')
}, 3000)
document.getElementById('reloadPage').style.display = 'block'
}
}

76
settings/keybuffer2.js Normal file
View File

@ -0,0 +1,76 @@
let started = false
function onError(error) {
console.error(`Error: ${error}`);
}
function doSendMsg(msg, tabs) {
for (let tab of tabs) {
browser.tabs.sendMessage(
tab.id,
{keys: msg, getCurrent: false}
).then(response => {
console.log("Message from the content script:");
console.log(response.response);
}).catch(onError);
}
}
let sender = async function(e){
if (e.key == 'Tab') {
e.preventDefault();
let start = this.selectionStart;
let end = this.selectionEnd;
// set textarea value to: text before caret + tab + text after caret
this.value = this.value.substring(0, start) +
"\t" + this.value.substring(end);
// put caret at right position again
this.selectionStart =
this.selectionEnd = start + 1;
}
let sendMessageToTabs = function(tabs){
doSendMsg(document.getElementById('keyBuffer').value, tabs)
}
browser.tabs.query({
currentWindow: true,
active: true
}).then(sendMessageToTabs).catch(onError);
}
function getCurrent(){
browser.tabs.query({
currentWindow: true,
active: true
}).then(function(tabs){
for (let tab of tabs) {
browser.tabs.sendMessage(
tab.id,
{keys: "", getCurrent: true}
).then(response => {
console.debug(response)
if (response.response === undefined || response.response === false){
//document.getElementById('keyBuffer').value = "No element in focus. Focus an element and reopen this."
return
}
document.getElementById('keyBuffer').value = response.response
}).catch(onError);
}
}).catch(onError);
}
getCurrent()
document.getElementById("keyBuffer").focus()
document.getElementById('keyBuffer').onkeydown = sender
document.getElementById('keyBuffer').onpaste = sender

View File

@ -9,6 +9,9 @@
<body>
<form>
<input type="checkbox" id="usePrompt">
<label for="usePrompt">Use prompt() dialogs on single-line inputs. Faster typing and less CPU usage, but can break some websites and features like autocomplete.</label>
<br>
<input type="checkbox" id="whitelistLAN">
<label for="whitelistLAN">Whitelist private IP + localhost range hostnames (e.g. routers)</label>
<h1>Trusted domains (comma delimited)</h1>

View File

@ -24,6 +24,9 @@ function saveOptions(e) {
browser.storage.sync.set({
keyboardprivacylan: document.querySelector("#whitelistLAN").checked
})
browser.storage.sync.set({
keyboardprivacyprompt: document.querySelector("#usePrompt").checked
})
document.getElementById('saved').innerHTML = '<br><b>Saved</b>'
setTimeout(function(){
document.getElementById('saved').innerHTML = '<br>'
@ -42,6 +45,10 @@ function saveOptions(e) {
document.querySelector("#whitelistLAN").checked = result['keyboardprivacylan']
}
function setCurrentPrompt(result){
document.querySelector("#usePrompt").checked = result['keyboardprivacyprompt']
}
function onError(error) {
console.log(`Error: ${error}`);
}
@ -51,6 +58,9 @@ function saveOptions(e) {
let gettingLAN = browser.storage.sync.get("keyboardprivacylan");
gettingLAN.then(setCurrentLAN, onError);
let gettingPrompt = browser.storage.sync.get("keyboardprivacyprompt");
gettingPrompt.then(setCurrentPrompt, onError);
}
document.addEventListener("DOMContentLoaded", restoreOptions);