From b770837c80f2affb50e1af8c3175bdbaf889fe4e Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Sun, 26 Oct 2025 22:18:21 +0200 Subject: [PATCH] added private key object and tests --- src/CMS/PrivateKey.php | 86 ++++++++++++++++++++++++++++++++++ tests/CMS/PrivateKeyTest.php | 64 +++++++++++++++++++++++++ tests/fixtures/private_key.key | 22 +++++++++ 3 files changed, 172 insertions(+) create mode 100644 src/CMS/PrivateKey.php create mode 100644 tests/CMS/PrivateKeyTest.php create mode 100644 tests/fixtures/private_key.key diff --git a/src/CMS/PrivateKey.php b/src/CMS/PrivateKey.php new file mode 100644 index 0000000..d914a21 --- /dev/null +++ b/src/CMS/PrivateKey.php @@ -0,0 +1,86 @@ + + * @copyright 2025 Alexander Danilov + * @license https://en.wikipedia.org/wiki/MIT_License MIT License + * @link https://github.com/Adapik/CMS + */ + +namespace Adapik\CMS; + +use Adapik\CMS\Interfaces\CMSInterface; +use Adapik\CMS\Interfaces\PEMConvertable; +use FG\ASN1\ASN1ObjectInterface; +use FG\ASN1\Universal\OctetString; +use FG\ASN1\Universal\Sequence; + +/** + * Class PrivateKey + * + * @see Maps\PrivateKeyInfo + * @package Adapik\CMS + */ +class PrivateKey extends CMSBase implements PEMConvertable +{ + const PEM_HEADER = "BEGIN PRIVATE KEY"; + const PEM_FOOTER = "END PRIVATE KEY"; + + /** + * @param string $content + * + * @return PrivateKey + * @throws Exception\FormatException + */ + public static function createFromContent(string $content): CMSInterface + { + return new self(self::makeFromContent($content, Maps\PrivateKeyInfo::class, Sequence::class)); + } + + /** + * @return OctetString|ASN1ObjectInterface + */ + public function getKey(): OctetString + { + /** @var OctetString $octetString */ + $octetString = $this->object->getChildren()[2]; + + return $octetString->detach(); + } + + /** + * @return AlgorithmIdentifier + */ + public function getKeyAlgorithm(): AlgorithmIdentifier + { + return new AlgorithmIdentifier($this->object->getChildren()[1]); + } + + /** + * @return int + */ + public function getVersion(): int + { + /** @var \FG\ASN1\Universal\Integer $version */ + $version = $this->object->getChildren()[0]; + + return (int)$version->__toString(); + } + + /** + * @return string + */ + public function getPEMHeader(): string + { + return self::PEM_HEADER; + } + + /** + * @return string + */ + public function getPEMFooter(): string + { + return self::PEM_FOOTER; + } +} diff --git a/tests/CMS/PrivateKeyTest.php b/tests/CMS/PrivateKeyTest.php new file mode 100644 index 0000000..9de2915 --- /dev/null +++ b/tests/CMS/PrivateKeyTest.php @@ -0,0 +1,64 @@ + + * @copyright 2025 Alexander Danilov + * @license https://en.wikipedia.org/wiki/MIT_License MIT License + * @link https://github.com/Adapik/CMS + */ + +declare(strict_types=1); + +namespace Adapik\Test\CMS; + +use Adapik\CMS\AlgorithmIdentifier; +use Adapik\CMS\Exception\FormatException; +use Adapik\CMS\PEMConverter; +use Adapik\CMS\PrivateKey; +use Exception; +use FG\ASN1\Universal\OctetString; +use PHPUnit\Framework\TestCase; + +class PrivateKeyTest extends TestCase +{ + public function testBase() + { + $privateKey = PrivateKey::createFromContent(base64_decode(file_get_contents(__DIR__ . '/../fixtures/private_key.key'))); + + self::assertInstanceOf(OctetString::class, $privateKey->getKey()); + self::assertInstanceOf(AlgorithmIdentifier::class, $privateKey->getKeyAlgorithm()); + self::assertIsInt($privateKey->getVersion()); + self::assertEquals(0, $privateKey->getVersion()); + + $binary = $privateKey->getBinary(); + $newPrivateKey = PrivateKey::createFromContent($binary); + + self::assertEquals($binary, $newPrivateKey->getBinary()); + } + + /** + * @throws FormatException + * @throws Exception + */ + public function testGetPEM() + { + $privateKey = PrivateKey::createFromContent(base64_decode(file_get_contents(__DIR__ . '/../fixtures/private_key.key'))); + + $pem = PEMConverter::toPEM($privateKey); + preg_match('/-+([^-]+)-+(.*?)-+([^-]+)-+/ms', $pem, $matches); + self::assertSame($privateKey->getPEMHeader(), $matches[1]); + self::assertSame($privateKey->getPEMFooter(), $matches[3]); + self::assertSame($privateKey->getBase64(false), str_replace(["\r", "\n", "\r\n"], "", $matches[2])); + } + + public function testKeyAlgorithm() + { + $privateKey = PrivateKey::createFromContent(base64_decode(file_get_contents(__DIR__ . '/../fixtures/private_key.key'))); + $algorithm = $privateKey->getKeyAlgorithm(); + + self::assertInstanceOf(AlgorithmIdentifier::class, $algorithm); + // RSA encryption OID is 1.2.840.113549.1.1.1 + self::assertStringContainsString('1.2.840.113549.1.1.1', $algorithm->getAlgorithmOid()); + } +} diff --git a/tests/fixtures/private_key.key b/tests/fixtures/private_key.key new file mode 100644 index 0000000..1a4e802 --- /dev/null +++ b/tests/fixtures/private_key.key @@ -0,0 +1,22 @@ +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbls8JdwiYQzIwUA3xIxLg20AA +D8f8YU0SZqB0Mlrj+OpMYq1POzbj2ZGRhs5vGaSqbEM9aRyOYs97SC7IgQpkIf9L9joUy97i5v0c +gR+F3qIGsJU4UrTOr3XWbrmnuTGTvlNuYUyn956ZNctVuWud4O6Uf6CI+KJyg+U6faORBY6g5hPx ++XpOcpncXzO1m/1NYk4bHLVBhgWjILjAj5SnYKzukefh7VDJL1f981nDUTe5caCGOHQ1m2UMX+J8 +hhEc9cGqDDX4va9psU2bMX6hHaRV0HFyVo6W/loyFnMclPqpKVJi18igZsuWNysXddSsIdf1yry3 +ouk+zk0FAek/AgMBAAECggEAKVoBfAlP84h365Qvw/C0bear6zcNlI/oOSZdYfRXnGW0DZVpiKmg +gRDoMkz+E/rywOspUGe6am/eHZuuv7BZOhkS4AWq016/BuCz2onQW1/dCEB5oUUl6mQtXyezy4t6 +9BUb1gX5rwnY8oYoV+jWhMEmfI16OEEGKmV/JeAiDL1zU7HK/kjDl1+UqOVlZJhRIpbQRkKioNgK +Kz67ks48xiexSkVOE474+tDqHDm2nyooPHeM78BwHa7QF6LU5edBAZSwCtowsq3CfsBP+UH4rF9T +AHqbbzUAlROhqCcVVg2Ur5J5/MuCVAOnLaSEF4idUSoftWQjX523QmmtPuvXhQKBgQD0MrPStybW +lugq3ggZuqqaIq/7Skb/XjEFh1HwboRRY+ylpw4ccDSccqnQ2zry2ocNSWRR0sAmTa/WyB1QV6DF +s6/Ux+bUgzCLrUpJcji3agZ/yxpP22USnWueJ4X4fAdPA7icjnuKps2It4QzL3Akyj8uCR5YYHot +PaZaIu0NCwKBgQDmM6LisR1rspRhEKe5Ps9Km+cWEXt4ZVCPp6DMingMQVaQaYgKMdekUYEEji7m +UMTI9rMJKgd/AzWFBtTHALohVmHyU91kGKTq+PHlgBqfMdz7XvufQ9vCpQ0iXv5teSdcZ5zBwEqg +8yiKJa18w60YwNfOs5onQeKizJA9y2WtHQKBgQDbqGv2xAbsiivmu3gf5LgnE8Km0MaSWODRr0p2 +coMVT0QEMgPQcuHwRHgaRcl9wDIxMm5J58At9D2SfslXhqs4iwxD41jwmd1mFwe2j0cTPQ0BkOln +IduF5hJy3ex1euN8IMXEFaptKuPA8gq5nBdNEJvKRiuEpavg3A6yD66ZxwKBgAkuihKsqdW/27c7 +aNU0siPxr/frXpdgwyPXtcwywbMLcB52dxyg/E7riU/bM/evxQmW/XEiIgZo5FuX+Civ2Ly3q1R1 +D+GMMhfFMjDLOUScQoosMbEajctntexnCZLoQC+pWlFIEDH3QOixtAfD7kWMe48dDsr+D8Iakq5E +TJvpAoGAY554roLw1gJNTDheBI54tkZtrJGxMEgE2BTxzc1OWN6Rd6haAzt/7TEwWn9lbvp0yj56 +XE75lr+IshtTYwCPBpYgRYp81sWO/Ip2+yybgH11BH2gNa8SZk25f+JUO3o9JDf7pZ8HFHVOBsYE +6AcYXjZYbHrKbgyxymTmYkXU/ig=