Skip to content
This repository has been archived by the owner on Jan 3, 2023. It is now read-only.

Commit

Permalink
Verify paths to private and public key [closes #23] [closes #24]
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Machala <jan.machala@email.cz>
  • Loading branch information
janlanger and jost125 committed Feb 17, 2020
1 parent 70be8db commit b5e6ba5
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 24 deletions.
27 changes: 26 additions & 1 deletion src/Cryptography/CryptographyService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use RobRichards\XMLSecLibs\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecurityKey;
use const OPENSSL_ALGO_SHA256;
use function is_file;

class CryptographyService
{
Expand All @@ -20,8 +21,17 @@ class CryptographyService
/** @var string */
private $publicKeyFile;

/** @var bool */
private $publicKeyVerified;

public function __construct(string $privateKeyFile, string $publicKeyFile, string $privateKeyPassword = '')
{
if (!is_file($privateKeyFile)) {
throw new PrivateKeyFileNotFoundException($privateKeyFile);
}
if (!is_file($publicKeyFile)) {
throw new PublicKeyFileNotFoundException($publicKeyFile);
}
$this->privateKeyFile = $privateKeyFile;
$this->publicKeyFile = $publicKeyFile;
$this->privateKeyPassword = $privateKeyPassword;
Expand Down Expand Up @@ -69,6 +79,8 @@ public function getBkpCode(string $pkpCode): string

public function addWSESignature(string $request): string
{
$publicKey = (string) file_get_contents($this->publicKeyFile);
$this->verifyPublicKey($publicKey);
$securityKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
$document = new DOMDocument('1.0');
$document->loadXML($request);
Expand All @@ -77,10 +89,23 @@ public function addWSESignature(string $request): string
$securityKey->loadKey($this->privateKeyFile, true);
$wse->addTimestamp();
$wse->signSoapDoc($securityKey, ['algorithm' => XMLSecurityDSig::SHA256]);
$binaryToken = $wse->addBinaryToken(file_get_contents($this->publicKeyFile));
$binaryToken = $wse->addBinaryToken($publicKey);
$wse->attachTokentoSig($binaryToken);

return $wse->saveXML();
}

private function verifyPublicKey(string $fileContents): void
{
if ($this->publicKeyVerified) {
return;
}
$publicKeyResource = openssl_get_publickey($fileContents);
if ($publicKeyResource === false) {
throw new InvalidPublicKeyException($this->publicKeyFile, (string) openssl_error_string());
}
openssl_free_key($publicKeyResource);
$this->publicKeyVerified = true;
}

}
19 changes: 19 additions & 0 deletions src/Cryptography/InvalidPublicKeyException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace SlevomatEET\Cryptography;

use Throwable;

class InvalidPublicKeyException extends PublicKeyFileException
{

public function __construct(string $publicKeyFile, string $openSslError, ?Throwable $previous = null)
{
parent::__construct(sprintf(
'Public key could not be loaded from file \'%s\'. Please make sure that the file contains valid public key in PEM format. (OpenSSL error: %s)',
$publicKeyFile,
$openSslError
), $publicKeyFile, $previous);
}

}
8 changes: 8 additions & 0 deletions src/Cryptography/PrivateKeyFileNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types = 1);

namespace SlevomatEET\Cryptography;

class PrivateKeyFileNotFoundException extends PrivateKeyFileException
{

}
26 changes: 26 additions & 0 deletions src/Cryptography/PublicKeyFileException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

namespace SlevomatEET\Cryptography;

use Exception;
use Throwable;

abstract class PublicKeyFileException extends Exception
{

/** @var string */
private $publicKeyFile;

public function __construct(string $message, string $publicKeyFile, ?Throwable $previous = null)
{
parent::__construct($message, 0, $previous);

$this->publicKeyFile = $publicKeyFile;
}

public function getPublicKeyFile(): string
{
return $this->publicKeyFile;
}

}
18 changes: 18 additions & 0 deletions src/Cryptography/PublicKeyFileNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);

namespace SlevomatEET\Cryptography;

use Throwable;

class PublicKeyFileNotFoundException extends PublicKeyFileException
{

public function __construct(string $publicKeyFile, ?Throwable $previous = null)
{
parent::__construct(sprintf(
'Public key could not be loaded from file \'%s\'.',
$publicKeyFile
), $publicKeyFile, $previous);
}

}
88 changes: 65 additions & 23 deletions tests/SlevomatEET/Cryptography/CryptographyServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,61 @@ class CryptographyServiceTest extends TestCase

private const EXPECTED_PKP = 'hdBqjqCTaEfJ6JI06H+c4OLvRGtntcwLlG0fucEkla++g9RLxP55jYlPLFf6Sdpm5jPC+hpBHry98zsPBlbwkcFiWdmgT2VBCtXxrwfRmJQOHNRdWhItDsHC4p45G+KmtC4uJCFAqFNL+E999wevPaS6Q02WktmvWI5+XUZnN75hR+G94oznpJS8T140850/FsYDlvPw0ZVWJwDMBzVrOWWxPSN3SBwa40TjD3dVIMlMC1Bo0NccnFp0y7GxNMSfIzDhF5R4S2Rmawe85znZ0PiHXMkPDhXLLpPx1pNiMsTwfeoEnhEMSU/PjjmLpbUzaRfLwZzgf+7Bl0ZX+/lsqA==';
private const EXPECTED_BKP = 'F049C3F1-165CDCDA-2E35BC3A-FCB5C660-4B84D0B7';
private const PRIVATE_KEY_WITHOUT_PASSWORD_PATH = __DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.key';
private const PRIVATE_KEY_WITH_PASSWORD_PATH = __DIR__ . '/../../../cert/EET_CA1_Playground_With_Password-CZ00000019.key';
private const PUBLIC_KEY_PATH = __DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.pub';
private const INVALID_KEY_PATH = __DIR__ . '/invalid-certificate.pem';

public function testGetCodes(): void
{
$data = $this->getReceiptData();
$crypto = new CryptographyService(__DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.key', __DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.pub');
$crypto = $this->createCryptographyServiceWithoutPassword();

$expectedPkp = self::EXPECTED_PKP;
$pkpCode = $crypto->getPkpCode($data);
self::assertSame($expectedPkp, base64_encode($pkpCode));
self::assertSame(self::EXPECTED_BKP, $crypto->getBkpCode($pkpCode));
}

public function testExceptions(): void
/**
* @dataProvider provideInvalidKeyPaths
*
* @param string $privateKeyPath
* @param string $publicKeyPath
* @param string $expectedExceptionType
*
* @phpstan-param class-string<\Throwable> $expectedExceptionType
*/
public function testInvalidKeyPaths(string $privateKeyPath, string $publicKeyPath, string $expectedExceptionType): void
{
$this->expectException($expectedExceptionType);
new CryptographyService($privateKeyPath, $publicKeyPath);
}

/**
* @return array[]
*/
public function provideInvalidKeyPaths(): array
{
return [
[self::PRIVATE_KEY_WITHOUT_PASSWORD_PATH, './foo/path', PublicKeyFileNotFoundException::class],
['./foo/path', self::PUBLIC_KEY_PATH, PrivateKeyFileNotFoundException::class],
];
}

public function testInvalidPrivateKeyInPkpCalculation(): void
{
$cryptoService = new CryptographyService(
__DIR__ . '/invalid-certificate.pem',
__DIR__ . '/invalid-certificate.pem'
self::INVALID_KEY_PATH,
self::PUBLIC_KEY_PATH
);

try {
$cryptoService->getPkpCode($this->getReceiptData());
$this->fail();

} catch (PrivateKeyFileException $e) {
$this->assertSame(__DIR__ . '/invalid-certificate.pem', $e->getPrivateKeyFile());
$this->assertSame(self::INVALID_KEY_PATH, $e->getPrivateKeyFile());
}
}

Expand All @@ -49,10 +79,7 @@ public function testExceptions2(): void
{
include __DIR__ . '/OpenSslFunctionsMock.php';

$cryptoService = new CryptographyService(
__DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.key',
__DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.pub'
);
$cryptoService = $this->createCryptographyServiceWithoutPassword();

try {
$cryptoService->getPkpCode($this->getReceiptData());
Expand All @@ -66,40 +93,41 @@ public function testExceptions2(): void
public function testWSESignatureWithoutPrivateKeyPassword(): void
{
$request = $this->getRequestData();
$crypto = new CryptographyService(
__DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.key',
__DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.pub'
);
$crypto = $this->createCryptographyServiceWithoutPassword();

$this->assertNotEmpty($crypto->addWSESignature($request));
}

public function testWSESignatureWithPrivateKeyPassword(): void
{
$request = $this->getRequestData();
$crypto = new CryptographyService(
__DIR__ . '/../../../cert/EET_CA1_Playground_With_Password-CZ00000019.key',
__DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.pub',
'eet'
);
$crypto = $this->createCryptographyServiceWithPassword('eet');

$this->assertNotEmpty($crypto->addWSESignature($request));
}

public function testWSESignatureWithInvalidPrivateKeyPassword(): void
{
$request = $this->getRequestData();
$crypto = new CryptographyService(
__DIR__ . '/../../../cert/EET_CA1_Playground_With_Password-CZ00000019.key',
__DIR__ . '/../../../cert/EET_CA1_Playground-CZ00000019.pub',
'invalid'
);
$crypto = $this->createCryptographyServiceWithPassword('invalid');

$this->expectException(Error::class);
$this->expectExceptionMessage('openssl_sign(): supplied key param cannot be coerced into a private key');
$crypto->addWSESignature($request);
}

public function testWSESignatureWithInvalidPublicKey(): void
{
$request = $this->getRequestData();
$crypto = new CryptographyService(
self::PRIVATE_KEY_WITHOUT_PASSWORD_PATH,
self::INVALID_KEY_PATH
);

$this->expectException(InvalidPublicKeyException::class);
$crypto->addWSESignature($request);
}

/**
* @return mixed[]
*/
Expand Down Expand Up @@ -133,4 +161,18 @@ private function getRequestData(): string
return (string) preg_replace($patterns, $replacements, $requestTemplate);
}

private function createCryptographyServiceWithoutPassword(): CryptographyService
{
return new CryptographyService(self::PRIVATE_KEY_WITHOUT_PASSWORD_PATH, self::PUBLIC_KEY_PATH);
}

private function createCryptographyServiceWithPassword(string $password): CryptographyService
{
return new CryptographyService(
self::PRIVATE_KEY_WITH_PASSWORD_PATH,
self::PUBLIC_KEY_PATH,
$password
);
}

}

0 comments on commit b5e6ba5

Please sign in to comment.