Compare commits
1 Commits
e39251cafc
...
8f7bd620b1
Author | SHA1 | Date | |
---|---|---|---|
|
8f7bd620b1 |
11
README.md
11
README.md
@ -12,7 +12,7 @@ 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.**
|
It uses a less than ideal thread locking solution, as simply buffering text does not stop event listeners from spies.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@ -29,7 +29,6 @@ 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 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**
|
* **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
|
* 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.
|
* 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
|
||||||
@ -37,9 +36,9 @@ Development Roadmap:
|
|||||||
|
|
||||||
Sources:
|
Sources:
|
||||||
|
|
||||||
www dot keytrac dot net/en/
|
https://www.keytrac.net/en/
|
||||||
|
https://www.typingdna.com/
|
||||||
www dot typingdna dot com/
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
(Spy companies not linked to avoid helping their SEO)
|
106
background.js
106
background.js
@ -16,70 +16,13 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
let lastActive = null
|
|
||||||
const defaultHosts = "<all_urls>";
|
const defaultHosts = "<all_urls>";
|
||||||
|
|
||||||
let appCode = function (){
|
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 minValue = 75
|
||||||
let maxValue = 150
|
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(){
|
let mainKeyboardPrivacy = function(){
|
||||||
popupMode()
|
|
||||||
|
|
||||||
function getRandNum(){
|
function getRandNum(){
|
||||||
let buf = new Uint8Array(1)
|
let buf = new Uint8Array(1)
|
||||||
@ -120,24 +63,9 @@ let appCode = function (){
|
|||||||
}, )
|
}, )
|
||||||
|
|
||||||
document.addEventListener('keydown', function(e){
|
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;
|
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);
|
pausecomp(down);
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
@ -198,38 +126,6 @@ let appCode = function (){
|
|||||||
let whitelist = browser.storage.sync.get("keyboardprivacywhitelist");
|
let whitelist = browser.storage.sync.get("keyboardprivacywhitelist");
|
||||||
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 => {
|
|
||||||
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 = ''
|
const dummyStr = ''
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Private Keyboard",
|
"name": "Private Keyboard",
|
||||||
"version": "2.0",
|
"version": "1.8",
|
||||||
|
|
||||||
|
|
||||||
"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": {
|
||||||
|
@ -5,14 +5,10 @@
|
|||||||
<title>Private Keyboard</title>
|
<title>Private Keyboard</title>
|
||||||
<link rel="stylesheet" href="./button.css">
|
<link rel="stylesheet" href="./button.css">
|
||||||
<script src="./button.js" defer></script>
|
<script src="./button.js" defer></script>
|
||||||
<script src="./keybuffer2.js" defer></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header id="siteDomain"></header>
|
<header id="siteDomain"></header>
|
||||||
<button id="toggleSite">Disable Keyboard Privacy</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 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>
|
@ -116,4 +116,4 @@ document.getElementById('toggleSite').onclick = async function(){
|
|||||||
document.getElementById('toggleSite').removeAttribute('disabled')
|
document.getElementById('toggleSite').removeAttribute('disabled')
|
||||||
}, 3000)
|
}, 3000)
|
||||||
document.getElementById('reloadPage').style.display = 'block'
|
document.getElementById('reloadPage').style.display = 'block'
|
||||||
}
|
}
|
@ -1,76 +0,0 @@
|
|||||||
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
|
|
@ -9,9 +9,6 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<form>
|
<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">
|
<input type="checkbox" id="whitelistLAN">
|
||||||
<label for="whitelistLAN">Whitelist private IP + localhost range hostnames (e.g. routers)</label>
|
<label for="whitelistLAN">Whitelist private IP + localhost range hostnames (e.g. routers)</label>
|
||||||
<h1>Trusted domains (comma delimited)</h1>
|
<h1>Trusted domains (comma delimited)</h1>
|
||||||
|
@ -24,9 +24,6 @@ function saveOptions(e) {
|
|||||||
browser.storage.sync.set({
|
browser.storage.sync.set({
|
||||||
keyboardprivacylan: document.querySelector("#whitelistLAN").checked
|
keyboardprivacylan: document.querySelector("#whitelistLAN").checked
|
||||||
})
|
})
|
||||||
browser.storage.sync.set({
|
|
||||||
keyboardprivacyprompt: document.querySelector("#usePrompt").checked
|
|
||||||
})
|
|
||||||
document.getElementById('saved').innerHTML = '<br><b>Saved</b>'
|
document.getElementById('saved').innerHTML = '<br><b>Saved</b>'
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
document.getElementById('saved').innerHTML = '<br>'
|
document.getElementById('saved').innerHTML = '<br>'
|
||||||
@ -45,10 +42,6 @@ function saveOptions(e) {
|
|||||||
document.querySelector("#whitelistLAN").checked = result['keyboardprivacylan']
|
document.querySelector("#whitelistLAN").checked = result['keyboardprivacylan']
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCurrentPrompt(result){
|
|
||||||
document.querySelector("#usePrompt").checked = result['keyboardprivacyprompt']
|
|
||||||
}
|
|
||||||
|
|
||||||
function onError(error) {
|
function onError(error) {
|
||||||
console.log(`Error: ${error}`);
|
console.log(`Error: ${error}`);
|
||||||
}
|
}
|
||||||
@ -58,9 +51,6 @@ function saveOptions(e) {
|
|||||||
|
|
||||||
let gettingLAN = browser.storage.sync.get("keyboardprivacylan");
|
let gettingLAN = browser.storage.sync.get("keyboardprivacylan");
|
||||||
gettingLAN.then(setCurrentLAN, onError);
|
gettingLAN.then(setCurrentLAN, onError);
|
||||||
|
|
||||||
let gettingPrompt = browser.storage.sync.get("keyboardprivacyprompt");
|
|
||||||
gettingPrompt.then(setCurrentPrompt, onError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", restoreOptions);
|
document.addEventListener("DOMContentLoaded", restoreOptions);
|
||||||
|
Loading…
Reference in New Issue
Block a user