Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/AccessGridClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class AccessGridClient
{
public const VERSION = '1.3.0';
public const VERSION = '1.3.1';

private string $accountId;
private string $secretKey;
Expand Down
14 changes: 9 additions & 5 deletions src/Crypto/SmartTapRevealCrypto.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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);
Expand All @@ -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, '');
Expand All @@ -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(
Expand All @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions src/Exceptions/DecryptException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace AccessGrid\Exceptions;

/**
* Thrown when AES-GCM auth-tag verification fails while decrypting a
* SmartTap reveal envelope (wrong key, tampered envelope, or wire-format
* drift between server and SDK).
*/
class DecryptException extends AccessGridException
{
//
}
13 changes: 13 additions & 0 deletions src/Exceptions/InvalidEnvelopeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace AccessGrid\Exceptions;

/**
* Thrown when a SmartTap reveal envelope is missing required fields,
* contains non-base64 / non-PEM data, or otherwise can't be parsed
* before the cryptographic operations begin.
*/
class InvalidEnvelopeException extends AccessGridException
{
//
}
10 changes: 6 additions & 4 deletions tests/Crypto/SmartTapRevealCryptoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use PHPUnit\Framework\TestCase;
use AccessGrid\Crypto\SmartTapRevealCrypto;
use AccessGrid\Exceptions\DecryptException;
use AccessGrid\Exceptions\InvalidEnvelopeException;

class SmartTapRevealCryptoTest extends TestCase
{
Expand Down Expand Up @@ -59,7 +61,7 @@ public function testTamperedTagFailsDecryption(): void
$tag[0] = chr(ord($tag[0]) ^ 0x01);
$envelope['tag'] = base64_encode($tag);

$this->expectException(\RuntimeException::class);
$this->expectException(DecryptException::class);
$this->expectExceptionMessage('AES-GCM decryption failed');

SmartTapRevealCrypto::decryptEnvelope($envelope, self::fixtureCallerPrivateKey());
Expand All @@ -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);
Expand All @@ -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());
Expand All @@ -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());
Expand Down
Loading