Compare commits

..

6 Commits

Author SHA1 Message Date
kev
1e00177aa1 Update 'README.md' 2022-05-09 21:03:05 +00:00
Kevin F
abd9486a37 update readme 2022-02-20 19:13:26 -06:00
Kevin F
922a0bdbd1 Cleanup and lower timing delay 2022-02-20 18:17:10 -06:00
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
6 changed files with 121 additions and 97 deletions

View File

@ -12,14 +12,31 @@ 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. 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. **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. 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: # Usage
* Menu button to add whitelisted domain more quickly This addon has two methods of protecting yourself.
* Buffered window/menu/popup option to type quickly but prevent page surveillance
## On-page protection
The easiest is to simply type on a page as normal. This method breaks the least amount of websites, but it can be frustrating to type a lot and it causes CPU spikes because of a limitation in javascript event processing.
You can whitelist sites by using the button in the addon's popup or by manually adding them on the addon settings page.
## Toolbar input
You can also enter text into the textarea in the toolbar button which will be instantly transfered to the page. This method allows you to type without lag, but it breaks on many websites and can still be tracked if a website is checking input changes as opposed to key events.
To avoid using the mouse to do this, you can press ctrl+1 (config [the same way as any addon](https://support.mozilla.org/en-US/kb/manage-extension-shortcuts-firefox)) to open the popup and press esc when done.
# Development Roadmap:
* ~~Menu button to add whitelisted domain more quickly~~
* ~~Buffered window/menu/popup option to type quickly but prevent page surveillance~~
* Identify websites that break on-page protection by lighting up the icon when on page protection is active
* More throrough testing against different avenues of keyboard surveillance * More throrough testing against different avenues of keyboard surveillance
* Stylometry protection * Stylometry protection
@ -28,10 +45,11 @@ Development Roadmap:
# Caveats # Caveats
* **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 may not defeat all types of keyboard biometric surveillance**, however it was tested against the Keytrac and TypingDNA demos and it worked well.
* **It is recommended to turn on privacy.resistFingerprinting to avoid leaking your keyboard layout; however this addon protects against that as well if you strictly use the toolbar box**
* **This addon does not yet deal with stylometry** * **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 * Some websites override all key events in which case only the toolbar button can protect you (but it likely doesn't function if this is the case)
* You may see higher CPU usage while typing. This is due do the unideal locking solution described above * Typing directly on pages will have CPU spikes due to a browser limitation
* Do not confuse this with spying keyboard apps on mobile devices, this cannot address that. * Of course, this cannot protect against malware on your device outside the webpage.
* Not tested on Firefox Android * Not tested on Firefox Android
----- -----
@ -41,5 +59,9 @@ www dot keytrac dot net/en/
www dot typingdna dot com/ www dot typingdna dot com/
(If you try the spy software demos, keep in mind that this addon changes your fingerprint for on-page typing on reload)
Keep in mind that scripts embeded on pages are not the only way to spy on you. [CSS can record keystrokes](https://css-tricks.com/css-keylogger/) and things such as autocomplete or typing notifications may measure your typing speed server side.
https://www.whonix.org/wiki/Stylometry https://www.whonix.org/wiki/Stylometry
[https://www.whonix.org/wiki/Keystroke_Deanonymization](https://www.whonix.org/wiki/Keystroke_Deanonymization) [https://www.whonix.org/wiki/Keystroke_Deanonymization](https://www.whonix.org/wiki/Keystroke_Deanonymization)

View File

@ -20,9 +20,7 @@ let lastActive = null
const defaultHosts = "<all_urls>"; const defaultHosts = "<all_urls>";
let appCode = function (){ let appCode = function (){
let keyBuffer = "" let lastActive = document.createElement("p")
let elementToSendBuffer = null
let bufferTimeout = setTimeout(function(){}, 1)
let popupEnabled = false let popupEnabled = false
let popupGetter = browser.storage.sync.get("keyboardprivacyprompt") let popupGetter = browser.storage.sync.get("keyboardprivacyprompt")
popupGetter.then(function(val){ popupGetter.then(function(val){
@ -38,8 +36,8 @@ let appCode = function (){
document.selection.empty() document.selection.empty()
} }
} }
let minValue = 75 let minValue = 50
let maxValue = 150 let maxValue = 75
let time = 0 let time = 0
let last = null let last = null
@ -102,9 +100,8 @@ let appCode = function (){
function pausecomp(millis) function pausecomp(millis)
{ {
// Yes i know this wastes cpu. i don't like it either, but it seems a blocking // Yes i know this wastes cpu. i don't like it either, but it is the only way
// approach is needed to prevent spying event listeners from reading key events in *real time* // i know of to have real page keypress events that aren't trackable
// Might use an off-page buffer solution in the future
var date = new Date(); var date = new Date();
var curDate = null; var curDate = null;
do { curDate = new Date(); } do { curDate = new Date(); }
@ -201,62 +198,37 @@ let appCode = function (){
whitelist.then(shouldRunKeyboardPrivacy, noKeyboardPrivacySettings) 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 => { browser.runtime.onMessage.addListener(request => {
let active = document.activeElement
console.log(request.keys); if (request.getCurrent){
if (request.keys == "GetSet"){ if (active.tagName != "INPUT" && active.tagName != "TEXTAREA") {
keyBuffer = elementToSendBuffer.value if (lastActive.tagName != "INPUT" && lastActive.tagName != "TEXTAREA"){
return Promise.resolve({response: elementToSendBuffer.value}); 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"){
clearTimeout(bufferTimeout) lastActive.value = request.keys
if (request.keys == "Backspace"){
keyBuffer = keyBuffer.slice(0, -1)
}
else if(request.keys.length > 4){
// pass
} }
else{ else{
keyBuffer += request.keys; document.activeElement.value = request.keys
lastActive = document.activeElement
} }
if (request.isEmpty){
keyBuffer = ""
sendBuffer()
}
bufferTimeout = setTimeout(function(){sendBuffer()}, 100)
return Promise.resolve({response: "ack"}); return Promise.resolve({response: "ack"});
}); });
function sendBuffer(){
if (elementToSendBuffer == null){
bufferTimeout = setTimeout(function(){sendBuffer()}, 100)
return
}
console.debug("sending buffer to " + elementToSendBuffer)
elementToSendBuffer.value = keyBuffer
}
function setSendEl(e){
let activeEl = document.activeElement
if (activeEl.tagName != "INPUT" && activeEl.tagName != "TEXTAREA"){
keyBuffer = ""
return
}
console.debug("active el is " + activeEl.tagName)
elementToSendBuffer = activeEl
keyBuffer = activeEl.value
}
document.addEventListener('focus', setSendEl)
document.addEventListener('click', setSendEl)
} }
const dummyStr = '' const dummyStr = ''

View File

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

View File

@ -9,9 +9,11 @@
</head> </head>
<body> <body>
<header id="siteDomain"></header> <header id="siteDomain"></header>
<button id="toggleSite">Disable Keyboard Privacy</button> <button id="toggleSite">Disable Slow Typing</button>
<p id="reloadPage">Refresh the page to apply</p> <p id="reloadPage">Refresh the page to apply</p>
<textarea id="keyBuffer" cols="20" rows="10" placeholder="Type in this buffer then click where you want to send it on the page"></textarea> <br>
<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> </body>
</html> </html>

View File

@ -33,7 +33,7 @@ browser.storage.sync.get("keyboardprivacywhitelist")
let hostname = extractHostname(tabVal[0].url).replace('www.', '') let hostname = extractHostname(tabVal[0].url).replace('www.', '')
document.getElementById('siteDomain').innerText = hostname document.getElementById('siteDomain').innerText = hostname
if (val.keyboardprivacywhitelist.includes(hostname)) { if (val.keyboardprivacywhitelist.includes(hostname)) {
document.getElementById('toggleSite').innerText = 'Enable Keyboard Privacy' document.getElementById('toggleSite').innerText = 'Enable Slow Typing'
} }
}) })
.catch(reportError); .catch(reportError);
@ -104,11 +104,11 @@ document.getElementById('toggleSite').onclick = async function(){
} }
document.getElementById('toggleSite').setAttribute('disabled', true) document.getElementById('toggleSite').setAttribute('disabled', true)
if (document.getElementById('toggleSite').innerText.startsWith('Disable')){ if (document.getElementById('toggleSite').innerText.startsWith('Disable')){
document.getElementById('toggleSite').innerText = 'Enable Keyboard Privacy' document.getElementById('toggleSite').innerText = 'Enable Slow Typing'
changeWhitelist(document.getElementById('siteDomain').innerText, true) changeWhitelist(document.getElementById('siteDomain').innerText, true)
} }
else if (document.getElementById('toggleSite').innerText.startsWith('Enable')){ else if (document.getElementById('toggleSite').innerText.startsWith('Enable')){
document.getElementById('toggleSite').innerText = 'Disable Keyboard Privacy' document.getElementById('toggleSite').innerText = 'Disable Slow Typing'
changeWhitelist(document.getElementById('siteDomain').innerText, false) changeWhitelist(document.getElementById('siteDomain').innerText, false)
} }
setTimeout(function(){fixDuplicateCommas()}, 1000) setTimeout(function(){fixDuplicateCommas()}, 1000)

View File

@ -5,14 +5,11 @@ function onError(error) {
} }
function doSendMsg(msg, tabs) { function doSendMsg(msg, tabs) {
let empty = false;
if (document.getElementById('keyBuffer').value.length == 0){
empty = true;
}
for (let tab of tabs) { for (let tab of tabs) {
browser.tabs.sendMessage( browser.tabs.sendMessage(
tab.id, tab.id,
{keys: msg, isEmpty: empty} {keys: msg, getCurrent: false}
).then(response => { ).then(response => {
console.log("Message from the content script:"); console.log("Message from the content script:");
console.log(response.response); console.log(response.response);
@ -20,38 +17,60 @@ function onError(error) {
} }
} }
function getCurrent(){
browser.tabs.query({
currentWindow: true
}).then(function(tabs){
for (let tab of tabs) {
browser.tabs.sendMessage(
tab.id,
{keys: "GetSet", isEmpty: false}
).then(response => {
console.log("Message from the content script:");
console.log(response.response);
document.getElementById('keyBuffer').value = response.response
}).catch(onError);
}
}).catch(onError);
}
let sender = async function(e){ 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){ let sendMessageToTabs = function(tabs){
doSendMsg(e.key, tabs) doSendMsg(document.getElementById('keyBuffer').value, tabs)
} }
browser.tabs.query({ browser.tabs.query({
currentWindow: true currentWindow: true,
active: true
}).then(sendMessageToTabs).catch(onError); }).then(sendMessageToTabs).catch(onError);
} }
setTimeout(function(){getCurrent()}, 10)
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').onkeydown = sender
//document.getElementById('keyBuffer').onpaste = sender document.getElementById('keyBuffer').onpaste = sender