mirror of
https://github.com/SociallyDev/Spaces-API.git
synced 2025-08-20 05:13:42 -07:00
v2: Updates
* Simplifies & beautifies everything * Introduces a new Class system. * Errors are defaulted to AWS's handler. * New function names & more efficient handling. * Should fix a majority of the errors. Please read the README for more!
This commit is contained in:
parent
ad0726e41e
commit
e6d7753dc8
1095 changed files with 45088 additions and 2911 deletions
|
@ -6,11 +6,19 @@ use Aws\Crypto\Cipher\Cbc;
|
|||
use GuzzleHttp\Psr7\Stream;
|
||||
|
||||
/**
|
||||
* Legacy abstract encryption client. New workflows should use
|
||||
* AbstractCryptoClientV2.
|
||||
*
|
||||
* @deprecated
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractCryptoClient
|
||||
{
|
||||
private static $supportedCiphers = ['cbc', 'gcm'];
|
||||
public static $supportedCiphers = ['cbc', 'gcm'];
|
||||
|
||||
public static $supportedKeyWraps = [
|
||||
KmsMaterialsProvider::WRAP_ALGORITHM_NAME
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns if the passed cipher name is supported for encryption by the SDK.
|
||||
|
@ -35,10 +43,7 @@ abstract class AbstractCryptoClient
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCipherOpenSslName($cipherName, $keySize)
|
||||
{
|
||||
return "aes-{$keySize}-{$cipherName}";
|
||||
}
|
||||
abstract protected function getCipherOpenSslName($cipherName, $keySize);
|
||||
|
||||
/**
|
||||
* Constructs a CipherMethod for the given name, initialized with the other
|
||||
|
@ -53,18 +58,7 @@ abstract class AbstractCryptoClient
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function buildCipherMethod($cipherName, $iv, $keySize)
|
||||
{
|
||||
switch ($cipherName) {
|
||||
case 'cbc':
|
||||
return new Cbc(
|
||||
$iv,
|
||||
$keySize
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
abstract protected function buildCipherMethod($cipherName, $iv, $keySize);
|
||||
|
||||
/**
|
||||
* Performs a reverse lookup to get the openssl_* cipher name from the
|
||||
|
@ -76,18 +70,7 @@ abstract class AbstractCryptoClient
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function getCipherFromAesName($aesName)
|
||||
{
|
||||
switch ($aesName) {
|
||||
case 'AES/GCM/NoPadding':
|
||||
return 'gcm';
|
||||
case 'AES/CBC/PKCS5Padding':
|
||||
return 'cbc';
|
||||
default:
|
||||
throw new \RuntimeException('Unrecognized or unsupported'
|
||||
. ' AESName for reverse lookup.');
|
||||
}
|
||||
}
|
||||
abstract protected function getCipherFromAesName($aesName);
|
||||
|
||||
/**
|
||||
* Dependency to provide an interface for building an encryption stream for
|
||||
|
@ -119,8 +102,8 @@ abstract class AbstractCryptoClient
|
|||
*
|
||||
* @param string $cipherText Plain-text data to be decrypted using the
|
||||
* materials, algorithm, and data provided.
|
||||
* @param MaterialsProvider $provider A provider to supply and encrypt
|
||||
* materials used in encryption.
|
||||
* @param MaterialsProviderInterface $provider A provider to supply and encrypt
|
||||
* materials used in encryption.
|
||||
* @param MetadataEnvelope $envelope A storage envelope for encryption
|
||||
* metadata to be read from.
|
||||
* @param array $cipherOptions Additional verification options.
|
||||
|
@ -131,7 +114,7 @@ abstract class AbstractCryptoClient
|
|||
*/
|
||||
abstract public function decrypt(
|
||||
$cipherText,
|
||||
MaterialsProvider $provider,
|
||||
MaterialsProviderInterface $provider,
|
||||
MetadataEnvelope $envelope,
|
||||
array $cipherOptions = []
|
||||
);
|
||||
|
|
119
aws/Aws/Crypto/AbstractCryptoClientV2.php
Normal file
119
aws/Aws/Crypto/AbstractCryptoClientV2.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
use Aws\Crypto\Cipher\CipherMethod;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractCryptoClientV2
|
||||
{
|
||||
public static $supportedCiphers = ['gcm'];
|
||||
|
||||
public static $supportedKeyWraps = [
|
||||
KmsMaterialsProviderV2::WRAP_ALGORITHM_NAME
|
||||
];
|
||||
|
||||
public static $supportedSecurityProfiles = ['V2', 'V2_AND_LEGACY'];
|
||||
|
||||
public static $legacySecurityProfiles = ['V2_AND_LEGACY'];
|
||||
|
||||
/**
|
||||
* Returns if the passed cipher name is supported for encryption by the SDK.
|
||||
*
|
||||
* @param string $cipherName The name of a cipher to verify is registered.
|
||||
*
|
||||
* @return bool If the cipher passed is in our supported list.
|
||||
*/
|
||||
public static function isSupportedCipher($cipherName)
|
||||
{
|
||||
return in_array($cipherName, self::$supportedCiphers, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an identifier recognizable by `openssl_*` functions, such as
|
||||
* `aes-256-gcm`
|
||||
*
|
||||
* @param string $cipherName Name of the cipher being used for encrypting
|
||||
* or decrypting.
|
||||
* @param int $keySize Size of the encryption key, in bits, that will be
|
||||
* used.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getCipherOpenSslName($cipherName, $keySize);
|
||||
|
||||
/**
|
||||
* Constructs a CipherMethod for the given name, initialized with the other
|
||||
* data passed for use in encrypting or decrypting.
|
||||
*
|
||||
* @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 CipherMethod
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract protected function buildCipherMethod($cipherName, $iv, $keySize);
|
||||
|
||||
/**
|
||||
* Performs a reverse lookup to get the openssl_* cipher name from the
|
||||
* AESName passed in from the MetadataEnvelope.
|
||||
*
|
||||
* @param $aesName
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract protected function getCipherFromAesName($aesName);
|
||||
|
||||
/**
|
||||
* Dependency to provide an interface for building an encryption stream for
|
||||
* data given cipher details, metadata, and materials to do so.
|
||||
*
|
||||
* @param Stream $plaintext Plain-text data to be encrypted using the
|
||||
* materials, algorithm, and data provided.
|
||||
* @param array $options Options for use in encryption.
|
||||
* @param MaterialsProviderV2 $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 AesStreamInterface
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract public function encrypt(
|
||||
Stream $plaintext,
|
||||
array $options,
|
||||
MaterialsProviderV2 $provider,
|
||||
MetadataEnvelope $envelope
|
||||
);
|
||||
|
||||
/**
|
||||
* Dependency to provide an interface for building a decryption stream for
|
||||
* cipher text given metadata and materials to do so.
|
||||
*
|
||||
* @param string $cipherText Plain-text data to be decrypted using the
|
||||
* materials, algorithm, and data provided.
|
||||
* @param MaterialsProviderInterface $provider A provider to supply and encrypt
|
||||
* materials used in encryption.
|
||||
* @param MetadataEnvelope $envelope A storage envelope for encryption
|
||||
* metadata to be read from.
|
||||
* @param array $options Options used for decryption.
|
||||
*
|
||||
* @return AesStreamInterface
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract public function decrypt(
|
||||
$cipherText,
|
||||
MaterialsProviderInterfaceV2 $provider,
|
||||
MetadataEnvelope $envelope,
|
||||
array $options = []
|
||||
);
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
use Aws\Exception\CryptoException;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Psr7\StreamDecoratorTrait;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use GuzzleHttp\Psr7\LimitStream;
|
||||
use \RuntimeException;
|
||||
use Aws\Crypto\Polyfill\AesGcm;
|
||||
use Aws\Crypto\Polyfill\Key;
|
||||
|
||||
/**
|
||||
* @internal Represents a stream of data to be gcm decrypted.
|
||||
|
@ -46,12 +47,6 @@ class AesGcmDecryptingStream implements AesStreamInterface
|
|||
$tagLength = 128,
|
||||
$keySize = 256
|
||||
) {
|
||||
if (version_compare(PHP_VERSION, '7.1', '<')) {
|
||||
throw new RuntimeException(
|
||||
'AES-GCM decryption is only supported in PHP 7.1 or greater'
|
||||
);
|
||||
}
|
||||
|
||||
$this->cipherText = $cipherText;
|
||||
$this->key = $key;
|
||||
$this->initializationVector = $initializationVector;
|
||||
|
@ -78,15 +73,31 @@ class AesGcmDecryptingStream implements AesStreamInterface
|
|||
|
||||
public function createStream()
|
||||
{
|
||||
return Psr7\stream_for(openssl_decrypt(
|
||||
(string) $this->cipherText,
|
||||
$this->getOpenSslName(),
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$this->initializationVector,
|
||||
$this->tag,
|
||||
$this->aad
|
||||
));
|
||||
if (version_compare(PHP_VERSION, '7.1', '<')) {
|
||||
return Psr7\stream_for(AesGcm::decrypt(
|
||||
(string) $this->cipherText,
|
||||
$this->initializationVector,
|
||||
new Key($this->key),
|
||||
$this->aad,
|
||||
$this->tag,
|
||||
$this->keySize
|
||||
));
|
||||
} else {
|
||||
$result = \openssl_decrypt(
|
||||
(string)$this->cipherText,
|
||||
$this->getOpenSslName(),
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$this->initializationVector,
|
||||
$this->tag,
|
||||
$this->aad
|
||||
);
|
||||
if ($result === false) {
|
||||
throw new CryptoException('The requested object could not be'
|
||||
. ' decrypted due to an invalid authentication tag.');
|
||||
}
|
||||
return Psr7\stream_for($result);
|
||||
}
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
use Aws\Crypto\Polyfill\AesGcm;
|
||||
use Aws\Crypto\Polyfill\Key;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Psr7\StreamDecoratorTrait;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
@ -9,7 +11,7 @@ use \RuntimeException;
|
|||
/**
|
||||
* @internal Represents a stream of data to be gcm encrypted.
|
||||
*/
|
||||
class AesGcmEncryptingStream implements AesStreamInterface
|
||||
class AesGcmEncryptingStream implements AesStreamInterface, AesStreamInterfaceV2
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
|
||||
|
@ -27,6 +29,17 @@ class AesGcmEncryptingStream implements AesStreamInterface
|
|||
|
||||
private $tagLength;
|
||||
|
||||
/**
|
||||
* Same as non-static 'getAesName' method, allowing calls in a static
|
||||
* context.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getStaticAesName()
|
||||
{
|
||||
return 'AES/GCM/NoPadding';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StreamInterface $plaintext
|
||||
* @param string $key
|
||||
|
@ -43,11 +56,6 @@ class AesGcmEncryptingStream implements AesStreamInterface
|
|||
$tagLength = 16,
|
||||
$keySize = 256
|
||||
) {
|
||||
if (version_compare(PHP_VERSION, '7.1', '<')) {
|
||||
throw new RuntimeException(
|
||||
'AES-GCM decryption is only supported in PHP 7.1 or greater'
|
||||
);
|
||||
}
|
||||
|
||||
$this->plaintext = $plaintext;
|
||||
$this->key = $key;
|
||||
|
@ -62,9 +70,14 @@ class AesGcmEncryptingStream implements AesStreamInterface
|
|||
return "aes-{$this->keySize}-gcm";
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as static method and retained for backwards compatibility
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAesName()
|
||||
{
|
||||
return 'AES/GCM/NoPadding';
|
||||
return self::getStaticAesName();
|
||||
}
|
||||
|
||||
public function getCurrentIv()
|
||||
|
@ -74,16 +87,27 @@ class AesGcmEncryptingStream implements AesStreamInterface
|
|||
|
||||
public function createStream()
|
||||
{
|
||||
return Psr7\stream_for(openssl_encrypt(
|
||||
(string) $this->plaintext,
|
||||
$this->getOpenSslName(),
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$this->initializationVector,
|
||||
$this->tag,
|
||||
$this->aad,
|
||||
$this->tagLength
|
||||
));
|
||||
if (version_compare(PHP_VERSION, '7.1', '<')) {
|
||||
return Psr7\stream_for(AesGcm::encrypt(
|
||||
(string) $this->plaintext,
|
||||
$this->initializationVector,
|
||||
new Key($this->key),
|
||||
$this->aad,
|
||||
$this->tag,
|
||||
$this->keySize
|
||||
));
|
||||
} else {
|
||||
return Psr7\stream_for(\openssl_encrypt(
|
||||
(string)$this->plaintext,
|
||||
$this->getOpenSslName(),
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$this->initializationVector,
|
||||
$this->tag,
|
||||
$this->aad,
|
||||
$this->tagLength
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
31
aws/Aws/Crypto/AesStreamInterfaceV2.php
Normal file
31
aws/Aws/Crypto/AesStreamInterfaceV2.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
interface AesStreamInterfaceV2 extends StreamInterface
|
||||
{
|
||||
/**
|
||||
* Returns an AES recognizable name, such as 'AES/GCM/NoPadding'. V2
|
||||
* interface is accessible from a static context.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getStaticAesName();
|
||||
|
||||
/**
|
||||
* Returns an identifier recognizable by `openssl_*` functions, such as
|
||||
* `aes-256-cbc` or `aes-128-ctr`.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOpenSslName();
|
||||
|
||||
/**
|
||||
* Returns the IV that should be used to initialize the next block in
|
||||
* encrypt or decrypt.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentIv();
|
||||
}
|
|
@ -7,6 +7,12 @@ use \LogicException;
|
|||
/**
|
||||
* An implementation of the CBC cipher for use with an AesEncryptingStream or
|
||||
* AesDecrypting stream.
|
||||
*
|
||||
* This cipher method is deprecated and in maintenance mode - no new updates will be
|
||||
* released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html
|
||||
* for more information.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class Cbc implements CipherMethod
|
||||
{
|
||||
|
|
72
aws/Aws/Crypto/Cipher/CipherBuilderTrait.php
Normal file
72
aws/Aws/Crypto/Cipher/CipherBuilderTrait.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
namespace Aws\Crypto\Cipher;
|
||||
|
||||
use Aws\Exception\CryptoException;
|
||||
|
||||
trait CipherBuilderTrait
|
||||
{
|
||||
/**
|
||||
* Returns an identifier recognizable by `openssl_*` functions, such as
|
||||
* `aes-256-cbc` or `aes-128-ctr`.
|
||||
*
|
||||
* @param string $cipherName Name of the cipher being used for encrypting
|
||||
* or decrypting.
|
||||
* @param int $keySize Size of the encryption key, in bits, that will be
|
||||
* used.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCipherOpenSslName($cipherName, $keySize)
|
||||
{
|
||||
return "aes-{$keySize}-{$cipherName}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CipherMethod for the given name, initialized with the other
|
||||
* data passed for use in encrypting or decrypting.
|
||||
*
|
||||
* @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 CipherMethod
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function buildCipherMethod($cipherName, $iv, $keySize)
|
||||
{
|
||||
switch ($cipherName) {
|
||||
case 'cbc':
|
||||
return new Cbc(
|
||||
$iv,
|
||||
$keySize
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a reverse lookup to get the openssl_* cipher name from the
|
||||
* AESName passed in from the MetadataEnvelope.
|
||||
*
|
||||
* @param $aesName
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function getCipherFromAesName($aesName)
|
||||
{
|
||||
switch ($aesName) {
|
||||
case 'AES/GCM/NoPadding':
|
||||
return 'gcm';
|
||||
case 'AES/CBC/PKCS5Padding':
|
||||
return 'cbc';
|
||||
default:
|
||||
throw new CryptoException('Unrecognized or unsupported'
|
||||
. ' AESName for reverse lookup.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ namespace Aws\Crypto;
|
|||
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Psr7\LimitStream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
trait DecryptionTrait
|
||||
{
|
||||
|
@ -35,12 +36,13 @@ trait DecryptionTrait
|
|||
|
||||
/**
|
||||
* Builds an AesStreamInterface using cipher options loaded from the
|
||||
* MetadataEnvelope and MaterialsProvider.
|
||||
* MetadataEnvelope and MaterialsProvider. Can decrypt data from both the
|
||||
* legacy and V2 encryption client workflows.
|
||||
*
|
||||
* @param string $cipherText Plain-text data to be encrypted using the
|
||||
* materials, algorithm, and data provided.
|
||||
* @param MaterialsProvider $provider A provider to supply and encrypt
|
||||
* materials used in encryption.
|
||||
* @param MaterialsProviderInterface $provider A provider to supply and encrypt
|
||||
* materials used in encryption.
|
||||
* @param MetadataEnvelope $envelope A storage envelope for encryption
|
||||
* metadata to be read from.
|
||||
* @param array $cipherOptions Additional verification options.
|
||||
|
@ -52,9 +54,9 @@ trait DecryptionTrait
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function decrypt(
|
||||
public function decrypt(
|
||||
$cipherText,
|
||||
MaterialsProvider $provider,
|
||||
MaterialsProviderInterface $provider,
|
||||
MetadataEnvelope $envelope,
|
||||
array $cipherOptions = []
|
||||
) {
|
||||
|
@ -79,18 +81,18 @@ trait DecryptionTrait
|
|||
$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]
|
||||
);
|
||||
|
||||
$decryptionSteam = $this->getDecryptingStream(
|
||||
$decryptionStream = $this->getDecryptingStream(
|
||||
$cipherText,
|
||||
$cek,
|
||||
$cipherOptions
|
||||
);
|
||||
unset($cek);
|
||||
|
||||
return $decryptionSteam;
|
||||
return $decryptionStream;
|
||||
}
|
||||
|
||||
private function getTagFromCiphertextStream(
|
||||
Psr7\Stream $cipherText,
|
||||
StreamInterface $cipherText,
|
||||
$tagLength
|
||||
) {
|
||||
$cipherTextSize = $cipherText->getSize();
|
||||
|
@ -106,7 +108,7 @@ trait DecryptionTrait
|
|||
}
|
||||
|
||||
private function getStrippedCiphertextStream(
|
||||
Psr7\Stream $cipherText,
|
||||
StreamInterface $cipherText,
|
||||
$tagLength
|
||||
) {
|
||||
$cipherTextSize = $cipherText->getSize();
|
||||
|
|
249
aws/Aws/Crypto/DecryptionTraitV2.php
Normal file
249
aws/Aws/Crypto/DecryptionTraitV2.php
Normal file
|
@ -0,0 +1,249 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
use Aws\Exception\CryptoException;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Psr7\LimitStream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
trait DecryptionTraitV2
|
||||
{
|
||||
/**
|
||||
* Dependency to reverse lookup the openssl_* cipher name from the AESName
|
||||
* in the MetadataEnvelope.
|
||||
*
|
||||
* @param $aesName
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract protected function getCipherFromAesName($aesName);
|
||||
|
||||
/**
|
||||
* Dependency to generate a CipherMethod from a set of inputs for loading
|
||||
* in to an AesDecryptingStream.
|
||||
*
|
||||
* @param string $cipherName Name of the cipher to generate for decrypting.
|
||||
* @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 using cipher options loaded from the
|
||||
* MetadataEnvelope and MaterialsProvider. Can decrypt data from both the
|
||||
* legacy and V2 encryption client workflows.
|
||||
*
|
||||
* @param string $cipherText Plain-text data to be encrypted using the
|
||||
* materials, algorithm, and data provided.
|
||||
* @param MaterialsProviderInterfaceV2 $provider A provider to supply and encrypt
|
||||
* materials used in encryption.
|
||||
* @param MetadataEnvelope $envelope A storage envelope for encryption
|
||||
* metadata to be read from.
|
||||
* @param array $options Options used for decryption.
|
||||
*
|
||||
* @return AesStreamInterface
|
||||
*
|
||||
* @throws \InvalidArgumentException Thrown when a value in $cipherOptions
|
||||
* is not valid.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function decrypt(
|
||||
$cipherText,
|
||||
MaterialsProviderInterfaceV2 $provider,
|
||||
MetadataEnvelope $envelope,
|
||||
array $options = []
|
||||
) {
|
||||
$options['@CipherOptions'] = !empty($options['@CipherOptions'])
|
||||
? $options['@CipherOptions']
|
||||
: [];
|
||||
$options['@CipherOptions']['Iv'] = base64_decode(
|
||||
$envelope[MetadataEnvelope::IV_HEADER]
|
||||
);
|
||||
|
||||
$options['@CipherOptions']['TagLength'] =
|
||||
$envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] / 8;
|
||||
|
||||
$cek = $provider->decryptCek(
|
||||
base64_decode(
|
||||
$envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER]
|
||||
),
|
||||
json_decode(
|
||||
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER],
|
||||
true
|
||||
),
|
||||
$options
|
||||
);
|
||||
$options['@CipherOptions']['KeySize'] = strlen($cek) * 8;
|
||||
$options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName(
|
||||
$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]
|
||||
);
|
||||
|
||||
$this->validateOptionsAndEnvelope($options, $envelope);
|
||||
|
||||
$decryptionStream = $this->getDecryptingStream(
|
||||
$cipherText,
|
||||
$cek,
|
||||
$options['@CipherOptions']
|
||||
);
|
||||
unset($cek);
|
||||
|
||||
return $decryptionStream;
|
||||
}
|
||||
|
||||
private function getTagFromCiphertextStream(
|
||||
StreamInterface $cipherText,
|
||||
$tagLength
|
||||
) {
|
||||
$cipherTextSize = $cipherText->getSize();
|
||||
if ($cipherTextSize == null || $cipherTextSize <= 0) {
|
||||
throw new \RuntimeException('Cannot decrypt a stream of unknown'
|
||||
. ' size.');
|
||||
}
|
||||
return (string) new LimitStream(
|
||||
$cipherText,
|
||||
$tagLength,
|
||||
$cipherTextSize - $tagLength
|
||||
);
|
||||
}
|
||||
|
||||
private function getStrippedCiphertextStream(
|
||||
StreamInterface $cipherText,
|
||||
$tagLength
|
||||
) {
|
||||
$cipherTextSize = $cipherText->getSize();
|
||||
if ($cipherTextSize == null || $cipherTextSize <= 0) {
|
||||
throw new \RuntimeException('Cannot decrypt a stream of unknown'
|
||||
. ' size.');
|
||||
}
|
||||
return new LimitStream(
|
||||
$cipherText,
|
||||
$cipherTextSize - $tagLength,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
private function validateOptionsAndEnvelope($options, $envelope)
|
||||
{
|
||||
$allowedCiphers = AbstractCryptoClientV2::$supportedCiphers;
|
||||
$allowedKeywraps = AbstractCryptoClientV2::$supportedKeyWraps;
|
||||
if ($options['@SecurityProfile'] == 'V2_AND_LEGACY') {
|
||||
$allowedCiphers = array_unique(array_merge(
|
||||
$allowedCiphers,
|
||||
AbstractCryptoClient::$supportedCiphers
|
||||
));
|
||||
$allowedKeywraps = array_unique(array_merge(
|
||||
$allowedKeywraps,
|
||||
AbstractCryptoClient::$supportedKeyWraps
|
||||
));
|
||||
}
|
||||
|
||||
$v1SchemaException = new CryptoException("The requested object is encrypted"
|
||||
. " with V1 encryption schemas that have been disabled by"
|
||||
. " client configuration @SecurityProfile=V2. Retry with"
|
||||
. " V2_AND_LEGACY enabled or reencrypt the object.");
|
||||
|
||||
if (!in_array($options['@CipherOptions']['Cipher'], $allowedCiphers)) {
|
||||
if (in_array($options['@CipherOptions']['Cipher'], AbstractCryptoClient::$supportedCiphers)) {
|
||||
throw $v1SchemaException;
|
||||
}
|
||||
throw new CryptoException("The requested object is encrypted with"
|
||||
. " the cipher '{$options['@CipherOptions']['Cipher']}', which is not"
|
||||
. " supported for decryption with the selected security profile."
|
||||
. " This profile allows decryption with: "
|
||||
. implode(", ", $allowedCiphers));
|
||||
}
|
||||
if (!in_array(
|
||||
$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER],
|
||||
$allowedKeywraps
|
||||
)) {
|
||||
if (in_array(
|
||||
$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER],
|
||||
AbstractCryptoClient::$supportedKeyWraps)
|
||||
) {
|
||||
throw $v1SchemaException;
|
||||
}
|
||||
throw new CryptoException("The requested object is encrypted with"
|
||||
. " the keywrap schema '{$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}',"
|
||||
. " which is not supported for decryption with the current security"
|
||||
. " profile.");
|
||||
}
|
||||
|
||||
$matdesc = json_decode(
|
||||
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER],
|
||||
true
|
||||
);
|
||||
if (isset($matdesc['aws:x-amz-cek-alg'])
|
||||
&& $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !==
|
||||
$matdesc['aws:x-amz-cek-alg']
|
||||
) {
|
||||
throw new CryptoException("There is a mismatch in specified content"
|
||||
. " encryption algrithm between the materials description value"
|
||||
. " and the metadata envelope value: {$matdesc['aws:x-amz-cek-alg']}"
|
||||
. " vs. {$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]}.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a stream that wraps the cipher text with the proper cipher and
|
||||
* uses the content encryption key (CEK) to decrypt the data when read.
|
||||
*
|
||||
* @param string $cipherText 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 AesStreamInterface
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function getDecryptingStream(
|
||||
$cipherText,
|
||||
$cek,
|
||||
$cipherOptions
|
||||
) {
|
||||
$cipherTextStream = Psr7\stream_for($cipherText);
|
||||
switch ($cipherOptions['Cipher']) {
|
||||
case 'gcm':
|
||||
$cipherOptions['Tag'] = $this->getTagFromCiphertextStream(
|
||||
$cipherTextStream,
|
||||
$cipherOptions['TagLength']
|
||||
);
|
||||
|
||||
return new AesGcmDecryptingStream(
|
||||
$this->getStrippedCiphertextStream(
|
||||
$cipherTextStream,
|
||||
$cipherOptions['TagLength']
|
||||
),
|
||||
$cek,
|
||||
$cipherOptions['Iv'],
|
||||
$cipherOptions['Tag'],
|
||||
$cipherOptions['Aad'] = isset($cipherOptions['Aad'])
|
||||
? $cipherOptions['Aad']
|
||||
: null,
|
||||
$cipherOptions['TagLength'] ?: null,
|
||||
$cipherOptions['KeySize']
|
||||
);
|
||||
default:
|
||||
$cipherMethod = $this->buildCipherMethod(
|
||||
$cipherOptions['Cipher'],
|
||||
$cipherOptions['Iv'],
|
||||
$cipherOptions['KeySize']
|
||||
);
|
||||
return new AesDecryptingStream(
|
||||
$cipherTextStream,
|
||||
$cek,
|
||||
$cipherMethod
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ trait EncryptionTrait
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function encrypt(
|
||||
public function encrypt(
|
||||
Stream $plaintext,
|
||||
array $cipherOptions,
|
||||
MaterialsProvider $provider,
|
||||
|
@ -64,7 +64,9 @@ trait EncryptionTrait
|
|||
if (empty($cipherOptions['Cipher'])) {
|
||||
throw new \InvalidArgumentException('An encryption cipher must be'
|
||||
. ' specified in the "cipher_options".');
|
||||
} elseif (!self::isSupportedCipher($cipherOptions['Cipher'])) {
|
||||
}
|
||||
|
||||
if (!self::isSupportedCipher($cipherOptions['Cipher'])) {
|
||||
throw new \InvalidArgumentException('The cipher requested is not'
|
||||
. ' supported by the SDK.');
|
||||
}
|
||||
|
@ -75,7 +77,9 @@ trait EncryptionTrait
|
|||
if (!is_int($cipherOptions['KeySize'])) {
|
||||
throw new \InvalidArgumentException('The cipher "KeySize" must be'
|
||||
. ' an integer.');
|
||||
} elseif (!MaterialsProvider::isSupportedKeySize(
|
||||
}
|
||||
|
||||
if (!MaterialsProvider::isSupportedKeySize(
|
||||
$cipherOptions['KeySize']
|
||||
)) {
|
||||
throw new \InvalidArgumentException('The cipher "KeySize" requested'
|
||||
|
@ -112,8 +116,6 @@ trait EncryptionTrait
|
|||
$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName;
|
||||
$envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] =
|
||||
strlen($plaintext);
|
||||
$envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_MD5_HEADER] =
|
||||
base64_encode(md5($plaintext));
|
||||
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] =
|
||||
json_encode($materialsDescription);
|
||||
if (!empty($cipherOptions['Tag'])) {
|
||||
|
@ -159,6 +161,14 @@ trait EncryptionTrait
|
|||
$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()
|
||||
]);
|
||||
|
|
196
aws/Aws/Crypto/EncryptionTraitV2.php
Normal file
196
aws/Aws/Crypto/EncryptionTraitV2.php
Normal file
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Psr7\AppendStream;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
trait EncryptionTraitV2
|
||||
{
|
||||
private static $allowedOptions = [
|
||||
'Cipher' => true,
|
||||
'KeySize' => true,
|
||||
'Aad' => true,
|
||||
];
|
||||
|
||||
private static $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 array $options Options for use in encryption, including cipher
|
||||
* options, and encryption context.
|
||||
* @param MaterialsProviderV2 $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 StreamInterface
|
||||
*
|
||||
* @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions']
|
||||
* is not valid.
|
||||
*s
|
||||
* @internal
|
||||
*/
|
||||
public function encrypt(
|
||||
Stream $plaintext,
|
||||
array $options,
|
||||
MaterialsProviderV2 $provider,
|
||||
MetadataEnvelope $envelope
|
||||
) {
|
||||
$options = array_change_key_case($options);
|
||||
$cipherOptions = array_intersect_key(
|
||||
$options['@cipheroptions'],
|
||||
self::$allowedOptions
|
||||
);
|
||||
|
||||
if (empty($cipherOptions['Cipher'])) {
|
||||
throw new \InvalidArgumentException('An encryption cipher must be'
|
||||
. ' specified in @CipherOptions["Cipher"].');
|
||||
}
|
||||
|
||||
$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 (!MaterialsProviderV2::isSupportedKeySize(
|
||||
$cipherOptions['KeySize']
|
||||
)) {
|
||||
throw new \InvalidArgumentException('The cipher "KeySize" requested'
|
||||
. ' is not supported by AES (128 or 256).');
|
||||
}
|
||||
|
||||
$cipherOptions['Iv'] = $provider->generateIv(
|
||||
$this->getCipherOpenSslName(
|
||||
$cipherOptions['Cipher'],
|
||||
$cipherOptions['KeySize']
|
||||
)
|
||||
);
|
||||
|
||||
$encryptClass = self::$encryptClasses[$cipherOptions['Cipher']];
|
||||
$aesName = $encryptClass::getStaticAesName();
|
||||
$materialsDescription = ['aws:x-amz-cek-alg' => $aesName];
|
||||
|
||||
$keys = $provider->generateCek(
|
||||
$cipherOptions['KeySize'],
|
||||
$materialsDescription,
|
||||
$options
|
||||
);
|
||||
|
||||
// Some providers modify materials description based on options
|
||||
if (isset($keys['UpdatedContext'])) {
|
||||
$materialsDescription = $keys['UpdatedContext'];
|
||||
}
|
||||
|
||||
$encryptingStream = $this->getEncryptingStream(
|
||||
$plaintext,
|
||||
$keys['Plaintext'],
|
||||
$cipherOptions
|
||||
);
|
||||
|
||||
// Populate envelope data
|
||||
$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] =
|
||||
strlen($plaintext);
|
||||
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] =
|
||||
json_encode($materialsDescription);
|
||||
if (!empty($cipherOptions['Tag'])) {
|
||||
$envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] =
|
||||
strlen($cipherOptions['Tag']) * 8;
|
||||
}
|
||||
|
||||
return $encryptingStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [AesStreamInterface, string]
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function getEncryptingStream(
|
||||
Stream $plaintext,
|
||||
$cek,
|
||||
&$cipherOptions
|
||||
) {
|
||||
switch ($cipherOptions['Cipher']) {
|
||||
// Only 'gcm' is supported for encryption currently
|
||||
case 'gcm':
|
||||
$cipherOptions['TagLength'] = 16;
|
||||
$encryptClass = self::$encryptClasses['gcm'];
|
||||
$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()
|
||||
]);
|
||||
$cipherOptions['Tag'] = $cipherTextStream->getTag();
|
||||
$appendStream->addStream(Psr7\stream_for($cipherOptions['Tag']));
|
||||
return $appendStream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,9 +5,18 @@ use Aws\Kms\KmsClient;
|
|||
|
||||
/**
|
||||
* Uses KMS to supply materials for encrypting and decrypting data.
|
||||
*
|
||||
* Legacy implementation that supports legacy S3EncryptionClient and
|
||||
* S3EncryptionMultipartUploader, which use an older encryption workflow. Use
|
||||
* KmsMaterialsProviderV2 with S3EncryptionClientV2 or
|
||||
* S3EncryptionMultipartUploaderV2 if possible.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class KmsMaterialsProvider extends MaterialsProvider
|
||||
class KmsMaterialsProvider extends MaterialsProvider implements MaterialsProviderInterface
|
||||
{
|
||||
const WRAP_ALGORITHM_NAME = 'kms';
|
||||
|
||||
private $kmsClient;
|
||||
private $kmsKeyId;
|
||||
|
||||
|
@ -28,22 +37,26 @@ class KmsMaterialsProvider extends MaterialsProvider
|
|||
public function fromDecryptionEnvelope(MetadataEnvelope $envelope)
|
||||
{
|
||||
if (empty($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER])) {
|
||||
throw new \RuntimeException('Not able to detect kms_cmk_id from an'
|
||||
. ' empty materials description.');
|
||||
throw new \RuntimeException('Not able to detect the materials description.');
|
||||
}
|
||||
|
||||
$materialsDescription = json_decode(
|
||||
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER],
|
||||
true
|
||||
);
|
||||
if (empty($materialsDescription['kms_cmk_id'])) {
|
||||
throw new \RuntimeException('Not able to detect kms_cmk_id from kms'
|
||||
. ' materials description.');
|
||||
|
||||
if (empty($materialsDescription['kms_cmk_id'])
|
||||
&& empty($materialsDescription['aws:x-amz-cek-alg'])) {
|
||||
throw new \RuntimeException('Not able to detect kms_cmk_id (legacy'
|
||||
. ' implementation) or aws:x-amz-cek-alg (current implementation)'
|
||||
. ' from kms materials description.');
|
||||
}
|
||||
|
||||
return new KmsMaterialsProvider(
|
||||
return new self(
|
||||
$this->kmsClient,
|
||||
$materialsDescription['kms_cmk_id']
|
||||
isset($materialsDescription['kms_cmk_id'])
|
||||
? $materialsDescription['kms_cmk_id']
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -60,7 +73,7 @@ class KmsMaterialsProvider extends MaterialsProvider
|
|||
|
||||
public function getWrapAlgorithmName()
|
||||
{
|
||||
return 'kms';
|
||||
return self::WRAP_ALGORITHM_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
100
aws/Aws/Crypto/KmsMaterialsProviderV2.php
Normal file
100
aws/Aws/Crypto/KmsMaterialsProviderV2.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
use Aws\Exception\CryptoException;
|
||||
use Aws\Kms\KmsClient;
|
||||
|
||||
/**
|
||||
* Uses KMS to supply materials for encrypting and decrypting data. This
|
||||
* V2 implementation should be used with the V2 encryption clients (i.e.
|
||||
* S3EncryptionClientV2).
|
||||
*/
|
||||
class KmsMaterialsProviderV2 extends MaterialsProviderV2 implements MaterialsProviderInterfaceV2
|
||||
{
|
||||
const WRAP_ALGORITHM_NAME = 'kms+context';
|
||||
|
||||
private $kmsClient;
|
||||
private $kmsKeyId;
|
||||
|
||||
/**
|
||||
* @param KmsClient $kmsClient A KMS Client for use encrypting and
|
||||
* decrypting keys.
|
||||
* @param string $kmsKeyId The private KMS key id to be used for encrypting
|
||||
* and decrypting keys.
|
||||
*/
|
||||
public function __construct(
|
||||
KmsClient $kmsClient,
|
||||
$kmsKeyId = null
|
||||
) {
|
||||
$this->kmsClient = $kmsClient;
|
||||
$this->kmsKeyId = $kmsKeyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWrapAlgorithmName()
|
||||
{
|
||||
return self::WRAP_ALGORITHM_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function decryptCek($encryptedCek, $materialDescription, $options)
|
||||
{
|
||||
$params = [
|
||||
'CiphertextBlob' => $encryptedCek,
|
||||
'EncryptionContext' => $materialDescription
|
||||
];
|
||||
if (empty($options['@KmsAllowDecryptWithAnyCmk'])) {
|
||||
if (empty($this->kmsKeyId)) {
|
||||
throw new CryptoException('KMS CMK ID was not specified and the'
|
||||
. ' operation is not opted-in to attempting to use any valid'
|
||||
. ' CMK it discovers. Please specify a CMK ID, or explicitly'
|
||||
. ' enable attempts to use any valid KMS CMK with the'
|
||||
. ' @KmsAllowDecryptWithAnyCmk option.');
|
||||
}
|
||||
$params['KeyId'] = $this->kmsKeyId;
|
||||
}
|
||||
|
||||
$result = $this->kmsClient->decrypt($params);
|
||||
return $result['Plaintext'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function generateCek($keySize, $context, $options)
|
||||
{
|
||||
if (empty($this->kmsKeyId)) {
|
||||
throw new CryptoException('A KMS key id is required for encryption'
|
||||
. ' with KMS keywrap. Use a KmsMaterialsProviderV2 that has been'
|
||||
. ' instantiated with a KMS key id.');
|
||||
}
|
||||
$options = array_change_key_case($options);
|
||||
if (!isset($options['@kmsencryptioncontext'])
|
||||
|| !is_array($options['@kmsencryptioncontext'])
|
||||
) {
|
||||
throw new CryptoException("'@KmsEncryptionContext' is a"
|
||||
. " required argument when using KmsMaterialsProviderV2, and"
|
||||
. " must be an associative array (or empty array).");
|
||||
}
|
||||
if (isset($options['@kmsencryptioncontext']['aws:x-amz-cek-alg'])) {
|
||||
throw new CryptoException("Conflict in reserved @KmsEncryptionContext"
|
||||
. " key aws:x-amz-cek-alg. This value is reserved for the S3"
|
||||
. " Encryption Client and cannot be set by the user.");
|
||||
}
|
||||
$context = array_merge($options['@kmsencryptioncontext'], $context);
|
||||
$result = $this->kmsClient->generateDataKey([
|
||||
'KeyId' => $this->kmsKeyId,
|
||||
'KeySpec' => "AES_{$keySize}",
|
||||
'EncryptionContext' => $context
|
||||
]);
|
||||
return [
|
||||
'Plaintext' => $result['Plaintext'],
|
||||
'Ciphertext' => base64_encode($result['CiphertextBlob']),
|
||||
'UpdatedContext' => $context
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
abstract class MaterialsProvider
|
||||
abstract class MaterialsProvider implements MaterialsProviderInterface
|
||||
{
|
||||
private static $supportedKeySizes = [
|
||||
128 => true,
|
||||
|
|
61
aws/Aws/Crypto/MaterialsProviderInterface.php
Normal file
61
aws/Aws/Crypto/MaterialsProviderInterface.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
interface MaterialsProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns if the requested size is supported by AES.
|
||||
*
|
||||
* @param int $keySize Size of the requested key in bits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupportedKeySize($keySize);
|
||||
|
||||
/**
|
||||
* Performs further initialization of the MaterialsProvider based on the
|
||||
* data inside the MetadataEnvelope.
|
||||
*
|
||||
* @param MetadataEnvelope $envelope A storage envelope for encryption
|
||||
* metadata to be read from.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function fromDecryptionEnvelope(MetadataEnvelope $envelope);
|
||||
|
||||
/**
|
||||
* Returns the wrap algorithm name for this Provider.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWrapAlgorithmName();
|
||||
|
||||
/**
|
||||
* Takes an encrypted content encryption key (CEK) and material description
|
||||
* for use decrypting the key according to the Provider's specifications.
|
||||
*
|
||||
* @param string $encryptedCek Encrypted key to be decrypted by the Provider
|
||||
* for use decrypting other data.
|
||||
* @param string $materialDescription Material Description for use in
|
||||
* encrypting the $cek.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function decryptCek($encryptedCek, $materialDescription);
|
||||
|
||||
/**
|
||||
* @param string $keySize Length of a cipher key in bits for generating a
|
||||
* random content encryption key (CEK).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateCek($keySize);
|
||||
|
||||
/**
|
||||
* @param string $openSslName Cipher OpenSSL name to use for generating
|
||||
* an initialization vector.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateIv($openSslName);
|
||||
}
|
53
aws/Aws/Crypto/MaterialsProviderInterfaceV2.php
Normal file
53
aws/Aws/Crypto/MaterialsProviderInterfaceV2.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
interface MaterialsProviderInterfaceV2
|
||||
{
|
||||
/**
|
||||
* Returns if the requested size is supported by AES.
|
||||
*
|
||||
* @param int $keySize Size of the requested key in bits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupportedKeySize($keySize);
|
||||
|
||||
/**
|
||||
* Returns the wrap algorithm name for this Provider.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWrapAlgorithmName();
|
||||
|
||||
/**
|
||||
* Takes an encrypted content encryption key (CEK) and material description
|
||||
* for use decrypting the key according to the Provider's specifications.
|
||||
*
|
||||
* @param string $encryptedCek Encrypted key to be decrypted by the Provider
|
||||
* for use decrypting other data.
|
||||
* @param string $materialDescription Material Description for use in
|
||||
* decrypting the CEK.
|
||||
* @param array $options Options for use in decrypting the CEK.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function decryptCek($encryptedCek, $materialDescription, $options);
|
||||
|
||||
/**
|
||||
* @param string $keySize Length of a cipher key in bits for generating a
|
||||
* random content encryption key (CEK).
|
||||
* @param array $context Context map needed for key encryption
|
||||
* @param array $options Additional options to be used in CEK generation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function generateCek($keySize, $context, $options);
|
||||
|
||||
/**
|
||||
* @param string $openSslName Cipher OpenSSL name to use for generating
|
||||
* an initialization vector.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateIv($openSslName);
|
||||
}
|
66
aws/Aws/Crypto/MaterialsProviderV2.php
Normal file
66
aws/Aws/Crypto/MaterialsProviderV2.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
namespace Aws\Crypto;
|
||||
|
||||
abstract class MaterialsProviderV2 implements MaterialsProviderInterfaceV2
|
||||
{
|
||||
private static $supportedKeySizes = [
|
||||
128 => true,
|
||||
256 => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns if the requested size is supported by AES.
|
||||
*
|
||||
* @param int $keySize Size of the requested key in bits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupportedKeySize($keySize)
|
||||
{
|
||||
return isset(self::$supportedKeySizes[$keySize]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrap algorithm name for this Provider.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getWrapAlgorithmName();
|
||||
|
||||
/**
|
||||
* Takes an encrypted content encryption key (CEK) and material description
|
||||
* for use decrypting the key according to the Provider's specifications.
|
||||
*
|
||||
* @param string $encryptedCek Encrypted key to be decrypted by the Provider
|
||||
* for use decrypting other data.
|
||||
* @param string $materialDescription Material Description for use in
|
||||
* decrypting the CEK.
|
||||
* @param string $options Options for use in decrypting the CEK.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function decryptCek($encryptedCek, $materialDescription, $options);
|
||||
|
||||
/**
|
||||
* @param string $keySize Length of a cipher key in bits for generating a
|
||||
* random content encryption key (CEK).
|
||||
* @param array $context Context map needed for key encryption
|
||||
* @param array $options Additional options to be used in CEK generation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function generateCek($keySize, $context, $options);
|
||||
|
||||
/**
|
||||
* @param string $openSslName Cipher OpenSSL name to use for generating
|
||||
* an initialization vector.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateIv($openSslName)
|
||||
{
|
||||
return openssl_random_pseudo_bytes(
|
||||
openssl_cipher_iv_length($openSslName)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -22,7 +22,6 @@ class MetadataEnvelope implements ArrayAccess, IteratorAggregate, JsonSerializab
|
|||
const KEY_WRAP_ALGORITHM_HEADER = 'x-amz-wrap-alg';
|
||||
const CONTENT_CRYPTO_SCHEME_HEADER = 'x-amz-cek-alg';
|
||||
const CRYPTO_TAG_LENGTH_HEADER = 'x-amz-tag-len';
|
||||
const UNENCRYPTED_CONTENT_MD5_HEADER = 'x-amz-unencrypted-content-md5';
|
||||
const UNENCRYPTED_CONTENT_LENGTH_HEADER = 'x-amz-unencrypted-content-length';
|
||||
|
||||
private static $constants = [];
|
||||
|
@ -45,9 +44,9 @@ class MetadataEnvelope implements ArrayAccess, IteratorAggregate, JsonSerializab
|
|||
if (is_null($name) || !in_array($name, $constants)) {
|
||||
throw new InvalidArgumentException('MetadataEnvelope fields must'
|
||||
. ' must match a predefined offset; use the header constants.');
|
||||
} else {
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
|
|
228
aws/Aws/Crypto/Polyfill/AesGcm.php
Normal file
228
aws/Aws/Crypto/Polyfill/AesGcm.php
Normal file
|
@ -0,0 +1,228 @@
|
|||
<?php
|
||||
namespace Aws\Crypto\Polyfill;
|
||||
|
||||
use Aws\Exception\CryptoPolyfillException;
|
||||
use InvalidArgumentException;
|
||||
use RangeException;
|
||||
|
||||
/**
|
||||
* Class AesGcm
|
||||
*
|
||||
* This provides a polyfill for AES-GCM encryption/decryption, with caveats:
|
||||
*
|
||||
* 1. Only 96-bit nonces are supported.
|
||||
* 2. Only 128-bit authentication tags are supported. (i.e. non-truncated)
|
||||
*
|
||||
* Supports AES key sizes of 128-bit, 192-bit, and 256-bit.
|
||||
*
|
||||
* @package Aws\Crypto\Polyfill
|
||||
*/
|
||||
class AesGcm
|
||||
{
|
||||
use NeedsTrait;
|
||||
|
||||
/** @var Key $aesKey */
|
||||
private $aesKey;
|
||||
|
||||
/** @var int $keySize */
|
||||
private $keySize;
|
||||
|
||||
/** @var int $blockSize */
|
||||
protected $blockSize = 8192;
|
||||
|
||||
/**
|
||||
* AesGcm constructor.
|
||||
*
|
||||
* @param Key $aesKey
|
||||
* @param int $keySize
|
||||
* @param int $blockSize
|
||||
*
|
||||
* @throws CryptoPolyfillException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RangeException
|
||||
*/
|
||||
public function __construct(Key $aesKey, $keySize = 256, $blockSize = 8192)
|
||||
{
|
||||
/* Preconditions: */
|
||||
self::needs(
|
||||
\in_array($keySize, [128, 192, 256], true),
|
||||
"Key size must be 128, 192, or 256 bits; {$keySize} given",
|
||||
InvalidArgumentException::class
|
||||
);
|
||||
self::needs(
|
||||
\is_int($blockSize) && $blockSize > 0 && $blockSize <= PHP_INT_MAX,
|
||||
'Block size must be a positive integer.',
|
||||
RangeException::class
|
||||
);
|
||||
self::needs(
|
||||
$aesKey->length() << 3 === $keySize,
|
||||
'Incorrect key size; expected ' . $keySize . ' bits, got ' . ($aesKey->length() << 3) . ' bits.'
|
||||
);
|
||||
$this->aesKey = $aesKey;
|
||||
$this->keySize = $keySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption interface for AES-GCM
|
||||
*
|
||||
* @param string $plaintext Message to be encrypted
|
||||
* @param string $nonce Number to be used ONCE
|
||||
* @param Key $key AES Key
|
||||
* @param string $aad Additional authenticated data
|
||||
* @param string &$tag Reference to variable to hold tag
|
||||
* @param int $keySize Key size (bits)
|
||||
* @param int $blockSize Block size (bytes) -- How much memory to buffer
|
||||
* @return string
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function encrypt(
|
||||
$plaintext,
|
||||
$nonce,
|
||||
Key $key,
|
||||
$aad,
|
||||
&$tag,
|
||||
$keySize = 256,
|
||||
$blockSize = 8192
|
||||
) {
|
||||
self::needs(
|
||||
self::strlen($nonce) === 12,
|
||||
'Nonce must be exactly 12 bytes',
|
||||
InvalidArgumentException::class
|
||||
);
|
||||
|
||||
$encryptor = new AesGcm($key, $keySize, $blockSize);
|
||||
list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad);
|
||||
|
||||
$ciphertext = \openssl_encrypt(
|
||||
$plaintext,
|
||||
"aes-{$encryptor->keySize}-ctr",
|
||||
$key->get(),
|
||||
OPENSSL_NO_PADDING | OPENSSL_RAW_DATA,
|
||||
$nonce . "\x00\x00\x00\x02"
|
||||
);
|
||||
|
||||
/* Calculate auth tag in a streaming fashion to minimize memory usage: */
|
||||
$ciphertextLength = self::strlen($ciphertext);
|
||||
for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) {
|
||||
$cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize));
|
||||
$gmac->update($cBlock);
|
||||
}
|
||||
$tag = $gmac->finish($aadLength, $ciphertextLength)->toString();
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decryption interface for AES-GCM
|
||||
*
|
||||
* @param string $ciphertext Ciphertext to decrypt
|
||||
* @param string $nonce Number to be used ONCE
|
||||
* @param Key $key AES key
|
||||
* @param string $aad Additional authenticated data
|
||||
* @param string $tag Authentication tag
|
||||
* @param int $keySize Key size (bits)
|
||||
* @param int $blockSize Block size (bytes) -- How much memory to buffer
|
||||
* @return string Plaintext
|
||||
*
|
||||
* @throws CryptoPolyfillException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function decrypt(
|
||||
$ciphertext,
|
||||
$nonce,
|
||||
Key $key,
|
||||
$aad,
|
||||
&$tag,
|
||||
$keySize = 256,
|
||||
$blockSize = 8192
|
||||
) {
|
||||
/* Precondition: */
|
||||
self::needs(
|
||||
self::strlen($nonce) === 12,
|
||||
'Nonce must be exactly 12 bytes',
|
||||
InvalidArgumentException::class
|
||||
);
|
||||
|
||||
$encryptor = new AesGcm($key, $keySize, $blockSize);
|
||||
list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad);
|
||||
|
||||
/* Calculate auth tag in a streaming fashion to minimize memory usage: */
|
||||
$ciphertextLength = self::strlen($ciphertext);
|
||||
for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) {
|
||||
$cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize));
|
||||
$gmac->update($cBlock);
|
||||
}
|
||||
|
||||
/* Validate auth tag in constant-time: */
|
||||
$calc = $gmac->finish($aadLength, $ciphertextLength);
|
||||
$expected = new ByteArray($tag);
|
||||
self::needs($calc->equals($expected), 'Invalid authentication tag');
|
||||
|
||||
/* Return plaintext if auth tag check succeeded: */
|
||||
return \openssl_decrypt(
|
||||
$ciphertext,
|
||||
"aes-{$encryptor->keySize}-ctr",
|
||||
$key->get(),
|
||||
OPENSSL_NO_PADDING | OPENSSL_RAW_DATA,
|
||||
$nonce . "\x00\x00\x00\x02"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a Gmac object with the nonce and this object's key.
|
||||
*
|
||||
* @param string $nonce Must be exactly 12 bytes long.
|
||||
* @param string|null $aad
|
||||
* @return array
|
||||
*/
|
||||
protected function gmacInit($nonce, $aad = null)
|
||||
{
|
||||
$gmac = new Gmac(
|
||||
$this->aesKey,
|
||||
$nonce . "\x00\x00\x00\x01",
|
||||
$this->keySize
|
||||
);
|
||||
$aadBlock = new ByteArray($aad);
|
||||
$aadLength = $aadBlock->count();
|
||||
$gmac->update($aadBlock);
|
||||
$gmac->flush();
|
||||
return [$aadLength, $gmac];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the length of a string.
|
||||
*
|
||||
* Uses the appropriate PHP function without being brittle to
|
||||
* mbstring.func_overload.
|
||||
*
|
||||
* @param string $string
|
||||
* @return int
|
||||
*/
|
||||
protected static function strlen($string)
|
||||
{
|
||||
if (\is_callable('\\mb_strlen')) {
|
||||
return (int) \mb_strlen($string, '8bit');
|
||||
}
|
||||
return (int) \strlen($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a substring of the provided string.
|
||||
*
|
||||
* Uses the appropriate PHP function without being brittle to
|
||||
* mbstring.func_overload.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $offset
|
||||
* @param int|null $length
|
||||
* @return string
|
||||
*/
|
||||
protected static function substr($string, $offset = 0, $length = null)
|
||||
{
|
||||
if (\is_callable('\\mb_substr')) {
|
||||
return \mb_substr($string, $offset, $length, '8bit');
|
||||
} elseif (!\is_null($length)) {
|
||||
return \substr($string, $offset, $length);
|
||||
}
|
||||
return \substr($string, $offset);
|
||||
}
|
||||
}
|
258
aws/Aws/Crypto/Polyfill/ByteArray.php
Normal file
258
aws/Aws/Crypto/Polyfill/ByteArray.php
Normal file
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
namespace Aws\Crypto\Polyfill;
|
||||
|
||||
/**
|
||||
* Class ByteArray
|
||||
* @package Aws\Crypto\Polyfill
|
||||
*/
|
||||
class ByteArray extends \SplFixedArray
|
||||
{
|
||||
use NeedsTrait;
|
||||
|
||||
/**
|
||||
* ByteArray constructor.
|
||||
*
|
||||
* @param int|string|int[] $size
|
||||
* If you pass in an integer, it creates a ByteArray of that size.
|
||||
* If you pass in a string or array, it converts it to an array of
|
||||
* integers between 0 and 255.
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($size = 0)
|
||||
{
|
||||
$arr = null;
|
||||
// Integer? This behaves just like SplFixedArray.
|
||||
if (\is_array($size)) {
|
||||
// Array? We need to pass the count to parent::__construct() then populate
|
||||
$arr = $size;
|
||||
$size = \count($arr);
|
||||
} elseif (\is_string($size)) {
|
||||
// We need to avoid mbstring.func_overload
|
||||
if (\is_callable('\\mb_str_split')) {
|
||||
$tmp = \mb_str_split($size, 1, '8bit');
|
||||
} else {
|
||||
$tmp = \str_split($size, 1);
|
||||
}
|
||||
// Let's convert each character to an 8-bit integer and store in $arr
|
||||
$arr = [];
|
||||
if (!empty($tmp)) {
|
||||
foreach ($tmp as $t) {
|
||||
if (strlen($t) < 1) {
|
||||
continue;
|
||||
}
|
||||
$arr []= \unpack('C', $t)[1] & 0xff;
|
||||
}
|
||||
}
|
||||
$size = \count($arr);
|
||||
} elseif ($size instanceof ByteArray) {
|
||||
$arr = $size->toArray();
|
||||
$size = $size->count();
|
||||
} elseif (!\is_int($size)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Argument must be an integer, string, or array of integers.'
|
||||
);
|
||||
}
|
||||
|
||||
parent::__construct($size);
|
||||
|
||||
if (!empty($arr)) {
|
||||
// Populate this object with values from constructor argument
|
||||
foreach ($arr as $i => $v) {
|
||||
$this->offsetSet($i, $v);
|
||||
}
|
||||
} else {
|
||||
// Initialize to zero.
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
$this->offsetSet($i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an integer into a byte array. 32-bit (unsigned), big endian byte order.
|
||||
*
|
||||
* @param int $num
|
||||
* @return self
|
||||
*/
|
||||
public static function enc32be($num)
|
||||
{
|
||||
return new ByteArray(\pack('N', $num));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ByteArray $other
|
||||
* @return bool
|
||||
*/
|
||||
public function equals(ByteArray $other)
|
||||
{
|
||||
if ($this->count() !== $other->count()) {
|
||||
return false;
|
||||
}
|
||||
$d = 0;
|
||||
for ($i = $this->count() - 1; $i >= 0; --$i) {
|
||||
$d |= $this[$i] ^ $other[$i];
|
||||
}
|
||||
return $d === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ByteArray $array
|
||||
* @return ByteArray
|
||||
*/
|
||||
public function exclusiveOr(ByteArray $array)
|
||||
{
|
||||
self::needs(
|
||||
$this->count() === $array->count(),
|
||||
'Both ByteArrays must be equal size for exclusiveOr()'
|
||||
);
|
||||
$out = clone $this;
|
||||
for ($i = 0; $i < $this->count(); ++$i) {
|
||||
$out[$i] = $array[$i] ^ $out[$i];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ByteArray incremented by 1 (big endian byte order).
|
||||
*
|
||||
* @param int $increase
|
||||
* @return self
|
||||
*/
|
||||
public function getIncremented($increase = 1)
|
||||
{
|
||||
$clone = clone $this;
|
||||
$index = $clone->count();
|
||||
while ($index > 0) {
|
||||
--$index;
|
||||
$tmp = ($clone[$index] + $increase) & PHP_INT_MAX;
|
||||
$clone[$index] = $tmp & 0xff;
|
||||
$increase = $tmp >> 8;
|
||||
}
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value. See SplFixedArray for more.
|
||||
*
|
||||
* @param int $index
|
||||
* @param int $newval
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($index, $newval)
|
||||
{
|
||||
parent::offsetSet($index, $newval & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this ByteArray, bitshifted to the right by 1.
|
||||
* Used in Gmac.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function rshift()
|
||||
{
|
||||
$out = clone $this;
|
||||
for ($j = $this->count() - 1; $j > 0; --$j) {
|
||||
$out[$j] = (($out[$j - 1] & 1) << 7) | ($out[$j] >> 1);
|
||||
}
|
||||
$out[0] >>= 1;
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant-time conditional select. This is meant to read like a ternary operator.
|
||||
*
|
||||
* $z = ByteArray::select(1, $x, $y); // $z is equal to $x
|
||||
* $z = ByteArray::select(0, $x, $y); // $z is equal to $y
|
||||
*
|
||||
* @param int $select
|
||||
* @param ByteArray $left
|
||||
* @param ByteArray $right
|
||||
* @return ByteArray
|
||||
*/
|
||||
public static function select($select, ByteArray $left, ByteArray $right)
|
||||
{
|
||||
self::needs(
|
||||
$left->count() === $right->count(),
|
||||
'Both ByteArrays must be equal size for select()'
|
||||
);
|
||||
$rightLength = $right->count();
|
||||
$out = clone $right;
|
||||
$mask = (-($select & 1)) & 0xff;
|
||||
for ($i = 0; $i < $rightLength; $i++) {
|
||||
$out[$i] = $out[$i] ^ (($left[$i] ^ $right[$i]) & $mask);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite values of this ByteArray based on a separate ByteArray, with
|
||||
* a given starting offset and length.
|
||||
*
|
||||
* See JavaScript's Uint8Array.set() for more information.
|
||||
*
|
||||
* @param ByteArray $input
|
||||
* @param int $offset
|
||||
* @param int|null $length
|
||||
* @return self
|
||||
*/
|
||||
public function set(ByteArray $input, $offset = 0, $length = null)
|
||||
{
|
||||
self::needs(
|
||||
is_int($offset) && $offset >= 0,
|
||||
'Offset must be a positive integer or zero'
|
||||
);
|
||||
if (is_null($length)) {
|
||||
$length = $input->count();
|
||||
}
|
||||
|
||||
$i = 0; $j = $offset;
|
||||
while ($i < $length && $j < $this->count()) {
|
||||
$this[$j] = $input[$i];
|
||||
++$i;
|
||||
++$j;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a slice of this ByteArray.
|
||||
*
|
||||
* @param int $start
|
||||
* @param null $length
|
||||
* @return self
|
||||
*/
|
||||
public function slice($start = 0, $length = null)
|
||||
{
|
||||
return new ByteArray(\array_slice($this->toArray(), $start, $length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutates the current state and sets all values to zero.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function zeroize()
|
||||
{
|
||||
for ($i = $this->count() - 1; $i >= 0; --$i) {
|
||||
$this->offsetSet($i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the ByteArray to a raw binary string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString()
|
||||
{
|
||||
$count = $this->count();
|
||||
if ($count === 0) {
|
||||
return '';
|
||||
}
|
||||
$args = $this->toArray();
|
||||
\array_unshift($args, \str_repeat('C', $count));
|
||||
// constant-time, PHP <5.6 equivalent to pack('C*', ...$args);
|
||||
return \call_user_func_array('\\pack', $args);
|
||||
}
|
||||
}
|
223
aws/Aws/Crypto/Polyfill/Gmac.php
Normal file
223
aws/Aws/Crypto/Polyfill/Gmac.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
namespace Aws\Crypto\Polyfill;
|
||||
|
||||
/**
|
||||
* Class Gmac
|
||||
*
|
||||
* @package Aws\Crypto\Polyfill
|
||||
*/
|
||||
class Gmac
|
||||
{
|
||||
use NeedsTrait;
|
||||
|
||||
const BLOCK_SIZE = 16;
|
||||
|
||||
/** @var ByteArray $buf */
|
||||
protected $buf;
|
||||
|
||||
/** @var int $bufLength */
|
||||
protected $bufLength = 0;
|
||||
|
||||
/** @var ByteArray $h */
|
||||
protected $h;
|
||||
|
||||
/** @var ByteArray $hf */
|
||||
protected $hf;
|
||||
|
||||
/** @var Key $key */
|
||||
protected $key;
|
||||
|
||||
/** @var ByteArray $x */
|
||||
protected $x;
|
||||
|
||||
/**
|
||||
* Gmac constructor.
|
||||
*
|
||||
* @param Key $aesKey
|
||||
* @param string $nonce
|
||||
* @param int $keySize
|
||||
*/
|
||||
public function __construct(Key $aesKey, $nonce, $keySize = 256)
|
||||
{
|
||||
$this->buf = new ByteArray(16);
|
||||
$this->h = new ByteArray(
|
||||
\openssl_encrypt(
|
||||
\str_repeat("\0", 16),
|
||||
"aes-{$keySize}-ecb",
|
||||
$aesKey->get(),
|
||||
OPENSSL_RAW_DATA | OPENSSL_NO_PADDING
|
||||
)
|
||||
);
|
||||
$this->key = $aesKey;
|
||||
$this->x = new ByteArray(16);
|
||||
$this->hf = new ByteArray(
|
||||
\openssl_encrypt(
|
||||
$nonce,
|
||||
"aes-{$keySize}-ecb",
|
||||
$aesKey->get(),
|
||||
OPENSSL_RAW_DATA | OPENSSL_NO_PADDING
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the object with some data.
|
||||
*
|
||||
* This method mutates this Gmac object.
|
||||
*
|
||||
* @param ByteArray $blocks
|
||||
* @return self
|
||||
*/
|
||||
public function update(ByteArray $blocks)
|
||||
{
|
||||
if (($blocks->count() + $this->bufLength) < self::BLOCK_SIZE) {
|
||||
// Write to internal buffer until we reach enough to write.
|
||||
$this->buf->set($blocks, $this->bufLength);
|
||||
$this->bufLength += $blocks->count();
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Process internal buffer first.
|
||||
if ($this->bufLength > 0) {
|
||||
// 0 <= state.buf_len < BLOCK_SIZE is an invariant
|
||||
$tmp = new ByteArray(self::BLOCK_SIZE);
|
||||
$tmp->set($this->buf->slice(0, $this->bufLength));
|
||||
$remainingBlockLength = self::BLOCK_SIZE - $this->bufLength;
|
||||
$tmp->set($blocks->slice(0, $remainingBlockLength), $this->bufLength);
|
||||
$blocks = $blocks->slice($remainingBlockLength);
|
||||
$this->bufLength = 0;
|
||||
$this->x = $this->blockMultiply($this->x->exclusiveOr($tmp), $this->h);
|
||||
}
|
||||
|
||||
// Process full blocks.
|
||||
$numBlocks = $blocks->count() >> 4;
|
||||
for ($i = 0; $i < $numBlocks; ++$i) {
|
||||
$tmp = $blocks->slice($i << 4, self::BLOCK_SIZE);
|
||||
$this->x = $this->blockMultiply($this->x->exclusiveOr($tmp), $this->h);
|
||||
}
|
||||
$last = $numBlocks << 4;
|
||||
|
||||
// Zero-fill buffer
|
||||
for ($i = 0; $i < 16; ++$i) {
|
||||
$this->buf[$i] = 0;
|
||||
}
|
||||
// Feed leftover into buffer.
|
||||
if ($last < $blocks->count()) {
|
||||
$tmp = $blocks->slice($last);
|
||||
$this->buf->set($tmp);
|
||||
$this->bufLength += ($blocks->count() - $last);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish processing the authentication tag.
|
||||
*
|
||||
* This method mutates this Gmac object (effectively resetting it).
|
||||
*
|
||||
* @param int $aadLength
|
||||
* @param int $ciphertextLength
|
||||
* @return ByteArray
|
||||
*/
|
||||
public function finish($aadLength, $ciphertextLength)
|
||||
{
|
||||
$lengthBlock = new ByteArray(16);
|
||||
$state = $this->flush();
|
||||
|
||||
// AES-GCM expects bit lengths, not byte lengths.
|
||||
$lengthBlock->set(ByteArray::enc32be($aadLength >> 29), 0);
|
||||
$lengthBlock->set(ByteArray::enc32be($aadLength << 3), 4);
|
||||
$lengthBlock->set(ByteArray::enc32be($ciphertextLength >> 29), 8);
|
||||
$lengthBlock->set(ByteArray::enc32be($ciphertextLength << 3), 12);
|
||||
|
||||
$state->update($lengthBlock);
|
||||
$output = $state->x->exclusiveOr($state->hf);
|
||||
|
||||
// Zeroize the internal values as a best-effort.
|
||||
$state->buf->zeroize();
|
||||
$state->x->zeroize();
|
||||
$state->h->zeroize();
|
||||
$state->hf->zeroize();
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific bit from the provided array, at the given index.
|
||||
*
|
||||
* [01234567], 8+[01234567], 16+[01234567], ...
|
||||
*
|
||||
* @param ByteArray $x
|
||||
* @param int $i
|
||||
* @return int
|
||||
*/
|
||||
protected function bit(ByteArray $x, $i)
|
||||
{
|
||||
$byte = $i >> 3;
|
||||
return ($x[$byte] >> ((7 - $i) & 7)) & 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Galois Field Multiplication
|
||||
*
|
||||
* This function is the critical path that must be constant-time in order to
|
||||
* avoid timing side-channels against AES-GCM.
|
||||
*
|
||||
* The contents of each are always calculated, regardless of the branching
|
||||
* condition, to prevent another kind of timing leak.
|
||||
*
|
||||
* @param ByteArray $x
|
||||
* @param ByteArray $y
|
||||
* @return ByteArray
|
||||
*/
|
||||
protected function blockMultiply(ByteArray $x, ByteArray $y)
|
||||
{
|
||||
static $fieldPolynomial = null;
|
||||
if (!$fieldPolynomial) {
|
||||
$fieldPolynomial = new ByteArray([
|
||||
0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]);
|
||||
}
|
||||
self::needs($x->count() === 16, 'Argument 1 must be a ByteArray of exactly 16 bytes');
|
||||
self::needs($y->count() === 16, 'Argument 2 must be a ByteArray of exactly 16 bytes');
|
||||
|
||||
$v = clone $y;
|
||||
$z = new ByteArray(16);
|
||||
|
||||
for ($i = 0; $i < 128; ++$i) {
|
||||
// if ($b) $z = $z->exclusiveOr($v);
|
||||
$b = $this->bit($x, $i);
|
||||
$z = ByteArray::select(
|
||||
$b,
|
||||
$z->exclusiveOr($v),
|
||||
$z
|
||||
);
|
||||
|
||||
// if ($b) $v = $v->exclusiveOr($fieldPolynomial);
|
||||
$b = $v[15] & 1;
|
||||
$v = $v->rshift();
|
||||
$v = ByteArray::select(
|
||||
$b,
|
||||
$v->exclusiveOr($fieldPolynomial),
|
||||
$v
|
||||
);
|
||||
}
|
||||
return $z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish processing any leftover bytes in the internal buffer.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
if ($this->bufLength !== 0) {
|
||||
$this->x = $this->blockMultiply(
|
||||
$this->x->exclusiveOr($this->buf),
|
||||
$this->h
|
||||
);
|
||||
$this->bufLength = 0;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
77
aws/Aws/Crypto/Polyfill/Key.php
Normal file
77
aws/Aws/Crypto/Polyfill/Key.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
namespace Aws\Crypto\Polyfill;
|
||||
|
||||
/**
|
||||
* Class Key
|
||||
*
|
||||
* Wraps a string to keep it hidden from stack traces.
|
||||
*
|
||||
* @package Aws\Crypto\Polyfill
|
||||
*/
|
||||
class Key
|
||||
{
|
||||
/**
|
||||
* @var string $internalString
|
||||
*/
|
||||
private $internalString;
|
||||
|
||||
/**
|
||||
* Hide contents of
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Key constructor.
|
||||
* @param string $str
|
||||
*/
|
||||
public function __construct($str)
|
||||
{
|
||||
$this->internalString = $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defense in depth:
|
||||
*
|
||||
* PHP 7.2 includes the Sodium cryptography library, which (among other things)
|
||||
* exposes a function called sodium_memzero() that we can use to zero-fill strings
|
||||
* to minimize the risk of sensitive cryptographic materials persisting in memory.
|
||||
*
|
||||
* If this function is not available, we XOR the string in-place with itself as a
|
||||
* best-effort attempt.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (extension_loaded('sodium') && function_exists('sodium_memzero')) {
|
||||
try {
|
||||
\sodium_memzero($this->internalString);
|
||||
} catch (\SodiumException $ex) {
|
||||
// This is a best effort, but does not provide the same guarantees as sodium_memzero():
|
||||
$this->internalString ^= $this->internalString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->internalString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function length()
|
||||
{
|
||||
if (\is_callable('\\mb_strlen')) {
|
||||
return (int) \mb_strlen($this->internalString, '8bit');
|
||||
}
|
||||
return (int) \strlen($this->internalString);
|
||||
}
|
||||
}
|
38
aws/Aws/Crypto/Polyfill/NeedsTrait.php
Normal file
38
aws/Aws/Crypto/Polyfill/NeedsTrait.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
namespace Aws\Crypto\Polyfill;
|
||||
|
||||
use Aws\Exception\CryptoPolyfillException;
|
||||
|
||||
/**
|
||||
* Trait NeedsTrait
|
||||
* @package Aws\Crypto\Polyfill
|
||||
*/
|
||||
trait NeedsTrait
|
||||
{
|
||||
/**
|
||||
* Preconditions, postconditions, and loop invariants are very
|
||||
* useful for safe programing. They also document the specifications.
|
||||
* This function is to help simplify the semantic burden of parsing
|
||||
* these constructions.
|
||||
*
|
||||
* Instead of constructions like
|
||||
* if (!(GOOD CONDITION)) {
|
||||
* throw new \Exception('condition not true');
|
||||
* }
|
||||
*
|
||||
* you can write:
|
||||
* needs(GOOD CONDITION, 'condition not true');
|
||||
* @param $condition
|
||||
* @param $errorMessage
|
||||
* @param null $exceptionClass
|
||||
*/
|
||||
public static function needs($condition, $errorMessage, $exceptionClass = null)
|
||||
{
|
||||
if (!$condition) {
|
||||
if (!$exceptionClass) {
|
||||
$exceptionClass = CryptoPolyfillException::class;
|
||||
}
|
||||
throw new $exceptionClass($errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue