Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EasyEncryption] Improves AwsPkcs11Encryptor to work without the configure-pkcs11 tool #1354

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/EasyEncryption/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"paragonie/halite": "^5.1"
},
"require-dev": {
"aws/aws-sdk-php": "^3.269",
"laravel/lumen-framework": "^9.1",
"natepage/php-pkcs11-ide-helper": "^1.0",
"phpunit/phpunit": "^10.2",
Expand Down
139 changes: 14 additions & 125 deletions packages/EasyEncryption/src/AwsPkcs11Encryptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

namespace EonX\EasyEncryption;

use EonX\EasyEncryption\Exceptions\CouldNotConfigureAwsCloudHsmSdkException;
use EonX\EasyEncryption\Configurator\AwsCloudHsmSdkConfigurator;
use EonX\EasyEncryption\Exceptions\CouldNotEncryptException;
use EonX\EasyEncryption\Exceptions\InvalidConfigurationException;
use EonX\EasyEncryption\Exceptions\InvalidEncryptionKeyException;
use EonX\EasyEncryption\Interfaces\AwsPkcs11EncryptorInterface;
use ParagonIE\Halite\EncryptionKeyPair;
Expand All @@ -14,19 +13,15 @@
use Pkcs11\Mechanism;
use Pkcs11\Module;
use Pkcs11\Session;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\Process;
use Throwable;
use UnexpectedValueException;

final class AwsPkcs11Encryptor extends AbstractEncryptor implements AwsPkcs11EncryptorInterface
{
private const AWS_CLOUDHSM_CONFIGURE_TOOL = '/opt/cloudhsm/bin/configure-pkcs11';

private const AWS_CLOUDHSM_EXTENSION = '/opt/cloudhsm/lib/libcloudhsm_pkcs11.so';

private const AWS_GCM_TAG_LENGTH = 128;

private bool $awsCloudHsmConfigured = false;
private bool $awsCloudHsmSdkConfigured = false;

/**
* @var \Pkcs11\Key[]
Expand All @@ -39,15 +34,8 @@ final class AwsPkcs11Encryptor extends AbstractEncryptor implements AwsPkcs11Enc

public function __construct(
private readonly string $userPin,
private readonly string $hsmCaCert,
private readonly bool $disableKeyAvailabilityCheck = false,
private readonly ?string $hsmIpAddress = null,
private readonly ?string $cloudHsmClusterId = null,
private readonly string $awsRegion = 'ap-southeast-2',
private readonly AwsCloudHsmSdkConfigurator $awsCloudHsmSdkConfigurator,
private readonly string $aad = '',
private readonly ?string $serverClientCertFile = null,
private readonly ?string $serverClientKeyFile = null,
private readonly ?array $awsCloudHsmSdkOptions = null,
?string $defaultKeyName = null,
) {
parent::__construct($defaultKeyName);
Expand All @@ -60,9 +48,8 @@ public function reset(): void
}

$this->keys = [];
$this->session->logout();
$this->module?->C_CloseSession($this->session);
$this->session = null;
$this->module = null;
}

public function sign(string $text, ?string $keyName = null): string
Expand All @@ -72,11 +59,8 @@ public function sign(string $text, ?string $keyName = null): string
$this->validateKey($keyName);
$this->init();

/** @var string|null $keyAsString */
$keyAsString = $keyName;

return $this
->findKey($keyAsString)
->findKey($keyName)
->sign(new Mechanism(\Pkcs11\CKM_SHA512_HMAC, null), $text);
});
}
Expand Down Expand Up @@ -115,98 +99,6 @@ protected function doEncrypt(
return \bin2hex((string)$encrypted);
}

private function configureAwsCloudHsmSdk(): void
{
$filesystem = new Filesystem();
$isSetHsmIpAddress = $this->isNonEmptyString($this->hsmIpAddress);
$isSetCloudHsmClusterId = $this->isNonEmptyString($this->cloudHsmClusterId);
$isSetServerClientCertFile = $this->isNonEmptyString($this->serverClientCertFile);
$isSetServerClientKeyFile = $this->isNonEmptyString($this->serverClientKeyFile);

if ($filesystem->exists($this->hsmCaCert) === false) {
throw new InvalidConfigurationException(\sprintf(
'Given CA Cert filename "%s" does not exist',
$this->hsmCaCert
));
}
if ($isSetHsmIpAddress === false && $isSetCloudHsmClusterId === false) {
throw new InvalidConfigurationException(
'At least HSM IP address or CloudHSM cluster id has to be set'
);
}
if ($isSetHsmIpAddress && $isSetCloudHsmClusterId) {
throw new InvalidConfigurationException(
'Both HSM IP address and CloudHSM cluster id options cannot be set at the same time'
);
}
if ($isSetServerClientCertFile !== $isSetServerClientKeyFile) {
throw new InvalidConfigurationException('Both Server Client Cert and Key must be set at the same time');
}

$options = $this->awsCloudHsmSdkOptions ?? [];

if ($isSetHsmIpAddress) {
$options['-a'] = $this->hsmIpAddress;
}
if ($isSetCloudHsmClusterId) {
$options['--cluster-id'] = $this->cloudHsmClusterId;
}

$options['--hsm-ca-cert'] = $this->hsmCaCert;
$options['--region'] = $this->awsRegion;

if ($isSetServerClientCertFile && $isSetServerClientKeyFile) {
$sslFiles = [
'--server-client-cert-file' => $this->serverClientCertFile,
'--server-client-key-file' => $this->serverClientKeyFile,
];

/** @var string $filename */
foreach ($sslFiles as $option => $filename) {
if ($filesystem->exists($filename) === false) {
throw new InvalidConfigurationException(\sprintf(
'Filename "%s" for option "%s" does not exist',
$filename,
$option
));
}

$options[$option] = $filename;
}
}

if ($this->disableKeyAvailabilityCheck) {
$options[] = '--disable-key-availability-check';
}

$cmd = [self::AWS_CLOUDHSM_CONFIGURE_TOOL];

foreach ($options as $option => $value) {
\is_string($option)
? \array_push($cmd, $option, $value)
: \array_push($cmd, $value);
}

try {
$process = new Process($cmd);
$process->run();
} catch (Throwable $throwable) {
throw new CouldNotConfigureAwsCloudHsmSdkException(
$throwable->getMessage(),
$throwable->getCode(),
$throwable
);
}

if ($process->isSuccessful() === false) {
throw new CouldNotConfigureAwsCloudHsmSdkException(\sprintf(
'Output: %s. Error Output: %s',
$process->getOutput(),
$process->getErrorOutput()
));
}
}

/**
* @return \Pkcs11\Key
*/
Expand Down Expand Up @@ -246,29 +138,26 @@ private function init(): void
return;
}

if ($this->awsCloudHsmConfigured === false) {
$this->configureAwsCloudHsmSdk();
$this->awsCloudHsmConfigured = true;
if ($this->awsCloudHsmSdkConfigured === false) {
$this->awsCloudHsmSdkConfigurator->configure();
$this->awsCloudHsmSdkConfigured = true;
}

$this->module ??= new Module(self::AWS_CLOUDHSM_EXTENSION);
$slots = $this->module->getSlotList();
$firstSlot = $slots[0] ?? throw new UnexpectedValueException('Slot list is empty');

$session = $this->module->openSession($this->module->getSlotList()[0], \Pkcs11\CKF_RW_SESSION);
$session = $this->module->openSession($firstSlot, \Pkcs11\CKF_RW_SESSION);
$session->login(\Pkcs11\CKU_USER, $this->userPin);

$this->session = $session;
}

private function isNonEmptyString(mixed $string): bool
{
return \is_string($string) && $string !== '';
}

private function validateKey(EncryptionKey|EncryptionKeyPair|array|string|null $key = null): void
{
if ($key !== null && $this->isNonEmptyString($key) === false) {
if ($key !== null && (\is_string($key) === false || $key === '')) {
throw new InvalidEncryptionKeyException(\sprintf(
'Encryption key must be either null or string for %s',
'Encryption key must be either null or non-empty string for %s',
self::class
));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ interface BridgeConstantsInterface

public const PARAM_AWS_PKCS11_AWS_REGION = 'easy_encryption.aws_pkcs11_aws_region';

public const PARAM_AWS_PKCS11_AWS_ROLE_ARN = 'easy_encryption.aws_pkcs11_aws_role_arn';

public const PARAM_AWS_PKCS11_CLOUD_HSM_CLUSTER_ID = 'easy_encryption.aws_pkcs11_cloud_hsm_cluster_id';

public const PARAM_AWS_PKCS11_CLOUD_HSM_SDK_OPTIONS = 'easy_encryption.aws_pkcs11_cloud_hsm_sdk_options';

public const PARAM_AWS_PKCS11_DISABLE_KEY_AVAILABILITY_CHECK = 'easy_encryption.aws_pkcs11_disable_key_availability_check';

public const PARAM_AWS_PKCS11_HSM_CA_CERT = 'easy_encryption.aws_pkcs11_hsm_ca_cert';

public const PARAM_AWS_PKCS11_HSM_IP_ADDRESS = 'easy_encryption.aws_pkcs11_hsm_ip_address';

public const PARAM_AWS_PKCS11_HSM_SDK_OPTIONS = 'easy_encryption.aws_pkcs11_hsm_sdk_options';

public const PARAM_AWS_PKCS11_SERVER_CLIENT_CERT_FILE = 'easy_encryption.aws_pkcs11_server_client_cert_file';

public const PARAM_AWS_PKCS11_SERVER_CLIENT_KEY_FILE = 'easy_encryption.aws_pkcs11_server_client_key_file';

public const PARAM_AWS_PKCS11_USER_PIN = 'easy_encryption.aws_pkcs11_user_pin';

public const PARAM_AWS_PKCS11_USE_CLOUD_HSM_CONFIGURE_TOOL = 'easy_encryption.aws_pkcs11_use_cloud_hsm_configure_tool';

public const PARAM_DEFAULT_ENCRYPTION_KEY = 'easy_encryption.default_encryption_key';

public const PARAM_DEFAULT_KEY_NAME = 'easy_encryption.default_key_name';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ final class EasyEncryptionSymfonyBundle extends AbstractBundle
{
private const AWS_PKCS11_CONFIGS_TO_PARAMS = [
'aad' => BridgeConstantsInterface::PARAM_AWS_PKCS11_AAD,
'aws_cloud_hsm_sdk_options' => BridgeConstantsInterface::PARAM_AWS_PKCS11_HSM_SDK_OPTIONS,
'aws_cloud_hsm_sdk_options' => BridgeConstantsInterface::PARAM_AWS_PKCS11_CLOUD_HSM_SDK_OPTIONS,
'aws_region' => BridgeConstantsInterface::PARAM_AWS_PKCS11_AWS_REGION,
'aws_role_arn' => BridgeConstantsInterface::PARAM_AWS_PKCS11_AWS_ROLE_ARN,
'cloud_hsm_cluster_id' => BridgeConstantsInterface::PARAM_AWS_PKCS11_CLOUD_HSM_CLUSTER_ID,
'disable_key_availability_check' => BridgeConstantsInterface::PARAM_AWS_PKCS11_DISABLE_KEY_AVAILABILITY_CHECK,
'hsm_ca_cert' => BridgeConstantsInterface::PARAM_AWS_PKCS11_HSM_CA_CERT,
'hsm_ip_address' => BridgeConstantsInterface::PARAM_AWS_PKCS11_HSM_IP_ADDRESS,
'server_client_cert_file' => BridgeConstantsInterface::PARAM_AWS_PKCS11_SERVER_CLIENT_CERT_FILE,
'server_client_key_file' => BridgeConstantsInterface::PARAM_AWS_PKCS11_SERVER_CLIENT_KEY_FILE,
'use_aws_cloud_hsm_configure_tool' => BridgeConstantsInterface::PARAM_AWS_PKCS11_USE_CLOUD_HSM_CONFIGURE_TOOL,
'user_pin' => BridgeConstantsInterface::PARAM_AWS_PKCS11_USER_PIN,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

use EonX\EasyEncryption\AwsPkcs11Encryptor;
use EonX\EasyEncryption\Bridge\BridgeConstantsInterface;
use EonX\EasyEncryption\Builder\AwsCloudHsmSdkOptionsBuilder;
use EonX\EasyEncryption\Configurator\AwsCloudHsmSdkConfigurator;
use EonX\EasyEncryption\Interfaces\AwsPkcs11EncryptorInterface;

return static function (ContainerConfigurator $containerConfigurator): void {
Expand All @@ -14,8 +16,7 @@
->autoconfigure();

$services
->set(AwsPkcs11EncryptorInterface::class, AwsPkcs11Encryptor::class)
->arg('$userPin', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_USER_PIN))
->set(AwsCloudHsmSdkOptionsBuilder::class)
->arg('$hsmCaCert', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_HSM_CA_CERT))
->arg(
'$disableKeyAvailabilityCheck',
Expand All @@ -24,9 +25,21 @@
->arg('$hsmIpAddress', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_HSM_IP_ADDRESS))
->arg('$cloudHsmClusterId', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_CLOUD_HSM_CLUSTER_ID))
->arg('$awsRegion', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_AWS_REGION))
->arg('$aad', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_AAD))
->arg('$serverClientCertFile', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_SERVER_CLIENT_CERT_FILE))
->arg('$serverClientKeyFile', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_SERVER_CLIENT_KEY_FILE))
->arg('$awsCloudHsmSdkOptions', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_HSM_SDK_OPTIONS))
->arg('$cloudHsmSdkOptions', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_CLOUD_HSM_SDK_OPTIONS));

$services
->set(AwsCloudHsmSdkConfigurator::class)
->arg('$awsRoleArn', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_AWS_ROLE_ARN))
->arg(
'$useCloudHsmConfigureTool',
param(BridgeConstantsInterface::PARAM_AWS_PKCS11_USE_CLOUD_HSM_CONFIGURE_TOOL)
);

$services
->set(AwsPkcs11EncryptorInterface::class, AwsPkcs11Encryptor::class)
->arg('$userPin', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_USER_PIN))
->arg('$aad', param(BridgeConstantsInterface::PARAM_AWS_PKCS11_AAD))
->arg('$defaultKeyName', param(BridgeConstantsInterface::PARAM_DEFAULT_KEY_NAME));
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultFalse()->end()
->scalarNode('user_pin')->defaultNull()->end()
->scalarNode('hsm_ca_cert')->defaultNull()->end()
->booleanNode('disable_key_availability_check')->defaultFalse()->end()
->scalarNode('hsm_ip_address')->defaultNull()->end()
->scalarNode('cloud_hsm_cluster_id')->defaultNull()->end()