76 lines
3.7 KiB
PHP
76 lines
3.7 KiB
PHP
<?php
|
|||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
require_once __DIR__ . '/../app/Domain/Credential/TechnicalInformation.php';
|
||
|
|
require_once __DIR__ . '/../app/Domain/Credential/CredentialVault.php';
|
||
|
|
|
||
|
|
use App\Domain\Credential\CredentialVault;
|
||
|
|
use App\Domain\Credential\TechnicalInformation;
|
||
|
|
|
||
|
|
function credential_assert_same(mixed $expected, mixed $actual, string $message): void
|
||
|
|
{
|
||
|
|
if ($expected !== $actual) {
|
||
|
|
throw new RuntimeException($message . "\nExpected: " . var_export($expected, true) . "\nActual: " . var_export($actual, true));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$key = base64_encode(random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES));
|
||
|
|
$vault = new CredentialVault($key);
|
||
|
|
$secret = 'P@ssword & token 123';
|
||
|
|
$ciphertext = $vault->encrypt($secret);
|
||
|
|
if ($ciphertext === $secret || $vault->decrypt($ciphertext) !== $secret) {
|
||
|
|
throw new RuntimeException('Credentials must round-trip through authenticated encryption.');
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
(new CredentialVault(base64_encode(random_bytes(32))))->decrypt($ciphertext);
|
||
|
|
throw new RuntimeException('Decrypting with the wrong key should fail.');
|
||
|
|
} catch (RuntimeException $expected) {
|
||
|
|
if (!str_contains($expected->getMessage(), 'decrypt')) {
|
||
|
|
throw new RuntimeException('Wrong-key failure should be explicit.');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$masked = $vault->mask($secret);
|
||
|
|
credential_assert_same('••••••••••••••••••••', $masked, 'Secrets must never be shown in plaintext.');
|
||
|
|
credential_assert_same('••••••••••••••••••••', $vault->display(['secret' => $secret])['secret'], 'Display must mask secret fields.');
|
||
|
|
|
||
|
|
$credential = [
|
||
|
|
'id' => 7,
|
||
|
|
'category' => 'hosting',
|
||
|
|
'label' => ' Production Host ',
|
||
|
|
'username' => ' deploy ',
|
||
|
|
'notes' => ' SSH access ',
|
||
|
|
'secret' => $secret,
|
||
|
|
'internal_token' => 'do not expose',
|
||
|
|
];
|
||
|
|
$encrypted = $vault->encryptCredential($credential);
|
||
|
|
if (array_key_exists('secret', $encrypted) || !isset($encrypted['secret_ciphertext'])) {
|
||
|
|
throw new RuntimeException('Stored credential records must contain ciphertext, not plaintext.');
|
||
|
|
}
|
||
|
|
credential_assert_same($secret, $vault->decryptCredential($encrypted)['secret'], 'Credential records must decrypt their secret.');
|
||
|
|
$projection = $vault->projectMetadata($encrypted);
|
||
|
|
credential_assert_same(['id' => 7, 'category' => 'hosting', 'label' => 'Production Host', 'username' => 'deploy', 'notes' => 'SSH access'], $projection, 'Metadata projection must be allow-listed and plaintext-free.');
|
||
|
|
|
||
|
|
$info = new TechnicalInformation();
|
||
|
|
$valid = $info->validate(['category' => 'vpn', 'label' => ' Office VPN ', 'username' => ' alice ', 'notes' => ' MFA enabled ']);
|
||
|
|
credential_assert_same(true, $valid['valid'], 'Valid technical information should pass.');
|
||
|
|
credential_assert_same('Office VPN', $valid['label'], 'Labels should be normalized.');
|
||
|
|
$invalid = $info->validate(['category' => 'unknown', 'label' => ' ', 'username' => "bad\nname", 'notes' => str_repeat('x', 2001)]);
|
||
|
|
if ($invalid['valid'] || !isset($invalid['errors']['category'], $invalid['errors']['label'], $invalid['errors']['username'], $invalid['errors']['notes'])) {
|
||
|
|
throw new RuntimeException('Technical information must validate category, label, username, and notes.');
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (['missing' => null, 'short' => 'short', 'placeholder' => 'generate-a-long-random-secret'] as $name => $badKey) {
|
||
|
|
try {
|
||
|
|
new CredentialVault($badKey);
|
||
|
|
throw new RuntimeException("{$name} APP_KEY material should fail explicitly.");
|
||
|
|
} catch (RuntimeException $expected) {
|
||
|
|
if (!str_contains($expected->getMessage(), 'APP_KEY')) {
|
||
|
|
throw new RuntimeException("{$name} APP_KEY error should mention APP_KEY.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("Credential vault tests: 6 passed\n");
|