-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add shortcut key-store for combined PEM client certificates
- Loading branch information
Showing
9 changed files
with
309 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Soap\Psr18WsseMiddleware\OpenSSL\Exception; | ||
|
||
use RuntimeException; | ||
|
||
final class InvalidKeyException extends RuntimeException | ||
{ | ||
public static function unableToReadPrivateKey(): self | ||
{ | ||
return new self('Unable to read the format of the provided private key.'); | ||
} | ||
|
||
public static function unableToReadPublicKey(): self | ||
{ | ||
return new self('Unable to read the format of the provided public key.'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Soap\Psr18WsseMiddleware\OpenSSL\Parser; | ||
|
||
use ParagonIE\HiddenString\HiddenString; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Exception\InvalidKeyException; | ||
use Soap\Psr18WsseMiddleware\WSSecurity\KeyStore\Key; | ||
|
||
final class PrivateKeyParser | ||
{ | ||
public function __invoke(HiddenString $privateKey, ?HiddenString $password = null): Key | ||
{ | ||
$parsed = ''; | ||
$key = @openssl_pkey_get_private($privateKey->getString(), $password?->getString() ?: null); | ||
if (!$key) { | ||
throw InvalidKeyException::unableToReadPrivateKey(); | ||
} | ||
|
||
$result = @openssl_pkey_export($key, $parsed, $password?->getString() ?: null); | ||
if (!$result) { | ||
throw InvalidKeyException::unableToReadPrivateKey(); | ||
} | ||
|
||
return (new Key($parsed))->withPassphrase($password?->getString() ?? ''); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Soap\Psr18WsseMiddleware\OpenSSL\Parser; | ||
|
||
use ParagonIE\HiddenString\HiddenString; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Exception\InvalidKeyException; | ||
use Soap\Psr18WsseMiddleware\WSSecurity\KeyStore\Certificate; | ||
|
||
final class X509PublicCertificateParser | ||
{ | ||
public function __invoke(HiddenString $publicKey): Certificate | ||
{ | ||
$parsed = ''; | ||
$key = @openssl_x509_read($publicKey->getString()); | ||
if (!$key) { | ||
throw InvalidKeyException::unableToReadPublicKey(); | ||
} | ||
|
||
$result = @openssl_x509_export($key, $parsed); | ||
if (!$result) { | ||
throw InvalidKeyException::unableToReadPublicKey(); | ||
} | ||
|
||
return new Certificate($parsed); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Soap\Psr18WsseMiddleware\WSSecurity\KeyStore; | ||
|
||
use ParagonIE\HiddenString\HiddenString; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Parser\PrivateKeyParser; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Parser\X509PublicCertificateParser; | ||
use function Psl\File\read; | ||
|
||
/** | ||
* Contains a PEM bundle of both public X.509 Certificate and an (un)encrypted private key PKCS_8. | ||
*/ | ||
final class ClientCertificate implements KeyInterface | ||
{ | ||
private HiddenString $key; | ||
private HiddenString $passphrase; | ||
|
||
public function __construct(string $key) | ||
{ | ||
$this->key = new HiddenString($key); | ||
$this->passphrase = new HiddenString(''); | ||
} | ||
|
||
/** | ||
* @param non-empty-string $file | ||
*/ | ||
public static function fromFile(string $file): self | ||
{ | ||
return new self(read($file)); | ||
} | ||
|
||
/** | ||
* Parse out the private part of the bundled X509 certificate. | ||
*/ | ||
public function privateKey(): Key | ||
{ | ||
return (new PrivateKeyParser())($this->key, $this->passphrase); | ||
} | ||
|
||
/** | ||
* Parse out the public part of the bundled X509 certificate. | ||
*/ | ||
public function publicCertificate(): Certificate | ||
{ | ||
return (new X509PublicCertificateParser())($this->key); | ||
} | ||
|
||
/** | ||
* Provides the full content of the bundled pem certificate. | ||
*/ | ||
public function contents(): string | ||
{ | ||
return $this->key->getString(); | ||
} | ||
|
||
public function passphrase(): string | ||
{ | ||
return $this->passphrase->getString(); | ||
} | ||
|
||
public function isCertificate(): bool | ||
{ | ||
return true; | ||
} | ||
|
||
public function withPassphrase(string $passphrase): self | ||
{ | ||
$new = clone $this; | ||
$new->passphrase = new HiddenString($passphrase); | ||
|
||
return $new; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace SoapTest\Psr18WsseMiddleware\Unit\OpenSSL\Parser; | ||
|
||
use ParagonIE\HiddenString\HiddenString; | ||
use PHPUnit\Framework\TestCase; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Exception\InvalidKeyException; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Parser\PrivateKeyParser; | ||
use Soap\Psr18WsseMiddleware\WSSecurity\KeyStore\Key; | ||
use function Psl\File\read; | ||
|
||
final class PrivateKeyParserTest extends TestCase | ||
{ | ||
|
||
public function test_it_can_read_private_key(): void | ||
{ | ||
$key = $this->createPrivateKey(); | ||
$parser = new PrivateKeyParser(); | ||
|
||
$actual = $parser(new HiddenString($key)); | ||
|
||
static::assertInstanceOf(Key::class, $actual); | ||
static::assertSame($key, $actual->contents()); | ||
} | ||
|
||
|
||
public function test_it_can_read_encrypted_private_key(): void | ||
{ | ||
$key = $this->createPrivateKey($passPhrase = 'password'); | ||
$parser = new PrivateKeyParser(); | ||
|
||
static::assertStringContainsString('ENCRYPTED PRIVATE KEY', $key); | ||
|
||
$actual = $parser(new HiddenString($key), new HiddenString($passPhrase)); | ||
|
||
static::assertInstanceOf(Key::class, $actual); | ||
static::assertSame($passPhrase, $actual->passphrase()); | ||
static::assertStringContainsString('ENCRYPTED PRIVATE KEY', $actual->contents()); | ||
} | ||
|
||
|
||
public function test_it_can_read_from_bundle(): void | ||
{ | ||
$bundle = FIXTURE_DIR . '/certificates/wsse-client-x509.pem'; | ||
$parser = new PrivateKeyParser(); | ||
|
||
$actual = $parser(new HiddenString(read($bundle))); | ||
|
||
static::assertInstanceOf(Key::class, $actual); | ||
static::assertSame('', $actual->passphrase()); | ||
static::assertStringContainsString('PRIVATE KEY', $actual->contents()); | ||
} | ||
|
||
|
||
public function test_it_can_not_read_invalid_private_key(): void | ||
{ | ||
$key = 'notavalidkey'; | ||
$parser = new PrivateKeyParser(); | ||
|
||
$this->expectException(InvalidKeyException::class); | ||
$parser(new HiddenString($key)); | ||
} | ||
|
||
private function createPrivateKey(?string $passPhrase = null): string | ||
{ | ||
$key = openssl_pkey_new(); | ||
static::assertNotFalse($key); | ||
|
||
$parsed = ''; | ||
$result = openssl_pkey_export($key, $parsed, $passPhrase); | ||
static::assertNotFalse($result); | ||
|
||
return $parsed; | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
tests/Unit/OpenSSL/Parser/X509PublicCertificateParserTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace SoapTest\Psr18WsseMiddleware\Unit\OpenSSL\Parser; | ||
|
||
use ParagonIE\HiddenString\HiddenString; | ||
use PHPUnit\Framework\TestCase; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Exception\InvalidKeyException; | ||
use Soap\Psr18WsseMiddleware\OpenSSL\Parser\X509PublicCertificateParser; | ||
use Soap\Psr18WsseMiddleware\WSSecurity\KeyStore\Certificate; | ||
use function Psl\File\read; | ||
|
||
final class X509PublicCertificateParserTest extends TestCase | ||
{ | ||
|
||
public function test_it_can_read_public_x509_key(): void | ||
{ | ||
$parser = new X509PublicCertificateParser(); | ||
$file = FIXTURE_DIR . '/certificates/wsse-server-x509.crt'; | ||
|
||
$actual = $parser(new HiddenString(read($file))); | ||
|
||
static::assertInstanceOf(Certificate::class, $actual); | ||
static::assertStringEqualsFile($file, $actual->contents()); | ||
} | ||
|
||
|
||
public function test_it_can_read_from_bundle(): void | ||
{ | ||
$bundle = FIXTURE_DIR . '/certificates/wsse-client-x509.pem'; | ||
$parser = new X509PublicCertificateParser(); | ||
|
||
$actual = $parser(new HiddenString(read($bundle))); | ||
|
||
static::assertInstanceOf(Certificate::class, $actual); | ||
static::assertStringContainsString('CERTIFICATE', $actual->contents()); | ||
} | ||
|
||
|
||
public function test_it_can_not_read_invalid_certificate(): void | ||
{ | ||
$key = 'notavalidkey'; | ||
$parser = new X509PublicCertificateParser(); | ||
|
||
$this->expectException(InvalidKeyException::class); | ||
$parser(new HiddenString($key)); | ||
} | ||
} |