/home/mip/mip/public/img/credit/datatables/Crypto.tar
SMimeEncrypter.php000064400000004300151520545030010160 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Mime\Crypto;

use Symfony\Component\Mime\Exception\RuntimeException;
use Symfony\Component\Mime\Message;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
final class SMimeEncrypter extends SMime
{
    private string|array $certs;
    private int $cipher;

    /**
     * @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s)
     * @param int|null        $cipher      A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php
     */
    public function __construct(string|array $certificate, ?int $cipher = null)
    {
        if (!\extension_loaded('openssl')) {
            throw new \LogicException('PHP extension "openssl" is required to use SMime.');
        }

        if (\is_array($certificate)) {
            $this->certs = array_map($this->normalizeFilePath(...), $certificate);
        } else {
            $this->certs = $this->normalizeFilePath($certificate);
        }

        $this->cipher = $cipher ?? \OPENSSL_CIPHER_AES_256_CBC;
    }

    public function encrypt(Message $message): Message
    {
        $bufferFile = tmpfile();
        $outputFile = tmpfile();

        $this->iteratorToFile($message->toIterable(), $bufferFile);

        if (!@openssl_pkcs7_encrypt(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->certs, [], 0, $this->cipher)) {
            throw new RuntimeException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string()));
        }

        $mimePart = $this->convertMessageToSMimePart($outputFile, 'application', 'pkcs7-mime');
        $mimePart->getHeaders()
            ->addTextHeader('Content-Transfer-Encoding', 'base64')
            ->addParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'smime.p7m'])
        ;

        return new Message($message->getHeaders(), $mimePart);
    }
}
DkimSigner.php000064400000017725151520545030007325 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Mime\Crypto;

use Symfony\Component\Mime\Exception\InvalidArgumentException;
use Symfony\Component\Mime\Exception\RuntimeException;
use Symfony\Component\Mime\Header\UnstructuredHeader;
use Symfony\Component\Mime\Message;
use Symfony\Component\Mime\Part\AbstractPart;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * RFC 6376 and 8301
 */
final class DkimSigner
{
    public const CANON_SIMPLE = 'simple';
    public const CANON_RELAXED = 'relaxed';

    public const ALGO_SHA256 = 'rsa-sha256';
    public const ALGO_ED25519 = 'ed25519-sha256'; // RFC 8463

    private \OpenSSLAsymmetricKey $key;
    private string $domainName;
    private string $selector;
    private array $defaultOptions;

    /**
     * @param string $pk         The private key as a string or the path to the file containing the private key, should be prefixed with file:// (in PEM format)
     * @param string $passphrase A passphrase of the private key (if any)
     */
    public function __construct(string $pk, string $domainName, string $selector, array $defaultOptions = [], string $passphrase = '')
    {
        if (!\extension_loaded('openssl')) {
            throw new \LogicException('PHP extension "openssl" is required to use DKIM.');
        }
        $this->key = openssl_pkey_get_private($pk, $passphrase) ?: throw new InvalidArgumentException('Unable to load DKIM private key: '.openssl_error_string());
        $this->domainName = $domainName;
        $this->selector = $selector;
        $this->defaultOptions = $defaultOptions + [
            'algorithm' => self::ALGO_SHA256,
            'signature_expiration_delay' => 0,
            'body_max_length' => \PHP_INT_MAX,
            'body_show_length' => false,
            'header_canon' => self::CANON_RELAXED,
            'body_canon' => self::CANON_RELAXED,
            'headers_to_ignore' => [],
        ];
    }

    public function sign(Message $message, array $options = []): Message
    {
        $options += $this->defaultOptions;
        if (!\in_array($options['algorithm'], [self::ALGO_SHA256, self::ALGO_ED25519], true)) {
            throw new InvalidArgumentException(sprintf('Invalid DKIM signing algorithm "%s".', $options['algorithm']));
        }
        $headersToIgnore['return-path'] = true;
        $headersToIgnore['x-transport'] = true;
        foreach ($options['headers_to_ignore'] as $name) {
            $headersToIgnore[strtolower($name)] = true;
        }
        unset($headersToIgnore['from']);
        $signedHeaderNames = [];
        $headerCanonData = '';
        $headers = $message->getPreparedHeaders();
        foreach ($headers->getNames() as $name) {
            foreach ($headers->all($name) as $header) {
                if (isset($headersToIgnore[strtolower($header->getName())])) {
                    continue;
                }

                if ('' !== $header->getBodyAsString()) {
                    $headerCanonData .= $this->canonicalizeHeader($header->toString(), $options['header_canon']);
                    $signedHeaderNames[] = $header->getName();
                }
            }
        }

        [$bodyHash, $bodyLength] = $this->hashBody($message->getBody(), $options['body_canon'], $options['body_max_length']);

        $params = [
            'v' => '1',
            'q' => 'dns/txt',
            'a' => $options['algorithm'],
            'bh' => base64_encode($bodyHash),
            'd' => $this->domainName,
            'h' => implode(': ', $signedHeaderNames),
            'i' => '@'.$this->domainName,
            's' => $this->selector,
            't' => time(),
            'c' => $options['header_canon'].'/'.$options['body_canon'],
        ];

        if ($options['body_show_length']) {
            $params['l'] = $bodyLength;
        }
        if ($options['signature_expiration_delay']) {
            $params['x'] = $params['t'] + $options['signature_expiration_delay'];
        }
        $value = '';
        foreach ($params as $k => $v) {
            $value .= $k.'='.$v.'; ';
        }
        $value = trim($value);
        $header = new UnstructuredHeader('DKIM-Signature', $value);
        $headerCanonData .= rtrim($this->canonicalizeHeader($header->toString()."\r\n b=", $options['header_canon']));
        if (self::ALGO_SHA256 === $options['algorithm']) {
            if (!openssl_sign($headerCanonData, $signature, $this->key, \OPENSSL_ALGO_SHA256)) {
                throw new RuntimeException('Unable to sign DKIM hash: '.openssl_error_string());
            }
        } else {
            throw new \RuntimeException(sprintf('The "%s" DKIM signing algorithm is not supported yet.', self::ALGO_ED25519));
        }
        $header->setValue($value.' b='.trim(chunk_split(base64_encode($signature), 73, ' ')));
        $headers->add($header);

        return new Message($headers, $message->getBody());
    }

    private function canonicalizeHeader(string $header, string $headerCanon): string
    {
        if (self::CANON_RELAXED !== $headerCanon) {
            return $header."\r\n";
        }

        $exploded = explode(':', $header, 2);
        $name = strtolower(trim($exploded[0]));
        $value = str_replace("\r\n", '', $exploded[1]);
        $value = trim(preg_replace("/[ \t][ \t]+/", ' ', $value));

        return $name.':'.$value."\r\n";
    }

    private function hashBody(AbstractPart $body, string $bodyCanon, int $maxLength): array
    {
        $hash = hash_init('sha256');
        $relaxed = self::CANON_RELAXED === $bodyCanon;
        $currentLine = '';
        $emptyCounter = 0;
        $isSpaceSequence = false;
        $length = 0;
        foreach ($body->bodyToIterable() as $chunk) {
            $canon = '';
            for ($i = 0, $len = \strlen($chunk); $i < $len; ++$i) {
                switch ($chunk[$i]) {
                    case "\r":
                        break;
                    case "\n":
                        // previous char is always \r
                        if ($relaxed) {
                            $isSpaceSequence = false;
                        }
                        if ('' === $currentLine) {
                            ++$emptyCounter;
                        } else {
                            $currentLine = '';
                            $canon .= "\r\n";
                        }
                        break;
                    case ' ':
                    case "\t":
                        if ($relaxed) {
                            $isSpaceSequence = true;
                            break;
                        }
                        // no break
                    default:
                        if ($emptyCounter > 0) {
                            $canon .= str_repeat("\r\n", $emptyCounter);
                            $emptyCounter = 0;
                        }
                        if ($isSpaceSequence) {
                            $currentLine .= ' ';
                            $canon .= ' ';
                            $isSpaceSequence = false;
                        }
                        $currentLine .= $chunk[$i];
                        $canon .= $chunk[$i];
                }
            }

            if ($length + \strlen($canon) >= $maxLength) {
                $canon = substr($canon, 0, $maxLength - $length);
                $length += \strlen($canon);
                hash_update($hash, $canon);

                break;
            }

            $length += \strlen($canon);
            hash_update($hash, $canon);
        }

        // Add trailing Line return if last line is non empty
        if ('' !== $currentLine) {
            hash_update($hash, "\r\n");
            $length += \strlen("\r\n");
        }

        if (!$relaxed && 0 === $length) {
            hash_update($hash, "\r\n");
            $length = 2;
        }

        return [hash_final($hash, true), $length];
    }
}
SMimeSigner.php000064400000005252151520545030007443 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Mime\Crypto;

use Symfony\Component\Mime\Exception\RuntimeException;
use Symfony\Component\Mime\Message;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
final class SMimeSigner extends SMime
{
    private string $signCertificate;
    private string|array $signPrivateKey;
    private int $signOptions;
    private ?string $extraCerts;

    /**
     * @param string      $certificate          The path of the file containing the signing certificate (in PEM format)
     * @param string      $privateKey           The path of the file containing the private key (in PEM format)
     * @param string|null $privateKeyPassphrase A passphrase of the private key (if any)
     * @param string|null $extraCerts           The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate
     * @param int|null    $signOptions          Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php)
     */
    public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, ?int $signOptions = null)
    {
        if (!\extension_loaded('openssl')) {
            throw new \LogicException('PHP extension "openssl" is required to use SMime.');
        }

        $this->signCertificate = $this->normalizeFilePath($certificate);

        if (null !== $privateKeyPassphrase) {
            $this->signPrivateKey = [$this->normalizeFilePath($privateKey), $privateKeyPassphrase];
        } else {
            $this->signPrivateKey = $this->normalizeFilePath($privateKey);
        }

        $this->signOptions = $signOptions ?? \PKCS7_DETACHED;
        $this->extraCerts = $extraCerts ? realpath($extraCerts) : null;
    }

    public function sign(Message $message): Message
    {
        $bufferFile = tmpfile();
        $outputFile = tmpfile();

        $this->iteratorToFile($message->getBody()->toIterable(), $bufferFile);

        if (!@openssl_pkcs7_sign(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->signCertificate, $this->signPrivateKey, [], $this->signOptions, $this->extraCerts)) {
            throw new RuntimeException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string()));
        }

        return new Message($message->getHeaders(), $this->convertMessageToSMimePart($outputFile, 'multipart', 'signed'));
    }
}
DkimOptions.php000064400000003430151520545030007515 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Mime\Crypto;

/**
 * A helper providing autocompletion for available DkimSigner options.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class DkimOptions
{
    private array $options = [];

    public function toArray(): array
    {
        return $this->options;
    }

    /**
     * @return $this
     */
    public function algorithm(string $algo): static
    {
        $this->options['algorithm'] = $algo;

        return $this;
    }

    /**
     * @return $this
     */
    public function signatureExpirationDelay(int $show): static
    {
        $this->options['signature_expiration_delay'] = $show;

        return $this;
    }

    /**
     * @return $this
     */
    public function bodyMaxLength(int $max): static
    {
        $this->options['body_max_length'] = $max;

        return $this;
    }

    /**
     * @return $this
     */
    public function bodyShowLength(bool $show): static
    {
        $this->options['body_show_length'] = $show;

        return $this;
    }

    /**
     * @return $this
     */
    public function headerCanon(string $canon): static
    {
        $this->options['header_canon'] = $canon;

        return $this;
    }

    /**
     * @return $this
     */
    public function bodyCanon(string $canon): static
    {
        $this->options['body_canon'] = $canon;

        return $this;
    }

    /**
     * @return $this
     */
    public function headersToIgnore(array $headers): static
    {
        $this->options['headers_to_ignore'] = $headers;

        return $this;
    }
}
SMime.php000064400000006277151520545030006303 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Mime\Crypto;

use Symfony\Component\Mime\Exception\RuntimeException;
use Symfony\Component\Mime\Part\SMimePart;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * @internal
 */
abstract class SMime
{
    protected function normalizeFilePath(string $path): string
    {
        if (!file_exists($path)) {
            throw new RuntimeException(sprintf('File does not exist: "%s".', $path));
        }

        return 'file://'.str_replace('\\', '/', realpath($path));
    }

    protected function iteratorToFile(iterable $iterator, $stream): void
    {
        foreach ($iterator as $chunk) {
            fwrite($stream, $chunk);
        }
    }

    protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart
    {
        rewind($stream);

        $headers = '';

        while (!feof($stream)) {
            $buffer = fread($stream, 78);
            $headers .= $buffer;

            // Detect ending of header list
            if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) {
                $headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]);

                break;
            }
        }

        $headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd)));

        fseek($stream, $headersPosEnd + \strlen($headerBodySeparator));

        return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type']));
    }

    protected function getStreamIterator($stream): iterable
    {
        while (!feof($stream)) {
            yield str_replace("\n", "\r\n", str_replace("\r\n", "\n", fread($stream, 16372)));
        }
    }

    private function getMessageHeaders(string $headerData): array
    {
        $headers = [];
        $headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData)));
        $currentHeaderName = '';

        // Transform header lines into an associative array
        foreach ($headerLines as $headerLine) {
            // Empty lines between headers indicate a new mime-entity
            if ('' === $headerLine) {
                break;
            }

            // Handle headers that span multiple lines
            if (!str_contains($headerLine, ':')) {
                $headers[$currentHeaderName] .= ' '.trim($headerLine);
                continue;
            }

            $header = explode(':', $headerLine, 2);
            $currentHeaderName = strtolower($header[0]);
            $headers[$currentHeaderName] = trim($header[1]);
        }

        return $headers;
    }

    private function getParametersFromHeader(string $header): array
    {
        $params = [];

        preg_match_all('/(?P<name>[a-z-0-9]+)=(?P<value>"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches);

        foreach ($matches['value'] as $pos => $paramValue) {
            $params[$matches['name'][$pos]] = trim($paramValue, '"');
        }

        return $params;
    }
}
AesGcmDecryptingStream.php000064400000005314151520661330011626 0ustar00<?php
namespace Aws\Crypto;

use Aws\Exception\CryptoException;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\StreamDecoratorTrait;
use Psr\Http\Message\StreamInterface;
use Aws\Crypto\Polyfill\AesGcm;
use Aws\Crypto\Polyfill\Key;

/**
 * @internal Represents a stream of data to be gcm decrypted.
 */
class AesGcmDecryptingStream implements AesStreamInterface
{
    use StreamDecoratorTrait;

    private $aad;

    private $initializationVector;

    private $key;

    private $keySize;

    private $cipherText;

    private $tag;

    private $tagLength;

    /**
     * @var StreamInterface
     */
    private $stream;

    /**
     * @param StreamInterface $cipherText
     * @param string $key
     * @param string $initializationVector
     * @param string $tag
     * @param string $aad
     * @param int $tagLength
     * @param int $keySize
     */
    public function __construct(
        StreamInterface $cipherText,
        $key,
        $initializationVector,
        $tag,
        $aad = '',
        $tagLength = 128,
        $keySize = 256
    ) {
        $this->cipherText = $cipherText;
        $this->key = $key;
        $this->initializationVector = $initializationVector;
        $this->tag = $tag;
        $this->aad = $aad;
        $this->tagLength = $tagLength;
        $this->keySize = $keySize;
        // unsetting the property forces the first access to go through
        // __get().
        unset($this->stream);
    }

    public function getOpenSslName()
    {
        return "aes-{$this->keySize}-gcm";
    }

    public function getAesName()
    {
        return 'AES/GCM/NoPadding';
    }

    public function getCurrentIv()
    {
        return $this->initializationVector;
    }

    public function createStream()
    {
        if (version_compare(PHP_VERSION, '7.1', '<')) {
            return Psr7\Utils::streamFor(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\Utils::streamFor($result);
        }
    }

    public function isWritable(): bool
    {
        return false;
    }
}
MaterialsProvider.php000064400000006351151520661330010720 0ustar00<?php
namespace Aws\Crypto;

abstract class MaterialsProvider implements MaterialsProviderInterface
{
    private static $supportedKeySizes = [
        128 => true,
        192 => 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]);
    }

    /**
     * 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.
     *
     * @return MaterialsProvider
     *
     * @throws \RuntimeException Thrown when there is an empty or improperly
     *                           formed materials description in the envelope.
     *
     * @internal
     */
    abstract public function fromDecryptionEnvelope(MetadataEnvelope $envelope);

    /**
     * Returns the material description for this Provider so it can be verified
     * by encryption mechanisms.
     *
     * @return string
     */
    abstract public function getMaterialsDescription();

    /**
     * Returns the wrap algorithm name for this Provider.
     *
     * @return string
     */
    abstract public function getWrapAlgorithmName();

    /**
     * Takes a content encryption key (CEK) and description to return an
     * encrypted key according to the Provider's specifications.
     *
     * @param string $unencryptedCek Key for use in encrypting other data
     *                               that itself needs to be encrypted by the
     *                               Provider.
     * @param string $materialDescription Material Description for use in
     *                                    encrypting the $cek.
     *
     * @return string
     */
    abstract public function encryptCek($unencryptedCek, $materialDescription);

    /**
     * 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
     */
    abstract 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)
    {
        return openssl_random_pseudo_bytes($keySize / 8);
    }

    /**
     * @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)
        );
    }
}
AesEncryptingStream.php000064400000007267151520661330011222 0ustar00<?php
namespace Aws\Crypto;

use GuzzleHttp\Psr7\StreamDecoratorTrait;
use \LogicException;
use Psr\Http\Message\StreamInterface;
use Aws\Crypto\Cipher\CipherMethod;

/**
 * @internal Represents a stream of data to be encrypted with a passed cipher.
 */
class AesEncryptingStream implements AesStreamInterface
{
    const BLOCK_SIZE = 16; // 128 bits

    use StreamDecoratorTrait;

    /**
     * @var string
     */
    private $buffer = '';

    /**
     * @var CipherMethod
     */
    private $cipherMethod;

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

    /**
     * @var StreamInterface
     */
    private $stream;

    /**
     * @param StreamInterface $plainText
     * @param string $key
     * @param CipherMethod $cipherMethod
     */
    public function __construct(
        StreamInterface $plainText,
        $key,
        CipherMethod $cipherMethod
    ) {
        $this->stream = $plainText;
        $this->key = $key;
        $this->cipherMethod = clone $cipherMethod;
    }

    public function getOpenSslName()
    {
        return $this->cipherMethod->getOpenSslName();
    }

    public function getAesName()
    {
        return $this->cipherMethod->getAesName();
    }

    public function getCurrentIv()
    {
        return $this->cipherMethod->getCurrentIv();
    }

    public function getSize(): ?int
    {
        $plainTextSize = $this->stream->getSize();

        if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) {
            // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be
            // added to the plaintext to make it an even number of blocks.
            $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE;
            return $plainTextSize + $padding;
        }

        return $plainTextSize;
    }

    public function isWritable(): bool
    {
        return false;
    }

    public function read($length): string
    {
        if ($length > strlen($this->buffer)) {
            $this->buffer .= $this->encryptBlock(
                (int)
                self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE)
            );
        }

        $data = substr($this->buffer, 0, $length);
        $this->buffer = substr($this->buffer, $length);

        return $data ? $data : '';
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        if ($whence === SEEK_CUR) {
            $offset = $this->tell() + $offset;
            $whence = SEEK_SET;
        }

        if ($whence === SEEK_SET) {
            $this->buffer = '';
            $wholeBlockOffset
                = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE;
            $this->stream->seek($wholeBlockOffset);
            $this->cipherMethod->seek($wholeBlockOffset);
            $this->read($offset - $wholeBlockOffset);
        } else {
            throw new LogicException('Unrecognized whence.');
        }
    }

    private function encryptBlock($length)
    {
        if ($this->stream->eof()) {
            return '';
        }

        $plainText = '';
        do {
            $plainText .= $this->stream->read((int) ($length - strlen($plainText)));
        } while (strlen($plainText) < $length && !$this->stream->eof());

        $options = OPENSSL_RAW_DATA;
        if (!$this->stream->eof()
            || $this->stream->getSize() !== $this->stream->tell()
        ) {
            $options |= OPENSSL_ZERO_PADDING;
        }

        $cipherText = openssl_encrypt(
            $plainText,
            $this->cipherMethod->getOpenSslName(),
            $this->key,
            $options,
            $this->cipherMethod->getCurrentIv()
        );

        $this->cipherMethod->update($cipherText);

        return $cipherText;
    }
}
MaterialsProviderInterfaceV2.php000064400000003261151520661330012746 0ustar00<?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);
}
EncryptionTraitV2.php000064400000016061151520661330010631 0ustar00<?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\Utils::streamFor($cipherOptions['Tag']));
                return $appendStream;
        }
    }
}
AesDecryptingStream.php000064400000007010151520661330011172 0ustar00<?php
namespace Aws\Crypto;

use GuzzleHttp\Psr7\StreamDecoratorTrait;
use \LogicException;
use Psr\Http\Message\StreamInterface;
use Aws\Crypto\Cipher\CipherMethod;

/**
 * @internal Represents a stream of data to be decrypted with passed cipher.
 */
class AesDecryptingStream implements AesStreamInterface
{
    const BLOCK_SIZE = 16; // 128 bits

    use StreamDecoratorTrait;

    /**
     * @var string
     */
    private $buffer = '';

    /**
     * @var CipherMethod
     */
    private $cipherMethod;

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

    /**
     * @var StreamInterface
     */
    private $stream;

    /**
     * @param StreamInterface $cipherText
     * @param string $key
     * @param CipherMethod $cipherMethod
     */
    public function __construct(
        StreamInterface $cipherText,
        $key,
        CipherMethod $cipherMethod
    ) {
        $this->stream = $cipherText;
        $this->key = $key;
        $this->cipherMethod = clone $cipherMethod;
    }

    public function getOpenSslName()
    {
        return $this->cipherMethod->getOpenSslName();
    }

    public function getAesName()
    {
        return $this->cipherMethod->getAesName();
    }

    public function getCurrentIv()
    {
        return $this->cipherMethod->getCurrentIv();
    }

    public function getSize(): ?int
    {
        $plainTextSize = $this->stream->getSize();

        if ($this->cipherMethod->requiresPadding()) {
            // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be
            // added to the plaintext to make it an even number of blocks. The
            // plaintext is between strlen($cipherText) - self::BLOCK_SIZE and
            // strlen($cipherText) - 1
            return null;
        }

        return $plainTextSize;
    }

    public function isWritable(): bool
    {
        return false;
    }

    public function read($length): string
    {
        if ($length > strlen($this->buffer)) {
            $this->buffer .= $this->decryptBlock(
                (int) (
                        self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE)
                )
            );
        }

        $data = substr($this->buffer, 0, $length);
        $this->buffer = substr($this->buffer, $length);

        return $data ? $data : '';
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        if ($offset === 0 && $whence === SEEK_SET) {
            $this->buffer = '';
            $this->cipherMethod->seek(0, SEEK_SET);
            $this->stream->seek(0, SEEK_SET);
        } else {
            throw new LogicException('AES encryption streams only support being'
                . ' rewound, not arbitrary seeking.');
        }
    }

    private function decryptBlock($length)
    {
        if ($this->stream->eof()) {
            return '';
        }

        $cipherText = '';
        do {
            $cipherText .= $this->stream->read((int) ($length - strlen($cipherText)));
        } while (strlen($cipherText) < $length && !$this->stream->eof());

        $options = OPENSSL_RAW_DATA;
        if (!$this->stream->eof()
            && $this->stream->getSize() !== $this->stream->tell()
        ) {
            $options |= OPENSSL_ZERO_PADDING;
        }

        $plaintext = openssl_decrypt(
            $cipherText,
            $this->cipherMethod->getOpenSslName(),
            $this->key,
            $options,
            $this->cipherMethod->getCurrentIv()
        );

        $this->cipherMethod->update($cipherText);

        return $plaintext;
    }
}
AesGcmEncryptingStream.php000064400000005621151520661330011641 0ustar00<?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;
use \RuntimeException;

/**
 * @internal Represents a stream of data to be gcm encrypted.
 */
class AesGcmEncryptingStream implements AesStreamInterface, AesStreamInterfaceV2
{
    use StreamDecoratorTrait;

    private $aad;

    private $initializationVector;

    private $key;

    private $keySize;

    private $plaintext;

    private $tag = '';

    private $tagLength;

    /**
     * @var StreamInterface
     */
    private $stream;

    /**
     * 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
     * @param string $initializationVector
     * @param string $aad
     * @param int $tagLength
     * @param int $keySize
     */
    public function __construct(
        StreamInterface $plaintext,
        $key,
        $initializationVector,
        $aad = '',
        $tagLength = 16,
        $keySize = 256
    ) {

        $this->plaintext = $plaintext;
        $this->key = $key;
        $this->initializationVector = $initializationVector;
        $this->aad = $aad;
        $this->tagLength = $tagLength;
        $this->keySize = $keySize;
        // unsetting the property forces the first access to go through
        // __get().
        unset($this->stream);
    }

    public function getOpenSslName()
    {
        return "aes-{$this->keySize}-gcm";
    }

    /**
     * Same as static method and retained for backwards compatibility
     *
     * @return string
     */
    public function getAesName()
    {
        return self::getStaticAesName();
    }

    public function getCurrentIv()
    {
        return $this->initializationVector;
    }

    public function createStream()
    {
        if (version_compare(PHP_VERSION, '7.1', '<')) {
            return Psr7\Utils::streamFor(AesGcm::encrypt(
                (string) $this->plaintext,
                $this->initializationVector,
                new Key($this->key),
                $this->aad,
                $this->tag,
                $this->keySize
            ));
        } else {
            return Psr7\Utils::streamFor(\openssl_encrypt(
                (string)$this->plaintext,
                $this->getOpenSslName(),
                $this->key,
                OPENSSL_RAW_DATA,
                $this->initializationVector,
                $this->tag,
                $this->aad,
                $this->tagLength
            ));
        }
    }

    /**
     * @return string
     */
    public function getTag()
    {
        return $this->tag;
    }

    public function isWritable(): bool
    {
        return false;
    }
}
EncryptionTrait.php000064400000015566151520661330010432 0ustar00<?php
namespace Aws\Crypto;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\AppendStream;
use GuzzleHttp\Psr7\Stream;

trait EncryptionTrait
{
    private static $allowedOptions = [
        'Cipher' => true,
        'KeySize' => true,
        'Aad' => true,
    ];

    /**
     * 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 $cipherOptions Options for use in determining the cipher to
     *                             be used for encrypting data.
     * @param MaterialsProvider $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
     *
     * @throws \InvalidArgumentException Thrown when a value in $cipherOptions
     *                                   is not valid.
     *
     * @internal
     */
    public function encrypt(
        Stream $plaintext,
        array $cipherOptions,
        MaterialsProvider $provider,
        MetadataEnvelope $envelope
    ) {
        $materialsDescription = $provider->getMaterialsDescription();

        $cipherOptions = array_intersect_key(
            $cipherOptions,
            self::$allowedOptions
        );

        if (empty($cipherOptions['Cipher'])) {
            throw new \InvalidArgumentException('An encryption cipher must be'
                . ' specified in the "cipher_options".');
        }

        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 (!MaterialsProvider::isSupportedKeySize(
            $cipherOptions['KeySize']
        )) {
            throw new \InvalidArgumentException('The cipher "KeySize" requested'
                . ' is not supported by AES (128, 192, or 256).');
        }

        $cipherOptions['Iv'] = $provider->generateIv(
            $this->getCipherOpenSslName(
                $cipherOptions['Cipher'],
                $cipherOptions['KeySize']
            )
        );

        $cek = $provider->generateCek($cipherOptions['KeySize']);

        list($encryptingStream, $aesName) = $this->getEncryptingStream(
            $plaintext,
            $cek,
            $cipherOptions
        );

        // Populate envelope data
        $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] =
            $provider->encryptCek(
                $cek,
                $materialsDescription
            );
        unset($cek);

        $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']) {
            case 'gcm':
                $cipherOptions['TagLength'] = 16;

                $cipherTextStream = new AesGcmEncryptingStream(
                    $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\Utils::streamFor($cipherOptions['Tag']));
                return [$appendStream, $cipherTextStream->getAesName()];
            default:
                $cipherMethod = $this->buildCipherMethod(
                    $cipherOptions['Cipher'],
                    $cipherOptions['Iv'],
                    $cipherOptions['KeySize']
                );
                $cipherTextStream = new AesEncryptingStream(
                    $plaintext,
                    $cek,
                    $cipherMethod
                );
                return [$cipherTextStream, $cipherTextStream->getAesName()];
        }
    }
}
AesStreamInterfaceV2.php000064400000001330151520661330011171 0ustar00<?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();
}
DecryptionTrait.php000064400000013724151520661330010412 0ustar00<?php
namespace Aws\Crypto;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\LimitStream;
use Psr\Http\Message\StreamInterface;

trait DecryptionTrait
{
    /**
     * 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 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.
     *
     * @return AesStreamInterface
     *
     * @throws \InvalidArgumentException Thrown when a value in $cipherOptions
     *                                   is not valid.
     *
     * @internal
     */
    public function decrypt(
        $cipherText,
        MaterialsProviderInterface $provider,
        MetadataEnvelope $envelope,
        array $cipherOptions = []
    ) {
        $cipherOptions['Iv'] = base64_decode(
            $envelope[MetadataEnvelope::IV_HEADER]
        );

        $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
            )
        );
        $cipherOptions['KeySize'] = strlen($cek) * 8;
        $cipherOptions['Cipher'] = $this->getCipherFromAesName(
            $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]
        );

        $decryptionStream = $this->getDecryptingStream(
            $cipherText,
            $cek,
            $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
        );
    }

    /**
     * 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\Utils::streamFor($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']
                        : '',
                    $cipherOptions['TagLength'] ?: null,
                    $cipherOptions['KeySize']
                );
            default:
                $cipherMethod = $this->buildCipherMethod(
                    $cipherOptions['Cipher'],
                    $cipherOptions['Iv'],
                    $cipherOptions['KeySize']
                );
                return new AesDecryptingStream(
                    $cipherTextStream,
                    $cek,
                    $cipherMethod
                );
        }
    }
}
AbstractCryptoClientV2.php000064400000007605151520661330011602 0ustar00<?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 = []
    );
}
AesStreamInterface.php000064400000001220151520661330010757 0ustar00<?php
namespace Aws\Crypto;

use Psr\Http\Message\StreamInterface;

interface AesStreamInterface extends StreamInterface
{
    /**
     * Returns an identifier recognizable by `openssl_*` functions, such as
     * `aes-256-cbc` or `aes-128-ctr`.
     *
     * @return string
     */
    public function getOpenSslName();

    /**
     * Returns an AES recognizable name, such as 'AES/GCM/NoPadding'.
     *
     * @return string
     */
    public function getAesName();

    /**
     * Returns the IV that should be used to initialize the next block in
     * encrypt or decrypt.
     *
     * @return string
     */
    public function getCurrentIv();
}
MaterialsProviderInterface.php000064400000003473151520661330012543 0ustar00<?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);
}
AbstractCryptoClient.php000064400000007764151520661330011400 0ustar00<?php
namespace Aws\Crypto;

use Aws\Crypto\Cipher\CipherMethod;
use Aws\Crypto\Cipher\Cbc;
use GuzzleHttp\Psr7\Stream;

/**
 * Legacy abstract encryption client. New workflows should use
 * AbstractCryptoClientV2.
 *
 * @deprecated
 * @internal
 */
abstract class AbstractCryptoClient
{
    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.
     *
     * @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);
    }

    /**
     * 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
     */
    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 $cipherOptions Options for use in determining the cipher to
     *                             be used for encrypting data.
     * @param MaterialsProvider $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 $cipherOptions,
        MaterialsProvider $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 $cipherOptions Additional verification options.
     *
     * @return AesStreamInterface
     *
     * @internal
     */
    abstract public function decrypt(
        $cipherText,
        MaterialsProviderInterface $provider,
        MetadataEnvelope $envelope,
        array $cipherOptions = []
    );
}
MetadataEnvelope.php000064400000003213151520661330010474 0ustar00<?php
namespace Aws\Crypto;

use Aws\HasDataTrait;
use \ArrayAccess;
use \IteratorAggregate;
use \InvalidArgumentException;
use \JsonSerializable;

/**
 * Stores encryption metadata for reading and writing.
 *
 * @internal
 */
class MetadataEnvelope implements ArrayAccess, IteratorAggregate, JsonSerializable
{
    use HasDataTrait;

    const CONTENT_KEY_V2_HEADER = 'x-amz-key-v2';
    const IV_HEADER = 'x-amz-iv';
    const MATERIALS_DESCRIPTION_HEADER = 'x-amz-matdesc';
    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_LENGTH_HEADER = 'x-amz-unencrypted-content-length';

    private static $constants = [];

    public static function getConstantValues()
    {
        if (empty(self::$constants)) {
            $reflection = new \ReflectionClass(static::class);
            foreach (array_values($reflection->getConstants()) as $constant) {
                self::$constants[$constant] = true;
            }
        }

        return array_keys(self::$constants);
    }

    /**
     * @return void
     */
    #[\ReturnTypeWillChange]
    public function offsetSet($name, $value)
    {
        $constants = self::getConstantValues();
        if (is_null($name) || !in_array($name, $constants)) {
            throw new InvalidArgumentException('MetadataEnvelope fields must'
                . ' must match a predefined offset; use the header constants.');
        }

        $this->data[$name] = $value;
    }

    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        return $this->data;
    }
}
MetadataStrategyInterface.php000064400000001744151520661330012351 0ustar00<?php
namespace Aws\Crypto;

interface MetadataStrategyInterface
{
    /**
     * Places the information in the MetadataEnvelope to the strategy specific
     * location. Populates the PutObject arguments with any information
     * necessary for loading.
     *
     * @param MetadataEnvelope $envelope Encryption data to save according to
     *                                   the strategy.
     * @param array $args Starting arguments for PutObject.
     *
     * @return array Updated arguments for PutObject.
     */
    public function save(MetadataEnvelope $envelope, array $args);

    /**
     * Generates a MetadataEnvelope according to the specific strategy using the
     * passed arguments.
     *
     * @param array $args Arguments from Command and Result that contains
     *                    S3 Object information, relevant headers, and command
     *                    configuration.
     *
     * @return MetadataEnvelope
     */
    public function load(array $args);
}
DecryptionTraitV2.php000064400000022051151520661330010613 0ustar00<?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\Utils::streamFor($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']
                        : '',
                    $cipherOptions['TagLength'] ?: null,
                    $cipherOptions['KeySize']
                );
            default:
                $cipherMethod = $this->buildCipherMethod(
                    $cipherOptions['Cipher'],
                    $cipherOptions['Iv'],
                    $cipherOptions['KeySize']
                );
                return new AesDecryptingStream(
                    $cipherTextStream,
                    $cek,
                    $cipherMethod
                );
        }
    }
}
KmsMaterialsProviderV2.php000064400000006672151520661330011611 0ustar00<?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
        ];
    }
}
Cipher/CipherMethod.php000064400000003124151520661330011044 0ustar00<?php
namespace Aws\Crypto\Cipher;

interface CipherMethod
{
    /**
     * Returns an identifier recognizable by `openssl_*` functions, such as
     * `aes-256-cbc` or `aes-128-ctr`.
     *
     * @return string
     */
    public function getOpenSslName();

    /**
     * Returns an AES recognizable name, such as 'AES/GCM/NoPadding'.
     *
     * @return string
     */
    public function getAesName();

    /**
     * Returns the IV that should be used to initialize the next block in
     * encrypt or decrypt.
     *
     * @return string
     */
    public function getCurrentIv();

    /**
     * Indicates whether the cipher method used with this IV requires padding
     * the final block to make sure the plaintext is evenly divisible by the
     * block size.
     *
     * @return boolean
     */
    public function requiresPadding();

    /**
     * Adjust the return of this::getCurrentIv to reflect a seek performed on
     * the encryption stream using this IV object.
     *
     * @param int $offset
     * @param int $whence
     *
     * @throws LogicException   Thrown if the requested seek is not supported by
     *                          this IV implementation. For example, a CBC IV
     *                          only supports a full rewind ($offset === 0 &&
     *                          $whence === SEEK_SET)
     */
    public function seek($offset, $whence = SEEK_SET);

    /**
     * Take account of the last cipher text block to adjust the return of
     * this::getCurrentIv
     *
     * @param string $cipherTextBlock
     */
    public function update($cipherTextBlock);
}
Cipher/CipherBuilderTrait.php000064400000003751151520661330012224 0ustar00<?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.');
        }
    }
}Cipher/Cbc.php000064400000004063151520661330007163 0ustar00<?php
namespace Aws\Crypto\Cipher;

use \InvalidArgumentException;
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
{
    const BLOCK_SIZE = 16;

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

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

    /**
     * @var int
     */
    private $keySize;

    /**
     * @param string $iv Base Initialization Vector for the cipher.
     * @param int $keySize Size of the encryption key, in bits, that will be
     *                     used.
     *
     * @throws InvalidArgumentException Thrown if the passed iv does not match
     *                                  the iv length required by the cipher.
     */
    public function __construct($iv, $keySize = 256)
    {
        $this->baseIv = $this->iv = $iv;
        $this->keySize = $keySize;

        if (strlen($iv) !== openssl_cipher_iv_length($this->getOpenSslName())) {
            throw new InvalidArgumentException('Invalid initialization vector');
        }
    }

    public function getOpenSslName()
    {
        return "aes-{$this->keySize}-cbc";
    }

    public function getAesName()
    {
        return 'AES/CBC/PKCS5Padding';
    }

    public function getCurrentIv()
    {
        return $this->iv;
    }

    public function requiresPadding()
    {
        return true;
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        if ($offset === 0 && $whence === SEEK_SET) {
            $this->iv = $this->baseIv;
        } else {
            throw new LogicException('CBC initialization only support being'
                . ' rewound, not arbitrary seeking.');
        }
    }

    public function update($cipherTextBlock)
    {
        $this->iv = substr($cipherTextBlock, self::BLOCK_SIZE * -1);
    }
}
Polyfill/NeedsTrait.php000064400000001771151520661330011121 0ustar00<?php
namespace Aws\Crypto\Polyfill;

use Aws\Exception\CryptoPolyfillException;

/**
 * Trait NeedsTrait
 */
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);
        }
    }
}
Polyfill/Key.php000064400000003302151520661330007577 0ustar00<?php
namespace Aws\Crypto\Polyfill;

/**
 * Class Key
 *
 * Wraps a string to keep it hidden from stack traces.
 */
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);
    }
}
Polyfill/ByteArray.php000064400000015532151520661330010761 0ustar00<?php
namespace Aws\Crypto\Polyfill;

/**
 * Class ByteArray
 */
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
     */
    #[\ReturnTypeWillChange]
    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);
    }
}
Polyfill/AesGcm.php000064400000015462151520661330010220 0ustar00<?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.
 */
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);
    }
}
Polyfill/Gmac.php000064400000014232151520661330007722 0ustar00<?php
namespace Aws\Crypto\Polyfill;

/**
 * Class Gmac
 */
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;
    }
}
MaterialsProviderV2.php000064400000004007151520661330011124 0ustar00<?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)
        );
    }
}
KmsMaterialsProvider.php000064400000007625151520661330011400 0ustar00<?php
namespace Aws\Crypto;

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 implements MaterialsProviderInterface
{
    const WRAP_ALGORITHM_NAME = 'kms';

    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;
    }

    public function fromDecryptionEnvelope(MetadataEnvelope $envelope)
    {
        if (empty($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER])) {
            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'])
            && 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 self(
            $this->kmsClient,
            isset($materialsDescription['kms_cmk_id'])
                ? $materialsDescription['kms_cmk_id']
                : null
        );
    }

    /**
     * The KMS key id for use in matching this Provider to its keys,
     * consistently with other SDKs as 'kms_cmk_id'.
     *
     * @return array
     */
    public function getMaterialsDescription()
    {
        return ['kms_cmk_id' => $this->kmsKeyId];
    }

    public function getWrapAlgorithmName()
    {
        return self::WRAP_ALGORITHM_NAME;
    }

    /**
     * Takes a content encryption key (CEK) and description to return an encrypted
     * key by using KMS' Encrypt API.
     *
     * @param string $unencryptedCek Key for use in encrypting other data
     *                               that itself needs to be encrypted by the
     *                               Provider.
     * @param string $materialDescription Material Description for use in
     *                                    encrypting the $cek.
     *
     * @return string
     */
    public function encryptCek($unencryptedCek, $materialDescription)
    {
        $encryptedDataKey = $this->kmsClient->encrypt([
            'Plaintext' => $unencryptedCek,
            'KeyId' => $this->kmsKeyId,
            'EncryptionContext' => $materialDescription
        ]);
        return base64_encode($encryptedDataKey['CiphertextBlob']);
    }

    /**
     * Takes an encrypted content encryption key (CEK) and material description
     * for use decrypting the key by using KMS' Decrypt API.
     *
     * @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)
    {
        $result = $this->kmsClient->decrypt([
            'CiphertextBlob' => $encryptedCek,
            'EncryptionContext' => $materialDescription
        ]);

        return $result['Plaintext'];
    }
}