old mode 100755
new mode 100644
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -31,16 +31,17 @@
#include "nsIPrefService.h"
#include "nsIRandomGenerator.h"
#include "nsIXULAppInfo.h"
#include "nsIXULRuntime.h"
#include "nsJSUtils.h"
#include "prenv.h"
#include "nss.h"
+#include "ScopedNSSTypes.h"
#include "js/Date.h"
using namespace mozilla;
using namespace std;
static mozilla::LazyLogModule gResistFingerprintingLog("nsResistFingerprinting");
@@ -136,31 +137,31 @@ nsRFPService::IsTimerPrecisionReductionE
}
/*
* 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 is thread-safe.
*/
#define LRU_CACHE_SIZE (45)
-#define HASH_DIGEST_SIZE_BITS (256)
-#define HASH_DIGEST_SIZE_BYTES (HASH_DIGEST_SIZE_BITS / 8)
+#define AES_BLOCK_SIZE_BITS (128)
+#define AES_BLOCK_SIZE_BYTES (AES_BLOCK_SIZE_BITS / 8)
class LRUCache final
{
public:
LRUCache()
: mLock("mozilla.resistFingerprinting.LRUCache")
{
this->cache.SetLength(LRU_CACHE_SIZE);
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LRUCache)
- nsCString
+ shared_ptr<uint8_t>
Get(long long aKeyPart1, long long aKeyPart2)
{
for (auto & cacheEntry : this->cache) {
// Read optimistically befor locking
if (cacheEntry.keyPart1 == aKeyPart1 &&
cacheEntry.keyPart2 == aKeyPart2) {
MutexAutoLock lock(mLock);
@@ -168,67 +169,69 @@ public:
if (MOZ_UNLIKELY(cacheEntry.keyPart1 != aKeyPart1 ||
cacheEntry.keyPart2 != aKeyPart2)) {
// Got evicted in a race
long long tmp_keyPart1 = cacheEntry.keyPart1;
long long tmp_keyPart2 = cacheEntry.keyPart2;
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("LRU Cache HIT-MISS with %lli != %lli and %lli != %lli",
aKeyPart1, tmp_keyPart1, aKeyPart2, tmp_keyPart2));
- return EmptyCString();
+ return nullptr;
}
cacheEntry.accessTime = PR_Now();
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("LRU Cache HIT with %lli %lli", aKeyPart1, aKeyPart2));
return cacheEntry.data;
}
}
- return EmptyCString();
+ return nullptr;
}
- void
- Store(long long aKeyPart1, long long aKeyPart2, const nsCString& aValue)
+ shared_ptr<uint8_t>
+ Store(long long aKeyPart1, long long aKeyPart2, uint8_t* aValue)
{
- MOZ_DIAGNOSTIC_ASSERT(aValue.Length() == HASH_DIGEST_SIZE_BYTES);
MutexAutoLock lock(mLock);
CacheEntry* lowestKey = &this->cache[0];
for (auto & cacheEntry : this->cache) {
if (MOZ_UNLIKELY(cacheEntry.keyPart1 == aKeyPart1 &&
cacheEntry.keyPart2 == aKeyPart2)) {
// Another thread inserted before us, don't insert twice
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("LRU Cache DOUBLE STORE with %lli %lli", aKeyPart1, aKeyPart2));
- return;
+ return cacheEntry.data;
}
if (cacheEntry.accessTime < lowestKey->accessTime) {
lowestKey = &cacheEntry;
}
}
lowestKey->keyPart1 = aKeyPart1;
lowestKey->keyPart2 = aKeyPart2;
- lowestKey->data = aValue;
+ // REVIEWER: I found this idiom online; I think it is okay but not 100% sure
+ lowestKey->data = std::shared_ptr<uint8_t>(new uint8_t[AES_BLOCK_SIZE_BYTES], std::default_delete<uint8_t[]>());
+ memcpy(lowestKey->data.get(), aValue, AES_BLOCK_SIZE_BYTES);
lowestKey->accessTime = PR_Now();
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("LRU Cache STORE with %lli %lli", aKeyPart1, aKeyPart2));
+ return lowestKey->data;
}
private:
~LRUCache() = default;
struct CacheEntry
{
Atomic<long long, Relaxed> keyPart1;
Atomic<long long, Relaxed> keyPart2;
PRTime accessTime = 0;
- nsCString data;
+ shared_ptr<uint8_t> data;
CacheEntry()
{
this->keyPart1 = 0xFFFFFFFFFFFFFFFF;
this->keyPart2 = 0xFFFFFFFFFFFFFFFF;
this->accessTime = 0;
this->data = nullptr;
}
@@ -316,34 +319,34 @@ nsresult
nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
long long aResolutionUSec,
int64_t aContextMixin,
long long* aMidpointOut,
uint8_t * aSecretSeed /* = nullptr */)
{
nsresult rv;
const int kSeedSize = 16;
- const int kClampTimesPerDigest = HASH_DIGEST_SIZE_BITS / 32;
+ const int kClampTimesPerDigest = AES_BLOCK_SIZE_BITS / 32;
static uint8_t * sSecretMidpointSeed = nullptr;
if(MOZ_UNLIKELY(!aMidpointOut)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<LRUCache> cache;
{
StaticMutexAutoLock lock(sLock);
cache = sCache;
}
if(!cache) {
return NS_ERROR_FAILURE;
}
- /*
+ /* TODOFIXME
* 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.
@@ -356,19 +359,19 @@ nsRFPService::RandomMidpoint(long long a
* 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 = cache->Get(extraClampedTime, aContextMixin);
+ shared_ptr<uint8_t> deterministiclyRandomBytes = cache->Get(extraClampedTime, aContextMixin);
- if(hashResult.Length() != HASH_DIGEST_SIZE_BYTES) { // Cache Miss =(
+ if(deterministiclyRandomBytes == nullptr) { // Cache Miss =(
// If someone has pased in the testing-only parameter, replace our seed with it
if (aSecretSeed != nullptr) {
StaticMutexAutoLock lock(sLock);
if (sSecretMidpointSeed) {
delete[] sSecretMidpointSeed;
}
sSecretMidpointSeed = new uint8_t[kSeedSize];
memcpy(sSecretMidpointSeed, aSecretSeed, kSeedSize);
@@ -382,72 +385,72 @@ nsRFPService::RandomMidpoint(long long a
do_GetService("@mozilla.org/security/random-generator;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = randomGenerator->GenerateRandomBytes(kSeedSize, &sSecretMidpointSeed);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
}
- /*
- * 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.
- */
+ // We'd like to reuse as much of this as possible, but that doesn't seem possible with
+ // the PK11 interface.
+ UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot);
+
+ UniqueSECItem secItem(::SECITEM_AllocItem(nullptr, nullptr, AES_BLOCK_SIZE_BYTES));
+ MOZ_ASSERT(secItem);
+ memcpy(secItem->data, sSecretMidpointSeed, AES_BLOCK_SIZE_BYTES);
+
+ UniquePK11SymKey symKey(PK11_ImportSymKey(slot.get(), CKM_AES_CTR,
+ PK11_OriginUnwrap, CKA_ENCRYPT,
+ secItem.get(), nullptr));
+ MOZ_ASSERT(symKey);
- // 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, kSeedSize);
- NS_ENSURE_SUCCESS(rv, rv);
+ // Set up CTR Parameters
+ SECItem param = { siBuffer, nullptr, 0 };
+ CK_AES_CTR_PARAMS ctrParams;
+ ctrParams.ulCounterBits = AES_BLOCK_SIZE_BITS;
+ // This is the value of the entire IV, normally encompassing 64 bits of nonce and 64
+ // bits of counter. We set the IV to be the clamped time and the ontext mix-in
+ memset(&ctrParams.cb, 0x00, AES_BLOCK_SIZE_BYTES);
+ memcpy(&ctrParams.cb, &extraClampedTime, sizeof(extraClampedTime));
+ memcpy(&ctrParams.cb[sizeof(extraClampedTime)], &aContextMixin, sizeof(aContextMixin));
+ param.type = siBuffer;
+ param.data = (unsigned char*) &ctrParams;
+ param.len = sizeof(ctrParams);
- rv = hasher->Update((const uint8_t *)&aContextMixin, sizeof(aContextMixin));
- NS_ENSURE_SUCCESS(rv, rv);
+ // Plaintext
+ // We set the plaintext to be all zeros
+ uint8_t plaintext[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
- rv = hasher->Update((const uint8_t *)&extraClampedTime, sizeof(extraClampedTime));
- NS_ENSURE_SUCCESS(rv, rv);
+ // Where to store the result
+ uint8_t ciphertext[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
- nsAutoCStringN<HASH_DIGEST_SIZE_BYTES> derivedSecret;
- rv = hasher->Finish(false, derivedSecret);
- NS_ENSURE_SUCCESS(rv, rv);
+ // Perform the encryption/decryption
+ uint32_t outLen = 0;
+ rv = MapSECStatus(PK11_Encrypt(symKey.get(), CKM_AES_CTR, ¶m,
+ ciphertext, &outLen, sizeof(ciphertext),
+ plaintext, sizeof(plaintext)));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(outLen == sizeof(ciphertext));
- // Finally, store it in the cache
- cache->Store(extraClampedTime, aContextMixin, derivedSecret);
- hashResult = derivedSecret;
+ // Finally, store it in the cache
+ deterministiclyRandomBytes = cache->Store(extraClampedTime, aContextMixin, ciphertext);
}
// Offset the appropriate index into the hash output, and then turn it into a random midpoint
// between 0 and aResolutionUSec. Sometimes out input time is negative, we ride the negative
// out to the end until we start doing pointer math. (We also triple check we're in bounds.)
int byteOffset = abs(((aClampedTimeUSec - extraClampedTime) / aResolutionUSec) * 4);
- if (MOZ_UNLIKELY(byteOffset > (HASH_DIGEST_SIZE_BYTES - 4))) {
+ if (MOZ_UNLIKELY(byteOffset > (AES_BLOCK_SIZE_BYTES - 4))) {
byteOffset = 0;
}
- uint32_t deterministiclyRandomValue = *BitwiseCast<uint32_t*>(PromiseFlatCString(hashResult).get() + byteOffset);
+ uint32_t deterministiclyRandomValue = *BitwiseCast<uint32_t*>(deterministiclyRandomBytes.get() + byteOffset);
deterministiclyRandomValue %= aResolutionUSec;
*aMidpointOut = deterministiclyRandomValue;
return NS_OK;
}
/**