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'] : null, $cipherOptions['TagLength'] ?: null, $cipherOptions['KeySize'] ); default: $cipherMethod = $this->buildCipherMethod( $cipherOptions['Cipher'], $cipherOptions['Iv'], $cipherOptions['KeySize'] ); return new AesDecryptingStream( $cipherTextStream, $cek, $cipherMethod ); } } }