From 973377c99ccf8e0c3914f4cea491d8bf85250433 Mon Sep 17 00:00:00 2001 From: Vladimir Ilimurzin Date: Wed, 26 Jun 2024 10:50:06 +0300 Subject: [PATCH 1/2] feat: CryptoPro signers --- src/Esia/Signer/CliCryptoProSigner.php | 74 ++++++++++++++++++++++++++ src/Esia/Signer/CryptoProSigner.php | 47 ++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/Esia/Signer/CliCryptoProSigner.php create mode 100644 src/Esia/Signer/CryptoProSigner.php diff --git a/src/Esia/Signer/CliCryptoProSigner.php b/src/Esia/Signer/CliCryptoProSigner.php new file mode 100644 index 0000000..2f10465 --- /dev/null +++ b/src/Esia/Signer/CliCryptoProSigner.php @@ -0,0 +1,74 @@ +toolPath = $toolPath; + $this->thumbprint = $thumbprint; + $this->pin = $pin; + $this->tempDir = $tempDir ?? sys_get_temp_dir(); + + if (!file_exists($this->tempDir)) { + throw new NoSuchTmpDirException('Temporary folder is not found'); + } + if (!is_writable($this->tempDir)) { + throw new NoSuchTmpDirException('Temporary folder is not writable'); + } + } + + public function sign(string $message): string + { + $tempPath = tempnam($this->tempDir, 'cryptcp'); + file_put_contents($tempPath, $message); + + try { + return $this->signFile($tempPath); + } catch (SignFailException $e) { + unlink($tempPath); + + throw $e; + } + } + + private function signFile(string $tempPath): string + { + $command = "$this->toolPath -signf -dir $this->tempDir -cert -thumbprint $this->thumbprint"; + if ($this->pin) { + $command .= " -pin $this->pin"; + } + $command .= " $tempPath"; + + $output = null; + $resultCode = null; + exec($command, $output, $resultCode); + + if ($resultCode !== 0) { + throw new SignFailException('Failure signing: ' . implode("\n", $output)); + } + + $signatureFilePath = $tempPath . '.sgn'; + $signature = file_get_contents($signatureFilePath); + unlink($signatureFilePath); + + if (!$signature) { + throw new SignFailException("Failure reading $signatureFilePath"); + } + + return $signature; + } +} diff --git a/src/Esia/Signer/CryptoProSigner.php b/src/Esia/Signer/CryptoProSigner.php new file mode 100644 index 0000000..4ef6c04 --- /dev/null +++ b/src/Esia/Signer/CryptoProSigner.php @@ -0,0 +1,47 @@ +thumbprint = $thumbprint; + $this->pin = $pin; + } + + public function sign(string $message): string + { + $store = new \CPStore(); + $store->Open(CURRENT_USER_STORE, 'My', STORE_OPEN_READ_ONLY); + + $certificates = $store->get_Certificates(); + $found = $certificates->Find(CERTIFICATE_FIND_SHA1_HASH, $this->thumbprint, 0); + $certificate = $found->Item(1); + if (!$certificate) { + throw new SignFailException('Cannot read the certificate'); + } + if ($certificate->HasPrivateKey() === false) { + throw new SignFailException('Cannot read the private key'); + } + + $signer = new \CPSigner(); + $signer->set_Certificate($certificate); + if ($this->pin) { + $signer->set_KeyPin($this->pin); + } + + $sd = new \CPSignedData(); + $sd->set_ContentEncoding(BASE64_TO_BINARY); + $sd->set_Content(base64_encode($message)); + + return $sd->SignCades($signer, CADES_BES, true, ENCODE_BASE64); + } +} From 2412d799f58a78a28e005c1ae923ff65bc9de686 Mon Sep 17 00:00:00 2001 From: Vladimir Ilimurzin Date: Sun, 11 Aug 2024 23:47:01 +0300 Subject: [PATCH 2/2] feat: test CLI CryptoPro signer --- tests/unit/Signer/CliCryptoProSignerTest.php | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/unit/Signer/CliCryptoProSignerTest.php diff --git a/tests/unit/Signer/CliCryptoProSignerTest.php b/tests/unit/Signer/CliCryptoProSignerTest.php new file mode 100644 index 0000000..e686cd9 --- /dev/null +++ b/tests/unit/Signer/CliCryptoProSignerTest.php @@ -0,0 +1,44 @@ +expectException(\Esia\Signer\Exceptions\SignFailException::class); + $signer->sign('test'); + } + + public function testTempDirDoesNotExists(): void + { + $this->expectException(\Esia\Signer\Exceptions\NoSuchTmpDirException::class); + + new CliCryptoProSigner( + '/opt/cprocsp/bin/amd64/cryptcp', + '66821344ce484aceb984d887b303544bfdda8ea4', + null, + '/' + ); + } + + public function testTempDirIsNotWritable(): void + { + $this->expectException(\Esia\Signer\Exceptions\NoSuchTmpDirException::class); + + new CliCryptoProSigner( + '/opt/cprocsp/bin/amd64/cryptcp', + '66821344ce484aceb984d887b303544bfdda8ea4', + null, + codecept_log_dir('non_writable_directory') + ); + } +}