Bug 1425462 When reducing the precision of timestamps, also apply fuzzytime to them r=bkelly

Fuzzytime deterministically generates a random midpoint between two clamped values,
and if the unreduced timestamp is above the midpoint, the time is rounded upwards.
This allows safe time jittering to occur, as time will never go backwards on a given
timeline.

It _is_ possible for time to go backwards when comparing different (but related)
timelines, such as a relative timeline in one page (with its own
performance.timeOrigin) and a relative timeline in an iframe or Worker (which
also has its own performance.timeOrigin). This is the same behavior as the 2ms timer
reduction we previously landed; jitter doesn't make this any better or worse.

MozReview-Commit-ID: IdRLxcWDQBZ

--HG--
extra : rebase_source : 40b29d34e5cc99f9b8e6d5e711a03b9fe9bfa595
This commit is contained in:
Tom Ritter 2018-03-01 00:07:03 -06:00
Родитель 1cbe8f2b86
Коммит 845ef57dd6
8 изменённых файлов: 455 добавлений и 13 удалений

Просмотреть файл

@ -1231,6 +1231,7 @@ pref("services.sync.prefs.sync.privacy.trackingprotection.pbmode.enabled", true)
pref("services.sync.prefs.sync.privacy.resistFingerprinting", true);
pref("services.sync.prefs.sync.privacy.reduceTimerPrecision", true);
pref("services.sync.prefs.sync.privacy.resistFingerprinting.reduceTimerPrecision.microseconds", true);
pref("services.sync.prefs.sync.privacy.resistFingerprinting.reduceTimerPrecision.jitter", true);
pref("services.sync.prefs.sync.security.OCSP.enabled", true);
pref("services.sync.prefs.sync.security.OCSP.require", true);
pref("services.sync.prefs.sync.security.default_personal_cert", true);

Просмотреть файл

@ -293,6 +293,7 @@ const char* mozilla::dom::ContentPrefs::gEarlyPrefs[] = {
"privacy.reduceTimerPrecision",
"privacy.resistFingerprinting",
"privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts",
"privacy.resistFingerprinting.reduceTimerPrecision.jitter",
"privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
"privacy.resistFingerprinting.target_video_res",
"privacy.resistFingerprinting.video_dropped_ratio",

Просмотреть файл

@ -174,7 +174,7 @@ DayWithinYear(double time, double year);
// Sets the time resolution for fingerprinting protection.
// If it's set to zero, then no rounding will happen.
JS_PUBLIC_API(void)
SetTimeResolutionUsec(uint32_t resolution);
SetTimeResolutionUsec(uint32_t resolution, bool jitter);
} // namespace JS

Просмотреть файл

@ -63,6 +63,8 @@ using JS::ToInteger;
// When this value is non-zero, we'll round the time by this resolution.
static Atomic<uint32_t, Relaxed> sResolutionUsec;
// This is not implemented yet, but we will use this to know to jitter the time in the JS shell
static Atomic<bool, Relaxed> sJitter;
/*
* The JS 'Date' object is patterned after the Java 'Date' object.
@ -405,9 +407,10 @@ JS::DayWithinYear(double time, double year)
}
JS_PUBLIC_API(void)
JS::SetTimeResolutionUsec(uint32_t resolution)
JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter)
{
sResolutionUsec = resolution;
sJitter = jitter;
}
/*

Просмотреть файл

@ -1433,6 +1433,8 @@ pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", true);
pref("privacy.reduceTimerPrecision", true);
// Dynamically tune the resolution of the timer reduction for both of the two above prefs
pref("privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 2000);
// Enable jittering the clock one precision value forward
pref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", false);
// Lower the priority of network loads for resources on the tracking protection list.
// Note that this requires the privacy.trackingprotection.annotate_channels pref to be on in order to have any effect.
#ifdef NIGHTLY_BUILD

Просмотреть файл

@ -6,6 +6,7 @@
#include "nsRFPService.h"
#include <algorithm>
#include <memory>
#include <time.h>
#include "mozilla/ClearOnShutdown.h"
@ -23,9 +24,11 @@
#include "nsXULAppAPI.h"
#include "nsPrintfCString.h"
#include "nsICryptoHash.h"
#include "nsIObserverService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIRandomGenerator.h"
#include "nsIXULAppInfo.h"
#include "nsIXULRuntime.h"
#include "nsJSUtils.h"
@ -45,6 +48,8 @@ static mozilla::LazyLogModule gResistFingerprintingLog("nsResistFingerprinting")
#define RFP_TIMER_PREF "privacy.reduceTimerPrecision"
#define RFP_TIMER_VALUE_PREF "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
#define RFP_TIMER_VALUE_DEFAULT 2000
#define RFP_JITTER_VALUE_PREF "privacy.resistFingerprinting.reduceTimerPrecision.jitter"
#define RFP_JITTER_VALUE_DEFAULT false
#define RFP_SPOOFED_FRAMES_PER_SEC_PREF "privacy.resistFingerprinting.video_frames_per_sec"
#define RFP_SPOOFED_DROPPED_RATIO_PREF "privacy.resistFingerprinting.video_dropped_ratio"
#define RFP_TARGET_VIDEO_RES_PREF "privacy.resistFingerprinting.target_video_res"
@ -74,11 +79,15 @@ Atomic<bool, Relaxed> nsRFPService::sPrivacyResistFingerprinting;
Atomic<bool, Relaxed> nsRFPService::sPrivacyTimerPrecisionReduction;
// Note: anytime you want to use this variable, you should probably use TimerResolution() instead
Atomic<uint32_t, Relaxed> sResolutionUSec;
Atomic<bool, Relaxed> sJitter;
static uint32_t sVideoFramesPerSec;
static uint32_t sVideoDroppedRatio;
static uint32_t sTargetVideoRes;
nsDataHashtable<KeyboardHashKey, const SpoofingKeyboardCode*>*
nsRFPService::sSpoofingKeyboardCodes = nullptr;
UniquePtr<LRUCache> nsRFPService::sCache;
UniquePtr<uint8_t[]> nsRFPService::sSecretMidpointSeed;
mozilla::Mutex* nsRFPService::sLock = nullptr;
/* static */
nsRFPService*
@ -128,6 +137,258 @@ nsRFPService::IsTimerPrecisionReductionEnabled(TimerPrecisionType aType)
TimerResolution() > 0;
}
/*
* The below is a simple time-based Least Recently Used cache used to store the
* result of a cryptographic hash function. It has LRU_CACHE_SIZE slots, and will
* be used from multiple threads. It will be thread-safe in a future commit.
*/
#define LRU_CACHE_SIZE (45)
#define HASH_DIGEST_SIZE_BITS (256)
#define HASH_DIGEST_SIZE_BYTES (HASH_DIGEST_SIZE_BITS / 8)
// TODO: Fix Race Conditions
class LRUCache
{
public:
LRUCache() {
this->cache.SetLength(LRU_CACHE_SIZE);
}
nsCString Get(long long aKey) {
for (auto & cacheEntry : this->cache) {
if (cacheEntry.key == aKey) {
cacheEntry.accessTime = PR_Now();
#if defined(DEBUG)
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("LRU Cache HIT with %lli == %lli", aKey, cacheEntry.key));
#endif
return cacheEntry.data;
}
}
return EmptyCString();
}
void Store(long long aKey, const nsCString& aValue) {
MOZ_DIAGNOSTIC_ASSERT(aValue.Length() == HASH_DIGEST_SIZE_BYTES);
CacheEntry* lowestKey = &this->cache[0];
for (auto & cacheEntry : this->cache) {
if (cacheEntry.accessTime < lowestKey->accessTime) {
lowestKey = &cacheEntry;
}
}
lowestKey->key = aKey;
lowestKey->data = aValue;
lowestKey->accessTime = PR_Now();
#if defined(DEBUG)
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose, ("LRU Cache STORE with %lli", aKey));
#endif
}
private:
struct CacheEntry {
long long key;
PRTime accessTime = 0;
nsCString data;
CacheEntry() {
this->key = 0xFFFFFFFFFFFFFFFF;
this->accessTime = 0;
this->data = nullptr;
}
};
AutoTArray<CacheEntry, LRU_CACHE_SIZE> cache;
};
/**
* The purpose of this function is to deterministicly generate a random midpoint
* between a lower clamped value and an upper clamped value. Assuming a clamping
* resolution of 100, here is an example:
*
* |---------------------------------------|--------------------------|
* lower clamped value (e.g. 300) | upper clamped value (400)
* random midpoint (e.g. 360)
*
* If our actual timestamp (e.g. 325) is below the midpoint, we keep it clamped
* downwards. If it were equal to or above the midpoint (e.g. 365) we would
* round it upwards to the largest clamped value (in this example: 400).
*
* The question is: does time go backwards?
*
* The midpoint is deterministicly random
* and generated from two components: a secret seed and a clamped time.
*
* When comparing times across different seed values: time may go backwards.
* For a clamped time of 300, one seed may generate a midpoint of 305 and another
* 395. So comparing an (actual) timestamp of 325 and 351 could see the 325 clamped
* up to 400 and the 351 clamped down to 300. The seed is per-process, so this case
* occurs when one can compare timestamps cross-process. This is uncommon (because
* we don't have site isolation.) The circumstances this could occur are
* BroadcastChannel, Storage Notification, and in theory (but not yet implemented)
* SharedWorker. This should be an exhaustive list (at time of comment writing!).
*
* Aside from cross-process communication, derived timestamps across different
* time origins may go backwards. (Specifically, derived means adding two timestamps
* together to get an (approximate) absolute time.)
* Assume a page and a worker. If one calls performance.now() in the page and then
* triggers a call to performance.now() in the worker, the following invariant should
* hold true:
* page.performance.timeOrigin + page.performance.now() <
* worker.performance.timeOrigin + worker.performance.now()
*
* We break this invariant.
*
*
* TODO: The above comment is going to need to be entirely rewritten when we mix in
* a per-context shared secret. Context is 'Any new object that gets a time origin
* starting from zero'. The most obvious example is Documents and Workers. An attacker
* could let time go forward and observe (roughly) where the random midpoints fall.
* Then they create a new object, time starts back ovr at zero, and they know
* (approximately) where the random midpoints are.
*
* @param aClampedTimeUSec [in] The clamped input time in microseconds.
* @param aResolutionUSec [in] The current resolution for clamping in microseconds.
* @param aMidpointOut [out] The midpoint, in microseconds, between [0, aResolutionUSec].
* @param aSecretSeed [in] TESTING ONLY. When provided, the current seed will be
* replaced with this value.
* @return A nsresult indicating success of failure. If the function failed,
* nothing is written to aMidpointOut
*/
/* static */
nsresult
nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
long long aResolutionUSec,
long long* aMidpointOut,
uint8_t * aSecretSeed /* = nullptr */)
{
nsresult rv;
const int kSeedSize = 16;
const int kClampTimesPerDigest = HASH_DIGEST_SIZE_BITS / 32;
if(MOZ_UNLIKELY(!sCache)) {
MutexAutoLock lock(*sLock);
if(MOZ_LIKELY(!sCache)) {
sCache = MakeUnique<LRUCache>();
}
}
if(MOZ_UNLIKELY(!aMidpointOut)) {
return NS_ERROR_INVALID_ARG;
}
/*
* Below, we will call a cryptographic hash function. That's expensive. We look for ways to
* make it more efficient.
*
* We only need as much output from the hash function as the maximum resolution we will
* ever support, because we will reduce the output modulo that value. The maximum resolution
* we think is likely is in the low seconds value, or about 1-10 million microseconds.
* 2**24 is 16 million, so we only need 24 bits of output. Practically speaking though,
* it's way easier to work with 32 bits.
*
* So we're using 32 bits of output and throwing away the other DIGEST_SIZE - 32 (in the case of
* SHA-256, DIGEST_SIZE is 256.) That's a lot of waste.
*
* Instead of throwing it away, we're going to use all of it. We can handle DIGEST_SIZE / 32
* Clamped Time's per hash function - call that , so we reduce aClampedTime to a multiple of
* kClampTimesPerDigest (just like we reduced the real time value to aClampedTime!)
*
* Then we hash _that_ value (assuming it's not in the cache) and index into the digest result
* the appropriate bit offset.
*/
long long reducedResolution = aResolutionUSec * kClampTimesPerDigest;
long long extraClampedTime = (aClampedTimeUSec / reducedResolution) * reducedResolution;
nsCString hashResult = sCache->Get(extraClampedTime);
if(hashResult.Length() != HASH_DIGEST_SIZE_BYTES) { // Cache Miss =(
// If someone has pased in the testing-only parameter, replace our seed with it
if (aSecretSeed != nullptr) {
MutexAutoLock lock(*sLock);
if (sSecretMidpointSeed) {
// Deletes the object pointed to as well
sSecretMidpointSeed = nullptr;
}
sSecretMidpointSeed = MakeUnique<uint8_t[]>(kSeedSize);
memcpy(sSecretMidpointSeed.get(), aSecretSeed, kSeedSize);
}
// If we don't have a seed, we need to get one.
if(MOZ_UNLIKELY(!sSecretMidpointSeed)) {
MutexAutoLock lock(*sLock);
if(MOZ_LIKELY(!sSecretMidpointSeed)) {
nsCOMPtr<nsIRandomGenerator> randomGenerator =
do_GetService("@mozilla.org/security/random-generator;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
uint8_t* buffer;
rv = randomGenerator->GenerateRandomBytes(kSeedSize, &buffer);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
sSecretMidpointSeed.reset(buffer);
}
}
/*
* Use a cryptographicly secure hash function, but do _not_ use an HMAC.
* Obviously we're not using this data for authentication purposes, but
* even still an HMAC is a perfect fit here, as we're hashing a value
* using a seed that never changes, and an input that does. So why not
* use one?
*
* Basically - we don't need to, it's two invocations of the hash function,
* and speed really counts here.
*
* With authentication off the table, the properties we would get by
* using an HMAC here would be:
* - Resistence to length extension
* - Resistence to collision attacks on the underlying hash function
* - Resistence to chosen prefix attacks
*
* There is no threat of length extension here. Nor is there any real
* practical threat of collision: not only are we using a good hash
* function (you may mock me in 10 years if it is broken) but we don't
* provide the attacker much control over the input. Nor do we let them
* have the prefix.
*/
// Then hash extraClampedTime and store it in the cache
nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Init(nsICryptoHash::SHA256);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Update(sSecretMidpointSeed.get(), kSeedSize);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Update((const uint8_t *)&extraClampedTime, sizeof(extraClampedTime));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCStringN<HASH_DIGEST_SIZE_BYTES> derivedSecret;
rv = hasher->Finish(false, derivedSecret);
NS_ENSURE_SUCCESS(rv, rv);
// Finally, store it in the cache
sCache->Store(extraClampedTime, derivedSecret);
hashResult = derivedSecret;
}
// Offset the appropriate index into the hash output, and then turn it into a random midpoint
// between 0 and aResolutionUSec
int byteOffset = ((aClampedTimeUSec - extraClampedTime) / aResolutionUSec) * 4;
uint32_t deterministiclyRandomValue = *BitwiseCast<uint32_t*>(PromiseFlatCString(hashResult).get() + byteOffset);
deterministiclyRandomValue %= aResolutionUSec;
*aMidpointOut = deterministiclyRandomValue;
return NS_OK;
}
/**
* Given a precision value, this function will reduce a given input time to the nearest
* multiple of that precision.
@ -182,18 +443,31 @@ nsRFPService::ReduceTimePrecisionImpl(
// round consistently towards positive infinity or negative infinity (we chose negative.)
// This can't be done with a truncation, it must be done with floor.
long long clamped = floor(double(timeAsInt) / resolutionAsInt) * resolutionAsInt;
long long midpoint = 0,
clampedAndJittered = clamped;
if (sJitter) {
if(!NS_FAILED(RandomMidpoint(clamped, resolutionAsInt, &midpoint)) &&
timeAsInt >= clamped + midpoint) {
clampedAndJittered += resolutionAsInt;
}
}
// Cast it back to a double and reduce it to the correct units.
double ret = double(clamped) / (1000000.0 / aTimeScale);
double ret = double(clampedAndJittered) / (1000000.0 / aTimeScale);
#if defined(DEBUG)
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("Given: (%.*f, Scaled: %.*f, Converted: %lli), Rounding with (%lli, Originally %.*f), Intermediate: (%lli), Got: (%lli Converted: %.*f)",
DBL_DIG-1, aTime, DBL_DIG-1, timeScaled, timeAsInt, resolutionAsInt, DBL_DIG-1, aResolutionUSec,
(long long)floor(double(timeAsInt) / resolutionAsInt), clamped, DBL_DIG-1, ret));
bool tmp_jitter = sJitter;
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("Given: (%.*f, Scaled: %.*f, Converted: %lli), Rounding with (%lli, Originally %.*f), "
"Intermediate: (%lli), Clamped: (%lli) Jitter: (%i Midpoint: %lli) Final: (%lli Converted: %.*f)",
DBL_DIG-1, aTime, DBL_DIG-1, timeScaled, timeAsInt, resolutionAsInt, DBL_DIG-1, aResolutionUSec,
(long long)floor(double(timeAsInt) / resolutionAsInt), clamped, tmp_jitter, midpoint, clampedAndJittered, DBL_DIG-1, ret));
#endif
return ret;
}
return ret;
}
/* static */
double
@ -330,6 +604,8 @@ nsRFPService::Init()
nsresult rv;
sLock = new mozilla::Mutex("mozilla.resistFingerprinting.mLock");
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
@ -353,6 +629,9 @@ nsRFPService::Init()
rv = prefs->AddObserver(RFP_TIMER_VALUE_PREF, this, false);
NS_ENSURE_SUCCESS(rv, rv);
rv = prefs->AddObserver(RFP_JITTER_VALUE_PREF, this, false);
NS_ENSURE_SUCCESS(rv, rv);
Preferences::AddAtomicBoolVarCache(&sPrivacyTimerPrecisionReduction,
RFP_TIMER_PREF,
true);
@ -360,6 +639,9 @@ nsRFPService::Init()
Preferences::AddAtomicUintVarCache(&sResolutionUSec,
RFP_TIMER_VALUE_PREF,
RFP_TIMER_VALUE_DEFAULT);
Preferences::AddAtomicBoolVarCache(&sJitter,
RFP_JITTER_VALUE_PREF,
RFP_JITTER_VALUE_DEFAULT);
Preferences::AddUintVarCache(&sVideoFramesPerSec,
RFP_SPOOFED_FRAMES_PER_SEC_PREF,
RFP_SPOOFED_FRAMES_PER_SEC_DEFAULT);
@ -388,9 +670,9 @@ nsRFPService::UpdateTimers() {
MOZ_ASSERT(NS_IsMainThread());
if (sPrivacyResistFingerprinting || sPrivacyTimerPrecisionReduction) {
JS::SetTimeResolutionUsec(TimerResolution());
JS::SetTimeResolutionUsec(TimerResolution(), sJitter);
} else if (sInitialized) {
JS::SetTimeResolutionUsec(0);
JS::SetTimeResolutionUsec(0, false);
}
}
@ -456,8 +738,15 @@ nsRFPService::StartShutdown()
prefs->RemoveObserver(RESIST_FINGERPRINTING_PREF, this);
prefs->RemoveObserver(RFP_TIMER_PREF, this);
prefs->RemoveObserver(RFP_TIMER_VALUE_PREF, this);
prefs->RemoveObserver(RFP_JITTER_VALUE_PREF, this);
}
}
sSecretMidpointSeed = nullptr;
sCache = nullptr;
delete sLock;
sLock = nullptr;
}
/* static */
@ -692,7 +981,9 @@ nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
NS_ConvertUTF16toUTF8 pref(aMessage);
if (pref.EqualsLiteral(RFP_TIMER_PREF) || pref.EqualsLiteral(RFP_TIMER_VALUE_PREF)) {
if (pref.EqualsLiteral(RFP_TIMER_PREF) ||
pref.EqualsLiteral(RFP_TIMER_VALUE_PREF) ||
pref.EqualsLiteral(RFP_JITTER_VALUE_PREF)) {
UpdateTimers();
}
else if (pref.EqualsLiteral(RESIST_FINGERPRINTING_PREF)) {

Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/Atomics.h"
#include "mozilla/EventForwards.h"
#include "mozilla/Mutex.h"
#include "nsIDocument.h"
#include "nsIObserver.h"
@ -47,6 +48,9 @@
#define SPOOFED_APPNAME "Netscape"
#define LEGACY_BUILD_ID "20100101"
// Forward declare LRUCache, defined in nsRFPService.cpp
class LRUCache;
namespace mozilla {
enum KeyboardLang {
@ -175,13 +179,17 @@ public:
static double ReduceTimePrecisionAsSecs(
double aTime,
TimerPrecisionType aType = TimerPrecisionType::All);
// Public only for testing purposes
static double ReduceTimePrecisionImpl(
double aTime,
TimeScale aTimeScale,
double aResolutionUSec,
TimerPrecisionType aType);
static nsresult RandomMidpoint(long long aClampedTimeUSec,
long long aResolutionUSec,
long long* aMidpointOut,
uint8_t * aSecretSeed = nullptr);
// This method calculates the video resolution (i.e. height x width) based
// on the video quality (480p, 720p, etc).
@ -261,6 +269,10 @@ private:
static nsDataHashtable<KeyboardHashKey, const SpoofingKeyboardCode*>* sSpoofingKeyboardCodes;
static mozilla::Mutex* sLock;
static UniquePtr<LRUCache> sCache;
static UniquePtr<uint8_t[]> sSecretMidpointSeed;
nsCString mInitialTZValue;
};

Просмотреть файл

@ -7,6 +7,8 @@
#include <math.h>
#include "gtest/gtest.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsRFPService.h"
using namespace mozilla;
@ -42,6 +44,25 @@ using namespace mozilla;
They're supposed to be equal. They're not. But they both round to 2064.83.
*/
bool setupJitter(bool enabled) {
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
bool jitterEnabled = false;
if (prefs) {
prefs->GetBoolPref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", &jitterEnabled);
prefs->SetBoolPref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", enabled);
}
return jitterEnabled;
}
void cleanupJitter(bool jitterWasEnabled) {
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
prefs->SetBoolPref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", jitterWasEnabled);
}
}
void process(double clock, nsRFPService::TimeScale clockUnits, double precision) {
double reduced1 = nsRFPService::ReduceTimePrecisionImpl(clock, clockUnits, precision, TimerPrecisionType::All);
double reduced2 = nsRFPService::ReduceTimePrecisionImpl(reduced1, clockUnits, precision, TimerPrecisionType::All);
@ -54,36 +75,45 @@ TEST(ResistFingerprinting, ReducePrecision_Assumptions) {
}
TEST(ResistFingerprinting, ReducePrecision_Reciprocal) {
bool jitterEnabled = setupJitter(false);
// This one has a rounding error in the Reciprocal case:
process(2064.8338460, nsRFPService::TimeScale::MicroSeconds, 20);
// These are just big values
process(1516305819, nsRFPService::TimeScale::MicroSeconds, 20);
process(69053.12, nsRFPService::TimeScale::MicroSeconds, 20);
cleanupJitter(jitterEnabled);
}
TEST(ResistFingerprinting, ReducePrecision_KnownGood) {
bool jitterEnabled = setupJitter(false);
process(2064.8338460, nsRFPService::TimeScale::MilliSeconds, 20);
process(69027.62, nsRFPService::TimeScale::MilliSeconds, 20);
process(69053.12, nsRFPService::TimeScale::MilliSeconds, 20);
cleanupJitter(jitterEnabled);
}
TEST(ResistFingerprinting, ReducePrecision_KnownBad) {
bool jitterEnabled = setupJitter(false);
process(1054.842405, nsRFPService::TimeScale::MilliSeconds, 20);
process(273.53038600000002, nsRFPService::TimeScale::MilliSeconds, 20);
process(628.66686500000003, nsRFPService::TimeScale::MilliSeconds, 20);
process(521.28919100000007, nsRFPService::TimeScale::MilliSeconds, 20);
cleanupJitter(jitterEnabled);
}
TEST(ResistFingerprinting, ReducePrecision_Edge) {
bool jitterEnabled = setupJitter(false);
process(2611.14, nsRFPService::TimeScale::MilliSeconds, 20);
process(2611.16, nsRFPService::TimeScale::MilliSeconds, 20);
process(2612.16, nsRFPService::TimeScale::MilliSeconds, 20);
process(2601.64, nsRFPService::TimeScale::MilliSeconds, 20);
process(2595.16, nsRFPService::TimeScale::MilliSeconds, 20);
process(2578.66, nsRFPService::TimeScale::MilliSeconds, 20);
cleanupJitter(jitterEnabled);
}
TEST(ResistFingerprinting, ReducePrecision_Expectations) {
bool jitterEnabled = setupJitter(false);
double result;
result = nsRFPService::ReduceTimePrecisionImpl(2611.14, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
ASSERT_EQ(result, 2611.14);
@ -97,9 +127,11 @@ TEST(ResistFingerprinting, ReducePrecision_Expectations) {
ASSERT_EQ(result, 2611.14);
result = nsRFPService::ReduceTimePrecisionImpl(2611.13, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
ASSERT_EQ(result, 2611.12);
cleanupJitter(jitterEnabled);
}
TEST(ResistFingerprinting, ReducePrecision_ExpectedLossOfPrecision) {
bool jitterEnabled = setupJitter(false);
double result;
// We lose integer precision at 9007199254740992 - let's confirm that.
result = nsRFPService::ReduceTimePrecisionImpl(9007199254740992.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
@ -113,6 +145,7 @@ TEST(ResistFingerprinting, ReducePrecision_ExpectedLossOfPrecision) {
// 9007199254743568 can be represented exactly, but will be clamped to 9007199254743564
result = nsRFPService::ReduceTimePrecisionImpl(9007199254743568.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
ASSERT_EQ(result, 9007199254743564.0);
cleanupJitter(jitterEnabled);
}
// Use an ugly but simple hack to turn an integer-based rand()
@ -127,6 +160,8 @@ TEST(ResistFingerprinting, ReducePrecision_Aggressive) {
return;
}
bool jitterEnabled = setupJitter(false);
for (int i=0; i<10000; i++) {
// Test three different time magnitudes, with decimals.
// Note that we need separate variables for the different units, as scaling
@ -171,4 +206,101 @@ TEST(ResistFingerprinting, ReducePrecision_Aggressive) {
process(time3_us, nsRFPService::TimeScale::MicroSeconds, precision1);
process(time3_us, nsRFPService::TimeScale::MicroSeconds, precision2);
}
cleanupJitter(jitterEnabled);
}
TEST(ResistFingerprinting, ReducePrecision_JitterTestVectors) {
bool jitterEnabled = setupJitter(true);
/*
* Here's our test vector. First we set the secret to the 16 byte value
* 0x000102030405060708 0x101112131415161718
*
* Then we work with a resolution of 500 us which will bucket things as such:
* Per-Clamp Buckets: [0, 500], [500, 1000], ...
* Per-Hash Buckets: [0, 4000], [4000, 8000], ...
*
* The first two hash values should be
* 0: SHA-256(0x000102030405060708 || 0x101112131415161718 || 0x0000000000000000)
* 32ca0459 bdb518be c72096dc 2667cd7a a76f94e4 c33fa679 9a1bd499 bfa4ec57
* 4000: SHA-256(0x000102030405060708 || 0x101112131415161718 || 0xa00f000000000000)
* bd0bf282 120fd8c2 459c4d05 0170179c 25136f6f 70db5c82 5807558d 148c7745
*
* The midpoints are:
* 0 : 32ca0459 % 500 = 130
* 500 : bdb518be % 500 = 429
* 1500: c72096dc % 500 = 311
* 2000: 2667cd7a % 500 = 138
* 2500: a76f94e4 % 500 = 159
* 3000: c33fa679 % 500 = 435
* 3500: 9a1bd499 % 500 = 246
* 4000: bfa4ec57 % 500 = 463
* 4500: bd0bf282 % 500 = 297
* 5000: 120fd8c2 % 500 = 38
* 5500: 459c4d05 % 500 = 357
*/
// Set the secret
long long throwAway;
uint8_t hardcodedSecret[16] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 };
nsRFPService::RandomMidpoint(0, 500, &throwAway, hardcodedSecret);
// Run the test vectors
double result;
result = nsRFPService::ReduceTimePrecisionImpl(1, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 0);
result = nsRFPService::ReduceTimePrecisionImpl(129, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 0);
result = nsRFPService::ReduceTimePrecisionImpl(130, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(131, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(499, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(500, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(600, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(928, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(929, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 1000);
result = nsRFPService::ReduceTimePrecisionImpl(930, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 1000);
result = nsRFPService::ReduceTimePrecisionImpl(1255, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 1000);
result = nsRFPService::ReduceTimePrecisionImpl(4000, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4000);
result = nsRFPService::ReduceTimePrecisionImpl(4295, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4000);
result = nsRFPService::ReduceTimePrecisionImpl(4296, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4000);
result = nsRFPService::ReduceTimePrecisionImpl(4297, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4500);
result = nsRFPService::ReduceTimePrecisionImpl(4298, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4500);
result = nsRFPService::ReduceTimePrecisionImpl(4499, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4500);
result = nsRFPService::ReduceTimePrecisionImpl(4500, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4500);
result = nsRFPService::ReduceTimePrecisionImpl(4536, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4500);
result = nsRFPService::ReduceTimePrecisionImpl(4537, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 4500);
result = nsRFPService::ReduceTimePrecisionImpl(4538, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 5000);
result = nsRFPService::ReduceTimePrecisionImpl(4539, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 5000);
result = nsRFPService::ReduceTimePrecisionImpl(5106, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
ASSERT_EQ(result, 5000);
cleanupJitter(jitterEnabled);
}