decryptCek(base64_decode($envelope[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::CONTENT_KEY_V2_HEADER]), json_decode($envelope[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], true), $options); $options['@CipherOptions']['KeySize'] = strlen($cek) * 8; $options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName($envelope[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]); $this->validateOptionsAndEnvelope($options, $envelope); $decryptionStream = $this->getDecryptingStream($cipherText, $cek, $options['@CipherOptions']); unset($cek); return $decryptionStream; } private function getTagFromCiphertextStream(\UglyRobot\Infinite_Uploads\Psr\Http\Message\StreamInterface $cipherText, $tagLength) { $cipherTextSize = $cipherText->getSize(); if ($cipherTextSize == null || $cipherTextSize <= 0) { throw new \RuntimeException('Cannot decrypt a stream of unknown' . ' size.'); } return (string) new \UglyRobot\Infinite_Uploads\GuzzleHttp\Psr7\LimitStream($cipherText, $tagLength, $cipherTextSize - $tagLength); } private function getStrippedCiphertextStream(\UglyRobot\Infinite_Uploads\Psr\Http\Message\StreamInterface $cipherText, $tagLength) { $cipherTextSize = $cipherText->getSize(); if ($cipherTextSize == null || $cipherTextSize <= 0) { throw new \RuntimeException('Cannot decrypt a stream of unknown' . ' size.'); } return new \UglyRobot\Infinite_Uploads\GuzzleHttp\Psr7\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 \UglyRobot\Infinite_Uploads\Aws\Exception\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 \UglyRobot\Infinite_Uploads\Aws\Exception\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[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], $allowedKeywraps)) { if (in_array($envelope[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], AbstractCryptoClient::$supportedKeyWraps)) { throw $v1SchemaException; } throw new \UglyRobot\Infinite_Uploads\Aws\Exception\CryptoException("The requested object is encrypted with" . " the keywrap schema '{$envelope[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}'," . " which is not supported for decryption with the current security" . " profile."); } $matdesc = json_decode($envelope[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], true); if (isset($matdesc['aws:x-amz-cek-alg']) && $envelope[\UglyRobot\Infinite_Uploads\Aws\Crypto\MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !== $matdesc['aws:x-amz-cek-alg']) { throw new \UglyRobot\Infinite_Uploads\Aws\Exception\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[\UglyRobot\Infinite_Uploads\Aws\Crypto\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 = \UglyRobot\Infinite_Uploads\GuzzleHttp\Psr7\stream_for($cipherText); switch ($cipherOptions['Cipher']) { case 'gcm': $cipherOptions['Tag'] = $this->getTagFromCiphertextStream($cipherTextStream, $cipherOptions['TagLength']); return new \UglyRobot\Infinite_Uploads\Aws\Crypto\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 \UglyRobot\Infinite_Uploads\Aws\Crypto\AesDecryptingStream($cipherTextStream, $cek, $cipherMethod); } } }