diff --git a/samples/Common.php b/samples/Common.php index 49bd4931..6024ed5c 100644 --- a/samples/Common.php +++ b/samples/Common.php @@ -8,8 +8,12 @@ } require_once __DIR__ . '/Config.php'; +use OSS\Crypto\BaseCryptoProvider; use OSS\OssClient; +use Oss\OssEncryptionClient; use OSS\Core\OssException; +use OSS\Crypto\RsaProvider; +use Oss\Crypto\KmsProvider; /** * Class Common @@ -39,6 +43,26 @@ public static function getOssClient() } return $ossClient; } + + + /** + * @param KmsProvider|RsaProvider $provider + * @return OssEncryptionClient|null + */ + public static function getOssEncryptionClient($provider) + { + if (!$provider instanceof BaseCryptoProvider){ + throw new OssException('Crypto provider must be an instance of BaseCryptoProvider'); + } + try { + $ossClient = new OssEncryptionClient(self::accessKeyId, self::accessKeySecret, self::endpoint, $provider); + } catch (OssException $e) { + printf(__FUNCTION__ . "creating OssClient instance: FAILED\n"); + printf($e->getMessage() . "\n"); + return null; + } + return $ossClient; + } public static function getBucketName() { diff --git a/samples/Config.php b/samples/Config.php index fc3a1673..a7fa1b56 100644 --- a/samples/Config.php +++ b/samples/Config.php @@ -12,4 +12,7 @@ final class Config const OSS_ACCESS_KEY = 'update me'; const OSS_ENDPOINT = 'update me'; const OSS_TEST_BUCKET = 'update me'; + const KMS_ENDPOINT_OTHER = 'update me'; + const KMS_ENDPOINT = 'update me'; + } diff --git a/samples/EncryptObject.php b/samples/EncryptObject.php new file mode 100644 index 00000000..44a537e1 --- /dev/null +++ b/samples/EncryptObject.php @@ -0,0 +1,549 @@ + $publicKey, + 'private_key' => $privateKey +); +$matDesc= array( + 'key1'=>'test-one' +); +$provider= new RsaProvider($keys,$matDesc); +// Use and manage multiple keys +/* + * +$publicKey2 = << $publicKey2, + 'private_key' => $privateKey2 +); +$cipher = new AesCtrCipher(); +$matDesc2= array( + 'key2'=>'test-one' +); +$encryptionMaterials = new RsaEncryptionMaterials($matDesc2,$keys2); +$provider->addEncryptionMaterials($encryptionMaterials); +*/ + +$ossEncryptionClient = Common::getOssEncryptionClient($provider); +$result = $ossEncryptionClient->putObject($bucket,'encrypt.txt',$content); +Common::println("encrypt.txt is created"); + + +// download the object by rsa encrypt +$content = $ossEncryptionClient->getObject($bucket,'encrypt.txt'); +Common::println("encrypt.txt is fetched, the content is: " . $content); + + +// Upload and download by kms +$matDesc= array( + 'key2'=>'test-kms' +); + +$cmkId= '*****-ccff-4a4f-8fb3-cdc7695fa67c'; +$provider= new KmsProvider(Config::OSS_ACCESS_ID,Config::OSS_ACCESS_KEY,Config::KMS_ENDPOINT,$cmkId,$matDesc); + +// Kms Use and manage multiple keys +$matDesc2 = array( + 'test-kms-one'=>'test-kms-one' +); +$otherKmsRegion = Config::OSS_ENDPOINT; +$cmkId2= '*****-ccff-4a4f-8fb3-cdc76957bcd'; +$encryptionMaterials = new KmsEncryptionMaterials($matDesc2,$otherKmsRegion,$cmkId2); +$provider->addEncryptionMaterials($encryptionMaterials); +$ossEncryptionClient = Common::getOssEncryptionClient($provider); + +// kms upload +$result = $ossEncryptionClient->putObject($bucket,'kms.php',file_get_contents(__FILE__)); +// kms download +$result = $ossEncryptionClient->getObject($bucket,'kms.php'); + +// =========================================Let's take the rsa encryption client as an example===================================================================== +// Multi Part Upload +$ossEncryptionClient = Common::getOssEncryptionClient($provider); +$partSize = 5 * 1024 * 1024; +$uploadFile = 'your-local-file'; +$uploadFileSize = sprintf('%u',filesize($uploadFile)); + +$object = "object-name"; +$options['headers'] = array( + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_DATA_SIZE => $uploadFileSize, + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_PART_SIZE=>$partSize +); +try { + /** + * step 1. Initialize a block upload event, that is, a multipart upload process to get an upload id + */ + $uploadId = $ossEncryptionClient->initiateMultipartUpload($bucket, $object,$options); + $pieces = $ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + /** + * step 2. Upload parts + */ + $responseUploadPart = array(); + $uploadPosition = 0; + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $ossEncryptionClient->uploadPart($bucket, $object, $uploadId, $upOptions); + printf( "initiateMultipartUpload, uploadPart - part#{$i} OK\n"); + } + $uploadParts = array(); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + /** + * step 3. Complete the upload + */ + $ossEncryptionClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); +}catch (OssException $e) { + printf($e->getMessage() . "\n"); + return; +} +printf("completeMultipartUpload OK\n"); + + +// Multi Part Download +$object = "object-name"; +$download = 'local-file-name'; +$ossEncryptionClient = Common::getOssEncryptionClient($provider); +$objectMeta = $ossEncryptionClient->getObjectMeta($bucket, $object); +$size = $objectMeta['content-length']; +$partSize =1024*1024*5; + +$pieces = $ossEncryptionClient->generateMultiuploadParts($size, $partSize); +$downloadPosition = 0; + +foreach ($pieces as $i => $piece) { + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $ossEncryptionClient->getObject($bucket,$object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + printf("Multi download, part - part#{$i} OK\n"); +} + +printf( "Object ".$object.'download complete'); + +// Range download +$ossEncryptionClient = Common::getOssEncryptionClient($provider); + +// The starting position of the file must be an integer multiple of 16 +$options = array( + OssClient::OSS_RANGE => '48-100' +); +$content = $ossEncryptionClient->getObject($bucket, $object,$options); +printf($object . " is fetched, the content is: " . $content); + +// Resumable upload ==================== + +$ossEncryptionClient = Common::getOssEncryptionClient($provider); +//Read the content of the record file, including fragment information, upload file, uploadId and other information +$str = file_get_contents("download.ucp"); +$uploadInfo = json_decode($str,true); +$uploadId = $uploadInfo['uploadId']; +$parts = $uploadInfo['parts']; +$object = $uploadInfo['object']; + +$partSize = $uploadInfo['partSize']; +$uploadFile = $uploadInfo['uploadFile']; +$uploadFileSize = sprintf('%u',filesize($uploadFile)); +/** + * step 1. Upload parts + */ +$pieces = $ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); +$responseUploadPart = array(); +$uploadPosition = 0; +$num = count($parts); +foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $ossEncryptionClient->uploadPart($bucket, $object, $uploadId, $upOptions); + printf("initiateMultipartUpload, uploadPart - part#{$i} OK\n"); +} +$responseUploadPart = array_merge($parts,$responseUploadPart); +$uploadParts = array(); +foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); +} +/** + * step 2. Complete the upload + */ +$ossEncryptionClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); +printf("completeMultipartUpload OK\n"); + + +// Resumable Download ================================================== + +$ossEncryptionClient = Common::getOssEncryptionClient($provider); +//Read the content of the record file, including fragment information, upload file, uploadId and other information +$str = file_get_contents('download.ucp'); +// Read the related information of the object from the file +/** The file content format is as follows + *{ +"object":"nextcloud-22.zip", +"partSize":20971520, +"pieces":[ +{ +"seekTo":0, +"length":20971520 +}, +{ +"seekTo":20971520, +"length":20971520 +}, +], +"parts":2 +} + */ +$downloadInfo = json_decode($str,true); +$num = $downloadInfo['parts']; +$pieces = $downloadInfo['pieces']; +$object = $downloadInfo['object']; +$downloadPosition = 0; +foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $ossEncryptionClient->getObject($bucket,$object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + printf("resume download, part - part#{$i} OK\n"); +} +printf( "Object ".$object.'download complete'.PHP_EOL); + + + +//******************************* For complete usage, see the following functions **************************************************** + +putObject($ossEncryptionClient, $bucket); +getObject($ossEncryptionClient, $bucket); +mutipartUpload($ossEncryptionClient, $bucket); +mutipartDownload($ossEncryptionClient,$bucket); +rangeDownload($ossEncryptionClient,$bucket); +resumeUpload($ossEncryptionClient,$bucket); +resumeDownload($ossEncryptionClient,$bucket); + + +/** + * Upload a encrypt data to an OSS object + * Simple upload + * @param OssEncryptionClient $ossEncryptionClient OssEncryptionClient instance + * @param string $bucket bucket name + */ +function putObject($ossEncryptionClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $content = file_get_contents(__FILE__); + $options = array(); + try { + $ossEncryptionClient->putObject($bucket, $object, $content, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + + +/** + * Get the content of an object. + * Simple download + * @param OssEncryptionClient $ossEncryptionClient OssEncryptionClient instance + * @param string $bucket bucket name + * @return null + */ +function getObject($ossEncryptionClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $options = array(); + try { + $content = $ossEncryptionClient->getObject($bucket, $object, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + if (file_get_contents(__FILE__) === $content) { + print(__FUNCTION__ . ": FileContent checked OK" . "\n"); + } else { + print(__FUNCTION__ . ": FileContent checked FAILED" . "\n"); + } +} + + + + + +/** + * Use basic multipart for file Upload. + * @param OssEncryptionClient $ossEncryptionClient OssEncryptionClient instance + * @param string $bucket bucket name + * @return null + */ +function mutipartUpload($ossEncryptionClient, $bucket) +{ + try { + $partSize = 5 * 1024 * 1024; + $uploadFile = 'your-local-file'; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $object = "object-name"; + // Set header metadata + $options['headers'] = array( + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_DATA_SIZE => $uploadFileSize, + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_PART_SIZE=>$partSize + ); + // Initialize segment upload and get a uoloadId + $uploadId = $ossEncryptionClient->initiateMultipartUpload($bucket, $object,$options); + $pieces = $ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + // Upload part content + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $ossEncryptionClient->uploadPart($bucket, $object, $uploadId, $upOptions); + printf( __FUNCTION__ ."initiateMultipartUpload, uploadPart - part#{$i} OK\n"); + } + $uploadParts = array(); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + // Upload part completed + $ossEncryptionClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); + }catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + + printf(__FUNCTION__ .": completeMultipartUpload OK\n"); +} + +/** + * Use basic multipart upload for file download. + * @param OssEncryptionClient $ossEncryptionClient OssEncryptionClient instance + * @param string $bucket bucket name + * @return null + */ + +function mutipartDownload($ossEncryptionClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $download = 'your-local-file-name'; + try { + $objectMeta = $ossEncryptionClient->getObjectMeta($bucket, $object); + $size = $objectMeta['content-length']; + $partSize = 5 * 1024 * 1024; + $pieces = $ossEncryptionClient->generateMultiuploadParts($size, $partSize); + $responseUploadPart = array(); + $downloadPosition = 0; + // download part content + foreach ($pieces as $i => $piece) { + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $ossEncryptionClient->getObject($bucket, $object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + printf( __FUNCTION__ ."initiateMultipartDownload, downloadPart - part#{$i} OK\n"); + } + }catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + + printf(__FUNCTION__ .": complete Multipart Download OK\n"); +} + +/** + * Range file download + * @param OssEncryptionClient $ossEncryptionClient OssEncryptionClient instance + * @param string $bucket bucket name + * @return null + */ +function rangeDownload($ossEncryptionClient,$bucket){ + try { + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $options = array( + OssClient::OSS_RANGE => '48-100' + ); + $content = $ossEncryptionClient->getObject($bucket, $object,$options); + }catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + printf(__FUNCTION__ .": Range Download OK\n"); + printf($object . " is fetched, the content is: " . $content); +} + +/** + * Resume Upload for a big file + * @param OssEncryptionClient $ossEncryptionClient OssEncryptionClient instance + * @param string $bucket bucket name + * @return null + */ +function resumeUpload($ossEncryptionClient,$bucket){ + $str = file_get_contents("upload.ucp"); + $uploadInfo = json_decode($str,true); + $uploadId = $uploadInfo['uploadId']; + $parts = $uploadInfo['parts']; + $object = $uploadInfo['object']; + $partSize = $uploadInfo['partSize']; + $uploadFile = $uploadInfo['uploadFile']; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + try { + $pieces = $ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + $num = count($parts); + foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $ossEncryptionClient->uploadPart($bucket, $object, $uploadId, $upOptions); + printf( "resume upload, uploadPart - part#{$i} OK\n"); + } + $responseUploadPart = array_merge($parts,$responseUploadPart); + $uploadParts = array(); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + $ossEncryptionClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); + }catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + } + printf(__FUNCTION__ . "resume upload OK\n"); +} +/** + * Resume Download for a big file + * @param OssEncryptionClient $ossEncryptionClient OssEncryptionClient instance + * @param string $bucket bucket name + * @return null + */ +function resumeDownload($ossEncryptionClient,$bucket){ + try { + $str = file_get_contents('download.ucp'); + $downloadInfo = json_decode($str,true); + $num = $downloadInfo['parts']; + $pieces = $downloadInfo['pieces']; + $object = $downloadInfo['object']; + $download = 'your-local-file'; + $downloadPosition = 0; + foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $ossEncryptionClient->getObject($bucket,$object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + printf("resume download, part - part#{$i} OK\n"); + } + }catch (OssException $e){ + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + printf( "Object ".$object.' resume download complete'); +} + + + + + + + + + + + diff --git a/src/OSS/Core/OssUtil.php b/src/OSS/Core/OssUtil.php index 2e005d67..d4db905b 100644 --- a/src/OSS/Core/OssUtil.php +++ b/src/OSS/Core/OssUtil.php @@ -320,6 +320,55 @@ public static function getMd5SumForFile($filename, $from_pos, $to_pos) return $content_md5; } + + /** + * Get date of the file. + * + * @param $filename + * @param $from_pos + * @param $to_pos + * @return string + */ + public static function getDataFromFile($filename, $from_pos, $to_pos) + { + $content = ""; + if (($to_pos - $from_pos) > self::OSS_MAX_PART_SIZE) { + return $content; + } + $filesize = sprintf('%u',filesize($filename)); + if ($from_pos >= $filesize || $to_pos >= $filesize || $from_pos < 0 || $to_pos < 0) { + return $content; + } + + $total_length = $to_pos - $from_pos + 1; + $buffer = 8192; + $left_length = $total_length; + if (!file_exists($filename)) { + return $content; + } + + if (false === $fh = fopen($filename, 'rb')) { + return $content; + } + fseek($fh, $from_pos); + $data = ''; + while (!feof($fh)) { + if ($left_length >= $buffer) { + $read_length = $buffer; + } else { + $read_length = $left_length; + } + if ($read_length <= 0) { + break; + } else { + $data .= fread($fh, $read_length); + $left_length = $left_length - $read_length; + } + } + fclose($fh); + return $data; + } + /** * Check if the OS is Windows. The default encoding in Windows is GBK. * diff --git a/src/OSS/Crypto/BaseCryptoProvider.php b/src/OSS/Crypto/BaseCryptoProvider.php new file mode 100644 index 00000000..c106b3fe --- /dev/null +++ b/src/OSS/Crypto/BaseCryptoProvider.php @@ -0,0 +1,100 @@ +cipherAdaptor = $cipherAdaptor; + $class = "OSS\\Crypto\\Cipher\\".$cipherAdaptor; + if (class_exists($class)) { + $this->cipher = new $class(); + } else { + throw new OssException('Error: Could not load Cipher adaptor ' . $cipherAdaptor . '!'); + } + if($matDesc != null){ + if(is_array($matDesc)){ + $this->matDesc = $matDesc; + }else{ + throw new OssException('Invalid type, the type of mat_desc must be array!'); + } + } + } + + /** + * @param $encryptedKey string + */ + public function decryptKey($encryptedKey){} + + /** + * @param $encryptedIv + */ + public function decryptIv($encryptedIv){} + + /** + * Assemble encrypted information + * @param $encryptionMaterials RsaEncryptionMaterials|KmsEncryptionMaterials + */ + public function addEncryptionMaterials($encryptionMaterials){ + $key = key($encryptionMaterials->getDesc()); + $this->encryptionMaterials[$key] = $encryptionMaterials; + } + + + /** + * @param $desc + * @return KmsEncryptionMaterials | RsaEncryptionMaterials + */ + public function getEncryptionMaterials($desc){ + $key = key($desc); + if(array_key_exists($key,$this->encryptionMaterials)){ + return $this->encryptionMaterials[$key]; + } + } + + /** + * Assemble encrypted information + * @return ContentCryptoMaterial + */ + public function createContentMaterial(){} + + /** + * @param $encryptionMaterials KmsEncryptionMaterials | RsaEncryptionMaterials + */ + public function resetEncryptionMaterials($encryptionMaterials){} + +} \ No newline at end of file diff --git a/src/OSS/Crypto/Cipher/Aes/AesCipher.php b/src/OSS/Crypto/Cipher/Aes/AesCipher.php new file mode 100644 index 00000000..65757b5c --- /dev/null +++ b/src/OSS/Crypto/Cipher/Aes/AesCipher.php @@ -0,0 +1,208 @@ +alg = null; + $this->keyLen = self::AES_256_KEY_SIZE; + $this->blockSizeLen = self::AES_BLOCK_LEN; + $this->blockSizeLenInBits = self::AES_BLOCK_BITS_LEN; + } + + public function init($key,$iv){ + $this->key = $key; + $this->iv = $iv; + } + + public function resetContext(){ + } + + /** + * @param int $a + * @param int $b + * @param int $c + * @param int $d + * @return int + */ + private function mixColumns($a,$b,$c,$d) + { + return Constants::MIXCOLUMNS_0[$a >> 24] ^ + Constants::MIXCOLUMNS_1[$b >> 16 & 0xff] ^ + Constants::MIXCOLUMNS_2[$c >> 8 & 0xff] ^ + Constants::MIXCOLUMNS_3[$d & 0xff]; + } + + + /** + * @param int $a + * @param int $b + * @param int $c + * @param int $d + * @return int + */ + private function mixColumnsInverse($a,$b,$c, $d) + { + return Constants::MIXCOLUMNS_INVERSE_0[$a >> 24] ^ + Constants::MIXCOLUMNS_INVERSE_1[$b >> 16 & 0xff] ^ + Constants::MIXCOLUMNS_INVERSE_2[$c >> 8 & 0xff] ^ + Constants::MIXCOLUMNS_INVERSE_3[$d & 0xff]; + } + + + /** + * @param int $a + * @param int $b + * @param int $c + * @param int $d + * @return int + */ + private function subBytes($a,$b,$c,$d) + { + return (Constants::SUBBYTES[$a >> 24] << 24) | + (Constants::SUBBYTES[$b >> 16 & 0xff] << 16) | + (Constants::SUBBYTES[$c >> 8 & 0xff] << 8) | + Constants::SUBBYTES[$d & 0xff]; + } + + + /** + * @param int $a + * @param int $b + * @param int $c + * @param int $d + * @return int + */ + private function subBytesInverse($a,$b,$c,$d) + { + return (Constants::SUBBYTES_INVERSE[$a >> 24] << 24) | + (Constants::SUBBYTES_INVERSE[$b >> 16 & 0xff] << 16) | + (Constants::SUBBYTES_INVERSE[$c >> 8 & 0xff] << 8) | + Constants::SUBBYTES_INVERSE[$d & 0xff]; + } + + + /** + * @param Key $key + * @param string $block + * @return string + * @throws OssException + */ + public function encryptBlock($key,$block) + { + if (strlen($block) !== 16) { + throw new OssException("Invalid block length, the length of block must be 16 "); + } + $k = $key->encryptionKey(); + + list(, $a, $b, $c, $d) = unpack('N4', $block); + + $a ^= $k[0]; + $b ^= $k[1]; + $c ^= $k[2]; + $d ^= $k[3]; + + $i = 4; + $rounds = ($key->bits() >> 5) + 5; + while ($rounds--) { + list($a, $b, $c, $d) = [ + $this->mixColumns($a, $b, $c, $d) ^ $k[$i++], + $this->mixColumns($b, $c, $d, $a) ^ $k[$i++], + $this->mixColumns($c, $d, $a, $b) ^ $k[$i++], + $this->mixColumns($d, $a, $b, $c) ^ $k[$i++] + ]; + } + + return pack('N4', + $this->subBytes($a, $b, $c, $d) ^ $k[56], + $this->subBytes($b, $c, $d, $a) ^ $k[57], + $this->subBytes($c, $d, $a, $b) ^ $k[58], + $this->subBytes($d, $a, $b, $c) ^ $k[59] + ); + } + + + /** + * @param Key $key + * @param string $block + * @return string + * @throws OssException + */ + protected function decryptBlock($key,$block) + { + if (strlen($block) !== 16) { + throw new OssException("Invalid block length, the length of block must be 16 "); + } + + $k = $key->decryptionKey(); + + list(, $a, $b, $c, $d) = unpack('N4', $block); + + $d ^= $k[59]; + $c ^= $k[58]; + $b ^= $k[57]; + $a ^= $k[56]; + + $i = ($key->bits() >> 3) + 23; + while ($i > 3) { + list($d, $c, $b, $a) = [ + $this->mixColumnsInverse($d, $c, $b, $a) ^ $k[$i--], + $this->mixColumnsInverse($c, $b, $a, $d) ^ $k[$i--], + $this->mixColumnsInverse($b, $a, $d, $c) ^ $k[$i--], + $this->mixColumnsInverse($a, $d, $c, $b) ^ $k[$i--], + ]; + } + + return pack('N4', + $this->subBytesInverse($a, $d, $c, $b) ^ $k[0], + $this->subBytesInverse($b, $a, $d, $c) ^ $k[1], + $this->subBytesInverse($c, $b, $a, $d) ^ $k[2], + $this->subBytesInverse($d, $c, $b, $a) ^ $k[3] + ); + } + + + /** + * @return int + */ + public function getAlignLen(){ + return $this->blockSizeLen; + } + + + /** + * @return int + */ + public function getKeyLen(){ + return $this->keyLen; + } + + /** + * @return string + */ + public function getAlg(){ + return $this->alg; + } +} + diff --git a/src/OSS/Crypto/Cipher/Aes/Constants.php b/src/OSS/Crypto/Cipher/Aes/Constants.php new file mode 100644 index 00000000..1d700d2f --- /dev/null +++ b/src/OSS/Crypto/Cipher/Aes/Constants.php @@ -0,0 +1,329 @@ +generate128($key); + break; + case 192: + $this->generate192($key); + break; + case 256: + $this->generate256($key); + break; + default: + throw new OssException("Invalid key, the length of key must be 32!"); + } + + $this->bits = $bits; + } + + /** + * @return int + */ + public function bits() + { + return $this->bits; + } + + /** + * @return array + */ + public function encryptionKey() + { + return $this->encryptionKey; + } + + + /** + * @return mixed + */ + public function decryptionKey() + { + return $this->decryptionKey; + } + + + /** + * @param int $k + * @param int $rc + * @return int + */ + private function encryptionKeyRound( $k, $rc) + { + return (Constants::SUBBYTES[$k & 0xff] << 8) | + (Constants::SUBBYTES[$k >> 8 & 0xff] << 16) | + ((Constants::SUBBYTES[$k >> 16 & 0xff] ^ $rc) << 24) | + Constants::SUBBYTES[$k >> 24 ]; + } + + /** + * @param int $k + * @return int + */ + private function decryptionKeyRound($k) + { + return Constants::MIXCOLUMNS_INVERSE_0[Constants::SUBBYTES[$k >> 24 ]] ^ + Constants::MIXCOLUMNS_INVERSE_1[Constants::SUBBYTES[$k >> 16 & 0xff]] ^ + Constants::MIXCOLUMNS_INVERSE_2[Constants::SUBBYTES[$k >> 8 & 0xff]] ^ + Constants::MIXCOLUMNS_INVERSE_3[Constants::SUBBYTES[$k & 0xff]]; + } + + /** + * @param string $key + */ + private function generate128($key) + { + list(, $k0, $k1, $k2, $k3) = unpack('N4', $key); + + $encryptionKey = + $decryptionKey = [$k0, $k1, $k2, $k3]; + + for ($i = 4, $rc = 1; $i < 40; $rc = ($rc << 1) % 0xe5) { + $encryptionKey[$i ] = $k0 ^= $this->encryptionKeyRound($k3, $rc); + $decryptionKey[$i++] = $this->decryptionKeyRound($k0); + $encryptionKey[$i ] = $k1 ^= $k0; + $decryptionKey[$i++] = $this->decryptionKeyRound($k1); + $encryptionKey[$i ] = $k2 ^= $k1; + $decryptionKey[$i++] = $this->decryptionKeyRound($k2); + $encryptionKey[$i ] = $k3 ^= $k2; + $decryptionKey[$i++] = $this->decryptionKeyRound($k3); + } + + $encryptionKey[56] = $decryptionKey[56] = $k0 ^= $this->encryptionKeyRound($k3, 0x36); + $encryptionKey[57] = $decryptionKey[57] = $k1 ^= $k0; + $encryptionKey[58] = $decryptionKey[58] = $k2 ^= $k1; + $encryptionKey[59] = $decryptionKey[59] = $k3 ^ $k2; + + $this->encryptionKey = $encryptionKey; + $this->decryptionKey = $decryptionKey; + } + + /** + * @param string $key + */ + private function generate192($key) + { + list(, $k0, $k1, $k2, $k3, $k4, $k5) = unpack('N6', $key); + + $encryptionKey = [$k0, $k1, $k2, $k3, $k4, $k5]; + $decryptionKey = [ + $k0, $k1, $k2, $k3, + $this->decryptionKeyRound($k4), + $this->decryptionKeyRound($k5) + ]; + + for ($i = 6, $rc = 1; $i < 48; $rc <<= 1) { + $encryptionKey[$i ] = $k0 ^= $this->encryptionKeyRound($k5, $rc); + $decryptionKey[$i++] = $this->decryptionKeyRound($k0); + $encryptionKey[$i ] = $k1 ^= $k0; + $decryptionKey[$i++] = $this->decryptionKeyRound($k1); + $encryptionKey[$i ] = $k2 ^= $k1; + $decryptionKey[$i++] = $this->decryptionKeyRound($k2); + $encryptionKey[$i ] = $k3 ^= $k2; + $decryptionKey[$i++] = $this->decryptionKeyRound($k3); + $encryptionKey[$i ] = $k4 ^= $k3; + $decryptionKey[$i++] = $this->decryptionKeyRound($k4); + $encryptionKey[$i ] = $k5 ^= $k4; + $decryptionKey[$i++] = $this->decryptionKeyRound($k5); + } + + $encryptionKey[56] = $decryptionKey[56] = $k0 ^= $this->encryptionKeyRound($k5, 0x80); + $encryptionKey[57] = $decryptionKey[57] = $k1 ^= $k0; + $encryptionKey[58] = $decryptionKey[58] = $k2 ^= $k1; + $encryptionKey[59] = $decryptionKey[59] = $k3 ^ $k2; + + $this->encryptionKey = $encryptionKey; + $this->decryptionKey = $decryptionKey; + } + + /** + * @param string $key + */ + private function generate256($key) + { + list(, $k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7) = unpack('N8', $key); + + $encryptionKey = [$k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7]; + $decryptionKey = [ + $k0, $k1, $k2, $k3, + $this->decryptionKeyRound($k4), + $this->decryptionKeyRound($k5), + $this->decryptionKeyRound($k6), + $this->decryptionKeyRound($k7) + ]; + + for ($i = 8, $rc = 1; $i < 56; $rc <<= 1) { + $encryptionKey[$i ] = $k0 ^= $this->encryptionKeyRound($k7, $rc); + $decryptionKey[$i++] = $this->decryptionKeyRound($k0); + $encryptionKey[$i ] = $k1 ^= $k0; + $decryptionKey[$i++] = $this->decryptionKeyRound($k1); + $encryptionKey[$i ] = $k2 ^= $k1; + $decryptionKey[$i++] = $this->decryptionKeyRound($k2); + $encryptionKey[$i ] = $k3 ^= $k2; + $decryptionKey[$i++] = $this->decryptionKeyRound($k3); + $encryptionKey[$i ] = $k4 ^= (Constants::SUBBYTES[$k3 & 0xff] | (Constants::SUBBYTES[$k3 >> 8 & 0xff] << 8) | (Constants::SUBBYTES[$k3 >> 16 & 0xff] << 16) | (Constants::SUBBYTES[$k3 >> 24] << 24)); + $decryptionKey[$i++] = $this->decryptionKeyRound($k4); + $encryptionKey[$i ] = $k5 ^= $k4; + $decryptionKey[$i++] = $this->decryptionKeyRound($k5); + $encryptionKey[$i ] = $k6 ^= $k5; + $decryptionKey[$i++] = $this->decryptionKeyRound($k6); + $encryptionKey[$i ] = $k7 ^= $k6; + $decryptionKey[$i++] = $this->decryptionKeyRound($k7); + } + + $encryptionKey[56] = $decryptionKey[56] = $k0 ^= $this->encryptionKeyRound($k7, 0x40); + $encryptionKey[57] = $decryptionKey[57] = $k1 ^= $k0; + $encryptionKey[58] = $decryptionKey[58] = $k2 ^= $k1; + $encryptionKey[59] = $decryptionKey[59] = $k3 ^ $k2; + + $this->encryptionKey = $encryptionKey; + $this->decryptionKey = $decryptionKey; + } +} diff --git a/src/OSS/Crypto/Cipher/AesCtrCipher.php b/src/OSS/Crypto/Cipher/AesCtrCipher.php new file mode 100644 index 00000000..055a6bb3 --- /dev/null +++ b/src/OSS/Crypto/Cipher/AesCtrCipher.php @@ -0,0 +1,177 @@ +alg = self::AES_CTR; + $this->cipher ='AES-256-CTR'; + $this->context = new Context; + } + + public function resetContext(){ + $this->context = new Context; + } + + + /** + * @param $key + * @param $iv + * @throws OssException + */ + public function init($key,$iv){ + parent::init($key,$iv); + $this->context->key = new Key($key); + $this->context->nonce = array_values(unpack('N4', $iv)); + if($this->offset > 0){ + $this->incNonce($this->context,$this->offset); + } + } + + + /** + * @param Context $context + * @param string $message + * @return string + * @throws OssException + */ + private function transcrypt($context,$message) + { + $nonce = $context->nonce; + $keyStream = $context->keyStream; + $bytesRequired = strlen($message) - strlen($keyStream); + $bytesOver = $bytesRequired % 16; + $blockCount = ($bytesRequired >> 4) + ($bytesOver > 0); + while ($blockCount-- > 0) { + $keyStream .= $this->encryptBlock($context->key, pack('N4', ...$nonce)); + for($i = 3; $i >= 0; $i--) { + $nonce[$i]++; + $nonce[$i] &= 0xffffffff; + if ($nonce[$i]) { + break; + } + } + } + $context->keyStream = substr($keyStream, $bytesRequired); + $context->nonce = $nonce; + return $message ^ $keyStream; + } + + + /** + * @param $offset + */ + public function calcOffset($offset) + { + $this->offset = $offset; + } + + + /** + * @param Context $context + * @param string | int $offset + */ + public function incNonce($context,$offset){ + $nonce = $context->nonce; + $blockCount = ($offset/16); + while ($blockCount-- > 0) { + for($i = 3; $i >= 0; $i--) { + $nonce[$i]++; + $nonce[$i] &= 0xffffffff; + if ($nonce[$i]) { + break; + } + } + } + $context->nonce = $nonce; + } + + /** + * @param Context $ctx + * @param string $message + * @return string + * @throws OssException + */ + function streamEncrypt( $ctx, $message) + { + return $this->transcrypt($ctx, $message); + } + + + /** + * @param Context $ctx + * @param string $message + * @return string + * @throws OssException + */ + function streamDecrypt( $ctx, $message) + { + return $this->transcrypt($ctx, $message); + } + + + /** + * get a random key + * @return string + */ + public function getKey() + { + return $this->key = openssl_random_pseudo_bytes($this->keyLen); + } + + /** + * get a random iv + * @return string + */ + public function getIv() + { + return $this->iv = openssl_random_pseudo_bytes(16); + } + + /** + * @param $data + * @param AesCipher $cipher + * @return int|string + * @throws OssException + */ + public function encrypt($data,$cipher) + { + return $this->streamEncrypt($this->context, $data); + } + + /** + * @param $data + * @param AesCipher $cipher + * @return int|string + * @throws OssException + */ + public function decrypt($data,$cipher) + { + return $this->streamDecrypt($this->context, $data); + } + + public function getCipher() { + return $this->cipher; + } +} diff --git a/src/OSS/Crypto/Crypto.php b/src/OSS/Crypto/Crypto.php new file mode 100644 index 00000000..1c7478de --- /dev/null +++ b/src/OSS/Crypto/Crypto.php @@ -0,0 +1,12 @@ +kmsRegion = $kmsRegion; + $this->desc = $desc; + $this->kmsId = $kmsId; + } + + + /** + * @return string + */ + public function getKmsId(){ + return $this->kmsId; + } + + + /** + * @return array + */ + public function getDesc(){ + return $this->desc; + } + + + /** + * @return string + */ + public function getKmsRegion(){ + return $this->kmsRegion; + } +} diff --git a/src/OSS/Crypto/KmsProvider.php b/src/OSS/Crypto/KmsProvider.php new file mode 100644 index 00000000..890d3bd8 --- /dev/null +++ b/src/OSS/Crypto/KmsProvider.php @@ -0,0 +1,192 @@ +accessKeyId = $accessKeyId; + $this->accessKeySecret = $accessKeySecret; + $this->kmsClient = new KmsClient($accessKeyId,$accessKeySecret,$region); + $this->customerKeyId = $cmkId; + $this->wrapAlg = Crypto::KMS_ALI_WRAP_ALGORITHM; + } + + /** + * get a random key + * @return array + */ + public function getKey() + { + list($key,$encryptedKey) = $this->generateData(32); + return array($key,$encryptedKey); + } + + /** + * get a random key + * @return array + */ + public function getIv() + { + list($iv,$encryptedIv) = $this->generateData(16); + return array($iv,$encryptedIv); + } + + /** + * @return ContentCryptoMaterial + * @throws GuzzleException|OssException + */ + public function createContentMaterial(){ + list($key,$encryptedKey) = $this->getKey(); + list($iv,$encryptedIv) = $this->getIv(); + $wrapAlg = $this->wrapAlg; + $matDesc = $this->matDesc; + $this->cipher->init(base64_decode($key),base64_decode($iv)); + return new ContentCryptoMaterial($this->cipher,$wrapAlg,$encryptedKey,$encryptedIv,$matDesc); + } + + /** + * @param KmsEncryptionMaterials $encryptionMaterials + * @return KmsProvider + */ + public function resetEncryptionMaterials($encryptionMaterials) + { + $provider = $this; + $this->kmsClient = new KmsClient($this->accessKeyId,$this->accessKeySecret,$encryptionMaterials->getKmsRegion()); + $provider->matDesc = $encryptionMaterials->getDesc(); + $this->customerKeyId = $encryptionMaterials->getKmsId(); + return $provider; + } + + /** + * Assemble encrypted information + * @return void + */ + public function addEncryptionMaterials($encryptionMaterials){ + parent::addEncryptionMaterials($encryptionMaterials); + } + + /** + * Assemble encrypted information + * @return KmsEncryptionMaterials + */ + public function getEncryptionMaterials($desc){ + return parent::getEncryptionMaterials($desc); + } + + + /** + * @param $content + * @param AesCtrCipher $cipher + * @return int|string + * @throws OssException + */ + public function encryptAdapter($content, $cipher) + { + return $cipher->encrypt($content,$cipher); + } + + /** + * @param $content + * @param $cipher AesCtrCipher + * @return int|string + * @throws OssException + */ + public function decryptAdapter($content, $cipher) + { + return $cipher->decrypt($content,$cipher); + } + + + /** + * @param string $encryptedKey + * @return false|string + * @throws GuzzleException + */ + public function decryptKey($encryptedKey) + { + return base64_decode((string)$this->decryptData($encryptedKey)); + } + + /** + * @param $encryptedIv + * @return false|string + * @throws GuzzleException + */ + public function decryptIv($encryptedIv) + { + return base64_decode((string)$this->decryptData($encryptedIv)); + } + + /** + * @return array + */ + public function generateData($len) + { + $result = $this->kmsClient->generateDataKey([ + 'KeyId' => $this->customerKeyId, + 'KeySpec' => "AES_256", + "NumberOfBytes" =>$len, + ]); + + return array( + $result['Plaintext'], + $result['CiphertextBlob'], + ); + } + + + /** + * @return array + * @throws GuzzleException + */ + public function decryptData($data) + { + $result = $this->kmsClient->decrypt([ + 'CiphertextBlob' =>$data, + ]); + + return $result['Plaintext']; + } + + public function getMatDesc() { + return $this->matDesc; + } + + public function getCipher() { + return $this->cipher; + } + + public function getWrapAlg() { + return $this->wrapAlg; + } +} diff --git a/src/OSS/Crypto/RsaEncryptionMaterials.php b/src/OSS/Crypto/RsaEncryptionMaterials.php new file mode 100644 index 00000000..2de069e3 --- /dev/null +++ b/src/OSS/Crypto/RsaEncryptionMaterials.php @@ -0,0 +1,53 @@ +keyPair = $keyPair; + $this->desc = $desc; + } + + + /** + * @return array + */ + public function getKeyPair(){ + return $this->keyPair; + } + + + /** + * @return array + */ + public function getDesc(){ + return $this->desc; + } +} diff --git a/src/OSS/Crypto/RsaProvider.php b/src/OSS/Crypto/RsaProvider.php new file mode 100644 index 00000000..485f18ce --- /dev/null +++ b/src/OSS/Crypto/RsaProvider.php @@ -0,0 +1,175 @@ +wrapAlg = Crypto::RSA_NONE_PKCS1Padding_WRAP_ALGORITHM; + if(array_key_exists('public_key',$keyPair)){ + $this->publicKey = openssl_pkey_get_public($keyPair['public_key']); + }else{ + throw new OssException('Public key is Required!'); + } + if(array_key_exists('private_key',$keyPair)){ + $this->privateKey = openssl_pkey_get_private($keyPair['private_key']); + }else{ + throw new OssException('Private key is Required!'); + } + } + + /** + * Assemble encrypted information + * @param $encryptionMaterials RsaEncryptionMaterials|KmsEncryptionMaterials + */ + public function addEncryptionMaterials($encryptionMaterials){ + parent::addEncryptionMaterials($encryptionMaterials); + } + + /** + * Assemble encrypted information + * @return KmsEncryptionMaterials | RsaEncryptionMaterials + */ + public function getEncryptionMaterials($desc){ + return parent::getEncryptionMaterials($desc); + } + + /** + * get a random key + * @return string + */ + public function getKey() + { + return $this->cipher->getKey(); + } + + /** + * get a random iv + * @return string + */ + public function getIv() + { + return $this->cipher->getIv(); + } + + /** + * @param string $encryptedKey + * @return string + */ + public function decryptKey($encryptedKey) + { + return $this->decryptData($encryptedKey); + } + + /** + * @param $encryptedIv + * @return string + */ + public function decryptIv($encryptedIv) + { + return $this->decryptData($encryptedIv); + } + + + /** + * @param $encryptionMaterials KmsEncryptionMaterials | RsaEncryptionMaterials + * @return RsaProvider + * @throws \Exception + */ + public function resetEncryptionMaterials($encryptionMaterials) + { + return new RsaProvider($encryptionMaterials->getKeyPair(),$encryptionMaterials->getDesc()); + } + + /** + * @param $content + * @param AesCtrCipher $cipher + * @return int|string + * @throws OssException + */ + public function encryptAdapter($content,$cipher){ + return $cipher->encrypt($content,$cipher); + } + + /** + * @param $content + * @param AesCipher $cipher + * @return int|string + * @throws OssException + */ + public function decryptAdapter($content, $cipher) + { + return $cipher->decrypt($content,$cipher); + } + + /** + * Assemble encrypted information + * @return ContentCryptoMaterial + * @throws OssException + */ + public function createContentMaterial(){ + $key = $this->getKey(); + $encryptedKey = $this->encryptData($key); + $iv = $this->getIv(); + $encryptedIv = $this->encryptData($iv); + $wrapAlg = $this->wrapAlg; + $matDesc = $this->matDesc; + $this->cipher->init($key,$iv); + $cipher = $this->cipher; + return new ContentCryptoMaterial($cipher,$wrapAlg,$encryptedKey,$encryptedIv,$matDesc); + } + + /** + * encrypt a string + * @param $data + * @return string|null + */ + public function encryptData($data){ + return openssl_public_encrypt($data, $encrypted, $this->publicKey) ? base64_encode($encrypted) : null; + } + + /** + * decrypt a string + * @param $data + * @return mixed|null + */ + public function decryptData($data) + { + return openssl_private_decrypt($data, $decrypted, $this->privateKey) ? $decrypted : null; + } + + + public function getMatDesc() { + return $this->matDesc; + } + + public function getCipher() { + return $this->cipher; + } + + public function getWrapAlg() { + return $this->wrapAlg; + } +} \ No newline at end of file diff --git a/src/OSS/KmsClient.php b/src/OSS/KmsClient.php new file mode 100644 index 00000000..a80bf265 --- /dev/null +++ b/src/OSS/KmsClient.php @@ -0,0 +1,693 @@ +accessKeyId = $accessKeyId; + $this->accessKeySecret = $accessKeySecret; + $this->endpoint = $endpoint; + $this->uri = new Uri(); + } + + /** + * @param array $request + * @param array $runtime + * @return string + * @throws GuzzleException + */ + protected function request(array $request, array $runtime) + { + $options = [ + 'verify' => isset($runtime['ignoreSSL']) ? (boolean)$runtime['ignoreSSL'] : false, + 'http_errors' => isset($runtime['http_errors']) ? (boolean)$runtime['http_errors'] : false, + ]; + + $options = array_merge_recursive([$options, $runtime]); + $this->uri = $this->uri->withScheme($request['protocol']); + $this->uri = $this->uri->withPath($request['pathname']); + $this->uri = $this->uri->withQuery($request['query']); + $this->uri = $this->uri->withHost($request['headers']['host']); + try { + $result = (new Client())->request($request['method'], (string)$this->uri, $options); + } catch (ClientException $e) { + $result = $e->getResponse(); + } + return $result->getBody()->getContents(); + + } + + /** + * @param array $query + * + * @param array $request + * + * @return string + */ + protected function getQuery(array $query, array $request) + { + $query['Format'] = 'json'; + $query['Version'] = $this->version; + $query['AccessKeyId'] = $this->accessKeyId; + $query['SignatureMethod'] = $this->getMethod(); + $query['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z'); + $query['SignatureVersion'] = $this->getVersion(); + $query['SignatureNonce'] = md5(uniqid(mt_rand(), true)); + $query['Signature'] = $this->sign( + $this->prepareStringToSigned($query, $request), + $this->accessKeySecret . '&' + ); + + return http_build_query($query); + } + + /** + * @param $query + * @param $request + * + * @return string + */ + protected function prepareStringToSigned($query, $request) + { + ksort($query, SORT_STRING); + $canonicalizedQuery = ''; + foreach ($query as $key => $value) { + $canonicalizedQuery .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value); + } + + return $request['method'] + . '&%2F&' + . $this->percentEncode(substr($canonicalizedQuery, 1)); + } + + /** + * @param string $string + * + * @return null|string|string[] + */ + protected function percentEncode($string) + { + $result = urlencode($string); + $result = str_replace(['+', '*'], ['%20', '%2A'], $result); + $result = preg_replace('/%7E/', '~', $result); + + return $result; + } + + + /** + * @description cancel key deletion which can enable this key + * @see https://help.aliyun.com/document_detail/44197.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * @param $runtime + * + * @return string json + */ + public function cancelKeyDeletion($query = [], $runtime = []) + { + $query['Action'] = 'CancelKeyDeletion'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description make alias to key + * @see https://help.aliyun.com/document_detail/68624.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * - AliasName string required: cmk alias, prefix must be 'alias/' + * @param $runtime + * + * @return string json + */ + public function createAlias($query = [], $runtime = []) + { + $query['Action'] = 'CreateAlias'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description create new key + * @see https://help.aliyun.com/document_detail/28947.html + * + * @param $query + * - Action string required + * - Origin string optional: Aliyun_KMS (default) or EXTERNAL + * - Description string optional: description of key + * - KeyUsage string optional: usage of key, default is ENCRYPT/DECRYPT + * @param $runtime + * + * @return string json + */ + public function createKey($query = [], $runtime = []) + { + $query['Action'] = 'CreateKey'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description decrypt body of CiphertextBlob + * @see https://help.aliyun.com/document_detail/28950.html + * + * @param $query + * - Action string required + * - CiphertextBlob string required: ciphertext to be decrypted. + * - EncryptionContext string optional: key/value string, must be {string: string} + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function decrypt($query = [], $runtime = []) + { + $query['Action'] = 'Decrypt'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description delete alias + * @see https://help.aliyun.com/document_detail/68626.html + * + * @param $query + * - Action string required + * - AliasName string required: alias name, prefix must be 'alias/' + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function deleteAlias($query = [], $runtime = []) + { + $query['Action'] = 'DeleteAlias'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description delete key material + * @see https://help.aliyun.com/document_detail/68623.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function deleteKeyMaterial($query = [], $runtime = []) + { + $query['Action'] = 'DeleteKeyMaterial'; + $request['protocol'] = 'http'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description get description of main key + * @see https://help.aliyun.com/document_detail/28952.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function describeKey($query = [], $runtime = []) + { + $query['Action'] = 'DescribeKey'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description query available regions + * @see https://help.aliyun.com/document_detail/54560.html + * + * @param $query + * - Action string required + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function describeRegions($query = [], $runtime = []) + { + $query['Action'] = 'DescribeRegions'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description disable key + * @see https://help.aliyun.com/document_detail/35151.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function disableKey($query = [], $runtime = []) + { + $query['Action'] = 'DisableKey'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description enable key + * @see https://help.aliyun.com/document_detail/35150.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function enableKey($query = [], $runtime = []) + { + $query['Action'] = 'EnableKey'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description encrypt content + * @see https://help.aliyun.com/document_detail/28949.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * - Plaintext string required: plaintext to be encrypted (must be Base64 encoded) + * - EncryptionContext string optional: key/value string, must be {string: string} + * @param $runtime + * + * @return string json + * @throws GuzzleException + */ + public function encrypt($query = [], $runtime = []) + { + $query['Action'] = 'Encrypt'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + + /** + * @description generate local data key + * @see https://help.aliyun.com/document_detail/28948.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * - KeySpec string optional: AES_256 or AES_128 + * - NumberOfBytes int optional: length of key + * - EncryptionContext string optional: key/value string, must be {string: string} + * @param $runtime + * + * @return string json + */ + public function generateDataKey($query = [], $runtime = []) + { + $query['Action'] = 'GenerateDataKey'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + return $this->json($response); + } + + /** + * @description get the imported master key (CMK) material + * @see https://help.aliyun.com/document_detail/68621.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * - WrappingAlgorithm string required: algorithm for encrypting key material, RSAES_PKCS1_V1_5, RSAES_OAEP_SHA_1 + * or RSAES_OAEP_SHA_256 + * - WrappingKeySpec string required: public key type used to encrypt key material, RSA_2048 + * @param $runtime + * + * @return string json + */ + public function getParametersForImport($query = [], $runtime = []) + { + $query['Action'] = 'GetParametersForImport'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description + * @see https://help.aliyun.com/document_detail/68622.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * - EncryptedKeyMaterial string required: key material encrypted with base64 + * - ImportToken string required: obtained by calling GetParametersForImport + * - KeyMaterialExpireUnix {timestamp} optional: Key material expiration time + * @param $runtime + * + * @return string json + */ + public function importKeyMaterial($query = [], $runtime = []) + { + $query['Action'] = 'ImportKeyMaterial'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description returns all aliases of the current user in the current zone + * @see https://help.aliyun.com/document_detail/68627.html + * + * @param $query + * - Action string required + * - PageNumber int optional: current page, default 1 + * - PageSize int optional: result count (0 - 100), default 10 + * @param $runtime + * + * @return json string + */ + public function listAliases($query = [], $runtime = []) + { + $query['Action'] = 'ListAliases'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description list all aliases corresponding to the specified master key (CMK) + * @see https://help.aliyun.com/document_detail/68628.html + * + * @param $query + * - Action string required + * - KeyId string required: global unique identifier + * - PageNumber int optional: current page, default 1 + * - PageSize int optional: result count (0 - 100), default 10 + * @param $runtime + * + * @return json string + */ + public function listAliasesByKeyId($query = [], $runtime = []) + { + $query['Action'] = 'ListAliasesByKeyId'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + + /** + * @description Returns all the master key IDs of the caller in the calling area + * @see https://help.aliyun.com/document_detail/28951.html + * @param array $query + * @param array $runtime + * @return string json + */ + public function listKeys($query = [], $runtime = []) + { + $query['Action'] = 'ListKeys'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description request to delete a specified master key (CMK) + * @see https://help.aliyun.com/document_detail/44196.html + * @param array $query + * @param array $runtime + * @return json string + */ + public function scheduleKeyDeletion($query = [], $runtime = []) + { + $query['Action'] = 'ScheduleKeyDeletion'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + + $response = $this->request($request, $runtime); + + return $this->json($response); + } + + /** + * @description update the master key (CMK) represented by an existing alias + * @see https://help.aliyun.com/document_detail/68625.html + * @param array $query + * @param array $runtime + * @return json string + */ + public function updateAlias($query = [], $runtime = []) + { + $query['Action'] = 'UpdateAlias'; + $request['protocol'] = 'https'; + $request['method'] = 'GET'; + $request['pathname'] = '/'; + $request['query'] = $this->getQuery($query, $request); + $request['headers'] = [ + 'host' => $this->endpoint, + ]; + $response = $this->request($request, $runtime); + return $this->json($response); + } + + /** + * @param $response json + * @return mixed array() + */ + public function json($response){ + return $result = json_decode($response,true); + } + + + /** + * @return string + */ + public function getMethod() + { + return 'HMAC-SHA1'; + } + + /** + * @return string + */ + public function getType() + { + return ''; + } + + /** + * @return string + */ + public function getVersion() + { + return '1.0'; + } + + /** + * @param string $string + * @param string $accessKeySecret + * + * @return string + */ + public function sign($string, $accessKeySecret) + { + return base64_encode(hash_hmac('sha1', $string, $accessKeySecret, true)); + } + + +} \ No newline at end of file diff --git a/src/OSS/Model/ContentCryptoMaterial.php b/src/OSS/Model/ContentCryptoMaterial.php new file mode 100644 index 00000000..8bd936bd --- /dev/null +++ b/src/OSS/Model/ContentCryptoMaterial.php @@ -0,0 +1,121 @@ +cipher = $cipher; + $this->cekAlg = $cipher->getAlg(); + $this->wrapAlg = $wrapAlg; + $this->encryptedKey = $encryptedKey; + $this->encryptedIv = $encryptedIv; + $this->matDesc = $matDesc; + } + + + /** + * Assemble headers data + * @param $headers + * @return mixed + */ + public function addObjectMeta($headers){ + if(is_array($headers)){ + if (array_key_exists(OssClient::OSS_CONTENT_MD5,$headers)){ + $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_UNENCRYPTED_CONTENT_MD5] = $headers[OssClient::OSS_CONTENT_MD5]; + unset($headers[OssClient::OSS_CONTENT_MD5]); + } + + if (array_key_exists(OssClient::OSS_CONTENT_LENGTH,$headers)){ + $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_UNENCRYPTED_CONTENT_LENGTH] = $headers[OssClient::OSS_CONTENT_LENGTH]; + unset($headers[OssClient::OSS_CONTENT_LENGTH]); + } + + } + $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_KEY] = $this->encryptedKey; + $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_START] = $this->encryptedIv; + $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_CEK_ALG] = $this->cekAlg; + $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_WRAP_ALG] = $this->wrapAlg; + + if($this->matDesc){ + $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_MATDESC] = json_encode($this->matDesc); + } + + return $headers; + } + + /** + * Resolve headers data + * @param $headers + */ + public function fromObjectMeta($headers){ + $this->encryptedKey = $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_KEY]; + $this->encryptedIv = $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_START]; + $this->cekAlg = $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_CEK_ALG]; + $this->wrapAlg = $headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_WRAP_ALG]; + + $this->matDesc = json_decode($headers[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_MATDESC],true); + if($this->wrapAlg == 'kms' || $this->wrapAlg == Crypto::KMS_ALI_WRAP_ALGORITHM){ + $this->wrapAlg = Crypto::KMS_ALI_WRAP_ALGORITHM; + }else{ + $this->encryptedKey = base64_decode($this->encryptedKey); + $this->encryptedIv = base64_decode($this->encryptedIv); + } + } + + /** + * @return bool + */ + public function isUnencrypted(){ + if($this->encryptedKey == null && $this->encryptedIv == null && $this->cekAlg == null && $this->wrapAlg){ + return true; + }else{ + return false; + } + } + + public function getCipher(){ + return $this->cipher; + } + + public function getEncryptedIv(){ + return $this->encryptedIv; + } + + public function getEncryptedKey(){ + return $this->encryptedKey; + } + + public function getWrapAlg(){ + return $this->wrapAlg; + } + + public function getCekAlg(){ + return $this->cekAlg; + } + + public function getMatDesc(){ + return $this->matDesc; + } + +} diff --git a/src/OSS/OssClient.php b/src/OSS/OssClient.php index 0922a0b7..a6c8164f 100644 --- a/src/OSS/OssClient.php +++ b/src/OSS/OssClient.php @@ -42,6 +42,7 @@ use OSS\Result\ListLiveChannelResult; use OSS\Result\AppendResult; use OSS\Model\ObjectListInfo; +use OSS\Result\Result; use OSS\Result\SymlinkResult; use OSS\Result\UploadPartResult; use OSS\Model\BucketListInfo; @@ -1624,7 +1625,6 @@ public function putObject($bucket, $object, $content, $options = NULL) $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object); } $response = $this->auth($options); - if (isset($options[self::OSS_CALLBACK]) && !empty($options[self::OSS_CALLBACK])) { $result = new CallbackResult($response); } else { @@ -1633,6 +1633,32 @@ public function putObject($bucket, $object, $content, $options = NULL) return $result->getData(); } + + /** + * Upload the part content of Encrypt object to oss + * @param $bucket + * @param $object + * @param $content + * @param $uploadId + * @param null $options + * @return Etag + * @throws OssException + * @throws RequestCore_Exception + */ + public function uploadPartEncrypt($bucket, $object, $content,$uploadId, $options = NULL) + { + + $this->precheckCommon($bucket, $object, $options); + $this->precheckParam($options, self::OSS_PART_NUM, __FUNCTION__); + $options[self::OSS_CONTENT] = $content; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + $response = $this->auth($options); + $result = new UploadPartResult($response); + return $result->getData(); + } /** @@ -2028,6 +2054,9 @@ public function getObject($bucket, $object, $options = NULL) unset($options[self::OSS_RANGE]); } $response = $this->auth($options); + if(isset($response->header[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_KEY])){ + return $response; + } $result = new BodyResult($response); return $result->getData(); } @@ -2845,7 +2874,6 @@ private function auth($options) $conjunction = '&'; } $requestUrl = $scheme . $hostname . $resource_uri . $signable_query_string . $non_signable_resource; - //Creates the request $request = new RequestCore($requestUrl, $this->requestProxy); $request->set_useragent($this->generateUserAgent()); @@ -2895,9 +2923,8 @@ private function auth($options) if ($headers[self::OSS_CONTENT_TYPE] === 'application/x-www-form-urlencoded') { $headers[self::OSS_CONTENT_TYPE] = 'application/octet-stream'; } - - $headers[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); - $headers[self::OSS_CONTENT_MD5] = base64_encode(md5($options[self::OSS_CONTENT], true)); + $headers[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); + $headers[self::OSS_CONTENT_MD5] = base64_encode(md5($options[self::OSS_CONTENT], true)); } if (isset($options[self::OSS_CALLBACK])) { @@ -2956,7 +2983,6 @@ private function auth($options) if ($this->connectTimeout !== 0) { $request->connect_timeout = $this->connectTimeout; } - try { $request->send_request(); } catch (RequestCore_Exception $e) { @@ -3495,6 +3521,7 @@ public function setConnectTimeout($connectTimeout) const OSS_ACL_TYPE_PUBLIC_READ_WRITE = 'public-read-write'; const OSS_ENCODING_TYPE = "encoding-type"; const OSS_ENCODING_TYPE_URL = "url"; + // Domain Types const OSS_HOST_TYPE_NORMAL = "normal";//http://bucket.oss-cn-hangzhou.aliyuncs.com/object diff --git a/src/OSS/OssEncryptionClient.php b/src/OSS/OssEncryptionClient.php new file mode 100644 index 00000000..093578bf --- /dev/null +++ b/src/OSS/OssEncryptionClient.php @@ -0,0 +1,224 @@ +cryptoProvider = $cryptoProvider; + } + + /** + * Uploads the $content object to OSS. + * + * @param string $bucket bucket name + * @param string $object object name + * @param string $content The content object + * @param array $options + * @return null + * @throws OssException|GuzzleException + */ + public function putObject($bucket, $object, $content, $options = NULL) + { + $this->cryptoProvider->getCipher()->resetContext(); + $contentCryptoMaterial = $this->cryptoProvider->createContentMaterial(); + $encryptContent = $this->cryptoProvider->encryptAdapter($content,$contentCryptoMaterial->getCipher()); + $headers = isset($options['headers']) ? $options['headers'] :array(); + $options['headers'] = $contentCryptoMaterial->addObjectMeta($headers); + return parent::putObject($bucket, $object, $encryptContent, $options); + } + + + /** + * @param string $bucket + * @param string $object + * @param null $options + * @return false|string + * @throws OssException + */ + public function getObject($bucket, $object,$options = NULL) + { + $metaInfo = parent::getObjectMeta($bucket, $object,$options); + $isEncryptedObj = $this->isEncryptedObject($metaInfo); + if (!$isEncryptedObj) { + return parent::getObject($bucket, $object,$options); + } + $this->cryptoProvider->getCipher()->resetContext(); + $discardFrontAlignLen = 0; + if (isset($options[OssClient::OSS_FILE_DOWNLOAD])){ + unset($options[OssClient::OSS_FILE_DOWNLOAD]); + } + if(isset($options[OssClient::OSS_RANGE])){ + $range = explode('-',$options[OssClient::OSS_RANGE]); + if (is_numeric($range[0]) && 0 != $range[0] % $this->cryptoProvider->getCipher()->getAlignLen()){ + $start = $this->adjustRangeStart($range[0],$this->cryptoProvider->getCipher()); + $options[OssClient::OSS_RANGE] = $start . '-'. $range[1]; + $discardFrontAlignLen = $range[0] - $start; + }else{ + $start = $range[0]; + } + $this->cryptoProvider->getCipher()->calcOffset($start); + }else{ + $this->cryptoProvider->getCipher()->calcOffset(0); + } + $response = parent::getObject($bucket, $object, $options); + return $this->getObjectResult($response,$discardFrontAlignLen); + } + + + /** + * Initialize a multi-part upload + * + * @param string $bucket bucket name + * @param string $object object name + * @param array $options Key-Value array + * @return string returns upload id + * @throws OssException|GuzzleException + */ + public function initiateMultipartUpload($bucket, $object, $options = NULL) + { + $this->cryptoProvider->getCipher()->resetContext(); + $contentCryptoMaterial = $this->cryptoProvider->createContentMaterial(); + $headers = isset($options['headers']) ? $options['headers'] :array(); + $options['headers'] = $contentCryptoMaterial->addObjectMeta($headers); + return parent::initiateMultipartUpload($bucket, $object, $options); + } + + /** + * Computes the parts count, size and start position according to the file size and the part size. + * It must be only called by upload_Part(). + * + * @param integer $file_size File size + * @param integer $partSize part大小,part size. Default is 5MB + * @return array An array contains key-value pairs--the key is `seekTo`and value is `length`. + */ + public function generateMultiuploadParts($file_size, $partSize = 5242880) + { + return parent::generateMultiuploadParts($file_size, $partSize); + } + + /** + * Upload a part in a multiparts upload. + * + * @param string $bucket bucket name + * @param string $object object name + * @param string $uploadId + * @param array $options Key-Value array + * @return Etag eTag + * @throws OssException|RequestCore_Exception|GuzzleException + */ + public function uploadPart($bucket, $object, $uploadId, $options = null) + { + $encryptContent = $this->cryptoProvider->encryptAdapter($options[OssClient::OSS_CONTENT],$this->cryptoProvider->getCipher()); + return parent::uploadPartEncrypt($bucket, $object, $encryptContent,$uploadId, $options); + } + + + /** + * @param ResponseCore $response + * @param int $discardFrontAlignLen + * @return false|string + * @throws OssException + */ + private function getObjectResult(ResponseCore $response,$discardFrontAlignLen=0){ + $contentCryptoMaterial = new ContentCryptoMaterial($this->cryptoProvider->getCipher(),$this->cryptoProvider->getWrapAlg()); + $contentCryptoMaterial->fromObjectMeta($response->header); + if(!$contentCryptoMaterial->isUnencrypted()){ + if ($contentCryptoMaterial->getMatDesc() != $this->cryptoProvider->getMatDesc()){ + $encryptionMaterials = $this->cryptoProvider->getEncryptionMaterials($contentCryptoMaterial->getMatDesc()); + if($encryptionMaterials){ + $this->cryptoProvider = $this->cryptoProvider->resetEncryptionMaterials($encryptionMaterials); + }else{ + throw new OssException('There is no encryption materials match the material description of the object'); + } + } + $key = $this->cryptoProvider->decryptKey($contentCryptoMaterial->getEncryptedKey()); + $iv = $this->cryptoProvider->decryptIv($contentCryptoMaterial->getEncryptedIv()); + $this->cryptoProvider->getCipher()->init($key,$iv); + $content = $this->cryptoProvider->decryptAdapter($response->body,$this->cryptoProvider->getCipher()); + if ($discardFrontAlignLen > 0){ + return substr($content,$discardFrontAlignLen); + }else{ + return $content; + } + } + } + + /** + * Check object is Encrypted Object or nor + * @param $header array + * @return bool + */ + public function isEncryptedObject($header) + { + if(isset($header[OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_KEY])){ + return true; + }else{ + return false; + } + } + + /** + * @param $start + * @param $aesCtr AesCipher + * @return float|int + */ + public function adjustRangeStart($start, $aesCtr) { + $alignLen = $aesCtr->getAlignLen(); + return floor($start / $alignLen) * $alignLen; + } +} \ No newline at end of file diff --git a/tests/OSS/Tests/Common.php b/tests/OSS/Tests/Common.php index 15289448..231de886 100644 --- a/tests/OSS/Tests/Common.php +++ b/tests/OSS/Tests/Common.php @@ -4,8 +4,12 @@ require_once __DIR__ . '/../../../autoload.php'; +use OSS\Crypto\BaseCryptoProvider; +use Oss\Crypto\KmsProvider; +use OSS\Crypto\RsaProvider; use OSS\OssClient; use OSS\Core\OssException; +use Oss\OssEncryptionClient; /** * Class Common @@ -34,6 +38,44 @@ public static function getOssClient() return $ossClient; } + + /** + * @param KmsProvider|RsaProvider $provider + * @return OssEncryptionClient|null + */ + public static function getOssEncryptionClient($provider) + { + if (!$provider instanceof BaseCryptoProvider){ + throw new OssException('Crypto provider must be an instance of BaseCryptoProvider'); + } + try { + $ossClient = new OssEncryptionClient( getenv('OSS_ACCESS_KEY_ID'), + getenv('OSS_ACCESS_KEY_SECRET'), + getenv('OSS_ENDPOINT'), $provider); + } catch (OssException $e) { + printf(__FUNCTION__ . "creating OssClient instance: FAILED\n"); + printf($e->getMessage() . "\n"); + return null; + } + return $ossClient; + } + + + public static function getAccessKeyId() + { + return getenv('OSS_ACCESS_KEY_ID'); + } + + public static function getAccessKeySecret() + { + return getenv('OSS_ACCESS_KEY_SECRET'); + } + + public static function getEndPoint() + { + return getenv('OSS_ENDPOINT'); + } + public static function getBucketName() { return getenv('OSS_BUCKET'); @@ -49,6 +91,26 @@ public static function getCallbackUrl() return getenv('OSS_CALLBACK_URL'); } + public static function getKmsEndPoint() + { + return getenv('KMS_ENDPOINT');; + } + + public static function getKmsId() + { + return getenv('KMS_ID'); + } + + public static function getKmsEndPointOther() + { + return getenv('KMS_ENDPOINT_OTHER'); + } + + public static function getKmsIdOther() + { + return getenv('KMS_ID_OTHER'); + } + /** * Tool method, create a bucket */ diff --git a/tests/OSS/Tests/OssEncryptionClientObjectTest.php b/tests/OSS/Tests/OssEncryptionClientObjectTest.php new file mode 100644 index 00000000..3a2f18ec --- /dev/null +++ b/tests/OSS/Tests/OssEncryptionClientObjectTest.php @@ -0,0 +1,811 @@ + $this->publicKey, + 'private_key' => $this->privateKey + ); + $matDesc= array( + 'key1'=>'test-one' + ); + $provider= new RsaProvider($keys,$matDesc); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + try { + $result = $this->ossEncryptionClient->putObject($this->bucket,$object,$content); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + $result = $this->ossEncryptionClient->getObject($this->bucket,$object); + $this->assertEquals($result,$content); + }catch (OssException $e){ + $this->assertTrue(false); + } + + + $content2 = "Hi,hello This is a test"; + $object2 = "encry2.txt"; + + try { + $result = $this->ossEncryptionClient->putObject($this->bucket,$object2,$content2); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + $result2 = $this->ossEncryptionClient->getObject($this->bucket,$object2); + $this->assertEquals($result2,$content2); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + $result = $this->ossEncryptionClient->putObject($this->bucket,$object2,$content2); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + $result2 = $this->ossEncryptionClient->getObject($this->bucket,$object2); + $this->assertEquals($result2,$content2); + }catch (OssException $e){ + $this->assertTrue(false); + } + + + try { + Common::waitMetaSync(); + $keys = array( + 'public_key' => $this->publicKey2, + 'private_key' => $this->privateKey2 + ); + $matDesc= array( + 'key2'=>'test-two' + ); + $provider= new RsaProvider($keys,$matDesc); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + $result = $this->ossEncryptionClient->putObject($this->bucket,$object2,$content2); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + Common::waitMetaSync(); + $keys2 = array( + 'public_key' => $this->publicKey2, + 'private_key' => $this->privateKey2 + ); + $matDesc2= array( + 'key2'=>'test-two' + ); + $encryptionMaterials = new RsaEncryptionMaterials($matDesc2,$keys2); + $provider->addEncryptionMaterials($encryptionMaterials); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + $result = $this->ossEncryptionClient->getObject($this->bucket,$object2); + $this->assertEquals($result,$content2); + }catch (OssException $e){ + $this->assertTrue(false); + } + + } + + public function testRsaMultiUploadAndDownload(){ + ini_set('memory_limit', '8G'); + try { + print_r( "testRsaMultiUploadAndDownload Begin".PHP_EOL); + $object = "multi-upload.rar"; + $keys = array( + 'public_key' => $this->publicKey, + 'private_key' => $this->privateKey + ); + $matDesc= array( + 'key1'=>'test-one' + ); + $provider= new RsaProvider($keys,$matDesc); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + $partSize = 100 * 1024; + $bigFileName = __DIR__ . DIRECTORY_SEPARATOR . "/bigfile.tmp"; + if (file_exists($bigFileName)){ + unlink($bigFileName); + } + OssUtil::generateFile($bigFileName, 300 * 1024); + $uploadFile = $bigFileName; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $options['headers'] = array( + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_DATA_SIZE => $uploadFileSize, + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_PART_SIZE=>$partSize + ); + $uploadId = $this->ossEncryptionClient->initiateMultipartUpload($this->bucket, $object,$options); + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $this->ossEncryptionClient->uploadPart($this->bucket, $object, $uploadId, $upOptions); + printf( "initiateMultipartUpload, uploadPart - part#{$i} OK\n"); + } + $uploadParts = array(); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + $this->ossEncryptionClient->completeMultipartUpload($this->bucket, $object, $uploadId, $uploadParts); + printf("completeMultipartUpload OK\n"); + }catch (OssException $e) { + print_r($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + try { + $download = 'download.tmp'; + $objectMeta = $this->ossEncryptionClient->getObjectMeta($this->bucket, $object); + $size = $objectMeta['content-length']; + $partSize =1024*100; + $pieces2 = $this->ossEncryptionClient->generateMultiuploadParts($size, $partSize); + $downloadPosition = 0; + if (file_exists($download)){ + unlink($download); + } + foreach ($pieces2 as $i => $piece2) { + $fromPos2 = $downloadPosition + (integer)$piece2[OssClient::OSS_SEEK_TO]; + $toPos2 = (integer)$piece2[OssClient::OSS_LENGTH] + $fromPos2 - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos2.'-'.$toPos2 + ); + $content2 = $this->ossEncryptionClient->getObject($this->bucket,$object,$downOptions); + file_put_contents($download, $content2,FILE_APPEND ); + printf("Multi download, part - part#{$i} OK\n"); + } + $this->assertEquals(md5_file($uploadFile),md5_file($download)); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + unlink($bigFileName); + unlink($download); + } + + public function testResumeUploadAndDownload(){ + ini_set('memory_limit', '8G'); + $object = "multi-upload.rar"; + $keys = array( + 'public_key' => $this->publicKey, + 'private_key' => $this->privateKey + ); + $matDesc= array( + 'key1'=>'test-one' + ); + $provider= new RsaProvider($keys,$matDesc); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + try{ + $partSize = 100 * 1024; + $bigFileName = __DIR__ . DIRECTORY_SEPARATOR . "/bigfile.tmp"; + if (file_exists($bigFileName)){ + unlink($bigFileName); + } + OssUtil::generateFile($bigFileName, 120 * 1024); + $uploadFile = $bigFileName; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $options['headers'] = array( + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_DATA_SIZE => $uploadFileSize, + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_PART_SIZE=>$partSize + ); + $uploadId = $this->ossEncryptionClient->initiateMultipartUpload($this->bucket, $object,$options); + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + $uploadInfo = array( + 'uploadId' =>$uploadId, + 'object'=>$object, + 'uploadFile'=>$uploadFile, + 'partSize'=>$partSize, + ); + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $this->ossEncryptionClient->uploadPart($this->bucket, $object, $uploadId, $upOptions); + $uploadInfo['parts'] = $responseUploadPart; + file_put_contents('upload.ucp',json_encode($uploadInfo)); + if ($i == 2){ + break; + } + } + }catch (OssException $e) { + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + try{ + $str = file_get_contents("upload.ucp"); + $uploadInfo = json_decode($str,true); + $uploadId = $uploadInfo['uploadId']; + $parts = $uploadInfo['parts']; + $object = $uploadInfo['object']; + $partSize = $uploadInfo['partSize']; + $uploadFile = $uploadInfo['uploadFile']; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $num = count($parts); + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $this->ossEncryptionClient->uploadPart($this->bucket, $object, $uploadId, $upOptions); + } + $responseUploadPart = array_merge($parts,$responseUploadPart); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + $this->ossEncryptionClient->completeMultipartUpload($this->bucket, $object, $uploadId, $uploadParts); + }catch (OssException $e) { + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + try { + $download = "download.tmp"; + if (file_exists($download)){ + unlink($download); + } + $objectMeta = $this->ossEncryptionClient->getObjectMeta($this->bucket, $object); + $size = $objectMeta['content-length']; + + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($size, $partSize); + $downloadPosition = 0; + $downloadArray = array( + "object" => $object, + "pieces" => $pieces, + ); + foreach ($pieces as $i => $piece) { + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $this->ossEncryptionClient->getObject($this->bucket,$object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + $downloadArray['parts'] = $i+1; + + if ($i == 2){ + break; + } + } + file_put_contents("download.ucp",json_encode($downloadArray)); + printf( "Object ".$object.' download complete'); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + try { + $str = file_get_contents('download.ucp'); + $downloadInfo = json_decode($str,true); + $num = $downloadInfo['parts']; + $pieces = $downloadInfo['pieces']; + $object = $downloadInfo['object']; + $downloadPosition = 0; + foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $this->ossEncryptionClient->getObject($this->bucket,$object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + } + $this->assertEquals(md5_file($download),md5_file($uploadFile)); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + try { + if (file_exists($download)){ + unlink($download); + } + $result = $this->ossEncryptionClient->getObject($this->bucket, $object); + file_put_contents($download, $result); + $this->assertEquals(md5_file($uploadFile),md5_file($download)); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + unlink($bigFileName); + unlink($download); + unlink("download.ucp"); + unlink('upload.ucp'); + + } + + public function testRsaRangeDownload(){ + $content = file_get_contents(__FILE__); + $object = "encrypt.txt"; + $keys = array( + 'public_key' => $this->publicKey, + 'private_key' => $this->privateKey + ); + $matDesc= array( + 'key1'=>'test-one' + ); + $provider= new RsaProvider($keys,$matDesc); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + try { + $result = $this->ossEncryptionClient->putObject($this->bucket,$object,$content); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + $this->assertTrue(false); + } + try { + $options = array( + OssClient::OSS_RANGE => '48-100' + ); + $result = $this->ossEncryptionClient->getObject($this->bucket,$object,$options); + $this->assertEquals($result,OssUtil::getDataFromFile(__FILE__,48,100)); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + $options = array( + OssClient::OSS_RANGE => '13-100' + ); + $result = $this->ossEncryptionClient->getObject($this->bucket,$object,$options); + $this->assertEquals($result,OssUtil::getDataFromFile(__FILE__,13,100)); + }catch (OssException $e){ + $this->assertTrue(false); + } + } + + public function testKmsObject(){ + + $content = file_get_contents(__FILE__); + $object = "kms-encrypt.txt"; + $matDesc= array( + 'key2'=>'test-kms' + ); + $cmkId= Common::getKmsId(); + $provider= new KmsProvider(Common::getAccessKeyId(),Common::getAccessKeySecret(),Common::getKmsEndPoint(),$cmkId,$matDesc); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + try { + $result = $this->ossEncryptionClient->putObject($this->bucket,$object,$content); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + try { + $result = $this->ossEncryptionClient->getObject($this->bucket,$object); + $this->assertEquals($result,$content); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + + $content2 = "Hi,hello This is a test"; + $object2 = "kms-encrypt2.txt"; + $matDesc= array( + 'key1'=>'test-kms-two' + ); + $provider= new KmsProvider(Common::getAccessKeyId(),Common::getAccessKeySecret(),Common::getKmsEndPointOther(),$cmkId,$matDesc); + $otherKmsRegion = Common::getKmsEndPoint(); + $matDesc2= array( + 'key2'=>'test-kms' + ); + $kmsId2 = Common::getKmsIdOther(); + $encryptionMaterials = new KmsEncryptionMaterials($matDesc2,$otherKmsRegion,$kmsId2); + $provider->addEncryptionMaterials($encryptionMaterials); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + + try { + $result = $this->ossEncryptionClient->putObject($this->bucket,$object2,$content2); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + $this->assertTrue(false); + } + try { + $result = $this->ossEncryptionClient->getObject($this->bucket,$object); + $this->assertEquals($result,$content); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + $result = $this->ossEncryptionClient->putObject($this->bucket,$object2,$content2); + $this->assertNotNull($result['oss-requestheaders']['x-oss-meta-client-side-encryption-cek-alg']); + }catch (OssException $e){ + $this->assertTrue(false); + } + + try { + $result2 = $this->ossEncryptionClient->getObject($this->bucket,$object2); + $this->assertEquals($result2,$content2); + }catch (OssException $e){ + $this->assertTrue(false); + } + + } + + public function testKmsMultiUploadAndDownload(){ + $object = "multi-upload.rar"; + try { + $matDesc= array( + 'key1'=>'test-kms-two' + ); + $cmkId= Common::getKmsId(); + $provider= new KmsProvider(Common::getAccessKeyId(),Common::getAccessKeySecret(),Common::getKmsEndPointOther(),$cmkId,$matDesc); + $otherKmsRegion = Common::getKmsEndPoint(); + $matDesc2= array( + 'key2'=>'test-kms' + ); + $kmsId2 = Common::getKmsIdOther(); + $encryptionMaterials = new KmsEncryptionMaterials($matDesc2,$otherKmsRegion,$kmsId2); + $provider->addEncryptionMaterials($encryptionMaterials); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + $partSize = 100 * 1024; + $bigFileName = __DIR__ . DIRECTORY_SEPARATOR . "/bigfile.tmp"; + if (file_exists($bigFileName)){ + unlink($bigFileName); + } + OssUtil::generateFile($bigFileName, 300 * 1024); + $uploadFile = $bigFileName; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $options['headers'] = array( + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_DATA_SIZE => $uploadFileSize, + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_PART_SIZE=>$partSize + ); + $uploadId = $this->ossEncryptionClient->initiateMultipartUpload($this->bucket, $object,$options); + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $this->ossEncryptionClient->uploadPart($this->bucket, $object, $uploadId, $upOptions); + printf( "initiateMultipartUpload, uploadPart - part#{$i} OK\n"); + } + $uploadParts = array(); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + $this->ossEncryptionClient->completeMultipartUpload($this->bucket, $object, $uploadId, $uploadParts); + printf("completeMultipartUpload OK\n"); + }catch (OssException $e) { + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + + try { + $download = 'download.rar'; + $objectMeta = $this->ossEncryptionClient->getObjectMeta($this->bucket, $object); + $size = $objectMeta['content-length']; + $partSize =1024*100; + $pieces2 = $this->ossEncryptionClient->generateMultiuploadParts($size, $partSize); + $downloadPosition = 0; + if (file_exists($download)){ + unlink($download); + } + foreach ($pieces2 as $i => $piece2) { + $fromPos2 = $downloadPosition + (integer)$piece2[OssClient::OSS_SEEK_TO]; + $toPos2 = (integer)$piece2[OssClient::OSS_LENGTH] + $fromPos2 - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos2.'-'.$toPos2 + ); + $content2 = $this->ossEncryptionClient->getObject($this->bucket,$object,$downOptions); + file_put_contents($download, $content2,FILE_APPEND ); + printf("Multi download, part - part#{$i} OK\n"); + } + $this->assertEquals(md5_file($uploadFile),md5_file($download)); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + } + + public function testKmsResumeUploadAndDownload(){ + $object = "multi-upload.rar"; + $matDesc= array( + 'key1'=>'test-kms-two' + ); + $cmkId= Common::getKmsId(); + $provider= new KmsProvider(Common::getAccessKeyId(),Common::getAccessKeySecret(),Common::getKmsEndPointOther(),$cmkId,$matDesc); + $otherKmsRegion = Common::getKmsEndPoint(); + $matDesc2= array( + 'key2'=>'test-kms' + ); + $cmkId2 = Common::getKmsIdOther(); + $encryptionMaterials = new KmsEncryptionMaterials($matDesc2,$otherKmsRegion,$cmkId2); + $provider->addEncryptionMaterials($encryptionMaterials); + $this->ossEncryptionClient = Common::getOssEncryptionClient($provider); + try{ + $partSize = 100 * 1024; + $bigFileName = __DIR__ . DIRECTORY_SEPARATOR . "/bigfile.tmp"; + if (file_exists($bigFileName)){ + unlink($bigFileName); + } + OssUtil::generateFile($bigFileName, 300 * 1024); + $uploadFile = $bigFileName; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $options['headers'] = array( + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_DATA_SIZE => $uploadFileSize, + OssEncryptionClient::X_OSS_META_CLIENT_SIDE_ENCRYPTION_PART_SIZE=>$partSize + ); + $uploadId = $this->ossEncryptionClient->initiateMultipartUpload($this->bucket, $object,$options); + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + $uploadInfo = array( + 'uploadId' =>$uploadId, + 'object'=>$object, + 'uploadFile'=>$uploadFile, + 'partSize'=>$partSize, + ); + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $this->ossEncryptionClient->uploadPart($this->bucket, $object, $uploadId, $upOptions); + $uploadInfo['parts'] = $responseUploadPart; + file_put_contents('upload.ucp',json_encode($uploadInfo)); + if ($i == 2){ + break; + } + } + }catch (OssException $e) { + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + try{ + $str = file_get_contents("upload.ucp"); + $uploadInfo = json_decode($str,true); + $uploadId = $uploadInfo['uploadId']; + $parts = $uploadInfo['parts']; + $object = $uploadInfo['object']; + $partSize = $uploadInfo['partSize']; + $uploadFile = $uploadInfo['uploadFile']; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $num = count($parts); + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $content = OssUtil::getDataFromFile($uploadFile,$fromPos,$toPos); + $upOptions = array( + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_CONTENT => $content, + ); + $responseUploadPart[] = $this->ossEncryptionClient->uploadPart($this->bucket, $object, $uploadId, $upOptions); + } + $responseUploadPart = array_merge($parts,$responseUploadPart); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + $this->ossEncryptionClient->completeMultipartUpload($this->bucket, $object, $uploadId, $uploadParts); + }catch (OssException $e) { + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + try { + $download = "demo.rar"; + $objectMeta = $this->ossEncryptionClient->getObjectMeta($this->bucket, $object); + $size = $objectMeta['content-length']; + $partSize =1024*100; + + $pieces = $this->ossEncryptionClient->generateMultiuploadParts($size, $partSize); + $downloadPosition = 0; + $downloadArray = array( + "object" => $object, + "pieces" => $pieces, + ); + foreach ($pieces as $i => $piece) { + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $this->ossEncryptionClient->getObject($this->bucket,$object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + $downloadArray['parts'] = $i+1; + + if ($i == 2){ + break; + } + } + + file_put_contents("download.ucp",json_encode($downloadArray)); + printf( "Object ".$object.'download complete'); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + + try { + $str = file_get_contents('download.ucp'); + $downloadInfo = json_decode($str,true); + $num = $downloadInfo['parts']; + $pieces = $downloadInfo['pieces']; + $object = $downloadInfo['object']; + $downloadPosition = 0; + foreach ($pieces as $i => $piece) { + if($i < $num){ + continue; + } + $fromPos = $downloadPosition + (integer)$piece[OssClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1; + $downOptions = array( + OssClient::OSS_RANGE => $fromPos.'-'.$toPos + ); + $content = $this->ossEncryptionClient->getObject($this->bucket,$object,$downOptions); + file_put_contents($download, $content,FILE_APPEND ); + } + $this->assertEquals(md5_file($download),md5_file($uploadFile)); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + try { + if (file_exists($download)){ + unlink($download); + } + $result = $this->ossEncryptionClient->getObject($this->bucket, $object); + file_put_contents($download, $result); + $this->assertEquals(md5_file($uploadFile),md5_file($download)); + }catch (OssException $e){ + printf($e->getMessage() . PHP_EOL); + $this->assertTrue(false); + } + + unlink($bigFileName); + unlink($download); + unlink("download.ucp"); + unlink('upload.ucp'); + } +} + + diff --git a/tests/OSS/Tests/OssUtilTest.php b/tests/OSS/Tests/OssUtilTest.php index 598a7cc5..721afba5 100644 --- a/tests/OSS/Tests/OssUtilTest.php +++ b/tests/OSS/Tests/OssUtilTest.php @@ -161,6 +161,15 @@ public function testGetMd5SumForFile() } + + public function testGetDataFromFile() + { + $this->assertEquals(OssUtil::getDataFromFile(__FILE__, 0, sprintf('%u',filesize(__FILE__)) - 1), file_get_contents(__FILE__)); + $this->assertEquals(OssUtil::getDataFromFile(__FILE__, 0, OssClient::OSS_MAX_PART_SIZE + 1), ""); + $this->assertEquals(OssUtil::getDataFromFile(__FILE__, 0, sprintf('%u',filesize(__FILE__)) + 1), ""); + + } + public function testGenerateFile() { $path = __DIR__ . DIRECTORY_SEPARATOR . "generatedFile.txt"; diff --git a/tests/OSS/Tests/TestOssClientBase.php b/tests/OSS/Tests/TestOssClientBase.php index b2048712..0aa2cbf8 100644 --- a/tests/OSS/Tests/TestOssClientBase.php +++ b/tests/OSS/Tests/TestOssClientBase.php @@ -22,7 +22,25 @@ protected function setUp(): void { $this->bucket = Common::getBucketName() .'-'. time(); $this->ossClient = Common::getOssClient(); + $options[OssClient::OSS_HEADERS]['prefix'] = Common::getBucketName() .'-'; + $list = $this->ossClient->listBuckets($options); + foreach ($list->getBucketList() as $bucketInfo){ + $objects = $this->ossClient->listObjects($bucketInfo->getName(), array('max-keys' => 1000, 'delimiter' => ''))->getObjectList(); + $keys = array(); + foreach ($objects as $obj) { + $keys[] = $obj->getKey(); + } + if (count($keys) > 0) { + $this->ossClient->deleteObjects($bucketInfo->getName(), $keys); + } + $uploads = $this->ossClient->listMultipartUploads($bucketInfo->getName())->getUploads(); + foreach ($uploads as $up) { + $this->ossClient->abortMultipartUpload($bucketInfo->getName(), $up->getKey(), $up->getUploadId()); + } + $this->ossClient->deleteBucket($bucketInfo->getName()); + } $this->ossClient->createBucket($this->bucket); + Common::waitMetaSync(); }