Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(85)

Side by Side Diff: chrome/browser/chromeos/platform_keys/platform_keys_service.cc

Issue 892103003: PlatformKeysService: Process state accessing operations sequentially. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@cert_impl_sign
Patch Set: Removed NOT_STARTED state. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/chromeos/platform_keys/platform_keys_service.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/chromeos/platform_keys/platform_keys_service.h" 5 #include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
6 6
7 #include "base/base64.h" 7 #include "base/base64.h"
8 #include "base/callback.h" 8 #include "base/callback.h"
9 #include "base/callback_helpers.h"
9 #include "base/values.h" 10 #include "base/values.h"
10 #include "chrome/browser/chromeos/platform_keys/platform_keys.h" 11 #include "chrome/browser/chromeos/platform_keys/platform_keys.h"
11 #include "content/public/browser/browser_thread.h" 12 #include "content/public/browser/browser_thread.h"
12 #include "extensions/browser/state_store.h" 13 #include "extensions/browser/state_store.h"
13 #include "net/cert/x509_certificate.h" 14 #include "net/cert/x509_certificate.h"
14 15
15 using content::BrowserThread; 16 using content::BrowserThread;
16 17
17 namespace chromeos { 18 namespace chromeos {
18 19
19 namespace { 20 namespace {
20 21
21 const char kErrorKeyNotAllowedForSigning[] = 22 const char kErrorKeyNotAllowedForSigning[] =
22 "This key is not allowed for signing. Either it was used for signing " 23 "This key is not allowed for signing. Either it was used for signing "
23 "before or it was not correctly generated."; 24 "before or it was not correctly generated.";
24 const char kStateStorePlatformKeys[] = "PlatformKeys"; 25 const char kStateStorePlatformKeys[] = "PlatformKeys";
25 26
26 scoped_ptr<base::StringValue> GetPublicKeyValue( 27 scoped_ptr<base::StringValue> GetPublicKeyValue(
27 const std::string& public_key_spki_der) { 28 const std::string& public_key_spki_der) {
28 std::string public_key_spki_der_b64; 29 std::string public_key_spki_der_b64;
29 base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64); 30 base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64);
30 return make_scoped_ptr(new base::StringValue(public_key_spki_der_b64)); 31 return make_scoped_ptr(new base::StringValue(public_key_spki_der_b64));
31 } 32 }
32 33
33 void RunGenerateKeyCallback(
34 const PlatformKeysService::GenerateKeyCallback& callback,
35 const std::string& public_key_spki_der) {
36 callback.Run(public_key_spki_der, std::string() /* no error */);
37 }
38
39 // Callback used by |PlatformKeysService::Sign|.
40 // Is called with the old validity of |public_key_spki_der| (or false if an
41 // error occurred during reading the StateStore). If allowed, starts the actual
42 // signing operation which will call back |callback|. If not allowed, calls
43 // |callback| with an error.
44 void CheckValidityAndSign(const std::string& token_id,
45 scoped_ptr<platform_keys::SignRSAParams> params,
46 const PlatformKeysService::SignCallback& callback,
47 content::BrowserContext* browser_context,
48 bool key_is_valid) {
49 if (!key_is_valid) {
50 callback.Run(std::string() /* no signature */,
51 kErrorKeyNotAllowedForSigning);
52 return;
53 }
54 platform_keys::subtle::SignRSA(token_id, params.Pass(), callback,
55 browser_context);
56 }
57
58 } // namespace 34 } // namespace
59 35
36 class PlatformKeysService::Task {
37 public:
38 Task() {}
39 virtual ~Task() {}
40 virtual void Start() = 0;
41 virtual bool IsDone() = 0;
42
43 private:
44 DISALLOW_ASSIGN(Task);
45 };
46
47 class PlatformKeysService::PermissionUpdateTask : public Task {
48 public:
49 enum class Step {
50 READ_PLATFORM_KEYS,
51 WRITE_UPDATE_AND_CALLBACK,
52 DONE,
53 };
54
55 PermissionUpdateTask(const bool new_permission_value,
kaliamoorthi 2015/02/09 15:13:15 Document
pneubeck (no reviews) 2015/02/09 15:36:11 Done.
56 const std::string& public_key_spki_der,
57 const std::string& extension_id,
58 base::Callback<void(Task*)> callback,
59 PlatformKeysService* service)
60 : new_permission_value_(new_permission_value),
61 public_key_spki_der_(public_key_spki_der),
62 extension_id_(extension_id),
63 callback_(callback),
64 service_(service),
65 weak_factory_(this) {}
66
67 ~PermissionUpdateTask() override {}
68
69 void Start() override {
70 CHECK(next_step_ == Step::READ_PLATFORM_KEYS);
71 DoStep();
72 }
73
74 bool IsDone() override { return next_step_ == Step::DONE; }
75
76 bool old_permission_value() { return old_permission_value_; }
77
78 private:
79 void DoStep() {
80 switch (next_step_) {
81 case Step::READ_PLATFORM_KEYS:
82 next_step_ = Step::WRITE_UPDATE_AND_CALLBACK;
83 ReadPlatformKeys();
84 return;
85 case Step::WRITE_UPDATE_AND_CALLBACK:
86 next_step_ = Step::DONE;
87 WriteUpdate();
88 if (!callback_.is_null()) {
89 // Make a local copy of the callback to run as it might be deleted
90 // during the Run().
91 base::ResetAndReturn(&callback_).Run(this);
92 // |this| might be invalid now.
93 }
94 return;
95 case Step::DONE:
96 NOTREACHED();
97 return;
98 }
99 }
100
101 // Reads the PlatformKeys value from the extension's state store and calls
102 // back to GotPlatformKeys().
103 void ReadPlatformKeys() {
104 service_->GetPlatformKeysOfExtension(
105 extension_id_, base::Bind(&PermissionUpdateTask::GotPlatformKeys,
106 weak_factory_.GetWeakPtr()));
107 }
108
109 void GotPlatformKeys(scoped_ptr<base::ListValue> platform_keys) {
110 platform_keys_ = platform_keys.Pass();
111 DoStep();
112 }
113
114 // Returns whether the extension has permission to use the key for signing
115 // according to the PlatformKeys value read from the extensions state store.
116 // Invalidates the key if it was found to be valid.
117 void WriteUpdate() {
118 scoped_ptr<base::StringValue> key_value(
119 GetPublicKeyValue(public_key_spki_der_));
120
121 base::ListValue::const_iterator it = platform_keys_->Find(*key_value);
122 old_permission_value_ = it != platform_keys_->end();
123 if (old_permission_value_ == new_permission_value_)
124 return;
125
126 if (new_permission_value_)
127 platform_keys_->Append(key_value.release());
128 else
129 platform_keys_->Remove(*key_value, nullptr);
130
131 service_->SetPlatformKeysOfExtension(extension_id_, platform_keys_.Pass());
132 }
133
134 Step next_step_ = Step::READ_PLATFORM_KEYS;
135 scoped_ptr<base::ListValue> platform_keys_;
136 bool old_permission_value_ = false;
137
138 const bool new_permission_value_;
139 const std::string public_key_spki_der_;
140 const std::string extension_id_;
141 base::Callback<void(Task*)> callback_;
142 PlatformKeysService* const service_;
143 base::WeakPtrFactory<PermissionUpdateTask> weak_factory_;
144
145 DISALLOW_COPY_AND_ASSIGN(PermissionUpdateTask);
146 };
147
148 class PlatformKeysService::SignTask : public Task {
149 public:
150 enum class Step {
151 UPDATE_PERMISSION,
152 SIGN_OR_ABORT,
153 DONE,
154 };
155
156 // This Task will check the permissions of the extension with |extension_id|
157 // for the key identified by |public_key_spki_der|, then updates the
158 // permission to prevent any future signing operation of that extension using
159 // that same key. If the permission check was positive, it will actually sign
160 // |data| with the key and pass the signature to |callback|.
161 // If an error occurs, an error message is passed to |callback| instead.
162 SignTask(const std::string& token_id,
163 scoped_ptr<platform_keys::SignRSAParams> params,
164 const std::string& extension_id,
165 const SignCallback& callback,
166 PlatformKeysService* service)
167 : token_id_(token_id),
168 params_(params.Pass()),
169 extension_id_(extension_id),
170 callback_(callback),
171 service_(service),
172 weak_factory_(this) {}
173 ~SignTask() override {}
174
175 void Start() override {
176 CHECK(next_step_ == Step::UPDATE_PERMISSION);
177 DoStep();
178 }
179 bool IsDone() override { return next_step_ == Step::DONE; }
180
181 private:
182 void DoStep() {
183 switch (next_step_) {
184 case Step::UPDATE_PERMISSION:
185 next_step_ = Step::SIGN_OR_ABORT;
186 UpdatePermission();
187 return;
188 case Step::SIGN_OR_ABORT:
189 next_step_ = Step::DONE;
190 if (!service_->permission_check_enabled_ ||
191 permission_update_->old_permission_value()) {
192 Sign();
193 } else {
194 callback_.Run(std::string() /* no signature */,
195 kErrorKeyNotAllowedForSigning);
196 DoStep();
197 }
198 return;
199 case Step::DONE:
200 service_->TaskFinished(this);
201 // |this| might be invalid now.
202 return;
203 }
204 }
205
206 // Reads the current permission of the extension with |extension_id_| for key
207 // |params_->public_key| and updates the permission to disable further
208 // signing operations with that key.
209 void UpdatePermission() {
210 permission_update_.reset(new PermissionUpdateTask(
211 false /* new permission value */, params_->public_key(), extension_id_,
212 base::Bind(&SignTask::DidUpdatePermission, weak_factory_.GetWeakPtr()),
213 service_));
214 permission_update_->Start();
215 }
216
217 void DidUpdatePermission(Task* /* task */) { DoStep(); }
218
219 // Starts the actual signing operation and afterwards passes the signature (or
220 // error) to |callback_|.
221 void Sign() {
222 platform_keys::subtle::SignRSA(
223 token_id_, params_.Pass(),
224 base::Bind(&SignTask::DidSign, weak_factory_.GetWeakPtr()),
225 service_->browser_context_);
226 }
227
228 void DidSign(const std::string& signature, const std::string& error_message) {
229 callback_.Run(signature, error_message);
230 DoStep();
231 }
232
233 Step next_step_ = Step::UPDATE_PERMISSION;
234 scoped_ptr<base::ListValue> platform_keys_;
235 scoped_ptr<PermissionUpdateTask> permission_update_;
236
237 const std::string token_id_;
238 scoped_ptr<platform_keys::SignRSAParams> params_;
239 const std::string extension_id_;
240 const SignCallback callback_;
241 PlatformKeysService* const service_;
242 base::WeakPtrFactory<SignTask> weak_factory_;
243
244 DISALLOW_COPY_AND_ASSIGN(SignTask);
245 };
246
60 PlatformKeysService::PlatformKeysService( 247 PlatformKeysService::PlatformKeysService(
61 content::BrowserContext* browser_context, 248 content::BrowserContext* browser_context,
62 extensions::StateStore* state_store) 249 extensions::StateStore* state_store)
63 : browser_context_(browser_context), 250 : browser_context_(browser_context),
64 state_store_(state_store), 251 state_store_(state_store),
65 weak_factory_(this) { 252 weak_factory_(this) {
66 DCHECK(state_store); 253 DCHECK(state_store);
67 } 254 }
68 255
69 PlatformKeysService::~PlatformKeysService() { 256 PlatformKeysService::~PlatformKeysService() {
70 } 257 }
71 258
72 void PlatformKeysService::DisablePermissionCheckForTesting() { 259 void PlatformKeysService::DisablePermissionCheckForTesting() {
73 permission_check_enabled_ = false; 260 permission_check_enabled_ = false;
74 } 261 }
75 262
76 void PlatformKeysService::GenerateRSAKey(const std::string& token_id, 263 void PlatformKeysService::GenerateRSAKey(const std::string& token_id,
77 unsigned int modulus_length, 264 unsigned int modulus_length,
78 const std::string& extension_id, 265 const std::string& extension_id,
79 const GenerateKeyCallback& callback) { 266 const GenerateKeyCallback& callback) {
80 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
81 268
82 platform_keys::subtle::GenerateRSAKey( 269 platform_keys::subtle::GenerateRSAKey(
83 token_id, 270 token_id, modulus_length,
84 modulus_length, 271 base::Bind(&PlatformKeysService::GeneratedKey, weak_factory_.GetWeakPtr(),
85 base::Bind(&PlatformKeysService::GenerateRSAKeyCallback, 272 extension_id, callback),
86 weak_factory_.GetWeakPtr(),
87 extension_id,
88 callback),
89 browser_context_); 273 browser_context_);
90 } 274 }
91 275
92 void PlatformKeysService::SignRSA( 276 void PlatformKeysService::SignRSA(
93 const std::string& token_id, 277 const std::string& token_id,
94 scoped_ptr<platform_keys::SignRSAParams> params, 278 scoped_ptr<platform_keys::SignRSAParams> params,
95 const std::string& extension_id, 279 const std::string& extension_id,
96 const SignCallback& callback) { 280 const SignCallback& callback) {
97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
98 ReadValidityAndInvalidateKey( 282 StartOrQueueTask(make_scoped_ptr(
99 extension_id, params->public_key(), 283 new SignTask(token_id, params.Pass(), extension_id, callback, this)));
100 base::Bind(&CheckValidityAndSign, token_id, base::Passed(&params),
101 callback, browser_context_));
102 } 284 }
103 285
104 void PlatformKeysService::SelectClientCertificates( 286 void PlatformKeysService::SelectClientCertificates(
105 const platform_keys::ClientCertificateRequest& request, 287 const platform_keys::ClientCertificateRequest& request,
106 const std::string& extension_id, 288 const std::string& extension_id,
107 const SelectCertificatesCallback& callback) { 289 const SelectCertificatesCallback& callback) {
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 290 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
109 291
110 platform_keys::subtle::SelectClientCertificates( 292 platform_keys::subtle::SelectClientCertificates(
111 request, 293 request,
112 base::Bind(&PlatformKeysService::SelectClientCertificatesCallback, 294 base::Bind(&PlatformKeysService::SelectClientCertificatesCallback,
113 weak_factory_.GetWeakPtr(), extension_id, callback), 295 weak_factory_.GetWeakPtr(), extension_id, callback),
114 browser_context_); 296 browser_context_);
115 } 297 }
116 298
117 void PlatformKeysService::RegisterPublicKey( 299 void PlatformKeysService::StartOrQueueTask(scoped_ptr<Task> task) {
118 const std::string& extension_id, 300 tasks_.push(make_linked_ptr(task.release()));
kaliamoorthi 2015/02/09 15:13:15 Is there are reason why linked_ptr is used here ra
pneubeck (no reviews) 2015/02/09 15:36:11 scoped_ptrs don't work in containers as it interna
119 const std::string& public_key_spki_der, 301 if (tasks_.size() == 1)
120 const base::Closure& callback) { 302 tasks_.front()->Start();
121 GetPlatformKeysOfExtension(
122 extension_id,
123 base::Bind(&PlatformKeysService::RegisterPublicKeyGotPlatformKeys,
124 weak_factory_.GetWeakPtr(),
125 extension_id,
126 public_key_spki_der,
127 callback));
128 } 303 }
129 304
130 void PlatformKeysService::ReadValidityAndInvalidateKey( 305 void PlatformKeysService::TaskFinished(Task* task) {
131 const std::string& extension_id, 306 DCHECK(!tasks_.empty());
132 const std::string& public_key_spki_der, 307 DCHECK(task == tasks_.front().get());
133 const base::Callback<void(bool)>& callback) { 308 while (!tasks_.empty() && tasks_.front()->IsDone())
kaliamoorthi 2015/02/09 15:13:15 Can multiple tasks execute at the same time? Eithe
pneubeck (no reviews) 2015/02/09 15:36:11 At most one task is running at a time. Do you sugg
134 GetPlatformKeysOfExtension(extension_id, 309 tasks_.pop();
135 base::Bind(&PlatformKeysService::InvalidateKey, 310
136 weak_factory_.GetWeakPtr(), 311 if (!tasks_.empty())
137 extension_id, 312 tasks_.front()->Start();
138 public_key_spki_der,
139 callback));
140 } 313 }
141 314
142 void PlatformKeysService::GetPlatformKeysOfExtension( 315 void PlatformKeysService::GetPlatformKeysOfExtension(
143 const std::string& extension_id, 316 const std::string& extension_id,
144 const GetPlatformKeysCallback& callback) { 317 const GetPlatformKeysCallback& callback) {
145 state_store_->GetExtensionValue( 318 state_store_->GetExtensionValue(
146 extension_id, kStateStorePlatformKeys, 319 extension_id, kStateStorePlatformKeys,
147 base::Bind(&PlatformKeysService::GotPlatformKeysOfExtension, 320 base::Bind(&PlatformKeysService::GotPlatformKeysOfExtension,
148 weak_factory_.GetWeakPtr(), extension_id, callback)); 321 weak_factory_.GetWeakPtr(), extension_id, callback));
149 } 322 }
150 323
151 void PlatformKeysService::SetPlatformKeysOfExtension( 324 void PlatformKeysService::SetPlatformKeysOfExtension(
152 const std::string& extension_id, 325 const std::string& extension_id,
153 scoped_ptr<base::ListValue> platform_keys) { 326 scoped_ptr<base::ListValue> platform_keys) {
154 state_store_->SetExtensionValue(extension_id, kStateStorePlatformKeys, 327 state_store_->SetExtensionValue(extension_id, kStateStorePlatformKeys,
155 platform_keys.Pass()); 328 platform_keys.Pass());
156 } 329 }
157 330
158 void PlatformKeysService::GenerateRSAKeyCallback( 331 void PlatformKeysService::GeneratedKey(const std::string& extension_id,
159 const std::string& extension_id, 332 const GenerateKeyCallback& callback,
160 const GenerateKeyCallback& callback, 333 const std::string& public_key_spki_der,
161 const std::string& public_key_spki_der, 334 const std::string& error_message) {
162 const std::string& error_message) {
163 if (!error_message.empty()) { 335 if (!error_message.empty()) {
164 callback.Run(std::string() /* no public key */, error_message); 336 callback.Run(std::string() /* no public key */, error_message);
165 return; 337 return;
166 } 338 }
167 base::Closure wrapped_callback( 339
168 base::Bind(&RunGenerateKeyCallback, callback, public_key_spki_der)); 340 StartOrQueueTask(make_scoped_ptr(new PermissionUpdateTask(
169 RegisterPublicKey(extension_id, public_key_spki_der, wrapped_callback); 341 true /* new permission value */, public_key_spki_der, extension_id,
342 base::Bind(&PlatformKeysService::DidRegisterGeneratedKey,
343 base::Unretained(this), callback, public_key_spki_der),
344 this)));
345 }
346
347 void PlatformKeysService::DidRegisterGeneratedKey(
348 const GenerateKeyCallback& callback,
349 const std::string& public_key_spki_der,
350 Task* task) {
351 callback.Run(public_key_spki_der, std::string() /* no error */);
kaliamoorthi 2015/02/09 15:13:15 Why is this parameter needed if it is empty? If it
pneubeck (no reviews) 2015/02/09 15:36:11 which parameter? the error parameter? See Generate
352 TaskFinished(task);
170 } 353 }
171 354
172 void PlatformKeysService::SelectClientCertificatesCallback( 355 void PlatformKeysService::SelectClientCertificatesCallback(
173 const std::string& extension_id, 356 const std::string& extension_id,
174 const SelectCertificatesCallback& callback, 357 const SelectCertificatesCallback& callback,
175 scoped_ptr<net::CertificateList> matches, 358 scoped_ptr<net::CertificateList> matches,
176 const std::string& error_message) { 359 const std::string& error_message) {
177 if (permission_check_enabled_) 360 if (permission_check_enabled_)
178 matches->clear(); 361 matches->clear();
179 362
180 // TODO(pneubeck): Remove all certs that the extension doesn't have access to. 363 // TODO(pneubeck): Remove all certs that the extension doesn't have access to.
181 callback.Run(matches.Pass(), error_message); 364 callback.Run(matches.Pass(), error_message);
182 } 365 }
183 366
184 void PlatformKeysService::RegisterPublicKeyGotPlatformKeys(
185 const std::string& extension_id,
186 const std::string& public_key_spki_der,
187 const base::Closure& callback,
188 scoped_ptr<base::ListValue> platform_keys) {
189 scoped_ptr<base::StringValue> key_value(
190 GetPublicKeyValue(public_key_spki_der));
191
192 DCHECK(platform_keys->end() == platform_keys->Find(*key_value))
193 << "Keys are assumed to be generated and not to be registered multiple "
194 "times.";
195 platform_keys->Append(key_value.release());
196 SetPlatformKeysOfExtension(extension_id, platform_keys.Pass());
197 callback.Run();
198 }
199
200 void PlatformKeysService::InvalidateKey(
201 const std::string& extension_id,
202 const std::string& public_key_spki_der,
203 const base::Callback<void(bool)>& callback,
204 scoped_ptr<base::ListValue> platform_keys) {
205 scoped_ptr<base::StringValue> key_value(
206 GetPublicKeyValue(public_key_spki_der));
207
208 size_t index = 0;
209 // If the key is found in |platform_keys|, it's valid for the extension to use
210 // it for signing.
211 bool key_was_valid = platform_keys->Remove(*key_value, &index);
212
213 if (key_was_valid) {
214 // Persist that the key is now invalid.
215 SetPlatformKeysOfExtension(extension_id, platform_keys.Pass());
216 }
217
218 if (permission_check_enabled_) {
219 // If permission checks are enabled, pass back the key permission (before
220 // it was removed above).
221 callback.Run(key_was_valid);
222 } else {
223 // Otherwise just allow signing with the key (which is enabled for testing
224 // only).
225 callback.Run(true);
226 }
227 }
228
229 void PlatformKeysService::GotPlatformKeysOfExtension( 367 void PlatformKeysService::GotPlatformKeysOfExtension(
230 const std::string& extension_id, 368 const std::string& extension_id,
231 const GetPlatformKeysCallback& callback, 369 const GetPlatformKeysCallback& callback,
232 scoped_ptr<base::Value> value) { 370 scoped_ptr<base::Value> value) {
233 if (!value) 371 if (!value)
234 value.reset(new base::ListValue); 372 value.reset(new base::ListValue);
235 373
236 base::ListValue* keys = NULL; 374 base::ListValue* keys = NULL;
237 if (!value->GetAsList(&keys)) { 375 if (!value->GetAsList(&keys)) {
238 LOG(ERROR) << "Found a value of wrong type."; 376 LOG(ERROR) << "Found a value of wrong type.";
239 377
240 keys = new base::ListValue; 378 keys = new base::ListValue;
241 value.reset(keys); 379 value.reset(keys);
242 } 380 }
243 381
244 ignore_result(value.release()); 382 ignore_result(value.release());
245 callback.Run(make_scoped_ptr(keys)); 383 callback.Run(make_scoped_ptr(keys));
246 } 384 }
247 385
248 } // namespace chromeos 386 } // namespace chromeos
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/platform_keys/platform_keys_service.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698