-
-
Notifications
You must be signed in to change notification settings - Fork 142
feat(cryptography): introduce cryptography component #1346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
d4edc5d
feat: add password hashing
innocenzi f00f895
feat: add hmac signer
innocenzi a19279c
feat: add timelock
innocenzi bf5f34c
feat: protect signing from timing attacks
innocenzi 441fe92
test: adapt time protection test for windows
innocenzi 96ae9b9
refactor: add signing key value object
innocenzi b1bf382
feat: add encryption
innocenzi 7c47ab9
feat: add command to create signing key
innocenzi 43ade47
Merge branch 'main' into feat/cryptography
brendt c2d3b37
refactor: do not use empty encryption key by default
innocenzi bb854fe
fix: rename license file
innocenzi 1359588
chore: add docblocks to encryption algorithm methods
innocenzi a511bcd
refactor: make encryption key readonly
innocenzi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Exclude build/test files from the release | ||
| .github/ export-ignore | ||
| tests/ export-ignore | ||
| .gitattributes export-ignore | ||
| .gitignore export-ignore | ||
| phpunit.xml export-ignore | ||
| README.md export-ignore | ||
|
|
||
| # Configure diff output | ||
| *.view.php diff=html | ||
| *.php diff=php | ||
| *.css diff=css | ||
| *.html diff=html | ||
| *.md diff=markdown |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| The MIT License (MIT) | ||
|
|
||
| Copyright (c) 2024 Brent Roose [email protected] | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "name": "tempest/cryptography", | ||
| "description": "A component for working with passwords, hashing, and cryptography.", | ||
| "license": "MIT", | ||
| "minimum-stability": "dev", | ||
| "require": { | ||
| "php": "^8.4", | ||
| "tempest/container": "dev-main", | ||
| "tempest/support": "dev-main", | ||
| "tempest/clock": "dev-main" | ||
| }, | ||
| "autoload": { | ||
| "psr-4": { | ||
| "Tempest\\Cryptography\\": "src" | ||
| } | ||
| }, | ||
| "autoload-dev": { | ||
| "psr-4": { | ||
| "Tempest\\Cryptography\\Tests\\": "tests" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <phpunit | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.4/phpunit.xsd" | ||
| bootstrap="vendor/autoload.php" | ||
| executionOrder="depends,defects" | ||
| beStrictAboutOutputDuringTests="true" | ||
| displayDetailsOnPhpunitDeprecations="true" | ||
| failOnPhpunitDeprecation="false" | ||
| failOnRisky="true" | ||
| failOnWarning="true" | ||
| > | ||
| <testsuites> | ||
| <testsuite name="Tempest cryptography"> | ||
| <directory>tests</directory> | ||
| </testsuite> | ||
| </testsuites> | ||
| <source restrictNotices="true" restrictWarnings="true" ignoreIndirectDeprecations="true"> | ||
| <include> | ||
| <directory>src</directory> | ||
| </include> | ||
| </source> | ||
| </phpunit> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography; | ||
|
|
||
| use Tempest\Console\Console; | ||
| use Tempest\Console\ConsoleCommand; | ||
| use Tempest\Console\ExitCode; | ||
| use Tempest\Cryptography\Encryption\EncryptionConfig; | ||
| use Tempest\Cryptography\Encryption\EncryptionKey; | ||
| use Tempest\Support\Filesystem; | ||
| use Tempest\Support\Regex; | ||
| use Tempest\Support\Str; | ||
|
|
||
| use function Tempest\root_path; | ||
|
|
||
| final readonly class CreateSigningKeyCommand | ||
| { | ||
| public function __construct( | ||
| private EncryptionConfig $encryptionConfig, | ||
| private Console $console, | ||
| ) {} | ||
|
|
||
| #[ConsoleCommand('key:generate', description: 'Generates the signing key required to sign and verify data.')] | ||
| public function __invoke(): ExitCode | ||
| { | ||
| $key = EncryptionKey::generate($this->encryptionConfig->algorithm); | ||
|
|
||
| $this->console->writeln(); | ||
| $this->console->success('Signing key generated successfully.'); | ||
|
|
||
| $this->createDotEnvIfNotExists(); | ||
| $this->addToDotEnv($key->toString()); | ||
|
|
||
| return ExitCode::SUCCESS; | ||
| } | ||
|
|
||
| private function getDotEnvPath(): string | ||
| { | ||
| return root_path('.env'); | ||
| } | ||
|
|
||
| private function addToDotEnv(string $key): void | ||
| { | ||
| $file = Filesystem\read_file($this->getDotEnvPath()); | ||
|
|
||
| if (! Str\contains($file, 'SIGNING_KEY=')) { | ||
| $file .= "\nSIGNING_KEY={$key}\n"; | ||
| } else { | ||
| $file = Regex\replace($file, '/^SIGNING_KEY=.*$/m', "SIGNING_KEY={$key}"); | ||
| } | ||
|
|
||
| Filesystem\write_file($this->getDotEnvPath(), $file); | ||
| } | ||
|
|
||
| private function createDotEnvIfNotExists(): void | ||
| { | ||
| if (Filesystem\exists($this->getDotEnvPath())) { | ||
| return; | ||
| } | ||
|
|
||
| Filesystem\create_file($this->getDotEnvPath()); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography\Encryption; | ||
|
|
||
| use Stringable; | ||
| use Tempest\Cryptography\Encryption\Exceptions\EncryptedDataWasInvalid; | ||
| use Tempest\Cryptography\Signing\Signature; | ||
| use Tempest\Support\Json; | ||
|
|
||
| final readonly class EncryptedData implements Stringable | ||
| { | ||
| public function __construct( | ||
| private(set) string $payload, | ||
| private(set) string $iv, | ||
| private(set) string $tag, | ||
| private(set) Signature $signature, | ||
| private(set) EncryptionAlgorithm $algorithm, | ||
| ) {} | ||
|
|
||
| public function serialize(): string | ||
| { | ||
| $data = [ | ||
| 'payload' => base64_encode($this->payload), | ||
| 'iv' => base64_encode($this->iv), | ||
| 'tag' => base64_encode($this->tag), | ||
| 'signature' => $this->signature->value, | ||
| 'algorithm' => $this->algorithm->value, | ||
| ]; | ||
|
|
||
| return base64_encode(Json\encode($data)); | ||
| } | ||
|
|
||
| public static function unserialize(string $data): self | ||
| { | ||
| $decoded = Json\decode(base64_decode($data, strict: true)); | ||
|
|
||
| if (! is_array($decoded) || ! isset($decoded['payload'], $decoded['iv'], $decoded['tag'], $decoded['signature'], $decoded['algorithm'])) { | ||
| throw EncryptedDataWasInvalid::dueToInvalidFormat(); | ||
| } | ||
|
|
||
| return new self( | ||
| payload: base64_decode($decoded['payload'], strict: true), | ||
| iv: base64_decode($decoded['iv'], strict: true), | ||
| tag: base64_decode($decoded['tag'], strict: true), | ||
| signature: new Signature($decoded['signature']), | ||
| algorithm: EncryptionAlgorithm::from($decoded['algorithm']), | ||
| ); | ||
| } | ||
|
|
||
| public function __toString(): string | ||
| { | ||
| return $this->serialize(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography\Encryption; | ||
|
|
||
| interface Encrypter | ||
| { | ||
| public EncryptionAlgorithm $algorithm { | ||
| get; | ||
| } | ||
|
|
||
| /** | ||
| * Encrypts the specified data. | ||
| */ | ||
| public function encrypt(#[\SensitiveParameter] string $data): EncryptedData; | ||
|
|
||
| /** | ||
| * Decrypts the specified data. | ||
| */ | ||
| public function decrypt(string|EncryptedData $data): string; | ||
| } |
20 changes: 20 additions & 0 deletions
20
packages/cryptography/src/Encryption/EncrypterInitializer.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography\Encryption; | ||
|
|
||
| use Tempest\Container\Container; | ||
| use Tempest\Container\Initializer; | ||
| use Tempest\Container\Singleton; | ||
| use Tempest\Cryptography\Signing\Signer; | ||
|
|
||
| final class EncrypterInitializer implements Initializer | ||
| { | ||
| #[Singleton] | ||
| public function initialize(Container $container): Encrypter | ||
| { | ||
| return new GenericEncrypter( | ||
| signer: $container->get(Signer::class), | ||
| config: $container->get(EncryptionConfig::class), | ||
| ); | ||
| } | ||
| } |
41 changes: 41 additions & 0 deletions
41
packages/cryptography/src/Encryption/EncryptionAlgorithm.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography\Encryption; | ||
|
|
||
| enum EncryptionAlgorithm: string | ||
| { | ||
| case AES_256_GCM = 'aes-256-gcm'; | ||
| case AES_256_CBC = 'aes-256-cbc'; | ||
| case AES_128_GCM = 'aes-128-gcm'; | ||
| case AES_128_CBC = 'aes-128-cbc'; | ||
| case CHACHA20_POLY1305 = 'chacha20-poly1305'; | ||
|
|
||
| /** | ||
| * Returns the length of the key, in bytes, for the encryption algorithm. | ||
| */ | ||
| public function getKeyLength(): int | ||
| { | ||
| return openssl_cipher_key_length($this->value); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the initialization vector (IV) length for the encryption algorithm. | ||
| */ | ||
| public function getIvLength(): int | ||
| { | ||
| return openssl_cipher_iv_length($this->value); | ||
| } | ||
|
|
||
| /** | ||
| * Determines if the encryption algorithm allows embedding associated data. | ||
| * | ||
| * @see https://en.wikipedia.org/wiki/Authenticated_encryption | ||
| */ | ||
| public function isAead(): bool | ||
| { | ||
| return match ($this) { | ||
| self::AES_256_GCM, self::AES_128_GCM, self::CHACHA20_POLY1305 => true, | ||
| self::AES_256_CBC, self::AES_128_CBC => false, | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography\Encryption; | ||
|
|
||
| final class EncryptionConfig | ||
| { | ||
| /** | ||
| * @param EncryptionAlgorithm $algorithm The algorithm used for encrypting and decrypting values. | ||
| * @param non-empty-string $key A private, secure encryption key. | ||
| */ | ||
| public function __construct( | ||
| public EncryptionAlgorithm $algorithm, | ||
| #[\SensitiveParameter] | ||
| public readonly ?string $key, | ||
| ) {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography\Encryption; | ||
|
|
||
| use Stringable; | ||
| use Tempest\Cryptography\Encryption\Exceptions\EncryptionKeyWasInvalid; | ||
|
|
||
| final readonly class EncryptionKey implements Stringable | ||
| { | ||
| public function __construct( | ||
| private(set) string $value, | ||
| private(set) EncryptionAlgorithm $algorithm, | ||
| ) { | ||
| if (trim($value) === '') { | ||
| throw EncryptionKeyWasInvalid::becauseItIsMissing($algorithm); | ||
| } | ||
|
|
||
| if (strlen($value) !== $algorithm->getKeyLength()) { | ||
| throw EncryptionKeyWasInvalid::becauseLengthMismatched($algorithm); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generates a new cryptographically secure key using the specified algorithm. | ||
| */ | ||
| public static function generate(EncryptionAlgorithm $algorithm): self | ||
innocenzi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| return new self(random_bytes($algorithm->getKeyLength()), $algorithm); | ||
| } | ||
|
|
||
| /** | ||
| * Creates an encryption key from a string. | ||
| */ | ||
| public static function fromString(?string $key, EncryptionAlgorithm $algorithm): self | ||
| { | ||
| return new self(base64_decode($key ?: '', strict: true), $algorithm); | ||
| } | ||
|
|
||
| public function toString(): string | ||
| { | ||
| return base64_encode($this->value); | ||
| } | ||
|
|
||
| public function __toString(): string | ||
| { | ||
| return $this->toString(); | ||
| } | ||
| } | ||
13 changes: 13 additions & 0 deletions
13
packages/cryptography/src/Encryption/Exceptions/AlgorithmMismatched.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Cryptography\Encryption\Exceptions; | ||
|
|
||
| use Exception; | ||
|
|
||
| final class AlgorithmMismatched extends Exception implements EncryptionException | ||
| { | ||
| public static function betweenKeyAndData(): self | ||
| { | ||
| return new self('The encryption algorithm used for the key does not match the algorithm used for the data. Ensure that both are using the same algorithm.'); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.