| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 | <?phpnamespace 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                );        }    }}
 |