Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix #3700: Pass farbling script arguments as concatenated parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
cuba committed Mar 28, 2022
1 parent 5a8ae48 commit 6a0521a
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import Foundation
import GameplayKit

/// A class that helps in creating farbling data
class FarblingProtectionHelper {
Expand Down Expand Up @@ -34,8 +35,23 @@ class FarblingProtectionHelper {
"Cecil", "Reuben", "Sylvester", "Jasper"
]

static func makeFarblingParams(from randomManager: RandomManager) -> JSDataType {
let randomSource = GKMersenneTwisterRandomSource(seed: randomManager.seed)
let fudgeFactor = JSDataType.number(0.99 + (randomSource.nextUniform() / 100))
let fakePluginData = FarblingProtectionHelper.makeFakePluginData(from: randomManager)
let fakeVoice = FarblingProtectionHelper.makeFakeVoiceName(from: randomManager)
let randomVoiceIndexScale = JSDataType.number(randomSource.nextUniform())

return JSDataType.object([
"fudgeFactor": fudgeFactor,
"fakePluginData": fakePluginData,
"fakeVoiceName": fakeVoice,
"randomVoiceIndexScale": randomVoiceIndexScale
])
}

/// Generate fake plugin data to be injected into the farbling protection script
static func makeFakePluginData(from randomManager: RandomManager) -> String {
private static func makeFakePluginData(from randomManager: RandomManager) -> JSDataType {
var generator = ARC4RandomNumberGenerator(seed: randomManager.seed)
let pluginCount = Int.random(in: 1...3, using: &generator)

Expand All @@ -61,19 +77,19 @@ class FarblingProtectionHelper {
}

// Convert the object into a string and return it
return String(describing: JSDataType.array(fakePlugins))
return JSDataType.array(fakePlugins)
}

/// Generate a fake voice name
static func makeFakeVoiceName(from randomManager: RandomManager) -> String {
private static func makeFakeVoiceName(from randomManager: RandomManager) -> JSDataType {
var generator = ARC4RandomNumberGenerator(seed: randomManager.seed)
let fakeName = fakeVoiceNames.randomElement(using: &generator) ?? fakeVoiceNames.first!
return String(describing: JSDataType.string(fakeName))
return JSDataType.string(fakeName)
}

/// Generate a random string using a prefix, middle and suffix where any of those may be empty.
/// - Note: May result in an empty string.
static func randomPluginName<T: RandomNumberGenerator>(from generator: inout T) -> String {
private static func randomPluginName<T: RandomNumberGenerator>(from generator: inout T) -> String {
return [
pluginNameFirstParts.randomElement(using: &generator) ?? nil,
pluginNameSecondParts.randomElement(using: &generator) ?? nil,
Expand Down
4 changes: 4 additions & 0 deletions Client/Frontend/Browser/User Scripts/JSDataType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Foundation
/// ```
enum JSDataType: CustomStringConvertible {
case string(String)
case number(Float)
case object([String: JSDataType])
case array([JSDataType])

Expand All @@ -41,6 +42,9 @@ enum JSDataType: CustomStringConvertible {

case .string(let value):
return "\"\(value)\""

case .number(let value):
return "\(value)"
}
}
}
32 changes: 6 additions & 26 deletions Client/Frontend/Browser/User Scripts/ScriptFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import Foundation
import WebKit
import GameplayKit

/// An error representing failures in loading scripts
enum ScriptLoadFailure: Error {
Expand Down Expand Up @@ -179,33 +178,14 @@ class ScriptFactory {
switch domainType {
case .farblingProtection(let etld):
let randomManager = RandomManager(etld: etld)
let seed = randomManager.seed

// From the seed, let's generate a fudge factor between 0.99 and 1
// `GKMersenneTwisterRandomSource` gives us a value between 0.0 and 1.0,
// so we convert it to a value between 0.99 and 1.
// It's important that this value is between 0.99 and 1 (especially less than 1)
// As we are multiplying it by values betwen -1 and 1 and if the value is too small
// It will manipulate the values too much and make it noticible to the user and if
// it is greater than 1 it has the potential to be out of the appropriate range giving us JS errors.
let randomSource = GKMersenneTwisterRandomSource(seed: seed)
let fudgeFactor = 0.99 + (randomSource.nextUniform() / 100)
let fakePluginData = FarblingProtectionHelper.makeFakePluginData(from: randomManager)
let fakeVoice = FarblingProtectionHelper.makeFakeVoiceName(from: randomManager)
let randomVoiceIndexScale = randomSource.nextUniform()
let fakeParams = FarblingProtectionHelper.makeFarblingParams(from: randomManager)

#if DEBUG
print("[ScriptFactory] eTLD+1: \(etld)")
print("[ScriptFactory] Seed: \(seed)")
print("[ScriptFactory] Fudge: \(fudgeFactor)")
print("[ScriptFactory] Voice: \(fakeVoice)")
print("[ScriptFactory] Scale: \(randomVoiceIndexScale)")
print("[ScriptFactory] Plgins: \(fakePluginData)")

source = source
.replacingOccurrences(of: "$<fudge_factor>", with: "\(fudgeFactor)", options: .literal)
.replacingOccurrences(of: "$<fake_plugin_data>", with: "\(fakePluginData)", options: .literal)
.replacingOccurrences(of: "$<fake_voice_name>", with: "\(fakeVoice)", options: .literal)
.replacingOccurrences(of: "$<random_voice_index_scale>", with: "\(randomVoiceIndexScale)", options: .literal)
print("[ScriptFactory] Seed: \(randomManager.seed)")
print("[ScriptFactory] Params: \(fakeParams)")
#endif
source = "\(source)\n(\(String(describing: fakeParams)))"

case .nacl:
// No modifications needed
Expand Down
64 changes: 34 additions & 30 deletions Client/Frontend/UserContent/UserScripts/FarblingProtection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// In this method we will decrease the weight of the destination value by our fudge factor.
// Our fudge factor is a random value between 0.99 and 1.
// This means the values in the destination will always be within the expected range of -1 and 1.
// This small decrease should not affect affect legitimite users of this api.
// But will affect fingerprinters by introducing a small random change.
(function () {
const fudgeFactor = $<fudge_factor>
"use strict";

((params) => {
// A value between 0.99 and 1 to fudge the audio data
// A value between 0.99 to 1 means the values in the destination will
// always be within the expected range of -1 and 1.
// This small decrease should not affect affect legitimite users of this api.
// But will affect fingerprinters by introducing a small random change.
const fudgeFactor = params['fudgeFactor']
// Fake data that is to be used to construct fake plugins
const fakePluginData = params['fakePluginData']
// A value representing a fake voice name that will be used to add a fake voice
const fakeVoiceName = params['fakeVoiceName']
// This value is used to get a random index between 0 and voices.length
// It's important to have a value between 0 - 1 in order to be within the
// array bounds
const randomVoiceIndexScale = params['randomVoiceIndexScale']

const farbledChannels = new WeakMap()
const braveNacl = window.nacl
delete window.nacl

function farbleArrayData (destination) {
const farbleArrayData = (destination) => {
// Let's fudge the data by our fudge factor.
for (const index in destination) {
destination[index] = destination[index] * fudgeFactor
Expand All @@ -23,7 +34,7 @@

// Convert an unsinged byte (Uint8) to a hex character
// Unsigned bytes must be between 0 and 255
function byteToHex (unsignedByte) {
const byteToHex = (unsignedByte) => {
// convert the possibly signed byte (-128 to 127) to an unsigned byte (0 to 255).
// if you know, that you only deal with unsigned bytes (Uint8Array), you can omit this line
// const unsignedByte = byte & 0xff
Expand All @@ -40,31 +51,32 @@
// Convert an array of unsigned bytes (Uint8Array) to a hex string.
// Each value in the array must be between 0 and 255,
// resulting in hex values between 0 to f (i.e. 0 to 15)
function toHexString (unsignedBytes) {
const toHexString = (unsignedBytes) => {
return Array.from(unsignedBytes)
.map(byte => byteToHex(byte))
.join('')
}

// Hash an array
function hashArray (a) {
const hashArray = (a) => {
const byteArray = new Uint8Array(a.buffer)
const hexArray = braveNacl.hash(byteArray)
return toHexString(hexArray)
}

// 1. Farble `getChannelData`
// This will also result in a farbled `copyFromChannel`
const getChannelData = window.AudioBuffer.prototype.getChannelData
window.AudioBuffer.prototype.getChannelData = function () {
const channelData = getChannelData.apply(this, arguments)
const channelData = Reflect.apply(getChannelData, this, arguments)
let hashes = farbledChannels.get(channelData)

// First let's check if we already farbled this set
if (hashes !== undefined) {
// We had this data set farbled already.
// Lets see if it changed it's shape since then.
const hash = hashArray(channelData)

console.log(hash)
if (hashes.has(hash)) {
// We already farbled this version of the channel data
// Let's not farble it again
Expand Down Expand Up @@ -102,11 +114,9 @@
}
}

// An array of fake data that will be used to make fake plugins
const fakePluginData = $<fake_plugin_data>

// 3. Farble plugin data
// Function that create a fake mime-type based on the given fake data
function makeFakeMimeType (fakeData) {
const makeFakeMimeType = (fakeData) => {
return Object.create(window.MimeType.prototype, {
suffixes: {
get: function () {
Expand All @@ -127,7 +137,7 @@
}

// Create a fake plugin given the plugin data
function makeFakePlugin (pluginData) {
const makeFakePlugin = (pluginData) => {
const newPlugin = Object.create(window.Plugin.prototype, {
description: {
get: function () {
Expand All @@ -152,8 +162,8 @@
})

// Create mime-types and link them to the new plugin
for (let index = 0; index < pluginData.mimeTypes.length; index++) {
const newMimeType = makeFakeMimeType(pluginData.mimeTypes[index])
for (const [index, mimeType] of pluginData.mimeTypes.entries()) {
const newMimeType = makeFakeMimeType(mimeType)

newPlugin[index] = newMimeType
newPlugin[newMimeType.type] = newMimeType
Expand Down Expand Up @@ -189,8 +199,7 @@
window.navigator.plugins[newPlugin.name] = newPlugin
}

for (let index = 0; index < fakePluginData.length; index++) {
const pluginData = fakePluginData[index]
for (const [index, pluginData] of fakePluginData.entries()) {
const newPlugin = makeFakePlugin(pluginData)
addPluginAtIndex(newPlugin, index)
}
Expand All @@ -212,12 +221,6 @@
}

// 4. Farble speech synthesizer
console.log('Farbling voices')
const fakeVoiceName = $<fake_voice_name>
// A value representing a random value between 0 and 1.
// This value is used to get a random index between 0 and voices.length
const randomVoiceScale = $<random_voice_index_scale>

function makeFakeVoiceFromVoice(voice) {
const voicePrototype = Object.getPrototypeOf(voice)

Expand Down Expand Up @@ -289,7 +292,7 @@
const voices = Reflect.apply(getVoices, this, arguments)

if (fakeVoice === undefined) {
const randomVoiceIndex = Math.round(randomVoiceScale * voices.length)
const randomVoiceIndex = Math.round(randomVoiceIndexScale * voices.length)
originalVoice = voices[randomVoiceIndex]
fakeVoice = makeFakeVoiceFromVoice(originalVoice)

Expand All @@ -301,4 +304,5 @@
}
return voices
}
})()
})
// Attach parameters here as JSON data
8 changes: 4 additions & 4 deletions ClientTests/User Scripts/FarblingProtectionHelperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class FarblingProtectionHelperTests: XCTestCase {
// Then
// Same results
XCTAssertEqual(
FarblingProtectionHelper.makeFakePluginData(from: randomManager),
FarblingProtectionHelper.makeFakePluginData(from: randomManager)
String(describing: FarblingProtectionHelper.makeFarblingParams(from: randomManager)),
String(describing: FarblingProtectionHelper.makeFarblingParams(from: randomManager))
)
}

Expand All @@ -32,8 +32,8 @@ class FarblingProtectionHelperTests: XCTestCase {
// Then
// Different results
XCTAssertNotEqual(
FarblingProtectionHelper.makeFakePluginData(from: firstRandomManager),
FarblingProtectionHelper.makeFakePluginData(from: secondRandomManager)
String(describing: FarblingProtectionHelper.makeFarblingParams(from: firstRandomManager)),
String(describing: FarblingProtectionHelper.makeFarblingParams(from: secondRandomManager))
)
}
}

0 comments on commit 6a0521a

Please # to comment.