Skip to content

Commit

Permalink
Feature/brad/js api overrides (#795)
Browse files Browse the repository at this point in the history
* Inject JS overrides

* Setup UI test

* Finish off UI test

* Clean up test

* Test optimization

* UI test target

* renable line_length

* suppress onboarding

* specify duckduckgo as the debug executable

* Updates to fp script

* set the ios version for compatibility with CI

* remove fingerprinting from standard tests

* Add hardware keyboard filtering for UI tests

* Delays

* Try waitForExistence

* Did I fix it?

* Add delay for site load

Co-authored-by: Chris Brind <brindy@duckduckgo.com>
  • Loading branch information
SlayterDev and brindy authored Dec 18, 2020
1 parent db5a1c5 commit 429930b
Show file tree
Hide file tree
Showing 15 changed files with 687 additions and 25 deletions.
37 changes: 37 additions & 0 deletions Core/FingerprintUserScript.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// FingerprintUserScript.swift
// DuckDuckGo
//
// Copyright © 2020 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import UIKit
import WebKit

public class FingerprintUserScript: NSObject, UserScript {
public var source: String {
return loadJS("fingerprint")
}

public var injectionTime: WKUserScriptInjectionTime = .atDocumentStart

public var forMainFrameOnly: Bool = false

public var messageNames: [String] = []

public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {

}
}
201 changes: 201 additions & 0 deletions Core/fingerprint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//
// fingerprint.js
// DuckDuckGo
//
// Copyright © 2017 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

(function protect () {
// Property values to be set and their original values.
const fingerprintPropertyValues = {
'screen': {
'availTop': {
'object': 'screen',
'origValue': screen.availTop,
'targetValue': 0
},
'availLeft': {
'object': 'screen',
'origValue': screen.availLeft,
'targetValue': 0
},
'availWidth': {
'object': 'screen',
'origValue': screen.availWidth,
'targetValue': screen.width
},
'availHeight': {
'object': 'screen',
'origValue': screen.availHeight,
'targetValue': screen.height
},
'screenY': {
'object': 'window',
'origValue': window.screenY,
'targetValue': 0
},
'screenLeft': {
'object': 'window',
'origValue': window.screenLeft,
'targetValue': 0
},
'colorDepth': {
'object': 'screen',
'origValue': screen.colorDepth,
'targetValue': 24
},
'pixelDepth': {
'object': 'screen',
'origValue': screen.pixelDepth,
'targetValue': 24
}
},
'options': {
'doNotTrack': {
'object': 'navigator',
'origValue': navigator.doNotTrack,
'targetValue': false
}
}
}

/*
* Return device specific battery value that prevents fingerprinting.
* On Desktop/Laptop - fully charged and plugged in.
* On Mobile, should not plugged in with random battery values every load.
* Property event functions are also defined, for setting later.
*/
function getBattery () {
let battery = {}
battery.value = {
charging: true,
chargingTime: 0,
dischargingTime: Infinity,
level: 1
}
battery.properties = ['onchargingchange', 'onchargingtimechange', 'ondischargingtimechange', 'onlevelchange']
return battery
}

/**
* For each property defined on the object, update it with the target value.
*/
function buildScriptProperties () {
let script = ''
for (const category in fingerprintPropertyValues) {
for (const [name, prop] of Object.entries(fingerprintPropertyValues[category])) {
// Don't update if existing value is undefined or null
if (!(prop.origValue === undefined)) {
script += `Object.defineProperty(${prop.object}, "${name}", { value: ${prop.targetValue} });\n`
}
}
}
return script
}

/**
* Build a script that overwrites the Battery API if present in the browser.
* It will return the values defined in the getBattery function to the client,
* as well as prevent any script from listening to events.
*/
function buildBatteryScript () {
if (navigator.getBattery) {
const battery = getBattery()
let batteryScript = `
navigator.getBattery = function getBattery () {
let battery = ${JSON.stringify(battery.value)}
`
for (const prop of battery.properties) {
// Prevent setting events via event handlers
batteryScript += `
Object.defineProperty(battery, '${prop}', {
enumerable: true,
configurable: false,
writable: false,
value: undefined
})
`
}

// Wrap event listener functions so handlers aren't added
for (const handler of ['addEventListener']) {
batteryScript += `
battery.${handler} = function ${handler} () {
return
}
`
}
batteryScript += `
return Promise.resolve(battery)
}
`
return batteryScript
} else {
return ''
}
}

/**
* Temporary storage can be used to determine hard disk usage and size.
* This will limit the max storage to 4GB without completely disabling the
* feature.
*/
function modifyTemporaryStorage () {
const script = `
if (navigator.webkitTemporaryStorage) {
try {
const org = navigator.webkitTemporaryStorage.queryUsageAndQuota
navigator.webkitTemporaryStorage.queryUsageAndQuota = function queryUsageAndQuota (callback, err) {
const modifiedCallback = function (usedBytes, grantedBytes) {
const maxBytesGranted = 4 * 1024 * 1024 * 1024
const spoofedGrantedBytes = Math.min(grantedBytes, maxBytesGranted)
callback(usedBytes, spoofedGrantedBytes)
}
org.call(navigator.webkitTemporaryStorage, modifiedCallback, err)
}
}
catch(e) {}
}
`
return script
}

/**
* All the steps for building the injection script. Should only be done at initial page load.
*/
function buildInjectionScript () {
let script = buildScriptProperties()
script += modifyTemporaryStorage()
script += buildBatteryScript()
return script
}

/**
* Inject all the overwrites into the page.
*/
function inject (scriptToInject, removeAfterExec) {
// Inject into main page
let e = document.createElement('script')
e.textContent = scriptToInject;
(document.head || document.documentElement).appendChild(e)

if (removeAfterExec) {
e.remove()
}
}

const injectionScript = buildInjectionScript()
inject(injectionScript)
})()
Loading

0 comments on commit 429930b

Please sign in to comment.