From bd0282b4a176793f5cdf16d87631f9841fb0e749 Mon Sep 17 00:00:00 2001 From: Chet Bortz Date: Wed, 27 May 2026 13:39:31 -0400 Subject: [PATCH] throw typed exceptions from reveal crypto path --- src/AccessGridClient.php | 2 +- src/Crypto/SmartTapRevealCrypto.php | 14 +++++++++----- src/Exceptions/DecryptException.php | 13 +++++++++++++ src/Exceptions/InvalidEnvelopeException.php | 13 +++++++++++++ tests/Crypto/SmartTapRevealCryptoTest.php | 10 ++++++---- 5 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 src/Exceptions/DecryptException.php create mode 100644 src/Exceptions/InvalidEnvelopeException.php diff --git a/src/AccessGridClient.php b/src/AccessGridClient.php index 596aa80..e475691 100644 --- a/src/AccessGridClient.php +++ b/src/AccessGridClient.php @@ -11,7 +11,7 @@ class AccessGridClient { - public const VERSION = '1.3.0'; + public const VERSION = '1.3.1'; private string $accountId; private string $secretKey; diff --git a/src/Crypto/SmartTapRevealCrypto.php b/src/Crypto/SmartTapRevealCrypto.php index 4994716..11e965b 100644 --- a/src/Crypto/SmartTapRevealCrypto.php +++ b/src/Crypto/SmartTapRevealCrypto.php @@ -2,6 +2,10 @@ namespace AccessGrid\Crypto; +use AccessGrid\Exceptions\AccessGridException; +use AccessGrid\Exceptions\DecryptException; +use AccessGrid\Exceptions\InvalidEnvelopeException; + /** * Internal crypto helpers for the SmartTap reveal flow. * @@ -27,7 +31,7 @@ public static function generateKeypair(): array 'private_key_type' => OPENSSL_KEYTYPE_EC, ]); if ($priv === false) { - throw new \RuntimeException('Failed to generate EC keypair: ' . openssl_error_string()); + throw new AccessGridException('Failed to generate EC keypair: ' . openssl_error_string()); } $details = openssl_pkey_get_details($priv); @@ -50,14 +54,14 @@ public static function decryptEnvelope(array $envelope, $privKey): string { $serverPub = openssl_pkey_get_public($envelope['ephemeral_public_key'] ?? ''); if ($serverPub === false) { - throw new \RuntimeException('Invalid ephemeral_public_key in envelope'); + throw new InvalidEnvelopeException('Invalid ephemeral_public_key in envelope'); } // Natural-length shared secret (32 bytes / P-256 X coord). The third // arg was deprecated in PHP 8.4 as either ignored or truncating. $sharedSecret = openssl_pkey_derive($serverPub, $privKey); if ($sharedSecret === false) { - throw new \RuntimeException('ECDH derivation failed: ' . openssl_error_string()); + throw new AccessGridException('ECDH derivation failed: ' . openssl_error_string()); } $aesKey = hash_hkdf('sha256', $sharedSecret, self::KEY_LEN, self::HKDF_INFO, ''); @@ -66,7 +70,7 @@ public static function decryptEnvelope(array $envelope, $privKey): string $ciphertext = base64_decode($envelope['ciphertext'] ?? '', true); $tag = base64_decode($envelope['tag'] ?? '', true); if ($iv === false || $ciphertext === false || $tag === false) { - throw new \RuntimeException('Envelope iv/ciphertext/tag must be base64-encoded'); + throw new InvalidEnvelopeException('Envelope iv/ciphertext/tag must be base64-encoded'); } $plaintext = openssl_decrypt( @@ -80,7 +84,7 @@ public static function decryptEnvelope(array $envelope, $privKey): string ); if ($plaintext === false) { - throw new \RuntimeException('AES-GCM decryption failed (auth tag verification)'); + throw new DecryptException('AES-GCM decryption failed (auth tag verification)'); } return $plaintext; diff --git a/src/Exceptions/DecryptException.php b/src/Exceptions/DecryptException.php new file mode 100644 index 0000000..28b9ddc --- /dev/null +++ b/src/Exceptions/DecryptException.php @@ -0,0 +1,13 @@ +expectException(\RuntimeException::class); + $this->expectException(DecryptException::class); $this->expectExceptionMessage('AES-GCM decryption failed'); SmartTapRevealCrypto::decryptEnvelope($envelope, self::fixtureCallerPrivateKey()); @@ -72,7 +74,7 @@ public function testWrongPrivateKeyFailsDecryption(): void 'private_key_type' => OPENSSL_KEYTYPE_EC, ]); - $this->expectException(\RuntimeException::class); + $this->expectException(DecryptException::class); $this->expectExceptionMessage('AES-GCM decryption failed'); SmartTapRevealCrypto::decryptEnvelope(self::fixtureEnvelope(), $wrong); @@ -83,7 +85,7 @@ public function testMissingEphemeralPublicKeyThrows(): void $envelope = self::fixtureEnvelope(); unset($envelope['ephemeral_public_key']); - $this->expectException(\RuntimeException::class); + $this->expectException(InvalidEnvelopeException::class); $this->expectExceptionMessage('Invalid ephemeral_public_key'); SmartTapRevealCrypto::decryptEnvelope($envelope, self::fixtureCallerPrivateKey()); @@ -94,7 +96,7 @@ public function testNonBase64IvThrows(): void $envelope = self::fixtureEnvelope(); $envelope['iv'] = 'not!base64!'; - $this->expectException(\RuntimeException::class); + $this->expectException(InvalidEnvelopeException::class); $this->expectExceptionMessage('base64'); SmartTapRevealCrypto::decryptEnvelope($envelope, self::fixtureCallerPrivateKey());