Compare commits
No commits in common. "master" and "button-approach" have entirely different histories.
master
...
button-app
38
README.md
38
README.md
@ -12,31 +12,14 @@ 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.
|
||||||
|
|
||||||
# Usage
|
Development Roadmap:
|
||||||
|
|
||||||
This addon has two methods of protecting yourself.
|
* Menu button to add whitelisted domain more quickly
|
||||||
|
* 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
|
||||||
|
|
||||||
@ -45,11 +28,10 @@ To avoid using the mouse to do this, you can press ctrl+1 (config [the same way
|
|||||||
# 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 only the toolbar button can protect you (but it likely doesn't function if this is the case)
|
* Some websites override all key events in which case it is not (yet) possible to protect against keyboarding analysis there
|
||||||
* Typing directly on pages will have CPU spikes due to a browser limitation
|
* You may see higher CPU usage while typing. This is due do the unideal locking solution described above
|
||||||
* Of course, this cannot protect against malware on your device outside the webpage.
|
* Do not confuse this with spying keyboard apps on mobile devices, this cannot address that.
|
||||||
* Not tested on Firefox Android
|
* Not tested on Firefox Android
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@ -59,9 +41,5 @@ 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)
|
||||||
|
@ -20,7 +20,9 @@ let lastActive = null
|
|||||||
const defaultHosts = "<all_urls>";
|
const defaultHosts = "<all_urls>";
|
||||||
|
|
||||||
let appCode = function (){
|
let appCode = function (){
|
||||||
let lastActive = document.createElement("p")
|
let keyBuffer = ""
|
||||||
|
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){
|
||||||
@ -36,8 +38,8 @@ let appCode = function (){
|
|||||||
document.selection.empty()
|
document.selection.empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let minValue = 50
|
let minValue = 75
|
||||||
let maxValue = 75
|
let maxValue = 150
|
||||||
let time = 0
|
let time = 0
|
||||||
let last = null
|
let last = null
|
||||||
|
|
||||||
@ -100,8 +102,9 @@ let appCode = function (){
|
|||||||
|
|
||||||
function pausecomp(millis)
|
function pausecomp(millis)
|
||||||
{
|
{
|
||||||
// Yes i know this wastes cpu. i don't like it either, but it is the only way
|
// Yes i know this wastes cpu. i don't like it either, but it seems a blocking
|
||||||
// i know of to have real page keypress events that aren't trackable
|
// approach is needed to prevent spying event listeners from reading key events in *real time*
|
||||||
|
// 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(); }
|
||||||
@ -198,37 +201,62 @@ 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
|
|
||||||
|
|
||||||
if (request.getCurrent){
|
console.log(request.keys);
|
||||||
if (active.tagName != "INPUT" && active.tagName != "TEXTAREA") {
|
if (request.keys == "GetSet"){
|
||||||
if (lastActive.tagName != "INPUT" && lastActive.tagName != "TEXTAREA"){
|
keyBuffer = elementToSendBuffer.value
|
||||||
console.debug("no current active or last active")
|
return Promise.resolve({response: elementToSendBuffer.value});
|
||||||
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
|
clearTimeout(bufferTimeout)
|
||||||
|
|
||||||
|
if (request.keys == "Backspace"){
|
||||||
|
keyBuffer = keyBuffer.slice(0, -1)
|
||||||
|
}
|
||||||
|
else if(request.keys.length > 4){
|
||||||
|
// pass
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
document.activeElement.value = request.keys
|
keyBuffer += 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 = ''
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Private Keyboard",
|
"name": "Private Keyboard",
|
||||||
"version": "2.0",
|
"version": "1.9",
|
||||||
|
|
||||||
|
|
||||||
"description": "Protect against keyboard biometrics",
|
"description": "Protect against keyboard biometrics",
|
||||||
@ -32,15 +32,6 @@
|
|||||||
"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": {
|
||||||
|
@ -9,11 +9,9 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header id="siteDomain"></header>
|
<header id="siteDomain"></header>
|
||||||
<button id="toggleSite">Disable Slow Typing</button>
|
<button id="toggleSite">Disable Keyboard Privacy</button>
|
||||||
<p id="reloadPage">Refresh the page to apply</p>
|
<p id="reloadPage">Refresh the page to apply</p>
|
||||||
|
|
||||||
<br>
|
<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>
|
|
||||||
<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>
|
@ -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 Slow Typing'
|
document.getElementById('toggleSite').innerText = 'Enable Keyboard Privacy'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.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 Slow Typing'
|
document.getElementById('toggleSite').innerText = 'Enable Keyboard Privacy'
|
||||||
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 Slow Typing'
|
document.getElementById('toggleSite').innerText = 'Disable Keyboard Privacy'
|
||||||
changeWhitelist(document.getElementById('siteDomain').innerText, false)
|
changeWhitelist(document.getElementById('siteDomain').innerText, false)
|
||||||
}
|
}
|
||||||
setTimeout(function(){fixDuplicateCommas()}, 1000)
|
setTimeout(function(){fixDuplicateCommas()}, 1000)
|
||||||
|
@ -5,11 +5,14 @@ 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, getCurrent: false}
|
{keys: msg, isEmpty: empty}
|
||||||
).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);
|
||||||
@ -17,60 +20,38 @@ 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(document.getElementById('keyBuffer').value, tabs)
|
doSendMsg(e.key, 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
|
Loading…
Reference in New Issue
Block a user