Skip to content

Commit

Permalink
Consistent encoding of ECDSA public keys, with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
paragonie-security committed Aug 3, 2021
1 parent 76e1914 commit 5743bb4
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 5 deletions.
35 changes: 34 additions & 1 deletion src/Keys/AsymmetricPublicKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ProtocolInterface,
Util
};
use FG\ASN1\Exception\ParserException;
use ParagonIE\Paseto\Protocol\{
Version1,
Version2,
Expand Down Expand Up @@ -143,9 +144,17 @@ public static function v4(string $keyMaterial): self
* @return string
*
* @throws TypeError
* @throws ParserException
*/
public function encode(): string
{
if (hash_equals($this->protocol::header(), Version3::HEADER)) {
return Base64UrlSafe::encodeUnpadded(
Hex::decode(
Version3::getPublicKeyCompressed($this->key)
)
);
}
return Base64UrlSafe::encodeUnpadded($this->key);
}

Expand All @@ -161,10 +170,34 @@ public function encode(): string
*/
public static function fromEncodedString(string $encoded, ProtocolInterface $version = null): self
{
$decoded = Base64UrlSafe::decode($encoded);
if (hash_equals($version::header(), Version3::HEADER)) {
$decoded = Version3::getPublicKeyPem(
Hex::encode(
Base64UrlSafe::decode($encoded)
)
);
} else {
$decoded = Base64UrlSafe::decode($encoded);
}
return new static($decoded, $version);
}

/**
* @return string
*/
public function toHexString(): string
{
if (hash_equals($this->protocol::header(), Version3::HEADER)) {
if (Binary::safeStrlen($this->key) === 98) {
return $this->key;
}
if (Binary::safeStrlen($this->key) !== 49) {
return Version3::getPublicKeyCompressed($this->key);
}
}
return Hex::encode($this->key);
}

/**
* Get the version of PASETO that this key is intended for.
*
Expand Down
6 changes: 3 additions & 3 deletions src/Protocol/Version3.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public static function sign(
$header = self::HEADER . '.public.';
$easyEcc = new EasyECC(self::CURVE);
// PASETO Version 3 - Sign - Step 2 & 3:
$pk = Hex::decode($key->getPublicKey()->raw());
$pk = Hex::decode($key->getPublicKey()->toHexString());
if (Binary::safeStrlen($pk) !== 49) {
throw new PasetoException(
'Invalid public key length: ' . Binary::safeStrlen($pk),
Expand Down Expand Up @@ -340,7 +340,7 @@ public static function verify(
$easyEcc = new EasyECC(self::CURVE);

// PASETO Version 3 - Verify - Step 4 & 5:
$pk = Hex::decode($key->raw());
$pk = Hex::decode($key->toHexString());
if (Binary::safeStrlen($pk) !== 49) {
throw new PasetoException(
'Invalid public key length: ' . Binary::safeStrlen($pk),
Expand All @@ -349,7 +349,7 @@ public static function verify(
}
$valid = $easyEcc->verify(
Util::preAuthEncode($pk, $givenHeader, $message, $footer, $implicit),
PublicKey::fromString($key->raw(), 'P384'),
PublicKey::fromString($key->toHexString(), 'P384'),
Hex::encode($signature),
true
);
Expand Down
13 changes: 13 additions & 0 deletions tests/Version1Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ public function testNonceDerivation()
);
}

public function testPublicKeyEncode()
{
$sk = AsymmetricSecretKey::generate();
$pk = $sk->getPublicKey();

$encoded = $pk->encode();
$decoded = AsymmetricPublicKey::fromEncodedString($encoded, new Version1());
$this->assertSame(
$pk->raw(),
$decoded->raw()
);
}

/**
* @covers Version1::decrypt()
* @covers Version1::encrypt()
Expand Down
13 changes: 13 additions & 0 deletions tests/Version2Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ public function testKeyGen()
$this->assertSame(64, Binary::safeStrlen($secret->raw()));
}

public function testPublicKeyEncode()
{
$sk = AsymmetricSecretKey::generate();
$pk = $sk->getPublicKey();

$encoded = $pk->encode();
$decoded = AsymmetricPublicKey::fromEncodedString($encoded, new Version2());
$this->assertSame(
$pk->raw(),
$decoded->raw()
);
}

/**
* @covers Version2::decrypt()
* @covers Version2::encrypt()
Expand Down
16 changes: 15 additions & 1 deletion tests/Version3Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
use ParagonIE\ConstantTime\Binary;
use ParagonIE\Paseto\Exception\InvalidVersionException;
use ParagonIE\Paseto\Exception\PasetoException;
use ParagonIE\Paseto\Keys\AsymmetricSecretKey;
use ParagonIE\Paseto\Keys\Version3\AsymmetricPublicKey;
use ParagonIE\Paseto\Keys\Version3\AsymmetricSecretKey;
use ParagonIE\Paseto\Keys\Version3\SymmetricKey;
use ParagonIE\Paseto\Protocol\{
Version2,
Expand All @@ -31,6 +32,19 @@ public function testKeyGen()
$this->assertGreaterThanOrEqual(48, Binary::safeStrlen($secret->raw())); // PEM encoded
}

public function testPublicKeyEncode()
{
$sk = AsymmetricSecretKey::generate(new Version3);
$pk = $sk->getPublicKey();

$encoded = $pk->encode();
$decoded = AsymmetricPublicKey::fromEncodedString($encoded, new Version3());
$this->assertSame(
$pk->raw(),
$decoded->raw()
);
}

/**
* @covers Version3::decrypt()
* @covers Version3::encrypt()
Expand Down
19 changes: 19 additions & 0 deletions tests/Version4Test.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?php
namespace ParagonIE\Paseto\Tests;

use FG\ASN1\Exception\ParserException;
use ParagonIE\ConstantTime\Binary;
use ParagonIE\Paseto\Exception\InvalidVersionException;
use ParagonIE\Paseto\Exception\PasetoException;
use ParagonIE\Paseto\Keys\Version4\AsymmetricPublicKey;
use ParagonIE\Paseto\Keys\Version4\AsymmetricSecretKey;
use ParagonIE\Paseto\Keys\Version4\SymmetricKey;
use ParagonIE\Paseto\Protocol\{
Version3,
Expand All @@ -30,6 +33,22 @@ public function testKeyGen()
$this->assertGreaterThanOrEqual(48, Binary::safeStrlen($secret->raw())); // PEM encoded
}

/**
* @throws ParserException
*/
public function testPublicKeyEncode()
{
$sk = AsymmetricSecretKey::generate();
$pk = $sk->getPublicKey();

$encoded = $pk->encode();
$decoded = AsymmetricPublicKey::fromEncodedString($encoded, new Version4());
$this->assertSame(
$pk->raw(),
$decoded->raw()
);
}

/**
* @covers Version4::decrypt()
* @covers Version4::encrypt()
Expand Down

0 comments on commit 5743bb4

Please sign in to comment.