Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
150
src/Framework/Security/RequestSigning/RequestVerifier.php
Normal file
150
src/Framework/Security/RequestSigning/RequestVerifier.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Security\RequestSigning;
|
||||
|
||||
use App\Framework\Http\Request;
|
||||
|
||||
/**
|
||||
* Verifies HTTP request signatures
|
||||
*/
|
||||
final readonly class RequestVerifier
|
||||
{
|
||||
public function __construct(
|
||||
private SigningKeyRepository $keyRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a request signature
|
||||
*/
|
||||
public function verify(Request $request): VerificationResult
|
||||
{
|
||||
try {
|
||||
// Extract signature from header
|
||||
$signatureHeader = $request->headers->getFirst('Signature');
|
||||
if ($signatureHeader === null) {
|
||||
return VerificationResult::failure('Missing Signature header');
|
||||
}
|
||||
|
||||
$signature = RequestSignature::fromHttpSignatureHeader($signatureHeader);
|
||||
|
||||
// Check if signature is expired
|
||||
if ($signature->isExpired()) {
|
||||
return VerificationResult::failure('Signature has expired');
|
||||
}
|
||||
|
||||
// Get signing key
|
||||
$key = $this->keyRepository->findByKeyId($signature->keyId);
|
||||
if ($key === null) {
|
||||
return VerificationResult::failure('Unknown key ID: ' . $signature->keyId);
|
||||
}
|
||||
|
||||
if (! $key->isValid()) {
|
||||
return VerificationResult::failure('Signing key is not valid');
|
||||
}
|
||||
|
||||
// Verify algorithm matches
|
||||
if ($key->algorithm !== $signature->algorithm) {
|
||||
return VerificationResult::failure('Algorithm mismatch');
|
||||
}
|
||||
|
||||
// Build signing string
|
||||
$signingString = new SigningString($request);
|
||||
$stringToSign = $signingString->build($signature->headers);
|
||||
|
||||
// Verify signature
|
||||
$isValid = $this->verifySignature($stringToSign, $signature->signature, $key);
|
||||
|
||||
if ($isValid) {
|
||||
return VerificationResult::success($signature, $key);
|
||||
} else {
|
||||
return VerificationResult::failure('Invalid signature');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return VerificationResult::failure('Signature verification error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify signature using the appropriate algorithm
|
||||
*/
|
||||
private function verifySignature(string $data, string $signature, SigningKey $key): bool
|
||||
{
|
||||
return match ($key->algorithm) {
|
||||
SigningAlgorithm::HMAC_SHA256 => $this->verifyHmacSignature($data, $signature, $key, 'sha256'),
|
||||
SigningAlgorithm::HMAC_SHA512 => $this->verifyHmacSignature($data, $signature, $key, 'sha512'),
|
||||
SigningAlgorithm::RSA_SHA256 => $this->verifyRsaSignature($data, $signature, $key),
|
||||
SigningAlgorithm::ED25519 => throw new \InvalidArgumentException('ED25519 not yet implemented'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify HMAC signature
|
||||
*/
|
||||
private function verifyHmacSignature(string $data, string $signature, SigningKey $key, string $algorithm): bool
|
||||
{
|
||||
$expectedSignature = hash_hmac($algorithm, $data, $key->keyMaterial, true);
|
||||
$expectedSignatureBase64 = base64_encode($expectedSignature);
|
||||
|
||||
return hash_equals($expectedSignatureBase64, $signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify RSA signature
|
||||
*/
|
||||
private function verifyRsaSignature(string $data, string $signature, SigningKey $key): bool
|
||||
{
|
||||
// Extract public key from private key
|
||||
$privateKey = openssl_pkey_get_private($key->keyMaterial);
|
||||
if ($privateKey === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$keyDetails = openssl_pkey_get_details($privateKey);
|
||||
if ($keyDetails === false || ! isset($keyDetails['key'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$publicKey = openssl_pkey_get_public($keyDetails['key']);
|
||||
if ($publicKey === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signatureBinary = base64_decode($signature);
|
||||
if ($signatureBinary === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = openssl_verify($data, $signatureBinary, $publicKey, $key->algorithm->getOpenSSLSignatureAlgorithm());
|
||||
|
||||
return $result === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify digest header if present
|
||||
*/
|
||||
public function verifyDigest(Request $request): bool
|
||||
{
|
||||
$digestHeader = $request->headers->getFirst('Digest');
|
||||
if ($digestHeader === null) {
|
||||
return true; // No digest to verify
|
||||
}
|
||||
|
||||
$body = $request->body;
|
||||
|
||||
// Parse digest header: "SHA256=base64hash"
|
||||
if (! preg_match('/^([A-Z0-9]+)=(.+)$/', $digestHeader, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorithm = strtolower($matches[1]);
|
||||
$expectedHash = $matches[2];
|
||||
|
||||
$actualHash = base64_encode(hash($algorithm, $body, true));
|
||||
|
||||
return hash_equals($expectedHash, $actualHash);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user