Lines
100.00%
37 / 37
Methods
100.00%
3 / 3
Classes
100.00%
1 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| decrypt | 100.00% | 25 / 25 | 100.00% | 1 / 1 | 16 | ||
| guard | 100.00% | 11 / 11 | 100.00% | 1 / 1 | 4 | ||
| 10 | final readonly class TatraBankaMailDecryptor | |
| 11 | { | |
| 12 | public function __construct( | |
| 13 | private string $privateKeyPath, | |
| 14 | private string $passphrase, | |
| 15 | ) {} | |
| 16 | ||
| 17 | public function decrypt(string $contents): string | |
| 18 | { | |
| 19 | if ($this->privateKeyPath === '' || !is_file($this->privateKeyPath)) { | |
| 20 | throw new ConfigurationException('missing path to TatraBanka PGP private key in config'); | |
| 21 | } | |
| 22 | ||
| 23 | if (!is_readable($this->privateKeyPath)) { | |
| 24 | throw new ConfigurationException('failed to read private key file'); | |
| 25 | } | |
| 26 | ||
| 27 | $fileContents = file_get_contents($this->privateKeyPath); | |
| 28 | if ($fileContents === false || $fileContents === '') { | |
| 29 | throw new ConfigurationException('failed to read private key file'); | |
| 30 | } | |
| 31 | ||
| 32 | return $this->guard(function () use ($fileContents, $contents): string { | |
| 33 | try { | |
| 34 | $parsedKey = \OpenPGP_Message::parse($fileContents); | |
| 35 | } catch (DecryptionException $exception) { | |
| 36 | throw new ConfigurationException('failed to read private key file', 0, $exception); | |
| 37 | } | |
| 38 | ||
| 39 | $secret = null; | |
| 40 | // @phpstan-ignore foreach.nonIterable (OpenPGP library has dynamic types) | |
| 41 | foreach ($parsedKey as $packet) { | |
| 42 | if (!($packet instanceof \OpenPGP_SecretKeyPacket || $packet instanceof \OpenPGP_SecretSubkeyPacket)) { | |
| 43 | continue; | |
| 44 | } | |
| 45 | ||
| 46 | $secret = \OpenPGP_Crypt_Symmetric::decryptSecretKey($this->passphrase, $packet); | |
| 47 | } | |
| 48 | ||
| 49 | if (!($secret instanceof \OpenPGP_SecretKeyPacket || $secret instanceof \OpenPGP_SecretSubkeyPacket)) { | |
| 50 | throw new ConfigurationException('Private key passphrase is incorrect or the key has no usable secret key'); | |
| 51 | } | |
| 52 | ||
| 53 | $msg = \OpenPGP_Message::parse(\OpenPGP::unarmor($contents, 'PGP MESSAGE')); | |
| 54 | $decryptor = new \OpenPGP_Crypt_RSA($secret); | |
| 55 | $decrypted = $decryptor->decrypt($msg); | |
| 56 | ||
| 57 | if ($decrypted && isset($decrypted->packets[0]->data) && is_string($decrypted->packets[0]->data) && $decrypted->packets[0]->data !== '') { | |
| 58 | return $decrypted->packets[0]->data; | |
| 59 | } | |
| 60 | ||
| 61 | throw new DecryptionException('PGP message could not be decrypted'); | |
| 62 | }); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * @param callable(): string $callback | |
| 67 | */ | |
| 68 | private function guard(callable $callback): string | |
| 69 | { | |
| 70 | set_error_handler(static function (int $severity, string $message): never { | |
| 71 | throw new DecryptionException($message); | |
| 72 | }); | |
| 73 | ||
| 74 | try { | |
| 75 | return $callback(); | |
| 76 | } catch (ConfigurationException $exception) { | |
| 77 | throw $exception; | |
| 78 | } catch (DecryptionException $exception) { | |
| 79 | throw $exception; | |
| 80 | } catch (\Throwable $exception) { | |
| 81 | throw new DecryptionException($exception->getMessage(), 0, $exception); | |
| 82 | } finally { | |
| 83 | restore_error_handler(); | |
| 84 | } | |
| 85 | } | |
| 86 | } |