From f048b071626bdfc8abc12ae6d7d6fccb63f20db9 Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Sun, 19 Jun 2022 21:39:50 -0400 Subject: [PATCH] Misuse-resistance Prevent mismatched public keys * https://github.com/MystenLabs/ed25519-unsafe-libs * https://old.reddit.com/r/crypto/comments/vfl2se/initial_impact_report_about_this_weeks_eddsa/ This is just an extra defense-in-depth against misuse. --- src/Keys/AsymmetricSecretKey.php | 18 +++++++++++++++++ tests/KeyTest.php | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/Keys/AsymmetricSecretKey.php b/src/Keys/AsymmetricSecretKey.php index 8db900e..23c79ee 100644 --- a/src/Keys/AsymmetricSecretKey.php +++ b/src/Keys/AsymmetricSecretKey.php @@ -11,6 +11,7 @@ use ParagonIE\Paseto\{ Exception\ExceptionCode, Exception\PasetoException, + Exception\SecurityException, SendingKey, ProtocolInterface, Util @@ -80,6 +81,23 @@ public function __construct( $keypair = sodium_crypto_sign_seed_keypair($keyData); $keyData = Binary::safeSubstr($keypair, 0, 64); } + + /* Misuse-resistance: Prevent mismatched public keys + * + * See: https://github.com/MystenLabs/ed25519-unsafe-libs + */ + $sk = Binary::safeSubstr( + sodium_crypto_sign_seed_keypair( + Binary::safeSubstr($keyData, 0, 32) + ), + 0, + 64 + ); + if (!hash_equals($keyData, $sk)) { + throw new SecurityException( + "Key mismatch: Public key doesn't belong to private key." + ); + } } $this->key = $keyData; $this->protocol = $protocol; diff --git a/tests/KeyTest.php b/tests/KeyTest.php index 9a94d06..54b166f 100644 --- a/tests/KeyTest.php +++ b/tests/KeyTest.php @@ -2,8 +2,11 @@ declare(strict_types=1); namespace ParagonIE\Paseto\Tests; +use ParagonIE\ConstantTime\Binary; +use ParagonIE\Paseto\Exception\SecurityException; use ParagonIE\Paseto\Keys\AsymmetricPublicKey; use ParagonIE\Paseto\Keys\AsymmetricSecretKey; +use ParagonIE\Paseto\ProtocolInterface; use ParagonIE\Paseto\Util; use ParagonIE\Paseto\Protocol\{ Version1, @@ -170,4 +173,34 @@ public function sampleKeyTrail(string $secret, string $public, string $base64): 'Re-encoding fails' ); } + + public function ed25519provider() + { + return [ + [new Version2], + [new Version4], + ]; + } + + /** + * @dataProvider ed25519provider + */ + public function testInvalidEdDSAKey(ProtocolInterface $version) + { + if (!extension_loaded('sodium')) { + $this->markTestSkipped('Slow test on sodium_compat'); + } + $keypair1 = sodium_crypto_sign_keypair(); + $keypair2 = sodium_crypto_sign_keypair(); + + $good1 = Binary::safeSubstr($keypair1, 0, 64); + $good2 = Binary::safeSubstr($keypair2, 0, 64); + $bad = Binary::safeSubstr($keypair1, 0, 32) . Binary::safeSubstr($keypair2, 32, 32); + + new AsymmetricSecretKey($good1, $version); + new AsymmetricSecretKey($good2, $version); + + $this->expectException(SecurityException::class); + new AsymmetricSecretKey($bad, $version); + } }