ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç SMimeEncrypter.php000064400000004240152267364630010201 0ustar00 * * 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 */ final class SMimeEncrypter extends SMime { private $certs; private $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($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); } } SMime.php000064400000006277152267364630006321 0ustar00 * * 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 * * @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[a-z-0-9]+)=(?P"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches); foreach ($matches['value'] as $pos => $paramValue) { $params[$matches['name'][$pos]] = trim($paramValue, '"'); } return $params; } } DkimSigner.php000064400000017707152267364630007343 0ustar00 * * 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 * * 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 $key; private $domainName; private $selector; private $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.'); } if (!$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.php000064400000005207152267364630007461 0ustar00 * * 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 */ final class SMimeSigner extends SMime { private $signCertificate; private $signPrivateKey; private $signOptions; private $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.php000064400000003404152267364630007534 0ustar00 * * 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 */ final class DkimOptions { private $options = []; public function toArray(): array { return $this->options; } /** * @return $this */ public function algorithm(string $algo): self { $this->options['algorithm'] = $algo; return $this; } /** * @return $this */ public function signatureExpirationDelay(int $show): self { $this->options['signature_expiration_delay'] = $show; return $this; } /** * @return $this */ public function bodyMaxLength(int $max): self { $this->options['body_max_length'] = $max; return $this; } /** * @return $this */ public function bodyShowLength(bool $show): self { $this->options['body_show_length'] = $show; return $this; } /** * @return $this */ public function headerCanon(string $canon): self { $this->options['header_canon'] = $canon; return $this; } /** * @return $this */ public function bodyCanon(string $canon): self { $this->options['body_canon'] = $canon; return $this; } /** * @return $this */ public function headersToIgnore(array $headers): self { $this->options['headers_to_ignore'] = $headers; return $this; } } S3EncryptionClient.php000064400000033010152267715200010760 0ustar00client = $client; $this->instructionFileSuffix = $instructionFileSuffix; MetricsBuilder::appendMetricsCaptureMiddleware( $this->client->getHandlerList(), MetricsBuilder::S3_CRYPTO_V1N ); } private static function getDefaultStrategy() { return new HeadersMetadataStrategy(); } /** * Encrypts the data in the 'Body' field of $args and promises to upload it * to the specified location on S3. * * @param array $args Arguments for encrypting an object and uploading it * to S3 via PutObject. * * The required configuration arguments are as follows: * * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek * encrypting/decrypting for encryption metadata. * - @CipherOptions: (array) Cipher options for encrypting data. Only the * Cipher option is required. Accepts the following: * - Cipher: (string) cbc|gcm * See also: AbstractCryptoClient::$supportedCiphers. Note that * cbc is deprecated and gcm should be used when possible. * - KeySize: (int) 128|192|256 * See also: MaterialsProvider::$supportedKeySizes * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. It is ignored when * using cbc. Note if you pass in Aad for gcm encryption, the * PHP SDK will be able to decrypt the resulting object, but other * AWS SDKs may not be able to do so. * * The optional configuration arguments are as follows: * * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing * MetadataEnvelope information. Defaults to using a * HeadersMetadataStrategy. Can either be a class implementing * MetadataStrategy, a class name of a predefined strategy, or empty/null * to default. * - @InstructionFileSuffix: (string|null) Suffix used when writing to an * instruction file if using an InstructionFileMetadataHandler. * * @return PromiseInterface * * @throws \InvalidArgumentException Thrown when arguments above are not * passed or are passed incorrectly. */ public function putObjectAsync(array $args) { $provider = $this->getMaterialsProvider($args); unset($args['@MaterialsProvider']); $instructionFileSuffix = $this->getInstructionFileSuffix($args); unset($args['@InstructionFileSuffix']); $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); unset($args['@MetadataStrategy']); $envelope = new MetadataEnvelope(); return Promise\Create::promiseFor($this->encrypt( Psr7\Utils::streamFor($args['Body']), $args['@CipherOptions'] ?: [], $provider, $envelope ))->then( function ($encryptedBodyStream) use ($args) { $hash = new PhpHash('sha256'); $hashingEncryptedBodyStream = new HashingStream( $encryptedBodyStream, $hash, self::getContentShaDecorator($args) ); return [$hashingEncryptedBodyStream, $args]; } )->then( function ($putObjectContents) use ($strategy, $envelope) { list($bodyStream, $args) = $putObjectContents; if ($strategy === null) { $strategy = self::getDefaultStrategy(); } $updatedArgs = $strategy->save($envelope, $args); $updatedArgs['Body'] = $bodyStream; return $updatedArgs; } )->then( function ($args) { unset($args['@CipherOptions']); return $this->client->putObjectAsync($args); } ); } private static function getContentShaDecorator(&$args) { return function ($hash) use (&$args) { $args['ContentSHA256'] = bin2hex($hash); }; } /** * Encrypts the data in the 'Body' field of $args and uploads it to the * specified location on S3. * * @param array $args Arguments for encrypting an object and uploading it * to S3 via PutObject. * * The required configuration arguments are as follows: * * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek * encrypting/decrypting for encryption metadata. * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher * is required. Accepts the following options: * - Cipher: (string) cbc|gcm * See also: AbstractCryptoClient::$supportedCiphers. Note that * cbc is deprecated and gcm should be used when possible. * - KeySize: (int) 128|192|256 * See also: MaterialsProvider::$supportedKeySizes * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. It is ignored when * using cbc. Note if you pass in Aad for gcm encryption, the * PHP SDK will be able to decrypt the resulting object, but other * AWS SDKs may not be able to do so. * * The optional configuration arguments are as follows: * * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing * MetadataEnvelope information. Defaults to using a * HeadersMetadataStrategy. Can either be a class implementing * MetadataStrategy, a class name of a predefined strategy, or empty/null * to default. * - @InstructionFileSuffix: (string|null) Suffix used when writing to an * instruction file if an using an InstructionFileMetadataHandler was * determined. * * @return \Aws\Result PutObject call result with the details of uploading * the encrypted file. * * @throws \InvalidArgumentException Thrown when arguments above are not * passed or are passed incorrectly. */ public function putObject(array $args) { return $this->putObjectAsync($args)->wait(); } /** * Promises to retrieve an object from S3 and decrypt the data in the * 'Body' field. * * @param array $args Arguments for retrieving an object from S3 via * GetObject and decrypting it. * * The required configuration argument is as follows: * * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek * encrypting/decrypting for decryption metadata. May have data loaded * from the MetadataEnvelope upon decryption. * * The optional configuration arguments are as follows: * * - SaveAs: (string) The path to a file on disk to save the decrypted * object data. This will be handled by file_put_contents instead of the * Guzzle sink. * * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading * MetadataEnvelope information. Defaults to determining based on object * response headers. Can either be a class implementing MetadataStrategy, * a class name of a predefined strategy, or empty/null to default. * - @InstructionFileSuffix: (string) Suffix used when looking for an * instruction file if an InstructionFileMetadataHandler is being used. * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher * is required. Accepts the following options: * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. It is ignored when * using cbc. * * @return PromiseInterface * * @throws \InvalidArgumentException Thrown when required arguments are not * passed or are passed incorrectly. */ public function getObjectAsync(array $args) { $provider = $this->getMaterialsProvider($args); unset($args['@MaterialsProvider']); $instructionFileSuffix = $this->getInstructionFileSuffix($args); unset($args['@InstructionFileSuffix']); $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); unset($args['@MetadataStrategy']); $saveAs = null; if (!empty($args['SaveAs'])) { $saveAs = $args['SaveAs']; } $promise = $this->client->getObjectAsync($args) ->then( function ($result) use ( $provider, $instructionFileSuffix, $strategy, $args ) { if ($strategy === null) { $strategy = $this->determineGetObjectStrategy( $result, $instructionFileSuffix ); } $envelope = $strategy->load($args + [ 'Metadata' => $result['Metadata'] ]); $provider = $provider->fromDecryptionEnvelope($envelope); $result['Body'] = $this->decrypt( $result['Body'], $provider, $envelope, isset($args['@CipherOptions']) ? $args['@CipherOptions'] : [] ); return $result; } )->then( function ($result) use ($saveAs) { if (!empty($saveAs)) { file_put_contents( $saveAs, (string)$result['Body'], LOCK_EX ); } return $result; } ); return $promise; } /** * Retrieves an object from S3 and decrypts the data in the 'Body' field. * * @param array $args Arguments for retrieving an object from S3 via * GetObject and decrypting it. * * The required configuration argument is as follows: * * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek * encrypting/decrypting for decryption metadata. May have data loaded * from the MetadataEnvelope upon decryption. * * The optional configuration arguments are as follows: * * - SaveAs: (string) The path to a file on disk to save the decrypted * object data. This will be handled by file_put_contents instead of the * Guzzle sink. * - @InstructionFileSuffix: (string|null) Suffix used when looking for an * instruction file if an InstructionFileMetadataHandler was detected. * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher * is required. Accepts the following options: * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. It is ignored when * using cbc. * * @return \Aws\Result GetObject call result with the 'Body' field * wrapped in a decryption stream with its metadata * information. * * @throws \InvalidArgumentException Thrown when arguments above are not * passed or are passed incorrectly. */ public function getObject(array $args) { return $this->getObjectAsync($args)->wait(); } } HeadersMetadataStrategy.php000064400000003127152267715200012026 0ustar00$value) { $args['Metadata'][$header] = $value; } return $args; } /** * Generates a MetadataEnvelope according to the metadata headers from the * GetObject result. * * @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) { $envelope = new MetadataEnvelope(); $constantValues = MetadataEnvelope::getConstantValues(); foreach ($constantValues as $constant) { if (!empty($args['Metadata'][$constant])) { $envelope[$constant] = $args['Metadata'][$constant]; } } return $envelope; } } CryptoParamsTraitV2.php000064400000000771152267715200011131 0ustar00appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); $this->client = $client; $config['params'] = []; if (!empty($config['bucket'])) { $config['params']['Bucket'] = $config['bucket']; } if (!empty($config['key'])) { $config['params']['Key'] = $config['key']; } $this->provider = $this->getMaterialsProvider($config); unset($config['@MaterialsProvider']); $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); unset($config['@InstructionFileSuffix']); $this->strategy = $this->getMetadataStrategy( $config, $this->instructionFileSuffix ); if ($this->strategy === null) { $this->strategy = self::getDefaultStrategy(); } unset($config['@MetadataStrategy']); $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); parent::__construct($client, $source, $config); } private static function getDefaultStrategy() { return new HeadersMetadataStrategy(); } private function getEncryptingDataPreparer() { return function() { // Defer encryption work until promise is executed $envelope = new MetadataEnvelope(); list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( $this->source, $this->config['@cipheroptions'] ?: [], $this->provider, $envelope ))->then( function ($bodyStream) use ($envelope) { $params = $this->strategy->save( $envelope, $this->config['params'] ); return [$bodyStream, $params]; } )->wait(); $this->source->rewind(); $this->config['params'] = $params; }; } }S3EncryptionMultipartUploaderV2.php000064400000016416152267715200013442 0ustar00appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); $this->client = $client; $config['params'] = []; if (!empty($config['bucket'])) { $config['params']['Bucket'] = $config['bucket']; } if (!empty($config['key'])) { $config['params']['Key'] = $config['key']; } $this->provider = $this->getMaterialsProvider($config); unset($config['@MaterialsProvider']); $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); unset($config['@InstructionFileSuffix']); $this->strategy = $this->getMetadataStrategy( $config, $this->instructionFileSuffix ); if ($this->strategy === null) { $this->strategy = self::getDefaultStrategy(); } unset($config['@MetadataStrategy']); $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); parent::__construct($client, $source, $config); } private static function getDefaultStrategy() { return new HeadersMetadataStrategy(); } private function getEncryptingDataPreparer() { return function() { // Defer encryption work until promise is executed $envelope = new MetadataEnvelope(); list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( $this->source, $this->config ?: [], $this->provider, $envelope ))->then( function ($bodyStream) use ($envelope) { $params = $this->strategy->save( $envelope, $this->config['params'] ); return [$bodyStream, $params]; } )->wait(); $this->source->rewind(); $this->config['params'] = $params; }; } } UserAgentTrait.php000064400000001710152267715200010164 0ustar00getHandlerList(); $list->appendBuild(Middleware::mapRequest( function(RequestInterface $req) use ($agentString) { if (!empty($req->getHeader('User-Agent')) && !empty($req->getHeader('User-Agent')[0]) ) { $userAgent = $req->getHeader('User-Agent')[0]; if (strpos($userAgent, $agentString) === false) { $userAgent .= " {$agentString}"; }; } else { $userAgent = $agentString; } $req = $req->withHeader('User-Agent', $userAgent); return $req; } )); } } InstructionFileMetadataStrategy.php000064400000005625152267715200013601 0ustar00suffix = empty($suffix) ? self::DEFAULT_FILE_SUFFIX : $suffix; $this->client = $client; } /** * Places the information in the MetadataEnvelope to a location on S3. * * @param MetadataEnvelope $envelope Encryption data to save according to * the strategy. * @param array $args Starting arguments for PutObject, used for saving * extra the instruction file. * * @return array Updated arguments for PutObject. */ public function save(MetadataEnvelope $envelope, array $args) { $this->client->putObject([ 'Bucket' => $args['Bucket'], 'Key' => $args['Key'] . $this->suffix, 'Body' => json_encode($envelope) ]); return $args; } /** * Uses the strategy's client to retrieve the instruction file from S3 and generates * a MetadataEnvelope from its contents. * * @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) { $result = $this->client->getObject([ 'Bucket' => $args['Bucket'], 'Key' => $args['Key'] . $this->suffix ]); $metadataHeaders = json_decode($result['Body'], true); $envelope = new MetadataEnvelope(); $constantValues = MetadataEnvelope::getConstantValues(); foreach ($constantValues as $constant) { if (!empty($metadataHeaders[$constant])) { $envelope[$constant] = $metadataHeaders[$constant]; } } return $envelope; } } S3EncryptionClientV2.php000064400000044022152267715200011175 0ustar00 * use Aws\Crypto\KmsMaterialsProviderV2; * use Aws\S3\Crypto\S3EncryptionClientV2; * use Aws\S3\S3Client; * * $encryptionClient = new S3EncryptionClientV2( * new S3Client([ * 'region' => 'us-west-2', * 'version' => 'latest' * ]) * ); * $materialsProvider = new KmsMaterialsProviderV2( * new KmsClient([ * 'profile' => 'default', * 'region' => 'us-east-1', * 'version' => 'latest', * ], * 'your-kms-key-id' * ); * * $encryptionClient->putObject([ * '@MaterialsProvider' => $materialsProvider, * '@CipherOptions' => [ * 'Cipher' => 'gcm', * 'KeySize' => 256, * ], * '@KmsEncryptionContext' => ['foo' => 'bar'], * 'Bucket' => 'your-bucket', * 'Key' => 'your-key', * 'Body' => 'your-encrypted-data', * ]); * * * Example read call (using objects from previous example): * * * $encryptionClient->getObject([ * '@MaterialsProvider' => $materialsProvider, * '@CipherOptions' => [ * 'Cipher' => 'gcm', * 'KeySize' => 256, * ], * 'Bucket' => 'your-bucket', * 'Key' => 'your-key', * ]); * */ class S3EncryptionClientV2 extends AbstractCryptoClientV2 { use CipherBuilderTrait; use CryptoParamsTraitV2; use DecryptionTraitV2; use EncryptionTraitV2; use UserAgentTrait; const CRYPTO_VERSION = '2.1'; private $client; private $instructionFileSuffix; private $legacyWarningCount; /** * @param S3Client $client The S3Client to be used for true uploading and * retrieving objects from S3 when using the * encryption client. * @param string|null $instructionFileSuffix Suffix for a client wide * default when using instruction * files for metadata storage. */ public function __construct( S3Client $client, $instructionFileSuffix = null ) { $this->client = $client; $this->instructionFileSuffix = $instructionFileSuffix; $this->legacyWarningCount = 0; MetricsBuilder::appendMetricsCaptureMiddleware( $this->client->getHandlerList(), MetricsBuilder::S3_CRYPTO_V2 ); } private static function getDefaultStrategy() { return new HeadersMetadataStrategy(); } /** * Encrypts the data in the 'Body' field of $args and promises to upload it * to the specified location on S3. * * Note that for PHP versions of < 7.1, this operation uses an AES-GCM * polyfill for encryption since there is no native PHP support. The * performance for large inputs will be a lot slower than for PHP 7.1+, so * upgrading older PHP version environments may be necessary to use this * effectively. * * @param array $args Arguments for encrypting an object and uploading it * to S3 via PutObject. * * The required configuration arguments are as follows: * * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek * encrypting/decrypting for encryption metadata. * - @CipherOptions: (array) Cipher options for encrypting data. Only the * Cipher option is required. Accepts the following: * - Cipher: (string) gcm * See also: AbstractCryptoClientV2::$supportedCiphers * - KeySize: (int) 128|256 * See also: MaterialsProvider::$supportedKeySizes * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. Note if you pass in * Aad, the PHP SDK will be able to decrypt the resulting object, * but other AWS SDKs may not be able to do so. * - @KmsEncryptionContext: (array) Only required if using * KmsMaterialsProviderV2. An associative array of key-value * pairs to be added to the encryption context for KMS key encryption. An * empty array may be passed if no additional context is desired. * * The optional configuration arguments are as follows: * * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing * MetadataEnvelope information. Defaults to using a * HeadersMetadataStrategy. Can either be a class implementing * MetadataStrategy, a class name of a predefined strategy, or empty/null * to default. * - @InstructionFileSuffix: (string|null) Suffix used when writing to an * instruction file if using an InstructionFileMetadataHandler. * * @return PromiseInterface * * @throws \InvalidArgumentException Thrown when arguments above are not * passed or are passed incorrectly. */ public function putObjectAsync(array $args) { $provider = $this->getMaterialsProvider($args); unset($args['@MaterialsProvider']); $instructionFileSuffix = $this->getInstructionFileSuffix($args); unset($args['@InstructionFileSuffix']); $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); unset($args['@MetadataStrategy']); $envelope = new MetadataEnvelope(); return Promise\Create::promiseFor($this->encrypt( Psr7\Utils::streamFor($args['Body']), $args, $provider, $envelope ))->then( function ($encryptedBodyStream) use ($args) { $hash = new PhpHash('sha256'); $hashingEncryptedBodyStream = new HashingStream( $encryptedBodyStream, $hash, self::getContentShaDecorator($args) ); return [$hashingEncryptedBodyStream, $args]; } )->then( function ($putObjectContents) use ($strategy, $envelope) { list($bodyStream, $args) = $putObjectContents; if ($strategy === null) { $strategy = self::getDefaultStrategy(); } $updatedArgs = $strategy->save($envelope, $args); $updatedArgs['Body'] = $bodyStream; return $updatedArgs; } )->then( function ($args) { unset($args['@CipherOptions']); return $this->client->putObjectAsync($args); } ); } private static function getContentShaDecorator(&$args) { return function ($hash) use (&$args) { $args['ContentSHA256'] = bin2hex($hash); }; } /** * Encrypts the data in the 'Body' field of $args and uploads it to the * specified location on S3. * * Note that for PHP versions of < 7.1, this operation uses an AES-GCM * polyfill for encryption since there is no native PHP support. The * performance for large inputs will be a lot slower than for PHP 7.1+, so * upgrading older PHP version environments may be necessary to use this * effectively. * * @param array $args Arguments for encrypting an object and uploading it * to S3 via PutObject. * * The required configuration arguments are as follows: * * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek * encrypting/decrypting for encryption metadata. * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher * is required. Accepts the following options: * - Cipher: (string) gcm * See also: AbstractCryptoClientV2::$supportedCiphers * - KeySize: (int) 128|256 * See also: MaterialsProvider::$supportedKeySizes * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. Note if you pass in * Aad, the PHP SDK will be able to decrypt the resulting object, * but other AWS SDKs may not be able to do so. * - @KmsEncryptionContext: (array) Only required if using * KmsMaterialsProviderV2. An associative array of key-value * pairs to be added to the encryption context for KMS key encryption. An * empty array may be passed if no additional context is desired. * * The optional configuration arguments are as follows: * * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing * MetadataEnvelope information. Defaults to using a * HeadersMetadataStrategy. Can either be a class implementing * MetadataStrategy, a class name of a predefined strategy, or empty/null * to default. * - @InstructionFileSuffix: (string|null) Suffix used when writing to an * instruction file if an using an InstructionFileMetadataHandler was * determined. * * @return \Aws\Result PutObject call result with the details of uploading * the encrypted file. * * @throws \InvalidArgumentException Thrown when arguments above are not * passed or are passed incorrectly. */ public function putObject(array $args) { return $this->putObjectAsync($args)->wait(); } /** * Promises to retrieve an object from S3 and decrypt the data in the * 'Body' field. * * @param array $args Arguments for retrieving an object from S3 via * GetObject and decrypting it. * * The required configuration argument is as follows: * * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek * encrypting/decrypting for decryption metadata. May have data loaded * from the MetadataEnvelope upon decryption. * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 * content encryption and key wrap schemas are able to be decrypted. * - 'V2_AND_LEGACY' indicates that objects encrypted with both * S3EncryptionClientV2 and older legacy encryption clients are able * to be decrypted. * * The optional configuration arguments are as follows: * * - SaveAs: (string) The path to a file on disk to save the decrypted * object data. This will be handled by file_put_contents instead of the * Guzzle sink. * * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading * MetadataEnvelope information. Defaults to determining based on object * response headers. Can either be a class implementing MetadataStrategy, * a class name of a predefined strategy, or empty/null to default. * - @InstructionFileSuffix: (string) Suffix used when looking for an * instruction file if an InstructionFileMetadataHandler is being used. * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher * is required. Accepts the following options: * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. It is ignored when * using cbc. * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with * KMS materials for any KMS key ID, instead of needing the KMS key ID to * be specified and provided to the decrypt operation. Ignored for non-KMS * materials providers. Defaults to false. * * @return PromiseInterface * * @throws \InvalidArgumentException Thrown when required arguments are not * passed or are passed incorrectly. */ public function getObjectAsync(array $args) { $provider = $this->getMaterialsProvider($args); unset($args['@MaterialsProvider']); $instructionFileSuffix = $this->getInstructionFileSuffix($args); unset($args['@InstructionFileSuffix']); $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); unset($args['@MetadataStrategy']); if (!isset($args['@SecurityProfile']) || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles) ) { throw new CryptoException("@SecurityProfile is required and must be" . " set to 'V2' or 'V2_AND_LEGACY'"); } // Only throw this legacy warning once per client if (in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) && $this->legacyWarningCount < 1 ) { $this->legacyWarningCount++; trigger_error( "This S3 Encryption Client operation is configured to" . " read encrypted data with legacy encryption modes. If you" . " don't have objects encrypted with these legacy modes," . " you should disable support for them to enhance security. ", E_USER_WARNING ); } $saveAs = null; if (!empty($args['SaveAs'])) { $saveAs = $args['SaveAs']; } $promise = $this->client->getObjectAsync($args) ->then( function ($result) use ( $provider, $instructionFileSuffix, $strategy, $args ) { if ($strategy === null) { $strategy = $this->determineGetObjectStrategy( $result, $instructionFileSuffix ); } $envelope = $strategy->load($args + [ 'Metadata' => $result['Metadata'] ]); $result['Body'] = $this->decrypt( $result['Body'], $provider, $envelope, $args ); return $result; } )->then( function ($result) use ($saveAs) { if (!empty($saveAs)) { file_put_contents( $saveAs, (string)$result['Body'], LOCK_EX ); } return $result; } ); return $promise; } /** * Retrieves an object from S3 and decrypts the data in the 'Body' field. * * @param array $args Arguments for retrieving an object from S3 via * GetObject and decrypting it. * * The required configuration argument is as follows: * * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek * encrypting/decrypting for decryption metadata. May have data loaded * from the MetadataEnvelope upon decryption. * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 * content encryption and key wrap schemas are able to be decrypted. * - 'V2_AND_LEGACY' indicates that objects encrypted with both * S3EncryptionClientV2 and older legacy encryption clients are able * to be decrypted. * * The optional configuration arguments are as follows: * * - SaveAs: (string) The path to a file on disk to save the decrypted * object data. This will be handled by file_put_contents instead of the * Guzzle sink. * - @InstructionFileSuffix: (string|null) Suffix used when looking for an * instruction file if an InstructionFileMetadataHandler was detected. * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher * is required. Accepts the following options: * - Aad: (string) Additional authentication data. This option is * passed directly to OpenSSL when using gcm. It is ignored when * using cbc. * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with * KMS materials for any KMS key ID, instead of needing the KMS key ID to * be specified and provided to the decrypt operation. Ignored for non-KMS * materials providers. Defaults to false. * * @return \Aws\Result GetObject call result with the 'Body' field * wrapped in a decryption stream with its metadata * information. * * @throws \InvalidArgumentException Thrown when arguments above are not * passed or are passed incorrectly. */ public function getObject(array $args) { return $this->getObjectAsync($args)->wait(); } } CryptoParamsTrait.php000064400000005010152267715200010710 0ustar00instructionFileSuffix; } protected function determineGetObjectStrategy( $result, $instructionFileSuffix ) { if (isset($result['Metadata'][MetadataEnvelope::CONTENT_KEY_V2_HEADER])) { return new HeadersMetadataStrategy(); } return new InstructionFileMetadataStrategy( $this->client, $instructionFileSuffix ); } protected function getMetadataStrategy(array $args, $instructionFileSuffix) { if (!empty($args['@MetadataStrategy'])) { if ($args['@MetadataStrategy'] instanceof MetadataStrategyInterface) { return $args['@MetadataStrategy']; } if (is_string($args['@MetadataStrategy'])) { switch ($args['@MetadataStrategy']) { case HeadersMetadataStrategy::class: return new HeadersMetadataStrategy(); case InstructionFileMetadataStrategy::class: return new InstructionFileMetadataStrategy( $this->client, $instructionFileSuffix ); default: throw new \InvalidArgumentException('Could not match the' . ' specified string in "MetadataStrategy" to a' . ' predefined strategy.'); } } else { throw new \InvalidArgumentException('The metadata strategy that' . ' was passed to "MetadataStrategy" was unrecognized.'); } } elseif ($instructionFileSuffix) { return new InstructionFileMetadataStrategy( $this->client, $instructionFileSuffix ); } return null; } } MetadataStrategyInterface.php000064400000001744152270000300012334 0ustar00stream = $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; } } EncryptionTrait.php000064400000015653152270000300010412 0ustar00 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 array returns an array with two elements as follows: [string, AesStreamInterface] * * @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()]; } } } AbstractCryptoClient.php000064400000007764152270000300011363 0ustar00getConstants()) 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; } } AesGcmEncryptingStream.php000064400000005621152270000300011624 0ustar00plaintext = $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; } } AesGcmDecryptingStream.php000064400000005314152270000300011611 0ustar00cipherText = $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; } } MaterialsProviderInterfaceV2.php000064400000003261152270000300012731 0ustar00buf = 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; } } Polyfill/ByteArray.php000064400000015532152270000300010744 0ustar00toArray(); $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/Key.php000064400000003302152270000300007562 0ustar00internalString = $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/AesGcm.php000064400000015462152270000300010203 0ustar00 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); } } AbstractCryptoClientV2.php000064400000007605152270000300011565 0ustar00decryptCek( 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 ); } } } MaterialsProviderV2.php000064400000004007152270000300011107 0ustar00 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) ); } } DecryptionTraitV2.php000064400000022051152270000300010576 0ustar00decryptCek( 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 ); } } } MaterialsProvider.php000064400000006351152270000300010703 0ustar00 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) ); } } KmsMaterialsProviderV2.php000064400000006672152270000300011574 0ustar00kmsClient = $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 ]; } } AesDecryptingStream.php000064400000007010152270000300011155 0ustar00stream = $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; } } EncryptionTraitV2.php000064400000016146152270000300010620 0ustar00 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 array returns an array with two elements as follows: [string, AesStreamInterface] * * @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; } } } KmsMaterialsProvider.php000064400000007625152270000300011363 0ustar00kmsClient = $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']; } } Cipher/CipherBuilderTrait.php000064400000003751152270000300012207 0ustar00baseIv = $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); } } Cipher/CipherMethod.php000064400000003124152270000300011027 0ustar00