blob: 6aa247bd87702158824716a665ee1b78696a6a47 [file] [log] [blame]
/*
* Copyright (C) 2018 Igalia S.L. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "DeviceIdHashSaltStorage.h"
#include "PersistencyUtils.h"
#include <WebCore/SharedBuffer.h>
#include <wtf/CryptographicallyRandomNumber.h>
#include <wtf/FileSystem.h>
#include <wtf/HexNumber.h>
#include <wtf/RunLoop.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringHash.h>
namespace WebKit {
using namespace WebCore;
static constexpr unsigned deviceIdHashSaltStorageVersion { 1 };
static constexpr unsigned hashSaltSize { 48 };
static constexpr unsigned randomDataSize { hashSaltSize / 16 };
Ref<DeviceIdHashSaltStorage> DeviceIdHashSaltStorage::create(const String& deviceIdHashSaltStorageDirectory)
{
auto deviceIdHashSaltStorage = adoptRef(*new DeviceIdHashSaltStorage(deviceIdHashSaltStorageDirectory));
return deviceIdHashSaltStorage;
}
void DeviceIdHashSaltStorage::completePendingHandler(CompletionHandler<void(HashSet<SecurityOriginData>&&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
HashSet<SecurityOriginData> origins;
for (auto& hashSaltForOrigin : m_deviceIdHashSaltForOrigins) {
origins.add(hashSaltForOrigin.value->documentOrigin);
origins.add(hashSaltForOrigin.value->parentOrigin);
}
RunLoop::main().dispatch([origins = WTFMove(origins), completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(WTFMove(origins));
});
}
DeviceIdHashSaltStorage::DeviceIdHashSaltStorage(const String& deviceIdHashSaltStorageDirectory)
: m_queue(WorkQueue::create("com.apple.WebKit.DeviceIdHashSaltStorage"))
, m_deviceIdHashSaltStorageDirectory(!deviceIdHashSaltStorageDirectory.isEmpty() ? FileSystem::pathByAppendingComponent(deviceIdHashSaltStorageDirectory, String::number(deviceIdHashSaltStorageVersion)) : String())
{
if (m_deviceIdHashSaltStorageDirectory.isEmpty()) {
m_isLoaded = true;
return;
}
loadStorageFromDisk([this, protectedThis = makeRef(*this)] (auto&& deviceIdHashSaltForOrigins) {
ASSERT(RunLoop::isMain());
m_deviceIdHashSaltForOrigins = WTFMove(deviceIdHashSaltForOrigins);
m_isLoaded = true;
auto pendingCompletionHandlers = WTFMove(m_pendingCompletionHandlers);
for (auto& completionHandler : pendingCompletionHandlers)
completionHandler();
});
}
DeviceIdHashSaltStorage::~DeviceIdHashSaltStorage()
{
auto pendingCompletionHandlers = WTFMove(m_pendingCompletionHandlers);
for (auto& completionHandler : pendingCompletionHandlers)
completionHandler();
}
static WTF::Optional<SecurityOriginData> getSecurityOriginData(const char* name, KeyedDecoder* decoder)
{
String origin;
if (!decoder->decodeString(name, origin))
return WTF::nullopt;
auto securityOriginData = SecurityOriginData::fromDatabaseIdentifier(origin);
if (!securityOriginData)
return WTF::nullopt;
return securityOriginData;
}
void DeviceIdHashSaltStorage::loadStorageFromDisk(CompletionHandler<void(HashMap<String, std::unique_ptr<HashSaltForOrigin>>&&)>&& completionHandler)
{
m_queue->dispatch([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable {
ASSERT(!RunLoop::isMain());
FileSystem::makeAllDirectories(m_deviceIdHashSaltStorageDirectory);
auto originPaths = FileSystem::listDirectory(m_deviceIdHashSaltStorageDirectory, "*");
HashMap<String, std::unique_ptr<HashSaltForOrigin>> deviceIdHashSaltForOrigins;
for (const auto& originPath : originPaths) {
URL url;
url.setProtocol("file"_s);
url.setPath(originPath);
String deviceIdHashSalt = url.lastPathComponent();
if (hashSaltSize != deviceIdHashSalt.length()) {
RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: The length of the hash salt (%d) is different to the length of the hash salts defined in WebKit (%d)", deviceIdHashSalt.length(), hashSaltSize);
continue;
}
long long fileSize = 0;
if (!FileSystem::getFileSize(originPath, fileSize)) {
RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: Impossible to get the file size of: '%s'", originPath.utf8().data());
continue;
}
auto decoder = createForFile(originPath);
if (!decoder) {
RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: Impossible to access the file to restore the hash salt: '%s'", originPath.utf8().data());
continue;
}
auto hashSaltForOrigin = getDataFromDecoder(decoder.get(), WTFMove(deviceIdHashSalt));
auto origins = makeString(hashSaltForOrigin->documentOrigin.toString(), hashSaltForOrigin->parentOrigin.toString());
auto deviceIdHashSaltForOrigin = deviceIdHashSaltForOrigins.ensure(origins, [hashSaltForOrigin = WTFMove(hashSaltForOrigin)] () mutable {
return WTFMove(hashSaltForOrigin);
});
if (!deviceIdHashSaltForOrigin.isNewEntry)
RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: There are two files with different hash salts for the same origin: '%s'", originPath.utf8().data());
}
RunLoop::main().dispatch([deviceIdHashSaltForOrigins = WTFMove(deviceIdHashSaltForOrigins), completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(WTFMove(deviceIdHashSaltForOrigins));
});
});
}
std::unique_ptr<DeviceIdHashSaltStorage::HashSaltForOrigin> DeviceIdHashSaltStorage::getDataFromDecoder(KeyedDecoder* decoder, String&& deviceIdHashSalt) const
{
auto securityOriginData = getSecurityOriginData("origin", decoder);
if (!securityOriginData) {
RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: The security origin data in the file is not correct: '%s'", deviceIdHashSalt.utf8().data());
return nullptr;
}
auto parentSecurityOriginData = getSecurityOriginData("parentOrigin", decoder);
if (!parentSecurityOriginData) {
RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: The parent security origin data in the file is not correct: '%s'", deviceIdHashSalt.utf8().data());
return nullptr;
}
double lastTimeUsed;
if (!decoder->decodeDouble("lastTimeUsed", lastTimeUsed)) {
RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: The last time used was not correctly restored for: '%s'", deviceIdHashSalt.utf8().data());
return nullptr;
}
auto hashSaltForOrigin = makeUnique<HashSaltForOrigin>(WTFMove(securityOriginData.value()), WTFMove(parentSecurityOriginData.value()), WTFMove(deviceIdHashSalt));
hashSaltForOrigin->lastTimeUsed = WallTime::fromRawSeconds(lastTimeUsed);
return hashSaltForOrigin;
}
std::unique_ptr<KeyedEncoder> DeviceIdHashSaltStorage::createEncoderFromData(const HashSaltForOrigin& hashSaltForOrigin) const
{
auto encoder = KeyedEncoder::encoder();
encoder->encodeString("origin", hashSaltForOrigin.documentOrigin.databaseIdentifier());
encoder->encodeString("parentOrigin", hashSaltForOrigin.parentOrigin.databaseIdentifier());
encoder->encodeDouble("lastTimeUsed", hashSaltForOrigin.lastTimeUsed.secondsSinceEpoch().value());
return encoder;
}
void DeviceIdHashSaltStorage::storeHashSaltToDisk(const HashSaltForOrigin& hashSaltForOrigin)
{
if (m_deviceIdHashSaltStorageDirectory.isEmpty())
return;
m_queue->dispatch([this, protectedThis = makeRef(*this), hashSaltForOrigin = hashSaltForOrigin.isolatedCopy()]() mutable {
auto encoder = createEncoderFromData(hashSaltForOrigin);
writeToDisk(WTFMove(encoder), FileSystem::pathByAppendingComponent(m_deviceIdHashSaltStorageDirectory, hashSaltForOrigin.deviceIdHashSalt));
});
}
void DeviceIdHashSaltStorage::completeDeviceIdHashSaltForOriginCall(SecurityOriginData&& documentOrigin, SecurityOriginData&& parentOrigin, CompletionHandler<void(String&&)>&& completionHandler)
{
auto origins = makeString(documentOrigin.toString(), parentOrigin.toString());
auto& deviceIdHashSalt = m_deviceIdHashSaltForOrigins.ensure(origins, [documentOrigin = WTFMove(documentOrigin), parentOrigin = WTFMove(parentOrigin)] () mutable {
uint64_t randomData[randomDataSize];
cryptographicallyRandomValues(reinterpret_cast<unsigned char*>(randomData), sizeof(randomData));
StringBuilder builder;
builder.reserveCapacity(hashSaltSize);
for (unsigned i = 0; i < randomDataSize; i++)
appendUnsignedAsHex(randomData[i], builder);
String deviceIdHashSalt = builder.toString();
auto newHashSaltForOrigin = makeUnique<HashSaltForOrigin>(WTFMove(documentOrigin), WTFMove(parentOrigin), WTFMove(deviceIdHashSalt));
return newHashSaltForOrigin;
}).iterator->value;
deviceIdHashSalt->lastTimeUsed = WallTime::now();
storeHashSaltToDisk(*deviceIdHashSalt.get());
completionHandler(String(deviceIdHashSalt->deviceIdHashSalt));
}
void DeviceIdHashSaltStorage::deviceIdHashSaltForOrigin(const SecurityOrigin& documentOrigin, const SecurityOrigin& parentOrigin, CompletionHandler<void(String&&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (!m_isLoaded) {
m_pendingCompletionHandlers.append([this, documentOrigin = documentOrigin.data().isolatedCopy(), parentOrigin = parentOrigin.data().isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
completeDeviceIdHashSaltForOriginCall(WTFMove(documentOrigin), WTFMove(parentOrigin), WTFMove(completionHandler));
});
return;
}
completeDeviceIdHashSaltForOriginCall(SecurityOriginData(documentOrigin.data()), SecurityOriginData(parentOrigin.data()), WTFMove(completionHandler));
}
void DeviceIdHashSaltStorage::getDeviceIdHashSaltOrigins(CompletionHandler<void(HashSet<SecurityOriginData>&&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (!m_isLoaded) {
m_pendingCompletionHandlers.append([this, completionHandler = WTFMove(completionHandler)]() mutable {
completePendingHandler(WTFMove(completionHandler));
});
return;
}
completePendingHandler(WTFMove(completionHandler));
}
void DeviceIdHashSaltStorage::deleteHashSaltFromDisk(const HashSaltForOrigin& hashSaltForOrigin)
{
m_queue->dispatch([this, protectedThis = makeRef(*this), deviceIdHashSalt = hashSaltForOrigin.deviceIdHashSalt.isolatedCopy()]() mutable {
ASSERT(!RunLoop::isMain());
String fileFullPath = FileSystem::pathByAppendingComponent(m_deviceIdHashSaltStorageDirectory, deviceIdHashSalt.utf8().data());
FileSystem::deleteFile(fileFullPath);
});
}
void DeviceIdHashSaltStorage::deleteDeviceIdHashSaltForOrigins(const Vector<SecurityOriginData>& origins, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
m_deviceIdHashSaltForOrigins.removeIf([this, &origins](auto& keyAndValue) {
bool needsRemoval = origins.contains(keyAndValue.value->documentOrigin) || origins.contains(keyAndValue.value->parentOrigin);
if (m_deviceIdHashSaltStorageDirectory.isEmpty())
return needsRemoval;
if (needsRemoval)
this->deleteHashSaltFromDisk(*keyAndValue.value.get());
return needsRemoval;
});
RunLoop::main().dispatch(WTFMove(completionHandler));
}
void DeviceIdHashSaltStorage::deleteDeviceIdHashSaltOriginsModifiedSince(WallTime time, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
m_deviceIdHashSaltForOrigins.removeIf([this, time](auto& keyAndValue) {
bool needsRemoval = keyAndValue.value->lastTimeUsed > time;
if (m_deviceIdHashSaltStorageDirectory.isEmpty())
return needsRemoval;
if (needsRemoval)
this->deleteHashSaltFromDisk(*keyAndValue.value.get());
return needsRemoval;
});
RunLoop::main().dispatch(WTFMove(completionHandler));
}
} // namespace WebKit