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); + } +} diff --git a/tests/unit/Signer/CliCryptoProSignerTest.php b/tests/unit/Signer/CliCryptoProSignerTest.php new file mode 100644 index 0000000..850622c --- /dev/null +++ b/tests/unit/Signer/CliCryptoProSignerTest.php @@ -0,0 +1,69 @@ +markTestSkipped('The cryptcp utility is not available'); + } + } + + public function testSign(): void + { + $signer = new CliCryptoProSigner( + 'cryptcp', + '745187e5c161cd2e3130d886f9df4492fa270685', + 'test' + ); + + $signature = $signer->sign('test'); + + file_put_contents(codecept_log_dir('content'), 'test'); + $signature = base64_decode(strtr($signature, '-_', '+/')); + file_put_contents(codecept_log_dir('signature'), $signature); + $command = sprintf( + "openssl smime -verify -inform DER -in %s -CAfile %s -content %s", + codecept_log_dir('signature'), + codecept_data_dir('server.crt'), + codecept_log_dir('content') + ); + $output = null; + $resultCode = null; + exec($command, $output, $resultCode); + self::assertEquals(0, $resultCode, 'OpenSSL verification failure'); + } + + 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') + ); + } +}