- .htaccess: refuse .git, .env, docker, nodejs, sdlc*, scripts, archives and app internals; security headers, HSTS over TLS, optional HTTPS redirect - uploads served through app/file.php to signed-in users only - Apache/PHP hardening config for the container (ServerTokens, expose_php) - least-privilege DB account and APP_SECRET_KEY via docker/php/provision.php; SMTP passwords re-encrypted with a random IV (secret_box.php) - Socket.IO rooms from a PHP-signed token; /emit and cron refuse empty secrets - escape notification text; CLI guards on build scripts; no fixed demo password
49 lines
2.1 KiB
PHP
49 lines
2.1 KiB
PHP
<?php
|
|
/**
|
|
* secret_box.php — reversible encryption for stored credentials (SMTP passwords).
|
|
*
|
|
* Format "v2:<base64(iv . ciphertext)>": AES-256-CBC with a random IV per value and
|
|
* a key derived from APP_SECRET_KEY (config.php, from the deployment environment).
|
|
*
|
|
* Older values were encrypted with the fixed key $pinkey ("wms") and a constant IV,
|
|
* which anyone reading the source can undo. secret_decrypt() still reads that legacy
|
|
* format so existing rows keep working; setup.php re-encrypts them to v2 and every
|
|
* save writes v2. Without APP_SECRET_KEY the legacy format is written (and logged)
|
|
* so a deployment that has not set the key yet keeps sending mail.
|
|
*/
|
|
|
|
const SECRET_BOX_LEGACY_IV = '1234567890123456';
|
|
|
|
function secret_box_key(): ?string {
|
|
if (!defined('APP_SECRET_KEY') || APP_SECRET_KEY === '') return null;
|
|
return hash('sha256', APP_SECRET_KEY, true);
|
|
}
|
|
|
|
function secret_encrypt(string $plain, string $legacy_key = 'wms'): string {
|
|
$key = secret_box_key();
|
|
if ($key === null) {
|
|
error_log('[secret_box] APP_SECRET_KEY is not set; storing a credential in the legacy format.');
|
|
return openssl_encrypt($plain, 'AES-256-CBC', $legacy_key, 0, SECRET_BOX_LEGACY_IV);
|
|
}
|
|
$iv = random_bytes(16);
|
|
$ct = openssl_encrypt($plain, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
|
return 'v2:' . base64_encode($iv . $ct);
|
|
}
|
|
|
|
/** Returns the plain text, or false when the value cannot be decrypted. */
|
|
function secret_decrypt(string $stored, string $legacy_key = 'wms') {
|
|
$stored = trim($stored);
|
|
if (strncmp($stored, 'v2:', 3) === 0) {
|
|
$key = secret_box_key();
|
|
$raw = base64_decode(substr($stored, 3), true);
|
|
if ($key === null || $raw === false || strlen($raw) <= 16) return false;
|
|
return openssl_decrypt(substr($raw, 16), 'AES-256-CBC', $key, OPENSSL_RAW_DATA, substr($raw, 0, 16));
|
|
}
|
|
return openssl_decrypt($stored, 'AES-256-CBC', $legacy_key, 0, SECRET_BOX_LEGACY_IV);
|
|
}
|
|
|
|
/** Whether a stored value still uses the legacy fixed-key format. */
|
|
function secret_is_legacy(string $stored): bool {
|
|
return $stored !== '' && strncmp(trim($stored), 'v2:', 3) !== 0;
|
|
}
|