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