Nuevas Características: • Visualizador de pagos mensuales con gráfica de dona (Chart.js) • Tarjetas estadísticas: clientes activos, pagados y pendientes • Tabla de clientes pendientes con saldos en tiempo real • Microservicio Node.js para metadata de Stripe (acceso directo a BD) Mejoras: • Fix crítico: Sincronización automática de saldo en CallBell al agregar facturas • Categorización mejorada de pagos OXXO y Transferencias Stripe • Normalización de valores: "OXXO" → "OXXO Pay" para evitar errores 422 • Configuración .env para credenciales de base de datos Correcciones: • Saldo y estado ahora se actualizan correctamente en CallBell • Fix networking Docker (ECONNREFUSED resuelto) • Fix validación de atributos en API de UCRM • Actualización automática de userId en pagos Stripe Archivos principales: public.php (visualizador de pagos) AbstractMessageNotifierFacade.php (logging sync) ClientCallBellAPI.php (comparación de campos) AbstractStripeOperationsFacade.php (normalización) manifest.json, README.md, CHANGELOG.md (docs)
475 lines
22 KiB
PHP
Executable File
475 lines
22 KiB
PHP
Executable File
<?php
|
|
namespace Aws\Crypto;
|
|
|
|
use Aws\Crypto\Cipher\CipherMethod;
|
|
use Aws\Exception\CryptoException;
|
|
use GuzzleHttp\Psr7;
|
|
use GuzzleHttp\Psr7\AppendStream;
|
|
use GuzzleHttp\Psr7\Stream;
|
|
use Psr\Http\Message\StreamInterface;
|
|
|
|
trait EncryptionTraitV3
|
|
{
|
|
private static array $allowedOptions = [
|
|
'Cipher' => true,
|
|
'KeySize' => true,
|
|
'Aad' => true,
|
|
];
|
|
|
|
private static array $encryptClasses = [
|
|
'gcm' => AesGcmEncryptingStream::class
|
|
];
|
|
|
|
/**
|
|
* Dependency to generate a CipherMethod from a set of inputs for loading
|
|
* in to an AesEncryptingStream.
|
|
*
|
|
* @param string $cipherName Name of the cipher to generate for encrypting.
|
|
* @param string $iv Base Initialization Vector for the cipher.
|
|
* @param int $keySize Size of the encryption key, in bits, that will be
|
|
* used.
|
|
*
|
|
* @return Cipher\CipherMethod
|
|
*
|
|
* @internal
|
|
*/
|
|
abstract protected function buildCipherMethod(
|
|
$cipherName,
|
|
$iv,
|
|
$keySize
|
|
);
|
|
|
|
/**
|
|
* Builds an AesStreamInterface and populates encryption metadata into the
|
|
* supplied envelope.
|
|
*
|
|
* @param Stream $plaintext Plain-text data to be encrypted using the
|
|
* materials, algorithm, and data provided.
|
|
* @param AlgorithmSuite $algorithmSuite Algorithm Suite for use in encryption
|
|
* @param array $options Options for use in encryption, including cipher
|
|
* options, and encryption context.
|
|
* @param MaterialsProviderV3 $provider A provider to supply and encrypt
|
|
* materials used in encryption.
|
|
* @param MetadataEnvelope $envelope A storage envelope for encryption
|
|
* metadata to be added to.
|
|
*
|
|
* @return AppendStream
|
|
*
|
|
* @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions']
|
|
* is not valid.
|
|
*s
|
|
* @internal
|
|
*/
|
|
public function encrypt(
|
|
Stream $plaintext,
|
|
AlgorithmSuite $algorithmSuite,
|
|
array $options,
|
|
MaterialsProviderV3 $provider,
|
|
MetadataEnvelope $envelope
|
|
): AppendStream
|
|
{
|
|
$options = array_change_key_case($options);
|
|
$cipherOptions = array_intersect_key(
|
|
$options['@cipheroptions'],
|
|
self::$allowedOptions
|
|
);
|
|
//= ../specification/s3-encryption/encryption.md#content-encryption
|
|
//= type=implication
|
|
//# The client MUST validate that the length of the plaintext bytes does not exceed
|
|
//# the algorithm suite's cipher's maximum content length in bytes.
|
|
if (strlen($plaintext) > $algorithmSuite->getCipherMaxContentLengthBytes()) {
|
|
throw new \InvalidArgumentException("The contentLength of the object you are attempting"
|
|
. " to encrypt exceeds the maximum length allowed for GCM encryption.");
|
|
}
|
|
$cipherOptions['Cipher'] = strtolower($cipherOptions['Cipher']);
|
|
|
|
if (!self::isSupportedCipher($cipherOptions['Cipher'])) {
|
|
throw new \InvalidArgumentException('The cipher requested is not'
|
|
. ' supported by the SDK.');
|
|
}
|
|
|
|
if (empty($cipherOptions['KeySize'])) {
|
|
$cipherOptions['KeySize'] = 256;
|
|
}
|
|
|
|
if (!is_int($cipherOptions['KeySize'])) {
|
|
throw new \InvalidArgumentException('The cipher "KeySize" must be'
|
|
. ' an integer.');
|
|
}
|
|
|
|
if (!MaterialsProviderV3::isSupportedKeySize($cipherOptions['KeySize'])) {
|
|
throw new \InvalidArgumentException('The cipher "KeySize" requested'
|
|
. ' is not supported by AES (256).');
|
|
}
|
|
|
|
$encryptClass = self::$encryptClasses[$algorithmSuite->getCipherName()];
|
|
$aesName = $encryptClass::getStaticAesName();
|
|
$materialsDescription = $algorithmSuite->isKeyCommitting()
|
|
? ['aws:x-amz-cek-alg' => '115']
|
|
: ['aws:x-amz-cek-alg' => $aesName];
|
|
|
|
$keys = $provider->generateCek(
|
|
$algorithmSuite->getDataKeyLengthBits(),
|
|
$materialsDescription,
|
|
$options
|
|
);
|
|
|
|
// Some providers modify materials description based on options
|
|
if (isset($keys['UpdatedContext'])) {
|
|
$materialsDescription = $keys['UpdatedContext'];
|
|
}
|
|
|
|
if ($algorithmSuite->isKeyCommitting()) {
|
|
return $this->encryptCommitingStream(
|
|
$plaintext,
|
|
$algorithmSuite,
|
|
$cipherOptions,
|
|
$keys,
|
|
$materialsDescription,
|
|
$provider,
|
|
$envelope
|
|
);
|
|
} else {
|
|
return $this->encryptNonCommitingStream(
|
|
$plaintext,
|
|
$cipherOptions,
|
|
$keys,
|
|
$materialsDescription,
|
|
$aesName,
|
|
$provider,
|
|
$envelope
|
|
);
|
|
}
|
|
}
|
|
|
|
private function encryptNonCommitingStream(
|
|
Stream $plaintext,
|
|
array &$cipherOptions,
|
|
array $keys,
|
|
array $materialsDescription,
|
|
string $aesName,
|
|
MaterialsProviderV3 $provider,
|
|
MetadataEnvelope $envelope
|
|
): AppendStream
|
|
{
|
|
//= ../specification/s3-encryption/encryption.md#content-encryption
|
|
//# The generated IV or Message ID MUST be set or returned from the
|
|
//# encryption process such that it can be included in the content metadata.
|
|
//= ../specification/s3-encryption/encryption.md#content-encryption
|
|
//# The client MUST generate an IV or Message ID using the length of the IV or Message ID defined in the algorithm suite.
|
|
$cipherOptions['Iv'] = $provider->generateIv(
|
|
$this->getCipherOpenSslName(
|
|
$cipherOptions['Cipher'],
|
|
$cipherOptions['KeySize']
|
|
)
|
|
);
|
|
// Some providers modify materials description based on options
|
|
if (isset($keys['UpdatedContext'])) {
|
|
$materialsDescription = $keys['UpdatedContext'];
|
|
}
|
|
|
|
$encryptingStream = $this->getNonCommittingEncryptingStream(
|
|
$plaintext,
|
|
$keys['Plaintext'],
|
|
$cipherOptions
|
|
);
|
|
|
|
// Populate envelope data
|
|
//= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
|
|
//# - If the metadata contains "x-amz-iv" and "x-amz-metadata-x-amz-key-v2" then the object MUST be considered as an S3EC-encrypted object using the V2 format.
|
|
$envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $keys['Ciphertext'];
|
|
unset($keys);
|
|
|
|
$envelope[MetadataEnvelope::IV_HEADER] =
|
|
base64_encode($cipherOptions['Iv']);
|
|
$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] =
|
|
$provider->getWrapAlgorithmName();
|
|
$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName;
|
|
$envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] =
|
|
(string) strlen($plaintext);
|
|
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] =
|
|
json_encode($materialsDescription);
|
|
if (!empty($cipherOptions['Tag'])) {
|
|
$envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] =
|
|
(string) (strlen($cipherOptions['Tag']) * 8);
|
|
}
|
|
if (!MetadataEnvelope::isV2Envelope($envelope)) {
|
|
throw new CryptoException("Error while writing metadata envelope."
|
|
. " Not all required fields were set.");
|
|
}
|
|
|
|
return $encryptingStream;
|
|
}
|
|
|
|
private function encryptCommitingStream(
|
|
Stream $plaintext,
|
|
AlgorithmSuite $algorithmSuite,
|
|
array &$options,
|
|
array $keys,
|
|
array $materialsDescription,
|
|
MaterialsProviderV3 $provider,
|
|
MetadataEnvelope $envelope
|
|
): AppendStream
|
|
{
|
|
//= ../specification/s3-encryption/encryption.md#content-encryption
|
|
//# The generated IV or Message ID MUST be set or returned from the
|
|
//# encryption process such that it can be included in the content metadata.
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# When encrypting or decrypting with ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY,
|
|
//# the IV used in the AES-GCM content encryption/decryption
|
|
//# MUST consist entirely of bytes with the value 0x01.
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# The IV's total length MUST match the IV length defined by the algorithm suite.
|
|
$options['Iv'] = str_repeat("\1", $algorithmSuite->getIvLengthBytes());
|
|
$messageId = $provider->generateIv(
|
|
$this->getCipherOpenSslName(
|
|
$algorithmSuite->getCipherName(),
|
|
//= ../specification/s3-encryption/encryption.md#content-encryption
|
|
//# The client MUST generate an IV or Message ID using the length of
|
|
//# the IV or Message ID defined in the algorithm suite.
|
|
$algorithmSuite->getKeyCommitmentSaltLengthBits()
|
|
)
|
|
);
|
|
// Some providers modify materials description based on options
|
|
if (isset($keys['UpdatedContext'])) {
|
|
$materialsDescription["aws:x-amz-cek-alg"] = (string) $algorithmSuite->getId();
|
|
}
|
|
$commitingEncryptingArray = $this->getCommitingEncryptionStream(
|
|
$plaintext,
|
|
$keys['Plaintext'],
|
|
$options,
|
|
$messageId,
|
|
$algorithmSuite
|
|
);
|
|
//= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-hkdf-sha512-commit-key
|
|
//= type=implication
|
|
//# The derived key commitment value MUST be set or returned from the encryption process
|
|
//# such that it can be included in the content metadata.
|
|
$commitmentKey = $commitingEncryptingArray[0];
|
|
$encryptionStream = $commitingEncryptingArray[1];
|
|
|
|
// Populate envelope data
|
|
$envelope[MetadataEnvelope::ENCRYPTED_DATA_KEY_V3] = $keys['Ciphertext'];
|
|
unset($keys);
|
|
|
|
$envelope[MetadataEnvelope::CONTENT_CIPHER_V3] = $algorithmSuite->getId();
|
|
// we are able to always set the encryption context in the envelope because PHP
|
|
// only supports a KMS Material Provider.
|
|
$envelope[MetadataEnvelope::ENCRYPTION_CONTEXT_V3] =
|
|
json_encode($materialsDescription);
|
|
//= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
|
|
//# The Encryption Context value MUST be used for wrapping algorithm `kms+context` or `12`.
|
|
$envelope[MetadataEnvelope::ENCRYPTED_DATA_KEY_ALGORITHM_V3] = '12';
|
|
$envelope[MetadataEnvelope::KEY_COMMITMENT_V3] = $commitmentKey;
|
|
$envelope[MetadataEnvelope::MESSAGE_ID_V3] = base64_encode($messageId);
|
|
if (!MetadataEnvelope::isV3Envelope($envelope)) {
|
|
throw new CryptoException("Error while writing metadata envelope."
|
|
. " Not all required fields were set.");
|
|
}
|
|
|
|
return $encryptionStream;
|
|
}
|
|
|
|
/**
|
|
* Generates a stream that wraps the plaintext with the proper cipher and
|
|
* uses the content encryption key (CEK) to encrypt the data when read.
|
|
*
|
|
* @param Stream $plaintext Plain-text data to be encrypted using the
|
|
* materials, algorithm, and data provided.
|
|
* @param string $cek A content encryption key for use by the stream for
|
|
* encrypting the plaintext data.
|
|
* @param array $cipherOptions Options for use in determining the cipher to
|
|
* be used for encrypting data.
|
|
*
|
|
* @return AppendStream returns an AppendStream
|
|
*
|
|
* @internal
|
|
*/
|
|
protected function getNonCommittingEncryptingStream(
|
|
Stream $plaintext,
|
|
string $cek,
|
|
array &$cipherOptions
|
|
): AppendStream
|
|
{
|
|
// Only 'gcm' is supported for encryption currently
|
|
switch ($cipherOptions['Cipher']) {
|
|
//= ../specification/s3-encryption/encryption.md#content-encryption
|
|
//= type=implication
|
|
//# The S3EC MUST use the encryption algorithm configured during [client](./client.md) initialization.
|
|
case 'gcm':
|
|
$cipherOptions['TagLength'] = 16;
|
|
$encryptClass = self::$encryptClasses['gcm'];
|
|
//= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
|
|
//= type=implication
|
|
//# The client MUST initialize the cipher, or call an AES-GCM encryption API,
|
|
//# with the plaintext data key, the generated IV, and the tag length defined
|
|
//# in the Algorithm Suite when encrypting with ALG_AES_256_GCM_IV12_TAG16_NO_KDF.
|
|
$cipherTextStream = new $encryptClass(
|
|
$plaintext,
|
|
$cek,
|
|
$cipherOptions['Iv'],
|
|
$cipherOptions['Aad'] = isset($cipherOptions['Aad'])
|
|
? $cipherOptions['Aad']
|
|
: '',
|
|
$cipherOptions['TagLength'],
|
|
$cipherOptions['KeySize']
|
|
);
|
|
|
|
if (!empty($cipherOptions['Aad'])) {
|
|
trigger_error("'Aad' has been supplied for content encryption"
|
|
. " with " . $cipherTextStream->getAesName() . ". The"
|
|
. " PHP SDK encryption client can decrypt an object"
|
|
. " encrypted in this way, but other AWS SDKs may not be"
|
|
. " able to.", E_USER_WARNING);
|
|
}
|
|
|
|
$appendStream = new AppendStream([
|
|
$cipherTextStream->createStream()
|
|
]);
|
|
//= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
|
|
//= type=implication
|
|
//# The client MUST append the GCM auth tag to the ciphertext if the underlying
|
|
//# crypto provider does not do so automatically.
|
|
$cipherOptions['Tag'] = $cipherTextStream->getTag();
|
|
$appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag']));
|
|
|
|
return $appendStream;
|
|
default:
|
|
throw new CryptoException("Unsupported Cipher used for key commitment messages."
|
|
. " Found {$cipherOptions["Cipher"]}. Only 'gcm' is supported.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a stream that wraps the plaintext with the proper cipher and
|
|
* uses the content encryption key (CEK) to encrypt the data when read.
|
|
*
|
|
* @param Stream $plaintext Plain-text data to be encrypted using the
|
|
* materials, algorithm, and data provided.
|
|
* @param string $cek A content encryption key for use by the stream for
|
|
* encrypting the plaintext data.
|
|
* @param array $cipherOptions Options for use in determining the cipher to
|
|
* be used for encrypting data.
|
|
* @param string $messageId salt value used for key extraction step in the key
|
|
* derivation process.
|
|
* @param AlgorithmSuite $algorithmSuite options used for key commitment
|
|
*
|
|
* @return array returns an array with two elements as follows: [commitmentKey, AppendStream]
|
|
*
|
|
* @internal
|
|
*/
|
|
protected function getCommitingEncryptionStream(
|
|
Stream $plaintext,
|
|
string $dek,
|
|
array &$cipherOptions,
|
|
string $messageId,
|
|
AlgorithmSuite $algorithmSuite
|
|
): array
|
|
{
|
|
$algorithmSuiteIdAsBytes = pack('n', $algorithmSuite->getId());
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The input info MUST be a concatenation of the algorithm suite ID as bytes followed by the string DERIVEKEY as UTF8 encoded bytes.
|
|
$derivedEncryptionKeyInfo = $algorithmSuiteIdAsBytes . "DERIVEKEY";
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The input info MUST be a concatenation of the algorithm suite ID as bytes followed by the string COMMITKEY as UTF8 encoded bytes.
|
|
$commitmentKeyInfo = $algorithmSuiteIdAsBytes . "COMMITKEY";
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The length of the input keying material MUST equal the key derivation
|
|
//# input length specified by the algorithm suite commit key derivation setting.
|
|
if (strlen($dek) !== $algorithmSuite->getDerivationInputKeyLengthBytes()) {
|
|
throw new CryptoException("Input Key Material length exceeds "
|
|
. "key derivation input length specified by the algorithm suite.");
|
|
}
|
|
//= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-hkdf-sha512-commit-key
|
|
//= type=implication
|
|
//# The client MUST use HKDF to derive the key commitment value and
|
|
//# the derived encrypting key as described in [Key Derivation](key-derivation.md).
|
|
$cek = hash_hkdf(
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The hash function MUST be specified by the algorithm suite commitment settings.
|
|
$algorithmSuite->getHashingAlgorithm(),
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The input keying material MUST be the plaintext data key (PDK) generated by the key provider.
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The DEK input pseudorandom key MUST be the output from the extract step.
|
|
$dek,
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The length of the output keying material MUST equal the encryption key length specified by the algorithm suite encryption settings.
|
|
$algorithmSuite->getDerivationOutputKeyLengthBytes(),
|
|
$derivedEncryptionKeyInfo,
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The salt MUST be the Message ID with the length defined in the algorithm suite.
|
|
$messageId
|
|
);
|
|
$commitmentKey = hash_hkdf(
|
|
$algorithmSuite->getHashingAlgorithm(),
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The CK input pseudorandom key MUST be the output from the extract step.
|
|
$dek,
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# - The length of the output keying material MUST equal the commit key length specified by the supported algorithm suites.
|
|
$algorithmSuite->getCommitmentOutputKeyLengthBytes(),
|
|
$commitmentKeyInfo,
|
|
$messageId
|
|
);
|
|
|
|
switch ($cipherOptions['Cipher']) {
|
|
// Only 'gcm' is supported for encryption currently
|
|
case 'gcm':
|
|
$cipherOptions['TagLength'] = $algorithmSuite->getCipherTagLengthInBytes();
|
|
$encryptClass = self::$encryptClasses[$algorithmSuite->getCipherName()];
|
|
|
|
if (!empty($cipherOptions['Aad'])) {
|
|
trigger_error("'Aad' has been supplied for content encryption"
|
|
. " with " . $encryptClass->getAesName() . ". The"
|
|
. " PHP SDK encryption client can decrypt an object"
|
|
. " encrypted in this way, but other AWS SDKs may not be"
|
|
. " able to.", E_USER_NOTICE);
|
|
}
|
|
$cipherOptions['Aad'] = isset($cipherOptions['Aad'])
|
|
? $cipherOptions['Aad'] + $algorithmSuiteIdAsBytes
|
|
: $algorithmSuiteIdAsBytes;
|
|
//= ../specification/s3-encryption/key-derivation.md#hkdf-operation
|
|
//= type=implication
|
|
//# The client MUST initialize the cipher, or call an AES-GCM encryption API,
|
|
//# with the derived encryption key, an IV containing only bytes with the value 0x01,
|
|
//# and the tag length defined in the Algorithm Suite when encrypting or
|
|
//# decrypting with ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.
|
|
$cipherTextStream = new $encryptClass(
|
|
$plaintext,
|
|
$cek,
|
|
$cipherOptions['Iv'],
|
|
$cipherOptions['Aad'],
|
|
$cipherOptions['TagLength'],
|
|
$cipherOptions['KeySize']
|
|
);
|
|
|
|
$appendStream = new AppendStream([
|
|
$cipherTextStream->createStream()
|
|
]);
|
|
//= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-hkdf-sha512-commit-key
|
|
//= type=implication
|
|
//# The client MUST append the GCM auth tag to the ciphertext if the underlying
|
|
//# crypto provider does not do so automatically.
|
|
$cipherOptions['Tag'] = $cipherTextStream->getTag();
|
|
$appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag']));
|
|
|
|
return [base64_encode($commitmentKey), $appendStream];
|
|
default:
|
|
throw new CryptoException("Unsupported Cipher used for content encryption");
|
|
}
|
|
}
|
|
}
|