Passed
Push — master ( d72133...87115d )
by Joas
14:58 queued 27s
created
lib/private/Authentication/Token/PublicKeyTokenProvider.php 2 patches
Indentation   +373 added lines, -373 removed lines patch added patch discarded remove patch
@@ -42,377 +42,377 @@
 block discarded – undo
42 42
 use Psr\Log\LoggerInterface;
43 43
 
44 44
 class PublicKeyTokenProvider implements IProvider {
45
-	/** @var PublicKeyTokenMapper */
46
-	private $mapper;
47
-
48
-	/** @var ICrypto */
49
-	private $crypto;
50
-
51
-	/** @var IConfig */
52
-	private $config;
53
-
54
-	/** @var LoggerInterface */
55
-	private $logger;
56
-
57
-	/** @var ITimeFactory */
58
-	private $time;
59
-
60
-	/** @var CappedMemoryCache */
61
-	private $cache;
62
-
63
-	public function __construct(PublicKeyTokenMapper $mapper,
64
-								ICrypto $crypto,
65
-								IConfig $config,
66
-								LoggerInterface $logger,
67
-								ITimeFactory $time) {
68
-		$this->mapper = $mapper;
69
-		$this->crypto = $crypto;
70
-		$this->config = $config;
71
-		$this->logger = $logger;
72
-		$this->time = $time;
73
-
74
-		$this->cache = new CappedMemoryCache();
75
-	}
76
-
77
-	/**
78
-	 * {@inheritDoc}
79
-	 */
80
-	public function generateToken(string $token,
81
-								  string $uid,
82
-								  string $loginName,
83
-								  ?string $password,
84
-								  string $name,
85
-								  int $type = IToken::TEMPORARY_TOKEN,
86
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
87
-		$dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
88
-		$this->mapper->insert($dbToken);
89
-
90
-		// Add the token to the cache
91
-		$this->cache[$dbToken->getToken()] = $dbToken;
92
-
93
-		return $dbToken;
94
-	}
95
-
96
-	public function getToken(string $tokenId): IToken {
97
-		$tokenHash = $this->hashToken($tokenId);
98
-
99
-		if (isset($this->cache[$tokenHash])) {
100
-			if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
101
-				$ex = $this->cache[$tokenHash];
102
-				throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
103
-			}
104
-			$token = $this->cache[$tokenHash];
105
-		} else {
106
-			try {
107
-				$token = $this->mapper->getToken($this->hashToken($tokenId));
108
-				$this->cache[$token->getToken()] = $token;
109
-			} catch (DoesNotExistException $ex) {
110
-				$this->cache[$tokenHash] = $ex;
111
-				throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
112
-			}
113
-		}
114
-
115
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
116
-			throw new ExpiredTokenException($token);
117
-		}
118
-
119
-		if ($token->getType() === IToken::WIPE_TOKEN) {
120
-			throw new WipeTokenException($token);
121
-		}
122
-
123
-		if ($token->getPasswordInvalid() === true) {
124
-			//The password is invalid we should throw an TokenPasswordExpiredException
125
-			throw new TokenPasswordExpiredException($token);
126
-		}
127
-
128
-		return $token;
129
-	}
130
-
131
-	public function getTokenById(int $tokenId): IToken {
132
-		try {
133
-			$token = $this->mapper->getTokenById($tokenId);
134
-		} catch (DoesNotExistException $ex) {
135
-			throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
136
-		}
137
-
138
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
139
-			throw new ExpiredTokenException($token);
140
-		}
141
-
142
-		if ($token->getType() === IToken::WIPE_TOKEN) {
143
-			throw new WipeTokenException($token);
144
-		}
145
-
146
-		if ($token->getPasswordInvalid() === true) {
147
-			//The password is invalid we should throw an TokenPasswordExpiredException
148
-			throw new TokenPasswordExpiredException($token);
149
-		}
150
-
151
-		return $token;
152
-	}
153
-
154
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
155
-		$this->cache->clear();
156
-
157
-		$token = $this->getToken($oldSessionId);
158
-
159
-		if (!($token instanceof PublicKeyToken)) {
160
-			throw new InvalidTokenException("Invalid token type");
161
-		}
162
-
163
-		$password = null;
164
-		if (!is_null($token->getPassword())) {
165
-			$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
166
-			$password = $this->decryptPassword($token->getPassword(), $privateKey);
167
-		}
168
-
169
-		$newToken = $this->generateToken(
170
-			$sessionId,
171
-			$token->getUID(),
172
-			$token->getLoginName(),
173
-			$password,
174
-			$token->getName(),
175
-			IToken::TEMPORARY_TOKEN,
176
-			$token->getRemember()
177
-		);
178
-
179
-		$this->mapper->delete($token);
180
-
181
-		return $newToken;
182
-	}
183
-
184
-	public function invalidateToken(string $token) {
185
-		$this->cache->clear();
186
-
187
-		$this->mapper->invalidate($this->hashToken($token));
188
-	}
189
-
190
-	public function invalidateTokenById(string $uid, int $id) {
191
-		$this->cache->clear();
192
-
193
-		$this->mapper->deleteById($uid, $id);
194
-	}
195
-
196
-	public function invalidateOldTokens() {
197
-		$this->cache->clear();
198
-
199
-		$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
200
-		$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
201
-		$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
202
-		$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
203
-		$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
204
-		$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
205
-	}
206
-
207
-	public function updateToken(IToken $token) {
208
-		$this->cache->clear();
209
-
210
-		if (!($token instanceof PublicKeyToken)) {
211
-			throw new InvalidTokenException("Invalid token type");
212
-		}
213
-		$this->mapper->update($token);
214
-	}
215
-
216
-	public function updateTokenActivity(IToken $token) {
217
-		$this->cache->clear();
218
-
219
-		if (!($token instanceof PublicKeyToken)) {
220
-			throw new InvalidTokenException("Invalid token type");
221
-		}
222
-
223
-		$activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
224
-		$activityInterval = min(max($activityInterval, 0), 300);
225
-
226
-		/** @var PublicKeyToken $token */
227
-		$now = $this->time->getTime();
228
-		if ($token->getLastActivity() < ($now - $activityInterval)) {
229
-			$token->setLastActivity($now);
230
-			$this->mapper->updateActivity($token, $now);
231
-		}
232
-	}
233
-
234
-	public function getTokenByUser(string $uid): array {
235
-		return $this->mapper->getTokenByUser($uid);
236
-	}
237
-
238
-	public function getPassword(IToken $savedToken, string $tokenId): string {
239
-		if (!($savedToken instanceof PublicKeyToken)) {
240
-			throw new InvalidTokenException("Invalid token type");
241
-		}
242
-
243
-		if ($savedToken->getPassword() === null) {
244
-			throw new PasswordlessTokenException();
245
-		}
246
-
247
-		// Decrypt private key with tokenId
248
-		$privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId);
249
-
250
-		// Decrypt password with private key
251
-		return $this->decryptPassword($savedToken->getPassword(), $privateKey);
252
-	}
253
-
254
-	public function setPassword(IToken $token, string $tokenId, string $password) {
255
-		$this->cache->clear();
256
-
257
-		if (!($token instanceof PublicKeyToken)) {
258
-			throw new InvalidTokenException("Invalid token type");
259
-		}
260
-
261
-		// When changing passwords all temp tokens are deleted
262
-		$this->mapper->deleteTempToken($token);
263
-
264
-		// Update the password for all tokens
265
-		$tokens = $this->mapper->getTokenByUser($token->getUID());
266
-		foreach ($tokens as $t) {
267
-			$publicKey = $t->getPublicKey();
268
-			$t->setPassword($this->encryptPassword($password, $publicKey));
269
-			$this->updateToken($t);
270
-		}
271
-	}
272
-
273
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
274
-		$this->cache->clear();
275
-
276
-		if (!($token instanceof PublicKeyToken)) {
277
-			throw new InvalidTokenException("Invalid token type");
278
-		}
279
-
280
-		// Decrypt private key with oldTokenId
281
-		$privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
282
-		// Encrypt with the new token
283
-		$token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
284
-
285
-		$token->setToken($this->hashToken($newTokenId));
286
-		$this->updateToken($token);
287
-
288
-		return $token;
289
-	}
290
-
291
-	private function encrypt(string $plaintext, string $token): string {
292
-		$secret = $this->config->getSystemValue('secret');
293
-		return $this->crypto->encrypt($plaintext, $token . $secret);
294
-	}
295
-
296
-	/**
297
-	 * @throws InvalidTokenException
298
-	 */
299
-	private function decrypt(string $cipherText, string $token): string {
300
-		$secret = $this->config->getSystemValue('secret');
301
-		try {
302
-			return $this->crypto->decrypt($cipherText, $token . $secret);
303
-		} catch (\Exception $ex) {
304
-			// Delete the invalid token
305
-			$this->invalidateToken($token);
306
-			throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex);
307
-		}
308
-	}
309
-
310
-	private function encryptPassword(string $password, string $publicKey): string {
311
-		openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
312
-		$encryptedPassword = base64_encode($encryptedPassword);
313
-
314
-		return $encryptedPassword;
315
-	}
316
-
317
-	private function decryptPassword(string $encryptedPassword, string $privateKey): string {
318
-		$encryptedPassword = base64_decode($encryptedPassword);
319
-		openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
320
-
321
-		return $password;
322
-	}
323
-
324
-	private function hashToken(string $token): string {
325
-		$secret = $this->config->getSystemValue('secret');
326
-		return hash('sha512', $token . $secret);
327
-	}
328
-
329
-	/**
330
-	 * @throws \RuntimeException when OpenSSL reports a problem
331
-	 */
332
-	private function newToken(string $token,
333
-							  string $uid,
334
-							  string $loginName,
335
-							  $password,
336
-							  string $name,
337
-							  int $type,
338
-							  int $remember): PublicKeyToken {
339
-		$dbToken = new PublicKeyToken();
340
-		$dbToken->setUid($uid);
341
-		$dbToken->setLoginName($loginName);
342
-
343
-		$config = array_merge([
344
-			'digest_alg' => 'sha512',
345
-			'private_key_bits' => 2048,
346
-		], $this->config->getSystemValue('openssl', []));
347
-
348
-		// Generate new key
349
-		$res = openssl_pkey_new($config);
350
-		if ($res === false) {
351
-			$this->logOpensslError();
352
-			throw new \RuntimeException('OpenSSL reported a problem');
353
-		}
354
-
355
-		if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
356
-			$this->logOpensslError();
357
-			throw new \RuntimeException('OpenSSL reported a problem');
358
-		}
359
-
360
-		// Extract the public key from $res to $pubKey
361
-		$publicKey = openssl_pkey_get_details($res);
362
-		$publicKey = $publicKey['key'];
363
-
364
-		$dbToken->setPublicKey($publicKey);
365
-		$dbToken->setPrivateKey($this->encrypt($privateKey, $token));
366
-
367
-		if (!is_null($password)) {
368
-			$dbToken->setPassword($this->encryptPassword($password, $publicKey));
369
-		}
370
-
371
-		$dbToken->setName($name);
372
-		$dbToken->setToken($this->hashToken($token));
373
-		$dbToken->setType($type);
374
-		$dbToken->setRemember($remember);
375
-		$dbToken->setLastActivity($this->time->getTime());
376
-		$dbToken->setLastCheck($this->time->getTime());
377
-		$dbToken->setVersion(PublicKeyToken::VERSION);
378
-
379
-		return $dbToken;
380
-	}
381
-
382
-	public function markPasswordInvalid(IToken $token, string $tokenId) {
383
-		$this->cache->clear();
384
-
385
-		if (!($token instanceof PublicKeyToken)) {
386
-			throw new InvalidTokenException("Invalid token type");
387
-		}
388
-
389
-		$token->setPasswordInvalid(true);
390
-		$this->mapper->update($token);
391
-	}
392
-
393
-	public function updatePasswords(string $uid, string $password) {
394
-		$this->cache->clear();
395
-
396
-		// prevent setting an empty pw as result of pw-less-login
397
-		if ($password === '') {
398
-			return;
399
-		}
400
-
401
-		// Update the password for all tokens
402
-		$tokens = $this->mapper->getTokenByUser($uid);
403
-		foreach ($tokens as $t) {
404
-			$publicKey = $t->getPublicKey();
405
-			$t->setPassword($this->encryptPassword($password, $publicKey));
406
-			$t->setPasswordInvalid(false);
407
-			$this->updateToken($t);
408
-		}
409
-	}
410
-
411
-	private function logOpensslError() {
412
-		$errors = [];
413
-		while ($error = openssl_error_string()) {
414
-			$errors[] = $error;
415
-		}
416
-		$this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
417
-	}
45
+    /** @var PublicKeyTokenMapper */
46
+    private $mapper;
47
+
48
+    /** @var ICrypto */
49
+    private $crypto;
50
+
51
+    /** @var IConfig */
52
+    private $config;
53
+
54
+    /** @var LoggerInterface */
55
+    private $logger;
56
+
57
+    /** @var ITimeFactory */
58
+    private $time;
59
+
60
+    /** @var CappedMemoryCache */
61
+    private $cache;
62
+
63
+    public function __construct(PublicKeyTokenMapper $mapper,
64
+                                ICrypto $crypto,
65
+                                IConfig $config,
66
+                                LoggerInterface $logger,
67
+                                ITimeFactory $time) {
68
+        $this->mapper = $mapper;
69
+        $this->crypto = $crypto;
70
+        $this->config = $config;
71
+        $this->logger = $logger;
72
+        $this->time = $time;
73
+
74
+        $this->cache = new CappedMemoryCache();
75
+    }
76
+
77
+    /**
78
+     * {@inheritDoc}
79
+     */
80
+    public function generateToken(string $token,
81
+                                    string $uid,
82
+                                    string $loginName,
83
+                                  ?string $password,
84
+                                    string $name,
85
+                                    int $type = IToken::TEMPORARY_TOKEN,
86
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken {
87
+        $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
88
+        $this->mapper->insert($dbToken);
89
+
90
+        // Add the token to the cache
91
+        $this->cache[$dbToken->getToken()] = $dbToken;
92
+
93
+        return $dbToken;
94
+    }
95
+
96
+    public function getToken(string $tokenId): IToken {
97
+        $tokenHash = $this->hashToken($tokenId);
98
+
99
+        if (isset($this->cache[$tokenHash])) {
100
+            if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
101
+                $ex = $this->cache[$tokenHash];
102
+                throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
103
+            }
104
+            $token = $this->cache[$tokenHash];
105
+        } else {
106
+            try {
107
+                $token = $this->mapper->getToken($this->hashToken($tokenId));
108
+                $this->cache[$token->getToken()] = $token;
109
+            } catch (DoesNotExistException $ex) {
110
+                $this->cache[$tokenHash] = $ex;
111
+                throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
112
+            }
113
+        }
114
+
115
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
116
+            throw new ExpiredTokenException($token);
117
+        }
118
+
119
+        if ($token->getType() === IToken::WIPE_TOKEN) {
120
+            throw new WipeTokenException($token);
121
+        }
122
+
123
+        if ($token->getPasswordInvalid() === true) {
124
+            //The password is invalid we should throw an TokenPasswordExpiredException
125
+            throw new TokenPasswordExpiredException($token);
126
+        }
127
+
128
+        return $token;
129
+    }
130
+
131
+    public function getTokenById(int $tokenId): IToken {
132
+        try {
133
+            $token = $this->mapper->getTokenById($tokenId);
134
+        } catch (DoesNotExistException $ex) {
135
+            throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
136
+        }
137
+
138
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
139
+            throw new ExpiredTokenException($token);
140
+        }
141
+
142
+        if ($token->getType() === IToken::WIPE_TOKEN) {
143
+            throw new WipeTokenException($token);
144
+        }
145
+
146
+        if ($token->getPasswordInvalid() === true) {
147
+            //The password is invalid we should throw an TokenPasswordExpiredException
148
+            throw new TokenPasswordExpiredException($token);
149
+        }
150
+
151
+        return $token;
152
+    }
153
+
154
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
155
+        $this->cache->clear();
156
+
157
+        $token = $this->getToken($oldSessionId);
158
+
159
+        if (!($token instanceof PublicKeyToken)) {
160
+            throw new InvalidTokenException("Invalid token type");
161
+        }
162
+
163
+        $password = null;
164
+        if (!is_null($token->getPassword())) {
165
+            $privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
166
+            $password = $this->decryptPassword($token->getPassword(), $privateKey);
167
+        }
168
+
169
+        $newToken = $this->generateToken(
170
+            $sessionId,
171
+            $token->getUID(),
172
+            $token->getLoginName(),
173
+            $password,
174
+            $token->getName(),
175
+            IToken::TEMPORARY_TOKEN,
176
+            $token->getRemember()
177
+        );
178
+
179
+        $this->mapper->delete($token);
180
+
181
+        return $newToken;
182
+    }
183
+
184
+    public function invalidateToken(string $token) {
185
+        $this->cache->clear();
186
+
187
+        $this->mapper->invalidate($this->hashToken($token));
188
+    }
189
+
190
+    public function invalidateTokenById(string $uid, int $id) {
191
+        $this->cache->clear();
192
+
193
+        $this->mapper->deleteById($uid, $id);
194
+    }
195
+
196
+    public function invalidateOldTokens() {
197
+        $this->cache->clear();
198
+
199
+        $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
200
+        $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
201
+        $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
202
+        $rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
203
+        $this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
204
+        $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
205
+    }
206
+
207
+    public function updateToken(IToken $token) {
208
+        $this->cache->clear();
209
+
210
+        if (!($token instanceof PublicKeyToken)) {
211
+            throw new InvalidTokenException("Invalid token type");
212
+        }
213
+        $this->mapper->update($token);
214
+    }
215
+
216
+    public function updateTokenActivity(IToken $token) {
217
+        $this->cache->clear();
218
+
219
+        if (!($token instanceof PublicKeyToken)) {
220
+            throw new InvalidTokenException("Invalid token type");
221
+        }
222
+
223
+        $activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
224
+        $activityInterval = min(max($activityInterval, 0), 300);
225
+
226
+        /** @var PublicKeyToken $token */
227
+        $now = $this->time->getTime();
228
+        if ($token->getLastActivity() < ($now - $activityInterval)) {
229
+            $token->setLastActivity($now);
230
+            $this->mapper->updateActivity($token, $now);
231
+        }
232
+    }
233
+
234
+    public function getTokenByUser(string $uid): array {
235
+        return $this->mapper->getTokenByUser($uid);
236
+    }
237
+
238
+    public function getPassword(IToken $savedToken, string $tokenId): string {
239
+        if (!($savedToken instanceof PublicKeyToken)) {
240
+            throw new InvalidTokenException("Invalid token type");
241
+        }
242
+
243
+        if ($savedToken->getPassword() === null) {
244
+            throw new PasswordlessTokenException();
245
+        }
246
+
247
+        // Decrypt private key with tokenId
248
+        $privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId);
249
+
250
+        // Decrypt password with private key
251
+        return $this->decryptPassword($savedToken->getPassword(), $privateKey);
252
+    }
253
+
254
+    public function setPassword(IToken $token, string $tokenId, string $password) {
255
+        $this->cache->clear();
256
+
257
+        if (!($token instanceof PublicKeyToken)) {
258
+            throw new InvalidTokenException("Invalid token type");
259
+        }
260
+
261
+        // When changing passwords all temp tokens are deleted
262
+        $this->mapper->deleteTempToken($token);
263
+
264
+        // Update the password for all tokens
265
+        $tokens = $this->mapper->getTokenByUser($token->getUID());
266
+        foreach ($tokens as $t) {
267
+            $publicKey = $t->getPublicKey();
268
+            $t->setPassword($this->encryptPassword($password, $publicKey));
269
+            $this->updateToken($t);
270
+        }
271
+    }
272
+
273
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
274
+        $this->cache->clear();
275
+
276
+        if (!($token instanceof PublicKeyToken)) {
277
+            throw new InvalidTokenException("Invalid token type");
278
+        }
279
+
280
+        // Decrypt private key with oldTokenId
281
+        $privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
282
+        // Encrypt with the new token
283
+        $token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
284
+
285
+        $token->setToken($this->hashToken($newTokenId));
286
+        $this->updateToken($token);
287
+
288
+        return $token;
289
+    }
290
+
291
+    private function encrypt(string $plaintext, string $token): string {
292
+        $secret = $this->config->getSystemValue('secret');
293
+        return $this->crypto->encrypt($plaintext, $token . $secret);
294
+    }
295
+
296
+    /**
297
+     * @throws InvalidTokenException
298
+     */
299
+    private function decrypt(string $cipherText, string $token): string {
300
+        $secret = $this->config->getSystemValue('secret');
301
+        try {
302
+            return $this->crypto->decrypt($cipherText, $token . $secret);
303
+        } catch (\Exception $ex) {
304
+            // Delete the invalid token
305
+            $this->invalidateToken($token);
306
+            throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex);
307
+        }
308
+    }
309
+
310
+    private function encryptPassword(string $password, string $publicKey): string {
311
+        openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
312
+        $encryptedPassword = base64_encode($encryptedPassword);
313
+
314
+        return $encryptedPassword;
315
+    }
316
+
317
+    private function decryptPassword(string $encryptedPassword, string $privateKey): string {
318
+        $encryptedPassword = base64_decode($encryptedPassword);
319
+        openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
320
+
321
+        return $password;
322
+    }
323
+
324
+    private function hashToken(string $token): string {
325
+        $secret = $this->config->getSystemValue('secret');
326
+        return hash('sha512', $token . $secret);
327
+    }
328
+
329
+    /**
330
+     * @throws \RuntimeException when OpenSSL reports a problem
331
+     */
332
+    private function newToken(string $token,
333
+                                string $uid,
334
+                                string $loginName,
335
+                                $password,
336
+                                string $name,
337
+                                int $type,
338
+                                int $remember): PublicKeyToken {
339
+        $dbToken = new PublicKeyToken();
340
+        $dbToken->setUid($uid);
341
+        $dbToken->setLoginName($loginName);
342
+
343
+        $config = array_merge([
344
+            'digest_alg' => 'sha512',
345
+            'private_key_bits' => 2048,
346
+        ], $this->config->getSystemValue('openssl', []));
347
+
348
+        // Generate new key
349
+        $res = openssl_pkey_new($config);
350
+        if ($res === false) {
351
+            $this->logOpensslError();
352
+            throw new \RuntimeException('OpenSSL reported a problem');
353
+        }
354
+
355
+        if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
356
+            $this->logOpensslError();
357
+            throw new \RuntimeException('OpenSSL reported a problem');
358
+        }
359
+
360
+        // Extract the public key from $res to $pubKey
361
+        $publicKey = openssl_pkey_get_details($res);
362
+        $publicKey = $publicKey['key'];
363
+
364
+        $dbToken->setPublicKey($publicKey);
365
+        $dbToken->setPrivateKey($this->encrypt($privateKey, $token));
366
+
367
+        if (!is_null($password)) {
368
+            $dbToken->setPassword($this->encryptPassword($password, $publicKey));
369
+        }
370
+
371
+        $dbToken->setName($name);
372
+        $dbToken->setToken($this->hashToken($token));
373
+        $dbToken->setType($type);
374
+        $dbToken->setRemember($remember);
375
+        $dbToken->setLastActivity($this->time->getTime());
376
+        $dbToken->setLastCheck($this->time->getTime());
377
+        $dbToken->setVersion(PublicKeyToken::VERSION);
378
+
379
+        return $dbToken;
380
+    }
381
+
382
+    public function markPasswordInvalid(IToken $token, string $tokenId) {
383
+        $this->cache->clear();
384
+
385
+        if (!($token instanceof PublicKeyToken)) {
386
+            throw new InvalidTokenException("Invalid token type");
387
+        }
388
+
389
+        $token->setPasswordInvalid(true);
390
+        $this->mapper->update($token);
391
+    }
392
+
393
+    public function updatePasswords(string $uid, string $password) {
394
+        $this->cache->clear();
395
+
396
+        // prevent setting an empty pw as result of pw-less-login
397
+        if ($password === '') {
398
+            return;
399
+        }
400
+
401
+        // Update the password for all tokens
402
+        $tokens = $this->mapper->getTokenByUser($uid);
403
+        foreach ($tokens as $t) {
404
+            $publicKey = $t->getPublicKey();
405
+            $t->setPassword($this->encryptPassword($password, $publicKey));
406
+            $t->setPasswordInvalid(false);
407
+            $this->updateToken($t);
408
+        }
409
+    }
410
+
411
+    private function logOpensslError() {
412
+        $errors = [];
413
+        while ($error = openssl_error_string()) {
414
+            $errors[] = $error;
415
+        }
416
+        $this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
417
+    }
418 418
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -99,7 +99,7 @@  discard block
 block discarded – undo
99 99
 		if (isset($this->cache[$tokenHash])) {
100 100
 			if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
101 101
 				$ex = $this->cache[$tokenHash];
102
-				throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
102
+				throw new InvalidTokenException("Token does not exist: ".$ex->getMessage(), 0, $ex);
103 103
 			}
104 104
 			$token = $this->cache[$tokenHash];
105 105
 		} else {
@@ -108,11 +108,11 @@  discard block
 block discarded – undo
108 108
 				$this->cache[$token->getToken()] = $token;
109 109
 			} catch (DoesNotExistException $ex) {
110 110
 				$this->cache[$tokenHash] = $ex;
111
-				throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
111
+				throw new InvalidTokenException("Token does not exist: ".$ex->getMessage(), 0, $ex);
112 112
 			}
113 113
 		}
114 114
 
115
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
115
+		if ((int) $token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
116 116
 			throw new ExpiredTokenException($token);
117 117
 		}
118 118
 
@@ -132,10 +132,10 @@  discard block
 block discarded – undo
132 132
 		try {
133 133
 			$token = $this->mapper->getTokenById($tokenId);
134 134
 		} catch (DoesNotExistException $ex) {
135
-			throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
135
+			throw new InvalidTokenException("Token with ID $tokenId does not exist: ".$ex->getMessage(), 0, $ex);
136 136
 		}
137 137
 
138
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
138
+		if ((int) $token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
139 139
 			throw new ExpiredTokenException($token);
140 140
 		}
141 141
 
@@ -197,10 +197,10 @@  discard block
 block discarded – undo
197 197
 		$this->cache->clear();
198 198
 
199 199
 		$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
200
-		$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
200
+		$this->logger->debug('Invalidating session tokens older than '.date('c', $olderThan), ['app' => 'cron']);
201 201
 		$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
202 202
 		$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
203
-		$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
203
+		$this->logger->debug('Invalidating remembered session tokens older than '.date('c', $rememberThreshold), ['app' => 'cron']);
204 204
 		$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
205 205
 	}
206 206
 
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
 
291 291
 	private function encrypt(string $plaintext, string $token): string {
292 292
 		$secret = $this->config->getSystemValue('secret');
293
-		return $this->crypto->encrypt($plaintext, $token . $secret);
293
+		return $this->crypto->encrypt($plaintext, $token.$secret);
294 294
 	}
295 295
 
296 296
 	/**
@@ -299,11 +299,11 @@  discard block
 block discarded – undo
299 299
 	private function decrypt(string $cipherText, string $token): string {
300 300
 		$secret = $this->config->getSystemValue('secret');
301 301
 		try {
302
-			return $this->crypto->decrypt($cipherText, $token . $secret);
302
+			return $this->crypto->decrypt($cipherText, $token.$secret);
303 303
 		} catch (\Exception $ex) {
304 304
 			// Delete the invalid token
305 305
 			$this->invalidateToken($token);
306
-			throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex);
306
+			throw new InvalidTokenException("Could not decrypt token password: ".$ex->getMessage(), 0, $ex);
307 307
 		}
308 308
 	}
309 309
 
@@ -323,7 +323,7 @@  discard block
 block discarded – undo
323 323
 
324 324
 	private function hashToken(string $token): string {
325 325
 		$secret = $this->config->getSystemValue('secret');
326
-		return hash('sha512', $token . $secret);
326
+		return hash('sha512', $token.$secret);
327 327
 	}
328 328
 
329 329
 	/**
@@ -413,6 +413,6 @@  discard block
 block discarded – undo
413 413
 		while ($error = openssl_error_string()) {
414 414
 			$errors[] = $error;
415 415
 		}
416
-		$this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
416
+		$this->logger->critical('Something is wrong with your openssl setup: '.implode(', ', $errors));
417 417
 	}
418 418
 }
Please login to merge, or discard this patch.