diff --git a/tests/Cryptography/Algorithms/RsaPss/AbstractRsaPssSignerTest.php b/tests/Cryptography/Algorithms/RsaPss/AbstractRsaPssSignerTest.php index fa0b52e..4a17710 100644 --- a/tests/Cryptography/Algorithms/RsaPss/AbstractRsaPssSignerTest.php +++ b/tests/Cryptography/Algorithms/RsaPss/AbstractRsaPssSignerTest.php @@ -16,14 +16,27 @@ class AbstractRsaPssSignerTest extends TestCase { /** * The signature the encoder must produce for the message `Text` under the 2041-bit fixture key with the - * salt fixed to `\xAB` * 32 — that modulus leaves no excess bits to clear and prepends a zero byte, so the - * whole encode path is pinned byte for byte. The OpenSSL CLI verifies this vector (PSS with salt length 32). + * salt `fixedSaltSigner()` pins — that modulus leaves no excess bits to clear and prepends a zero byte, so + * the whole encode path is pinned byte for byte and the encoded message keeps its `0xFF` leading byte. The + * OpenSSL CLI verifies this vector (PSS with salt length 32). */ private const FIXED_SALT_SIGNATURE_2041 = - 'ARZEDL31+Fo7wUwGXgh2nqc8J6I6Tbc5gRfzYIsVeJQhXW+1/nqoP9KDVDZy7O46jX52Fze7+c1wFBAuq3c1BOkBtcx0zZOf' - . 'Jp6aZh6Vpvb5xhxcPb8+btLtGffC4Ypp2WcnGYdIuBYa0OGINAUNn5P9fMMxk3WJFYUDHzQx3I7NlaRISXmTSqrJOJUlardQ' - . 'P5Agmo/DKbXJmSmCyGm+lgeFAG+Rn2XvFGHhLc8nOrMA2uA5GFxWzAb2cMbosN95SwlqSinL0iEJG7BBFtGjUNjDxuMdd2j9' - . 'nN+sU3fzm1aIRI7tuqOtUkzoXJodg6APJn5zSbCnOIZ/8OTJ3YDfTA=='; + 'ANo31z7z+wPHh8Afo/ZX+YErxy/Fj+ngyfgRVC8tVCyOEzdR9mec6bSG05nsE6oGubxSLyQ8V9MYQ0V7qSci+Wl6VT3digh8' + . 'mJ3JkEtm+pQ7k6+VrBp9MYCC+yQmI6setHLV6DGqwIIMjyz3KrKwjxaAtYYDYvuTbBzNSSdSxSQsZinGUVLliT+E879Nh4rH' + . '9JZLBX4egxByGyvTfoMKUAX3pNnQD/SqpEkJxdxJHiZtm1O2laOpk3oI4jp8Ke9OBJWOiV99JrjuSBiPuFzd1TzEpH2hEGHO' + . 'hkT34v3iiawUYZ/vziuRMpk/11KjfqvmNN9aILyalsLgL+tVcGUdGg=='; + + /** + * The same vector under the 2047-bit fixture key, whose two excess bits the encoder must clear: the `0xFF` + * leading byte of the encoded message has to come out as `0x3F`. Together with the 2041-bit vector, which + * clears nothing, this pins the bit-clearing step for both shapes of modulus — no run of the suite depends + * on a random salt happening to expose those bits. The OpenSSL CLI verifies this vector too. + */ + private const FIXED_SALT_SIGNATURE_2047 = + 'A8kN0RON8TzrHc/0ZFhoyegSJoegm+oMmkOynOMJjY3HqFd7Nx2M4cAvAwfh1otJd6GPDKsKG5bC5GyB+FRig4TWqrliLR0P' + . 'dyPMNNp5N0V+iXMsENOGtPEZ8ofnv6HK+Sn+PV2PuUr64K4suIrnSyvpcrZ1oUkCxHqOdbCYG7UwwnVg2AGNJoVoaE/qI5Y/' + . 'oBynmdxLyFlqvlRM7th/p0iAO7qRDDJ3nedAWxgbjdpGJzXSpgy2SASk1KxUOwsTTy+BByVGPUJ39Ear/EqZ04t3ujLHboCZ' + . '/iWYCiL2HfFDd/BCVW8amphnUMus3X4XpUVRGapGAyZGFs3NqCelkw=='; protected RsaPrivateKey $rsaPrivateKey; @@ -75,14 +88,39 @@ public function test_sign_it_should_produce_a_signature_of_the_modulus_size() */ public function test_sign_with_a_fixed_salt_it_should_produce_the_expected_signature() { - $signer = new class (new RsaPrivateKey(KeyFixtures::PRIVATE_KEY_2041)) extends PS256Signer { + $signer = $this->fixedSaltSigner(KeyFixtures::PRIVATE_KEY_2041); + + $this->assertSame((string)base64_decode(self::FIXED_SALT_SIGNATURE_2041), $signer->sign('Text')); + } + + /** + * The same for a modulus that leaves excess bits to clear, so the signature also pins the bit-clearing step + * of the encoding rather than only the bits a random salt happens to expose. + * + * @throws Throwable + */ + public function test_sign_with_a_fixed_salt_and_excess_bits_it_should_produce_the_expected_signature() + { + $signer = $this->fixedSaltSigner(KeyFixtures::PRIVATE_KEY_2047); + + $this->assertSame((string)base64_decode(self::FIXED_SALT_SIGNATURE_2047), $signer->sign('Text')); + } + + /** + * A signer whose salt is pinned, which makes EMSA-PSS deterministic. The salt is chosen so the masked data + * block starts with `0xFF` before the excess bits are cleared: every bit the encoder is supposed to clear + * (or leave alone) is then visible in the signature, whatever the modulus size leaves over. + * + * @throws Throwable + */ + private function fixedSaltSigner(string $privateKey): PS256Signer + { + return new class (new RsaPrivateKey($privateKey)) extends PS256Signer { protected function salt(int $length): string { - return str_repeat("\xAB", $length); + return str_repeat("\xAB", $length - 1) . "\x72"; } }; - - $this->assertSame((string)base64_decode(self::FIXED_SALT_SIGNATURE_2041), $signer->sign('Text')); } /**