1 | /* |
2 | * Copyright (C) 2018 Igalia S.L. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "DeviceIdHashSaltStorage.h" |
28 | |
29 | #include "PersistencyUtils.h" |
30 | |
31 | #include <WebCore/SharedBuffer.h> |
32 | #include <wtf/CryptographicallyRandomNumber.h> |
33 | #include <wtf/FileSystem.h> |
34 | #include <wtf/HexNumber.h> |
35 | #include <wtf/RunLoop.h> |
36 | #include <wtf/text/StringBuilder.h> |
37 | #include <wtf/text/StringHash.h> |
38 | |
39 | namespace WebKit { |
40 | using namespace WebCore; |
41 | |
42 | static constexpr unsigned deviceIdHashSaltStorageVersion { 1 }; |
43 | static constexpr unsigned hashSaltSize { 48 }; |
44 | static constexpr unsigned randomDataSize { hashSaltSize / 16 }; |
45 | |
46 | Ref<DeviceIdHashSaltStorage> DeviceIdHashSaltStorage::create(const String& deviceIdHashSaltStorageDirectory) |
47 | { |
48 | auto deviceIdHashSaltStorage = adoptRef(*new DeviceIdHashSaltStorage(deviceIdHashSaltStorageDirectory)); |
49 | return deviceIdHashSaltStorage; |
50 | } |
51 | |
52 | void DeviceIdHashSaltStorage::completePendingHandler(CompletionHandler<void(HashSet<SecurityOriginData>&&)>&& completionHandler) |
53 | { |
54 | ASSERT(RunLoop::isMain()); |
55 | |
56 | HashSet<SecurityOriginData> origins; |
57 | |
58 | for (auto& hashSaltForOrigin : m_deviceIdHashSaltForOrigins) { |
59 | origins.add(hashSaltForOrigin.value->documentOrigin); |
60 | origins.add(hashSaltForOrigin.value->parentOrigin); |
61 | } |
62 | |
63 | RunLoop::main().dispatch([origins = WTFMove(origins), completionHandler = WTFMove(completionHandler)]() mutable { |
64 | completionHandler(WTFMove(origins)); |
65 | }); |
66 | } |
67 | |
68 | DeviceIdHashSaltStorage::DeviceIdHashSaltStorage(const String& deviceIdHashSaltStorageDirectory) |
69 | : m_queue(WorkQueue::create("com.apple.WebKit.DeviceIdHashSaltStorage" )) |
70 | , m_deviceIdHashSaltStorageDirectory(!deviceIdHashSaltStorageDirectory.isEmpty() ? FileSystem::pathByAppendingComponent(deviceIdHashSaltStorageDirectory, String::number(deviceIdHashSaltStorageVersion)) : String()) |
71 | { |
72 | if (m_deviceIdHashSaltStorageDirectory.isEmpty()) { |
73 | m_isLoaded = true; |
74 | return; |
75 | } |
76 | |
77 | loadStorageFromDisk([this, protectedThis = makeRef(*this)] (auto&& deviceIdHashSaltForOrigins) { |
78 | ASSERT(RunLoop::isMain()); |
79 | m_deviceIdHashSaltForOrigins = WTFMove(deviceIdHashSaltForOrigins); |
80 | m_isLoaded = true; |
81 | |
82 | auto pendingCompletionHandlers = WTFMove(m_pendingCompletionHandlers); |
83 | for (auto& completionHandler : pendingCompletionHandlers) |
84 | completionHandler(); |
85 | }); |
86 | } |
87 | |
88 | DeviceIdHashSaltStorage::~DeviceIdHashSaltStorage() |
89 | { |
90 | auto pendingCompletionHandlers = WTFMove(m_pendingCompletionHandlers); |
91 | for (auto& completionHandler : pendingCompletionHandlers) |
92 | completionHandler(); |
93 | } |
94 | |
95 | static WTF::Optional<SecurityOriginData> getSecurityOriginData(const char* name, KeyedDecoder* decoder) |
96 | { |
97 | String origin; |
98 | |
99 | if (!decoder->decodeString(name, origin)) |
100 | return WTF::nullopt; |
101 | |
102 | auto securityOriginData = SecurityOriginData::fromDatabaseIdentifier(origin); |
103 | if (!securityOriginData) |
104 | return WTF::nullopt; |
105 | |
106 | return securityOriginData; |
107 | } |
108 | |
109 | void DeviceIdHashSaltStorage::loadStorageFromDisk(CompletionHandler<void(HashMap<String, std::unique_ptr<HashSaltForOrigin>>&&)>&& completionHandler) |
110 | { |
111 | m_queue->dispatch([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable { |
112 | ASSERT(!RunLoop::isMain()); |
113 | |
114 | FileSystem::makeAllDirectories(m_deviceIdHashSaltStorageDirectory); |
115 | |
116 | auto originPaths = FileSystem::listDirectory(m_deviceIdHashSaltStorageDirectory, "*" ); |
117 | |
118 | HashMap<String, std::unique_ptr<HashSaltForOrigin>> deviceIdHashSaltForOrigins; |
119 | for (const auto& originPath : originPaths) { |
120 | URL url; |
121 | url.setProtocol("file"_s ); |
122 | url.setPath(originPath); |
123 | |
124 | String deviceIdHashSalt = url.lastPathComponent(); |
125 | |
126 | if (hashSaltSize != deviceIdHashSalt.length()) { |
127 | 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); |
128 | continue; |
129 | } |
130 | |
131 | long long fileSize = 0; |
132 | if (!FileSystem::getFileSize(originPath, fileSize)) { |
133 | RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: Impossible to get the file size of: '%s'" , originPath.utf8().data()); |
134 | continue; |
135 | } |
136 | |
137 | auto decoder = createForFile(originPath); |
138 | |
139 | if (!decoder) { |
140 | RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: Impossible to access the file to restore the hash salt: '%s'" , originPath.utf8().data()); |
141 | continue; |
142 | } |
143 | |
144 | auto hashSaltForOrigin = getDataFromDecoder(decoder.get(), WTFMove(deviceIdHashSalt)); |
145 | |
146 | auto origins = makeString(hashSaltForOrigin->documentOrigin.toString(), hashSaltForOrigin->parentOrigin.toString()); |
147 | auto deviceIdHashSaltForOrigin = deviceIdHashSaltForOrigins.ensure(origins, [hashSaltForOrigin = WTFMove(hashSaltForOrigin)] () mutable { |
148 | return WTFMove(hashSaltForOrigin); |
149 | }); |
150 | |
151 | if (!deviceIdHashSaltForOrigin.isNewEntry) |
152 | RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: There are two files with different hash salts for the same origin: '%s'" , originPath.utf8().data()); |
153 | } |
154 | |
155 | RunLoop::main().dispatch([deviceIdHashSaltForOrigins = WTFMove(deviceIdHashSaltForOrigins), completionHandler = WTFMove(completionHandler)]() mutable { |
156 | completionHandler(WTFMove(deviceIdHashSaltForOrigins)); |
157 | }); |
158 | }); |
159 | } |
160 | |
161 | std::unique_ptr<DeviceIdHashSaltStorage::HashSaltForOrigin> DeviceIdHashSaltStorage::getDataFromDecoder(KeyedDecoder* decoder, String&& deviceIdHashSalt) const |
162 | { |
163 | auto securityOriginData = getSecurityOriginData("origin" , decoder); |
164 | if (!securityOriginData) { |
165 | RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: The security origin data in the file is not correct: '%s'" , deviceIdHashSalt.utf8().data()); |
166 | return nullptr; |
167 | } |
168 | |
169 | auto parentSecurityOriginData = getSecurityOriginData("parentOrigin" , decoder); |
170 | if (!parentSecurityOriginData) { |
171 | RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: The parent security origin data in the file is not correct: '%s'" , deviceIdHashSalt.utf8().data()); |
172 | return nullptr; |
173 | } |
174 | |
175 | double lastTimeUsed; |
176 | if (!decoder->decodeDouble("lastTimeUsed" , lastTimeUsed)) { |
177 | RELEASE_LOG_ERROR(DiskPersistency, "DeviceIdHashSaltStorage: The last time used was not correctly restored for: '%s'" , deviceIdHashSalt.utf8().data()); |
178 | return nullptr; |
179 | } |
180 | |
181 | auto hashSaltForOrigin = std::make_unique<HashSaltForOrigin>(WTFMove(securityOriginData.value()), WTFMove(parentSecurityOriginData.value()), WTFMove(deviceIdHashSalt)); |
182 | |
183 | hashSaltForOrigin->lastTimeUsed = WallTime::fromRawSeconds(lastTimeUsed); |
184 | |
185 | return hashSaltForOrigin; |
186 | } |
187 | |
188 | std::unique_ptr<KeyedEncoder> DeviceIdHashSaltStorage::createEncoderFromData(const HashSaltForOrigin& hashSaltForOrigin) const |
189 | { |
190 | auto encoder = KeyedEncoder::encoder(); |
191 | encoder->encodeString("origin" , hashSaltForOrigin.documentOrigin.databaseIdentifier()); |
192 | encoder->encodeString("parentOrigin" , hashSaltForOrigin.parentOrigin.databaseIdentifier()); |
193 | encoder->encodeDouble("lastTimeUsed" , hashSaltForOrigin.lastTimeUsed.secondsSinceEpoch().value()); |
194 | return encoder; |
195 | } |
196 | |
197 | void DeviceIdHashSaltStorage::storeHashSaltToDisk(const HashSaltForOrigin& hashSaltForOrigin) |
198 | { |
199 | if (m_deviceIdHashSaltStorageDirectory.isEmpty()) |
200 | return; |
201 | |
202 | m_queue->dispatch([this, protectedThis = makeRef(*this), hashSaltForOrigin = hashSaltForOrigin.isolatedCopy()]() mutable { |
203 | auto encoder = createEncoderFromData(hashSaltForOrigin); |
204 | writeToDisk(WTFMove(encoder), FileSystem::pathByAppendingComponent(m_deviceIdHashSaltStorageDirectory, hashSaltForOrigin.deviceIdHashSalt)); |
205 | }); |
206 | } |
207 | |
208 | void DeviceIdHashSaltStorage::completeDeviceIdHashSaltForOriginCall(SecurityOriginData&& documentOrigin, SecurityOriginData&& parentOrigin, CompletionHandler<void(String&&)>&& completionHandler) |
209 | { |
210 | auto origins = makeString(documentOrigin.toString(), parentOrigin.toString()); |
211 | auto& deviceIdHashSalt = m_deviceIdHashSaltForOrigins.ensure(origins, [documentOrigin = WTFMove(documentOrigin), parentOrigin = WTFMove(parentOrigin)] () mutable { |
212 | uint64_t randomData[randomDataSize]; |
213 | cryptographicallyRandomValues(reinterpret_cast<unsigned char*>(randomData), sizeof(randomData)); |
214 | |
215 | StringBuilder builder; |
216 | builder.reserveCapacity(hashSaltSize); |
217 | for (unsigned i = 0; i < randomDataSize; i++) |
218 | appendUnsignedAsHex(randomData[i], builder); |
219 | |
220 | String deviceIdHashSalt = builder.toString(); |
221 | |
222 | auto newHashSaltForOrigin = std::make_unique<HashSaltForOrigin>(WTFMove(documentOrigin), WTFMove(parentOrigin), WTFMove(deviceIdHashSalt)); |
223 | |
224 | return newHashSaltForOrigin; |
225 | }).iterator->value; |
226 | |
227 | deviceIdHashSalt->lastTimeUsed = WallTime::now(); |
228 | |
229 | storeHashSaltToDisk(*deviceIdHashSalt.get()); |
230 | |
231 | completionHandler(String(deviceIdHashSalt->deviceIdHashSalt)); |
232 | } |
233 | |
234 | void DeviceIdHashSaltStorage::deviceIdHashSaltForOrigin(const SecurityOrigin& documentOrigin, const SecurityOrigin& parentOrigin, CompletionHandler<void(String&&)>&& completionHandler) |
235 | { |
236 | ASSERT(RunLoop::isMain()); |
237 | |
238 | if (!m_isLoaded) { |
239 | m_pendingCompletionHandlers.append([this, documentOrigin = documentOrigin.data().isolatedCopy(), parentOrigin = parentOrigin.data().isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable { |
240 | completeDeviceIdHashSaltForOriginCall(WTFMove(documentOrigin), WTFMove(parentOrigin), WTFMove(completionHandler)); |
241 | }); |
242 | return; |
243 | } |
244 | |
245 | completeDeviceIdHashSaltForOriginCall(SecurityOriginData(documentOrigin.data()), SecurityOriginData(parentOrigin.data()), WTFMove(completionHandler)); |
246 | } |
247 | |
248 | void DeviceIdHashSaltStorage::getDeviceIdHashSaltOrigins(CompletionHandler<void(HashSet<SecurityOriginData>&&)>&& completionHandler) |
249 | { |
250 | ASSERT(RunLoop::isMain()); |
251 | |
252 | if (!m_isLoaded) { |
253 | m_pendingCompletionHandlers.append([this, completionHandler = WTFMove(completionHandler)]() mutable { |
254 | completePendingHandler(WTFMove(completionHandler)); |
255 | }); |
256 | return; |
257 | } |
258 | |
259 | completePendingHandler(WTFMove(completionHandler)); |
260 | } |
261 | |
262 | void DeviceIdHashSaltStorage::deleteHashSaltFromDisk(const HashSaltForOrigin& hashSaltForOrigin) |
263 | { |
264 | m_queue->dispatch([this, protectedThis = makeRef(*this), deviceIdHashSalt = hashSaltForOrigin.deviceIdHashSalt.isolatedCopy()]() mutable { |
265 | ASSERT(!RunLoop::isMain()); |
266 | |
267 | String fileFullPath = FileSystem::pathByAppendingComponent(m_deviceIdHashSaltStorageDirectory, deviceIdHashSalt.utf8().data()); |
268 | FileSystem::deleteFile(fileFullPath); |
269 | }); |
270 | } |
271 | |
272 | void DeviceIdHashSaltStorage::deleteDeviceIdHashSaltForOrigins(const Vector<SecurityOriginData>& origins, CompletionHandler<void()>&& completionHandler) |
273 | { |
274 | ASSERT(RunLoop::isMain()); |
275 | |
276 | m_deviceIdHashSaltForOrigins.removeIf([this, &origins](auto& keyAndValue) { |
277 | bool needsRemoval = origins.contains(keyAndValue.value->documentOrigin) || origins.contains(keyAndValue.value->parentOrigin); |
278 | if (m_deviceIdHashSaltStorageDirectory.isEmpty()) |
279 | return needsRemoval; |
280 | if (needsRemoval) |
281 | this->deleteHashSaltFromDisk(*keyAndValue.value.get()); |
282 | return needsRemoval; |
283 | }); |
284 | |
285 | RunLoop::main().dispatch(WTFMove(completionHandler)); |
286 | } |
287 | |
288 | void DeviceIdHashSaltStorage::deleteDeviceIdHashSaltOriginsModifiedSince(WallTime time, CompletionHandler<void()>&& completionHandler) |
289 | { |
290 | ASSERT(RunLoop::isMain()); |
291 | |
292 | m_deviceIdHashSaltForOrigins.removeIf([this, time](auto& keyAndValue) { |
293 | bool needsRemoval = keyAndValue.value->lastTimeUsed > time; |
294 | if (m_deviceIdHashSaltStorageDirectory.isEmpty()) |
295 | return needsRemoval; |
296 | if (needsRemoval) |
297 | this->deleteHashSaltFromDisk(*keyAndValue.value.get()); |
298 | return needsRemoval; |
299 | }); |
300 | |
301 | RunLoop::main().dispatch(WTFMove(completionHandler)); |
302 | } |
303 | |
304 | } // namespace WebKit |
305 | |