From cb5839cf854dc648aa27ee9d79262b0ced4c17dd Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 15 Sep 2025 10:08:11 -0700 Subject: [PATCH 01/26] feat: add php test servers --- test-server/php-v2-server/README.md | 17 +++++++++++++++++ test-server/php-v3-server/README.md | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 test-server/php-v2-server/README.md create mode 100644 test-server/php-v3-server/README.md diff --git a/test-server/php-v2-server/README.md b/test-server/php-v2-server/README.md new file mode 100644 index 00000000..de16f673 --- /dev/null +++ b/test-server/php-v2-server/README.md @@ -0,0 +1,17 @@ +# S3EC PHP v3 Test Server + +This is the PHP V2 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality. + +## Overview + +The S3ECPhpV2TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: + +- Creating S3 Encryption Clients +- Putting objects with encryption +- Getting and decrypting objects + +## Usage + +This will start the server running on port `8087`. + +The server is used as part of the testing framework to verify cross-language compatibility of the S3 Encryption Client implementations. diff --git a/test-server/php-v3-server/README.md b/test-server/php-v3-server/README.md new file mode 100644 index 00000000..96d55f00 --- /dev/null +++ b/test-server/php-v3-server/README.md @@ -0,0 +1,17 @@ +# S3EC PHP V3 Test Server + +This is the PHP V3 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality. + +## Overview + +The S3ECPhpV3TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: + +- Creating S3 Encryption Clients +- Putting objects with encryption +- Getting and decrypting objects + +## Usage + +This will start the server running on port `8093`. + +The server is used as part of the testing framework to verify cross-language compatibility of the S3 Encryption Client implementations. From 22bf41d0379be3449725f54309f7804c73521f43 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 15 Sep 2025 17:38:34 -0700 Subject: [PATCH 02/26] add s3ec client reuse across sessions via cookies --- .gitignore | 2 + test-server/php-v2-server/README.md | 62 +- test-server/php-v2-server/composer.json | 24 + test-server/php-v2-server/composer.lock | 1133 +++++++++++++++++++++++ test-server/php-v2-server/cookies.txt | 4 + test-server/php-v2-server/src/index.php | 151 +++ 6 files changed, 1371 insertions(+), 5 deletions(-) create mode 100644 test-server/php-v2-server/composer.json create mode 100644 test-server/php-v2-server/composer.lock create mode 100644 test-server/php-v2-server/cookies.txt create mode 100644 test-server/php-v2-server/src/index.php diff --git a/.gitignore b/.gitignore index 0e29a9fb..730f02cd 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ gradle-app.setting .DS_Store smithy-java-core/out + +**/**/vendor/* \ No newline at end of file diff --git a/test-server/php-v2-server/README.md b/test-server/php-v2-server/README.md index de16f673..c4ba49fe 100644 --- a/test-server/php-v2-server/README.md +++ b/test-server/php-v2-server/README.md @@ -1,4 +1,4 @@ -# S3EC PHP v3 Test Server +# S3EC PHP v2 Test Server This is the PHP V2 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality. @@ -6,12 +6,64 @@ This is the PHP V2 implementation of the S3ECTestServer framework. It provides a The S3ECPhpV2TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: -- Creating S3 Encryption Clients +- Creating S3 Encryption Clients with session-based caching - Putting objects with encryption - Getting and decrypting objects -## Usage +## Starting the Server -This will start the server running on port `8087`. +### Method 1: Using Composer (Recommended) +```bash +composer run start +``` -The server is used as part of the testing framework to verify cross-language compatibility of the S3 Encryption Client implementations. +The server will start on port `8087`. + +## Available Endpoints + +### Server Status +- **GET /** - Returns server status and available endpoints + +### Client Management +- **POST /client** - Creates an S3EncryptionClient and caches it with session persistence +- **GET /cache** - Shows current session state and cached clients (for debugging) + +### Object Operations +- **GET /object/{bucket}/{key}** - Handle GET requests using the S3EncryptionClient +- **PUT /object/{bucket}/{key}** - Handle PUT requests using the S3EncryptionClient + +## Testing with curl + +### Important: Session Cookie Management + +To properly test the server and maintain session persistence, you **must** use cookies with curl: + +#### First Request (creates session cookie): +```bash +curl -X POST http://localhost:8087/client \ + -H "Content-Type: application/json" \ + -c cookies.txt \ + -v +``` + +#### Subsequent Requests (reuses session cookie): +```bash +curl -X POST http://localhost:8087/client \ + -H "Content-Type: application/json" \ + -b cookies.txt \ + -c cookies.txt \ + -v +``` + +#### Check Cache Status: +```bash +curl http://localhost:8087/cache \ + -b cookies.txt +``` + +#### Helpful Notes +- **Session Storage**: Client configurations are stored in `$_SESSION['s3ecCache']` +- **Object Recreation**: AWS SDK objects are recreated from stored configuration (they cannot be serialized) +AWS SDK obbjects cannot be serialized due to internal resources and closures. +- **Helper Function**: `getCachedClient($clientId)` retrieves and recreates clients from cache +- **Debugging**: Enhanced logging and `/cache` endpoint for troubleshooting diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json new file mode 100644 index 00000000..01212e9d --- /dev/null +++ b/test-server/php-v2-server/composer.json @@ -0,0 +1,24 @@ +{ + "name": "aws/s3ec-php-v2-test-server", + "description": "PHP v2 implementation of the S3EC Test Server framework", + "type": "project", + "license": "Apache-2.0", + "require": { + "php": ">=7.4", + "aws/aws-sdk-php": "^3.356", + "ramsey/uuid": "^4.9" + }, + "autoload": { + "psr-4": { + "S3EC\\PhpV2Server\\": "src/" + } + }, + "scripts": { + "start": [ + "php -S localhost:8087 src/index.php" + ] + }, + "config": { + "optimize-autoloader": true + } +} diff --git a/test-server/php-v2-server/composer.lock b/test-server/php-v2-server/composer.lock new file mode 100644 index 00000000..170610bb --- /dev/null +++ b/test-server/php-v2-server/composer.lock @@ -0,0 +1,1133 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5dc3463978ebc22921e829339c210c35", + "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.356.18", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "af1bf3dee5e66cf9b3d5d2507b08d9465636df29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/af1bf3dee5e66cf9b3d5d2507b08d9465636df29", + "reference": "af1bf3dee5e66cf9b3d5d2507b08d9465636df29", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.2.3", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/promises": "^2.0", + "guzzlehttp/psr7": "^2.4.5", + "mtdowling/jmespath.php": "^2.8.0", + "php": ">=8.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^2.7.8", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "psr/cache": "^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", + "symfony/filesystem": "^v6.4.0 || ^v7.1.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + }, + "exclude-from-classmap": [ + "src/data/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://github.com/aws/aws-sdk-php/discussions", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.356.18" + }, + "time": "2025-09-15T18:21:28+00:00" + }, + { + "name": "brick/math", + "version": "0.14.0", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.14.0" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-08-29T12:40:03+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "21dc724a0583619cd1652f673303492272778051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-08-23T21:21:41+00:00" + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" + }, + "time": "2024-09-04T18:46:31+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.1" + }, + "time": "2025-09-04T20:59:21+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.4" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/test-server/php-v2-server/cookies.txt b/test-server/php-v2-server/cookies.txt new file mode 100644 index 00000000..c31d9899 --- /dev/null +++ b/test-server/php-v2-server/cookies.txt @@ -0,0 +1,4 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php new file mode 100644 index 00000000..2e3387f9 --- /dev/null +++ b/test-server/php-v2-server/src/index.php @@ -0,0 +1,151 @@ +routes[] = [ + 'method' => strtoupper($method), + 'path' => $path, + 'handler' => $handler + ]; + } + + public function handleRequest() + { + $method = $_SERVER['REQUEST_METHOD']; + $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); + + foreach ($this->routes as $route) { + if ($route['method'] === $method && $route['path'] === $path) { + return call_user_func($route['handler']); + } + } + + // Default 404 response + http_response_code(404); + return json_encode(['error' => 'Not Found']); + } +} + +// Helper function to get cached client by ID +function getCachedClient($clientId) +{ + if (!isset($_SESSION['s3ecCache'][$clientId])) { + return null; + } + + $config = $_SESSION['s3ecCache'][$clientId]; + + // Recreate the AWS clients from stored configuration + $s3Client = new S3Client($config['s3Config']); + $encryptionClient = new S3EncryptionClientV2($s3Client); + + $kmsClient = new KmsClient($config['kmsConfig']); + $materialsProvider = new KmsMaterialsProviderV2($kmsClient, $config['kmsKeyId']); + + return [ + 'encryptionClient' => $encryptionClient, + 'materialsProvider' => $materialsProvider, + 'config' => $config + ]; +} + +// Initialize router +$router = new SimpleRouter(); + +// Add basic routes +$router->addRoute('GET', '/', function () { + return json_encode([ + 'service' => 'S3EC PHP v2 Test Server', + 'status' => 'running', + 'port' => 8087, + 'endpoints' => [ + 'GET /' => 'Server status', + 'POST /client' => 'Create an S3EncryptionClient and cache it.', + 'GET /object/{bucket}/{key}' => 'Handle GET requests to /object/{bucket}/{key} by using the S3EncryptionClient to make a GetObject request to S3.', + 'PUT /object/{bucket}/{key}' => 'Handle PUT requests to /object/{bucket}/{key} by using the S3EncryptionClient to make a PutObject request to S3.', + ] + ]); +}); + +$router->addRoute('POST', '/client', function () { + $clientId = Uuid::uuid4()->toString(); + + // Debug session info + // error_log("Session ID: " . session_id()); + // error_log("Session status: " . session_status()); + // error_log("Cache before adding: " . json_encode(array_keys($_SESSION['s3ecCache'] ?? []))); + + // Store client configuration instead of objects (AWS objects can't be serialized) + $_SESSION['s3ecCache'][$clientId] = [ + 's3Config' => [ + 'profile' => 'default', + 'region' => 'us-west-2', + 'version' => 'latest', + ], + 'kmsConfig' => [ + 'profile' => 'default', + 'region' => 'us-east-1', + 'version' => 'latest', + ], + 'kmsKeyId' => 'kms-key-id', + 'created' => time() + ]; + + // Debug: show all cached clients after adding + error_log("Total clients in cache: " . count($_SESSION['s3ecCache'])); + + return json_encode([ + 'clientId' => $clientId, + ]); +}); + +$router->addRoute('GET', '/cache', function () { + return json_encode([ + 'sessionId' => session_id(), + 'sessionStatus' => session_status(), + 'totalCachedClients' => count($_SESSION['s3ecCache'] ?? []), + 'allClientIds' => array_keys($_SESSION['s3ecCache'] ?? []), + 'cacheDetails' => $_SESSION['s3ecCache'] ?? [] + ]); +}); + +$router->addRoute('GET', '/object', function () { + return json_encode([ + 'status' => 'healthy', + 'message' => 'implement me!' + ]); +}); + +$router->addRoute('PUT', '/object', function () { + return json_encode([ + 'status' => 'healthy', + 'message' => 'implement me!' + ]); +}); +// Set content type header +header('Content-Type: application/json'); + +// Handle the request and output response +echo $router->handleRequest(); From c4185e2b7e262607aba0cdd77285530d6b6d04c4 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Tue, 16 Sep 2025 16:33:10 -0700 Subject: [PATCH 03/26] add putobject and getobject --- .../amazon/encryption/s3/RoundTripTests.java | 3 + test-server/php-v2-server/cookies.txt | 1 + test-server/php-v2-server/src/index.php | 223 ++++++++++++++++-- 3 files changed, 203 insertions(+), 24 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 211269d7..6c849f6d 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -66,10 +66,12 @@ public class RoundTripTests { serverList = new ArrayList<>(2); serverList.add(new LanguageServerTarget("Java", "8080")); serverList.add(new LanguageServerTarget("Python", "8081")); + serverList.add(new LanguageServerTarget("PHP-V2", "8087")); serverMap = new HashMap<>(2); serverMap.put("Java", new LanguageServerTarget("Java", "8080")); serverMap.put("Python", new LanguageServerTarget("Python", "8081")); + serverMap.put("PHP-V2", new LanguageServerTarget("PHP-V2", "8087")); } static public class LanguageServerTarget { @@ -205,6 +207,7 @@ public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTar .build()); if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { + fail(String.format("Actual: %s", StandardCharsets.UTF_8.decode(output.getBody()).toString())); fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); } } diff --git a/test-server/php-v2-server/cookies.txt b/test-server/php-v2-server/cookies.txt index c31d9899..fcb0191b 100644 --- a/test-server/php-v2-server/cookies.txt +++ b/test-server/php-v2-server/cookies.txt @@ -2,3 +2,4 @@ # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. +localhost FALSE / FALSE 0 PHPSESSID ef2dbe1b5d98a271e57c7523079a04b4 diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index 2e3387f9..f0122b14 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -37,8 +37,11 @@ public function handleRequest() $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); foreach ($this->routes as $route) { - if ($route['method'] === $method && $route['path'] === $path) { - return call_user_func($route['handler']); + if ($route['method'] === $method) { + $params = $this->matchPathWithParams($route['path'], $path); + if ($params !== false) { + return call_user_func($route['handler'], $params); + } } } @@ -46,6 +49,34 @@ public function handleRequest() http_response_code(404); return json_encode(['error' => 'Not Found']); } + + private function matchPathWithParams($routePath, $requestPath) + { + // Handle exact matches first (for routes without parameters) + if ($routePath === $requestPath) { + return []; + } + + // Convert route path like '/object/{bucket}/{key}' to regex + $pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $routePath); + $pattern = '/^' . str_replace('/', '\/', $pattern) . '$/'; + + if (preg_match($pattern, $requestPath, $matches)) { + array_shift($matches); // Remove full match + + // Extract parameter names + preg_match_all('/\{([^}]+)\}/', $routePath, $paramNames); + $params = []; + + foreach ($paramNames[1] as $index => $paramName) { + $params[$paramName] = $matches[$index] ?? null; + } + + return $params; + } + + return false; + } } // Helper function to get cached client by ID @@ -71,6 +102,51 @@ function getCachedClient($clientId) ]; } +function createDefaultClientTuple(): array +{ + $s3Client = new S3Client([ + 'region' => 'us-west-2', + 'version' => 'latest', + ]); + $encryptionClient = new S3EncryptionClientV2($s3Client); + + $kmsClient = new KmsClient([ + 'region' => 'us-west-2', + 'version' => 'latest', + ]); + $materialsProvider = new KmsMaterialsProviderV2($kmsClient, 'arn:aws:kms:us-west-2:370957321024:alias/S3EC-Test-Server-Github-KMS-Key'); + + return [ + 'encryptionClient' => $encryptionClient, + 'materialsProvider' => $materialsProvider + ]; +} + +function metadataStringToMap($metadata): array +{ + $md = []; + + if (empty($metadata)) { + return $md; + } + + $mdList = explode(',', $metadata); + + foreach ($mdList as $entry) { + $parts = explode(']:[', $entry); + + if (count($parts) === 2) { + $key = substr($parts[0], 1); + $value = substr($parts[1], 0, -1); + $md[$key] = $value; + } else { + throw new InvalidArgumentException("Malformed metadata list entry: " . $entry); + } + } + + return $md; +} + // Initialize router $router = new SimpleRouter(); @@ -90,32 +166,40 @@ function getCachedClient($clientId) }); $router->addRoute('POST', '/client', function () { - $clientId = Uuid::uuid4()->toString(); + // Get the raw request body + $rawBody = file_get_contents('php://input'); - // Debug session info - // error_log("Session ID: " . session_id()); - // error_log("Session status: " . session_status()); - // error_log("Cache before adding: " . json_encode(array_keys($_SESSION['s3ecCache'] ?? []))); + // Parse JSON if the body contains JSON + $requestData = json_decode($rawBody, true); + if (json_last_error() !== JSON_ERROR_NONE) { + http_response_code(400); + return json_encode(['error' => 'Invalid JSON in request body']); + } + $config_data = $requestData['config'] ?? []; + $key_material = $config_data["keyMaterial"] ?? null; + + $clientId = Uuid::uuid4()->toString(); + $kms_key_id = $key_material["kmsKeyId"] ?? null; // Store client configuration instead of objects (AWS objects can't be serialized) $_SESSION['s3ecCache'][$clientId] = [ 's3Config' => [ - 'profile' => 'default', 'region' => 'us-west-2', 'version' => 'latest', ], 'kmsConfig' => [ - 'profile' => 'default', - 'region' => 'us-east-1', + 'region' => 'us-west-2', 'version' => 'latest', ], - 'kmsKeyId' => 'kms-key-id', + 'kmsKeyId' => $kms_key_id, 'created' => time() ]; // Debug: show all cached clients after adding error_log("Total clients in cache: " . count($_SESSION['s3ecCache'])); + error_log("ClientID: " . $clientId); + header("Content-Type: application/json"); return json_encode([ 'clientId' => $clientId, ]); @@ -131,21 +215,112 @@ function getCachedClient($clientId) ]); }); -$router->addRoute('GET', '/object', function () { - return json_encode([ - 'status' => 'healthy', - 'message' => 'implement me!' - ]); -}); +$router->addRoute('GET', '/object/{bucket}/{key}', function ($params) { + // Get ClientID from HTTP header + $clientId = $_SERVER['HTTP_X_CLIENT_ID'] ?? $_SERVER['HTTP_CLIENTID'] ?? null; + + if (empty($clientId)) { + http_response_code(400); + return json_encode(['error' => 'ClientID header is required']); + } + + error_log("clientId from get /object: " . $clientId); + + # Get the S3EncryptionClient from the client_cache + $s3ecClientTuple = getCachedClient($clientId); + if ($s3ecClientTuple === null) { + error_log("No cached client found :( " . $clientId); + error_log("Creating a default client now."); + $s3ecClientTuple = createDefaultClientTuple(); + } + + $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; + $encryptionContext = metadataStringToMap($metadata); + error_log("encryption context: " . json_encode($encryptionContext)); + + // Extract bucket and key from URL parameters + $bucket = $params['bucket'] ?? null; + $key = $params['key'] ?? null; + + $s3ec = $s3ecClientTuple["encryptionClient"]; + $materialProvider = $s3ecClientTuple["materialsProvider"]; + + try { + $result = $s3ec->getObject([ + '@KmsAllowDecryptWithAnyCmk' => true, + '@SecurityProfile' => 'V2', + '@MaterialsProvider' => $materialProvider, + 'Bucket' => $bucket, + 'Key' => $key, + ]); + + $body = $result['Body']->getContents(); + header("Content-Metadata:" . json_encode($result["Metadata"])); + // header("Content-Type: application/octet-stream"); + return $body; + } catch (InvalidArgumentException $e) { + http_response_code(400); + return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); + } catch (Exception $e) { + http_response_code(500); + return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + } -$router->addRoute('PUT', '/object', function () { - return json_encode([ - 'status' => 'healthy', - 'message' => 'implement me!' - ]); }); -// Set content type header -header('Content-Type: application/json'); +$router->addRoute('PUT', '/object/{bucket}/{key}', function ($params) { + // Get the raw request body + $rawBody = file_get_contents('php://input'); + // Get ClientID from HTTP header + $clientId = $_SERVER['HTTP_X_CLIENT_ID'] ?? $_SERVER['HTTP_CLIENTID'] ?? null; + + if (empty($clientId)) { + http_response_code(400); + return json_encode(['error' => 'ClientID header is required']); + } + + error_log("clientId from get /object: " . $clientId); + + # Get the S3EncryptionClient from the client_cache + $s3ecClientTuple = getCachedClient($clientId); + if ($s3ecClientTuple === null) { + error_log("No cached client found :( " . $clientId); + error_log("Creating a default client now."); + $s3ecClientTuple = createDefaultClientTuple(); + } + $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; + $encryptionContext = metadataStringToMap($metadata); + error_log("encryption context: " . json_encode($encryptionContext)); + + // Extract bucket and key from URL parameters + $bucket = $params['bucket'] ?? null; + $key = $params['key'] ?? null; + + $s3ec = $s3ecClientTuple["encryptionClient"]; + $materialProvider = $s3ecClientTuple["materialsProvider"]; + + try { + $result = $s3ec->putObject([ + '@MaterialsProvider' => $materialProvider, + '@KmsEncryptionContext' => $encryptionContext, + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => $rawBody, + ]); + + return json_encode([ + "bucket" => $bucket, + "key" => $key, + "metadata" => $encryptionContext, + ]); + + } catch (InvalidArgumentException $e) { + http_response_code(400); + return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); + } catch (Exception $e) { + http_response_code(500); + return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + } +}); // Handle the request and output response echo $router->handleRequest(); From da3f17a48b68ce7231300f7194e961d6574f0a54 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Wed, 17 Sep 2025 15:20:53 -0700 Subject: [PATCH 04/26] fix get/put --- .../amazon/encryption/s3/RoundTripTests.java | 7 ++-- test-server/php-v2-server/composer.json | 4 +- test-server/php-v2-server/src/index.php | 39 +++++++++++++++++-- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 6c849f6d..4eb8e0b7 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -66,12 +66,12 @@ public class RoundTripTests { serverList = new ArrayList<>(2); serverList.add(new LanguageServerTarget("Java", "8080")); serverList.add(new LanguageServerTarget("Python", "8081")); - serverList.add(new LanguageServerTarget("PHP-V2", "8087")); + serverList.add(new LanguageServerTarget("PHP", "8087")); serverMap = new HashMap<>(2); serverMap.put("Java", new LanguageServerTarget("Java", "8080")); serverMap.put("Python", new LanguageServerTarget("Python", "8081")); - serverMap.put("PHP-V2", new LanguageServerTarget("PHP-V2", "8087")); + serverMap.put("PHP", new LanguageServerTarget("PHP", "8087")); } static public class LanguageServerTarget { @@ -207,7 +207,8 @@ public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTar .build()); if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { - fail(String.format("Actual: %s", StandardCharsets.UTF_8.decode(output.getBody()).toString())); + System.out.println(String.format("Response body Length: %s", StandardCharsets.UTF_8.decode(output.getBody()).toString().length())); + System.out.println(String.format("Response body: %s", StandardCharsets.UTF_8.decode(output.getBody()).toString())); fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); } } diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json index 01212e9d..e9c399ac 100644 --- a/test-server/php-v2-server/composer.json +++ b/test-server/php-v2-server/composer.json @@ -15,10 +15,10 @@ }, "scripts": { "start": [ - "php -S localhost:8087 src/index.php" + "php -S 0.0.0.0:8087 src/index.php" ] }, "config": { "optimize-autoloader": true } -} +} \ No newline at end of file diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index f0122b14..559fb777 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -8,6 +8,7 @@ use Aws\Kms\KmsClient; use Ramsey\Uuid\Uuid; +use GuzzleHttp\Psr7\Response; // Start session to persist cache across requests session_start(); @@ -146,6 +147,23 @@ function metadataStringToMap($metadata): array return $md; } +function formatMetadataForResponse($metadata) +{ + $metadataList = []; + // Handle different metadata input types + if (is_array($metadata)) { + // If it's an associative array (like Python dict) + foreach ($metadata as $key => $value) { + $metadataList[] = $key . '=' . $value; + } + } elseif (is_string($metadata) && !empty($metadata)) { + // If it's already a string, assume it's in the correct format + return $metadata; + } + + // Convert array to comma-separated string + return implode(',', $metadataList); +} // Initialize router $router = new SimpleRouter(); @@ -236,7 +254,7 @@ function metadataStringToMap($metadata): array $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; $encryptionContext = metadataStringToMap($metadata); - error_log("encryption context: " . json_encode($encryptionContext)); + // error_log("encryption context: " . json_encode($encryptionContext)); // Extract bucket and key from URL parameters $bucket = $params['bucket'] ?? null; @@ -250,13 +268,17 @@ function metadataStringToMap($metadata): array '@KmsAllowDecryptWithAnyCmk' => true, '@SecurityProfile' => 'V2', '@MaterialsProvider' => $materialProvider, + '@KmsEncryptionContext' => $encryptionContext, 'Bucket' => $bucket, 'Key' => $key, ]); $body = $result['Body']->getContents(); - header("Content-Metadata:" . json_encode($result["Metadata"])); - // header("Content-Type: application/octet-stream"); + $formattedMetadata = formatMetadataForResponse($result["Metadata"]); + error_log("Response Object: " . $body); + header("Content-Metadata: " . $formattedMetadata); + header("Content-Type: application/octet-stream"); + header("Content-Length: " . strlen($body)); return $body; } catch (InvalidArgumentException $e) { http_response_code(400); @@ -298,16 +320,22 @@ function metadataStringToMap($metadata): array $s3ec = $s3ecClientTuple["encryptionClient"]; $materialProvider = $s3ecClientTuple["materialsProvider"]; + $cipherOptions = [ + 'Cipher' => 'gcm', + 'KeySize' => 256, + ]; try { $result = $s3ec->putObject([ '@MaterialsProvider' => $materialProvider, '@KmsEncryptionContext' => $encryptionContext, + '@CipherOptions' => $cipherOptions, 'Bucket' => $bucket, 'Key' => $key, 'Body' => $rawBody, ]); + header("Content-Type: application/json"); return json_encode([ "bucket" => $bucket, "key" => $key, @@ -323,4 +351,7 @@ function metadataStringToMap($metadata): array } }); // Handle the request and output response -echo $router->handleRequest(); +$result = $router->handleRequest(); +if ($result !== false) { + echo $result; +} From 37250e180b8be5f810d91dd354112724a9e52a33 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 13:25:18 -0700 Subject: [PATCH 05/26] add more --- .../amazon/encryption/s3/RoundTripTests.java | 475 +++++++++--------- test-server/php-v2-server/src/index.php | 45 +- 2 files changed, 274 insertions(+), 246 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 4eb8e0b7..7092718f 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -64,14 +64,14 @@ public class RoundTripTests { static { serverList = new ArrayList<>(2); - serverList.add(new LanguageServerTarget("Java", "8080")); + serverList.add(new LanguageServerTarget("Java", "8080")); serverList.add(new LanguageServerTarget("Python", "8081")); - serverList.add(new LanguageServerTarget("PHP", "8087")); + serverList.add(new LanguageServerTarget("PHP-V2", "8087")); serverMap = new HashMap<>(2); - serverMap.put("Java", new LanguageServerTarget("Java", "8080")); + serverMap.put("Java", new LanguageServerTarget("Java", "8080")); serverMap.put("Python", new LanguageServerTarget("Python", "8081")); - serverMap.put("PHP", new LanguageServerTarget("PHP", "8087")); + serverMap.put("PHP", new LanguageServerTarget("PHP-V2", "8087")); } static public class LanguageServerTarget { @@ -174,44 +174,42 @@ private List metadataMapToList(Map md) { return mdAsList; } - @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") - @MethodSource("crossLanguageClients") - public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTarget decLang) { - S3ECTestServerClient encClient = testServerClientFor(encLang); - final String objectKey = "cross-lang-test-key-" + encLang; - final String input = "simple-test-input"; - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String encS3ECId = encClientOutput.getClientId(); - encClient.putObject(PutObjectInput.builder() - .clientID(encS3ECId) - .key(objectKey) - .bucket(BUCKET) - .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) - .build()); - S3ECTestServerClient decClient = testServerClientFor(decLang); - CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String decS3ECId = decClientOutput.getClientId(); - GetObjectOutput output = decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .build()); - - if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { - System.out.println(String.format("Response body Length: %s", StandardCharsets.UTF_8.decode(output.getBody()).toString().length())); - System.out.println(String.format("Response body: %s", StandardCharsets.UTF_8.decode(output.getBody()).toString())); - fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); - } - } + @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") + @MethodSource("crossLanguageClients") + public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTarget decLang) { + S3ECTestServerClient encClient = testServerClientFor(encLang); + final String objectKey = "cross-lang-test-key-" + encLang; + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String encS3ECId = encClientOutput.getClientId(); + encClient.putObject(PutObjectInput.builder() + .clientID(encS3ECId) + .key(objectKey) + .bucket(BUCKET) + .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) + .build()); + S3ECTestServerClient decClient = testServerClientFor(decLang); + CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String decS3ECId = decClientOutput.getClientId(); + GetObjectOutput output = decClient.getObject(GetObjectInput.builder() + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .build()); + + if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { + fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); + } + } @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") @MethodSource("crossLanguageClients") @@ -239,202 +237,205 @@ public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, Languag .metadata(mdAsList) .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) .build()); - S3ECTestServerClient decClient = testServerClientFor(decLang); - CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String decS3ECId = decClientOutput.getClientId(); - GetObjectOutput output = decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .metadata(mdAsList) - .build()); - - if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { - fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); - } - } - - @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") - @MethodSource("crossLanguageClients") - public void crossLanguageTestKmsWithEncCtxMismatchFails(LanguageServerTarget encLang, LanguageServerTarget decLang) { - S3ECTestServerClient encClient = testServerClientFor(encLang); - final String objectKey = "cross-lang-test-key-kms-ec-mismatch-fails" + encLang; - final String input = "simple-test-input"; - final Map encCtx = new HashMap<>(); - encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); - encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); - final List mdAsList = metadataMapToList(encCtx); - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String encS3ECId = encClientOutput.getClientId(); - - encClient.putObject(PutObjectInput.builder() - .clientID(encS3ECId) - .key(objectKey) - .bucket(BUCKET) - .metadata(mdAsList) - .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) - .build()); - S3ECTestServerClient decClient = testServerClientFor(decLang); - CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String decS3ECId = decClientOutput.getClientId(); - try { - decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .build()); - fail("Expected exception!"); - } catch (S3EncryptionClientError e) { - assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); - } - } - - @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") - @MethodSource("clientsForTest") - public void kmsV1Legacy(String language) { - S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); - final String objectKey = "test-key-kms-v1-" + language; - final String input = "simple-test-input"; - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput output1 = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .enableLegacyWrappingAlgorithms(true) - .keyMaterial(kmsKeyArn) - .build()) - .build()); - String s3ECId = output1.getClientId(); - - // Create the object using the old client - // V1 Client - EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); - - CryptoConfiguration v1Config = - new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.ObjectMetadata) - .withAwsKmsRegion(KMS_REGION); - - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() - .withCryptoConfiguration(v1Config) - .withEncryptionMaterials(materialsProvider) - .build(); - - v1Client.putObject(BUCKET, objectKey, input); - - GetObjectOutput output = client.getObject(GetObjectInput.builder() - .clientID(s3ECId) - .bucket(BUCKET) - .key(objectKey) - .build()); - - assertEquals(input, new String(output.getBody().array())); - } - - @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") - @MethodSource("clientsForTest") - public void kmsV1LegacyWithEncCtx(String language) { - S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); - final String objectKey = "test-key-kms-v1-with-enc-ctx-" + language; - final String input = "simple-test-input"; - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput output1 = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .enableLegacyWrappingAlgorithms(true) - .keyMaterial(kmsKeyArn) - .build()) - .build()); - String s3ECId = output1.getClientId(); - - // Create the object using the old client - // V1 Client - final String ecKey = "user-metadata-key"; - final String ecValue = "user-metadata-value-v1"; - KMSEncryptionMaterials kmsMaterials = new KMSEncryptionMaterials(KMS_KEY_ARN); - kmsMaterials.addDescription(ecKey, ecValue); - EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(kmsMaterials); - - CryptoConfiguration v1Config = - new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.ObjectMetadata) - .withAwsKmsRegion(KMS_REGION); - - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() - .withCryptoConfiguration(v1Config) - .withEncryptionMaterials(materialsProvider) - .build(); - - v1Client.putObject(BUCKET, objectKey, input); - - final Map encCtx = new HashMap<>(); - encCtx.put(ecKey, ecValue); - GetObjectOutput output = client.getObject(GetObjectInput.builder() - .clientID(s3ECId) - .bucket(BUCKET) - .key(objectKey) - .metadata(metadataMapToList(encCtx)) - .build()); - - assertEquals(input, new String(output.getBody().array())); + S3ECTestServerClient decClient = testServerClientFor(decLang); + CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String decS3ECId = decClientOutput.getClientId(); + GetObjectOutput output = decClient.getObject(GetObjectInput.builder() + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .metadata(mdAsList) + .build()); + + if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { + fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); + } } - @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") - @MethodSource("clientsForTest") - public void kmsV1LegacyFailsWhenLegacyDisabled(String language) { - S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); - final String objectKey = "test-key-kms-v1-fails-disabled" + language; - final String input = "simple-test-input"; - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput output1 = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .enableLegacyWrappingAlgorithms(false) - .keyMaterial(kmsKeyArn) - .build()) - .build()); - String s3ECId = output1.getClientId(); - - // Create the object using the old client - // V1 Client - EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); - - CryptoConfiguration v1Config = - new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.ObjectMetadata) - .withAwsKmsRegion(KMS_REGION); - - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() - .withCryptoConfiguration(v1Config) - .withEncryptionMaterials(materialsProvider) - .build(); - - v1Client.putObject(BUCKET, objectKey, input); - - try { - client.getObject(GetObjectInput.builder() - .clientID(s3ECId) - .bucket(BUCKET) - .key(objectKey) - .build()); - fail("Expected Exception"); - } catch (S3EncryptionClientError e) { - assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms")); - } - } + @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") + @MethodSource("crossLanguageClients") + public void crossLanguageTestKmsWithEncCtxMismatchFails(LanguageServerTarget encLang, LanguageServerTarget decLang) { + if (decLang.getLanguageName().equals("PHP-V2")) { + return; + } + S3ECTestServerClient encClient = testServerClientFor(encLang); + final String objectKey = "cross-lang-test-key-kms-ec-mismatch-fails" + encLang; + final String input = "simple-test-input"; + final Map encCtx = new HashMap<>(); + encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); + encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); + final List mdAsList = metadataMapToList(encCtx); + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String encS3ECId = encClientOutput.getClientId(); + + encClient.putObject(PutObjectInput.builder() + .clientID(encS3ECId) + .key(objectKey) + .bucket(BUCKET) + .metadata(mdAsList) + .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) + .build()); + S3ECTestServerClient decClient = testServerClientFor(decLang); + CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String decS3ECId = decClientOutput.getClientId(); + try { + decClient.getObject(GetObjectInput.builder() + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .build()); + fail("Expected exception!"); + } catch (S3EncryptionClientError e) { + assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); + } + } + + // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + // @MethodSource("clientsForTest") + // public void kmsV1Legacy(String language) { + // S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); + // final String objectKey = "test-key-kms-v1-" + language; + // final String input = "simple-test-input"; + // KeyMaterial kmsKeyArn = KeyMaterial.builder() + // .kmsKeyId(KMS_KEY_ARN) + // .build(); + // CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + // .config(S3ECConfig.builder() + // .enableLegacyWrappingAlgorithms(true) + // .keyMaterial(kmsKeyArn) + // .build()) + // .build()); + // String s3ECId = output1.getClientId(); + + // // Create the object using the old client + // // V1 Client + // EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); + + // CryptoConfiguration v1Config = + // new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + // .withStorageMode(CryptoStorageMode.ObjectMetadata) + // .withAwsKmsRegion(KMS_REGION); + + // AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + // .withCryptoConfiguration(v1Config) + // .withEncryptionMaterials(materialsProvider) + // .build(); + + // v1Client.putObject(BUCKET, objectKey, input); + + // GetObjectOutput output = client.getObject(GetObjectInput.builder() + // .clientID(s3ECId) + // .bucket(BUCKET) + // .key(objectKey) + // .build()); + + // assertEquals(input, new String(output.getBody().array())); + // } + + // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + // @MethodSource("clientsForTest") + // public void kmsV1LegacyWithEncCtx(String language) { + // S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); + // final String objectKey = "test-key-kms-v1-with-enc-ctx-" + language; + // final String input = "simple-test-input"; + // KeyMaterial kmsKeyArn = KeyMaterial.builder() + // .kmsKeyId(KMS_KEY_ARN) + // .build(); + // CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + // .config(S3ECConfig.builder() + // .enableLegacyWrappingAlgorithms(true) + // .keyMaterial(kmsKeyArn) + // .build()) + // .build()); + // String s3ECId = output1.getClientId(); + + // // Create the object using the old client + // // V1 Client + // final String ecKey = "user-metadata-key"; + // final String ecValue = "user-metadata-value-v1"; + // KMSEncryptionMaterials kmsMaterials = new KMSEncryptionMaterials(KMS_KEY_ARN); + // kmsMaterials.addDescription(ecKey, ecValue); + // EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(kmsMaterials); + + // CryptoConfiguration v1Config = + // new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + // .withStorageMode(CryptoStorageMode.ObjectMetadata) + // .withAwsKmsRegion(KMS_REGION); + + // AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + // .withCryptoConfiguration(v1Config) + // .withEncryptionMaterials(materialsProvider) + // .build(); + + // v1Client.putObject(BUCKET, objectKey, input); + + // final Map encCtx = new HashMap<>(); + // encCtx.put(ecKey, ecValue); + // GetObjectOutput output = client.getObject(GetObjectInput.builder() + // .clientID(s3ECId) + // .bucket(BUCKET) + // .key(objectKey) + // .metadata(metadataMapToList(encCtx)) + // .build()); + + // assertEquals(input, new String(output.getBody().array())); + // } + + // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + // @MethodSource("clientsForTest") + // public void kmsV1LegacyFailsWhenLegacyDisabled(String language) { + // S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); + // final String objectKey = "test-key-kms-v1-fails-disabled" + language; + // final String input = "simple-test-input"; + // KeyMaterial kmsKeyArn = KeyMaterial.builder() + // .kmsKeyId(KMS_KEY_ARN) + // .build(); + // CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + // .config(S3ECConfig.builder() + // .enableLegacyWrappingAlgorithms(false) + // .keyMaterial(kmsKeyArn) + // .build()) + // .build()); + // String s3ECId = output1.getClientId(); + + // // Create the object using the old client + // // V1 Client + // EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); + + // CryptoConfiguration v1Config = + // new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + // .withStorageMode(CryptoStorageMode.ObjectMetadata) + // .withAwsKmsRegion(KMS_REGION); + + // AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + // .withCryptoConfiguration(v1Config) + // .withEncryptionMaterials(materialsProvider) + // .build(); + + // v1Client.putObject(BUCKET, objectKey, input); + + // try { + // client.getObject(GetObjectInput.builder() + // .clientID(s3ECId) + // .bucket(BUCKET) + // .key(objectKey) + // .build()); + // fail("Expected Exception"); + // } catch (S3EncryptionClientError e) { + // assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms")); + // } + // } } diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index 559fb777..3a7fea58 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -108,12 +108,28 @@ function createDefaultClientTuple(): array $s3Client = new S3Client([ 'region' => 'us-west-2', 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] ]); $encryptionClient = new S3EncryptionClientV2($s3Client); $kmsClient = new KmsClient([ 'region' => 'us-west-2', 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] ]); $materialsProvider = new KmsMaterialsProviderV2($kmsClient, 'arn:aws:kms:us-west-2:370957321024:alias/S3EC-Test-Server-Github-KMS-Key'); @@ -204,10 +220,26 @@ function formatMetadataForResponse($metadata) 's3Config' => [ 'region' => 'us-west-2', 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] ], 'kmsConfig' => [ 'region' => 'us-west-2', 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] ], 'kmsKeyId' => $kms_key_id, 'created' => time() @@ -242,8 +274,6 @@ function formatMetadataForResponse($metadata) return json_encode(['error' => 'ClientID header is required']); } - error_log("clientId from get /object: " . $clientId); - # Get the S3EncryptionClient from the client_cache $s3ecClientTuple = getCachedClient($clientId); if ($s3ecClientTuple === null) { @@ -254,7 +284,7 @@ function formatMetadataForResponse($metadata) $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; $encryptionContext = metadataStringToMap($metadata); - // error_log("encryption context: " . json_encode($encryptionContext)); + error_log("Encryption Context: " . json_encode($encryptionContext)); // Extract bucket and key from URL parameters $bucket = $params['bucket'] ?? null; @@ -265,7 +295,6 @@ function formatMetadataForResponse($metadata) try { $result = $s3ec->getObject([ - '@KmsAllowDecryptWithAnyCmk' => true, '@SecurityProfile' => 'V2', '@MaterialsProvider' => $materialProvider, '@KmsEncryptionContext' => $encryptionContext, @@ -275,7 +304,6 @@ function formatMetadataForResponse($metadata) $body = $result['Body']->getContents(); $formattedMetadata = formatMetadataForResponse($result["Metadata"]); - error_log("Response Object: " . $body); header("Content-Metadata: " . $formattedMetadata); header("Content-Type: application/octet-stream"); header("Content-Length: " . strlen($body)); @@ -301,8 +329,6 @@ function formatMetadataForResponse($metadata) return json_encode(['error' => 'ClientID header is required']); } - error_log("clientId from get /object: " . $clientId); - # Get the S3EncryptionClient from the client_cache $s3ecClientTuple = getCachedClient($clientId); if ($s3ecClientTuple === null) { @@ -310,9 +336,10 @@ function formatMetadataForResponse($metadata) error_log("Creating a default client now."); $s3ecClientTuple = createDefaultClientTuple(); } + // Capture all Content-Metadata headers $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; $encryptionContext = metadataStringToMap($metadata); - error_log("encryption context: " . json_encode($encryptionContext)); + error_log('Combined Encryption Context: ' . $metadata); // Extract bucket and key from URL parameters $bucket = $params['bucket'] ?? null; @@ -339,7 +366,7 @@ function formatMetadataForResponse($metadata) return json_encode([ "bucket" => $bucket, "key" => $key, - "metadata" => $encryptionContext, + // "metadata" => $encryptionContext ]); } catch (InvalidArgumentException $e) { From d9b9cfc4640c40237e7fd3f92ca236cf701e8d1a Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 13:30:01 -0700 Subject: [PATCH 06/26] refactor --- test-server/php-v2-server/src/client.php | 60 +++++++ test-server/php-v2-server/src/get_object.php | 54 ++++++ test-server/php-v2-server/src/index.php | 171 +------------------ test-server/php-v2-server/src/put_object.php | 62 +++++++ 4 files changed, 185 insertions(+), 162 deletions(-) create mode 100644 test-server/php-v2-server/src/client.php create mode 100644 test-server/php-v2-server/src/get_object.php create mode 100644 test-server/php-v2-server/src/put_object.php diff --git a/test-server/php-v2-server/src/client.php b/test-server/php-v2-server/src/client.php new file mode 100644 index 00000000..e27f85f9 --- /dev/null +++ b/test-server/php-v2-server/src/client.php @@ -0,0 +1,60 @@ + 'Invalid JSON in request body']); + } + $config_data = $requestData['config'] ?? []; + $key_material = $config_data["keyMaterial"] ?? null; + + $clientId = Uuid::uuid4()->toString(); + $kms_key_id = $key_material["kmsKeyId"] ?? null; + + // Store client configuration instead of objects (AWS objects can't be serialized) + $_SESSION['s3ecCache'][$clientId] = [ + 's3Config' => [ + 'region' => 'us-west-2', + 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] + ], + 'kmsConfig' => [ + 'region' => 'us-west-2', + 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] + ], + 'kmsKeyId' => $kms_key_id, + 'created' => time() + ]; + + // Debug: show all cached clients after adding + error_log("Total clients in cache: " . count($_SESSION['s3ecCache'])); + error_log("ClientID: " . $clientId); + + header("Content-Type: application/json"); + return json_encode([ + 'clientId' => $clientId, + ]); +} diff --git a/test-server/php-v2-server/src/get_object.php b/test-server/php-v2-server/src/get_object.php new file mode 100644 index 00000000..6ed515d2 --- /dev/null +++ b/test-server/php-v2-server/src/get_object.php @@ -0,0 +1,54 @@ + 'ClientID header is required']); + } + + # Get the S3EncryptionClient from the client_cache + $s3ecClientTuple = getCachedClient($clientId); + if ($s3ecClientTuple === null) { + error_log("No cached client found :( " . $clientId); + error_log("Creating a default client now."); + $s3ecClientTuple = createDefaultClientTuple(); + } + + $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; + $encryptionContext = metadataStringToMap($metadata); + error_log("Encryption Context: " . json_encode($encryptionContext)); + + // Extract bucket and key from URL parameters + $bucket = $params['bucket'] ?? null; + $key = $params['key'] ?? null; + + $s3ec = $s3ecClientTuple["encryptionClient"]; + $materialProvider = $s3ecClientTuple["materialsProvider"]; + + try { + $result = $s3ec->getObject([ + '@SecurityProfile' => 'V2', + '@MaterialsProvider' => $materialProvider, + '@KmsEncryptionContext' => $encryptionContext, + 'Bucket' => $bucket, + 'Key' => $key, + ]); + + $body = $result['Body']->getContents(); + $formattedMetadata = formatMetadataForResponse($result["Metadata"]); + header("Content-Metadata: " . $formattedMetadata); + header("Content-Type: application/octet-stream"); + header("Content-Length: " . strlen($body)); + return $body; + } catch (InvalidArgumentException $e) { + http_response_code(400); + return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); + } catch (Exception $e) { + http_response_code(500); + return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + } +} diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index 3a7fea58..90974010 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -1,6 +1,9 @@ addRoute('POST', '/client', function () { - // Get the raw request body - $rawBody = file_get_contents('php://input'); - - // Parse JSON if the body contains JSON - $requestData = json_decode($rawBody, true); - if (json_last_error() !== JSON_ERROR_NONE) { - http_response_code(400); - return json_encode(['error' => 'Invalid JSON in request body']); - } - $config_data = $requestData['config'] ?? []; - $key_material = $config_data["keyMaterial"] ?? null; - - $clientId = Uuid::uuid4()->toString(); - $kms_key_id = $key_material["kmsKeyId"] ?? null; - - // Store client configuration instead of objects (AWS objects can't be serialized) - $_SESSION['s3ecCache'][$clientId] = [ - 's3Config' => [ - 'region' => 'us-west-2', - 'version' => 'latest', - 'http' => [ - 'debug' => false, - 'verify' => true, - 'curl' => [ - CURLOPT_VERBOSE => false, - CURLOPT_NOPROGRESS => true - ] - ] - ], - 'kmsConfig' => [ - 'region' => 'us-west-2', - 'version' => 'latest', - 'http' => [ - 'debug' => false, - 'verify' => true, - 'curl' => [ - CURLOPT_VERBOSE => false, - CURLOPT_NOPROGRESS => true - ] - ] - ], - 'kmsKeyId' => $kms_key_id, - 'created' => time() - ]; - - // Debug: show all cached clients after adding - error_log("Total clients in cache: " . count($_SESSION['s3ecCache'])); - error_log("ClientID: " . $clientId); - - header("Content-Type: application/json"); - return json_encode([ - 'clientId' => $clientId, - ]); -}); - $router->addRoute('GET', '/cache', function () { return json_encode([ 'sessionId' => session_id(), @@ -266,117 +213,17 @@ function formatMetadataForResponse($metadata) }); $router->addRoute('GET', '/object/{bucket}/{key}', function ($params) { - // Get ClientID from HTTP header - $clientId = $_SERVER['HTTP_X_CLIENT_ID'] ?? $_SERVER['HTTP_CLIENTID'] ?? null; - - if (empty($clientId)) { - http_response_code(400); - return json_encode(['error' => 'ClientID header is required']); - } - - # Get the S3EncryptionClient from the client_cache - $s3ecClientTuple = getCachedClient($clientId); - if ($s3ecClientTuple === null) { - error_log("No cached client found :( " . $clientId); - error_log("Creating a default client now."); - $s3ecClientTuple = createDefaultClientTuple(); - } - - $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; - $encryptionContext = metadataStringToMap($metadata); - error_log("Encryption Context: " . json_encode($encryptionContext)); - - // Extract bucket and key from URL parameters - $bucket = $params['bucket'] ?? null; - $key = $params['key'] ?? null; - - $s3ec = $s3ecClientTuple["encryptionClient"]; - $materialProvider = $s3ecClientTuple["materialsProvider"]; - - try { - $result = $s3ec->getObject([ - '@SecurityProfile' => 'V2', - '@MaterialsProvider' => $materialProvider, - '@KmsEncryptionContext' => $encryptionContext, - 'Bucket' => $bucket, - 'Key' => $key, - ]); - - $body = $result['Body']->getContents(); - $formattedMetadata = formatMetadataForResponse($result["Metadata"]); - header("Content-Metadata: " . $formattedMetadata); - header("Content-Type: application/octet-stream"); - header("Content-Length: " . strlen($body)); - return $body; - } catch (InvalidArgumentException $e) { - http_response_code(400); - return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); - } catch (Exception $e) { - http_response_code(500); - return json_encode(['error' => 'Server error: ' . $e->getMessage()]); - } - + return handleGetObject($params); }); $router->addRoute('PUT', '/object/{bucket}/{key}', function ($params) { - // Get the raw request body - $rawBody = file_get_contents('php://input'); - // Get ClientID from HTTP header - $clientId = $_SERVER['HTTP_X_CLIENT_ID'] ?? $_SERVER['HTTP_CLIENTID'] ?? null; - - if (empty($clientId)) { - http_response_code(400); - return json_encode(['error' => 'ClientID header is required']); - } - - # Get the S3EncryptionClient from the client_cache - $s3ecClientTuple = getCachedClient($clientId); - if ($s3ecClientTuple === null) { - error_log("No cached client found :( " . $clientId); - error_log("Creating a default client now."); - $s3ecClientTuple = createDefaultClientTuple(); - } - // Capture all Content-Metadata headers - $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; - $encryptionContext = metadataStringToMap($metadata); - error_log('Combined Encryption Context: ' . $metadata); - - // Extract bucket and key from URL parameters - $bucket = $params['bucket'] ?? null; - $key = $params['key'] ?? null; - - $s3ec = $s3ecClientTuple["encryptionClient"]; - $materialProvider = $s3ecClientTuple["materialsProvider"]; - $cipherOptions = [ - 'Cipher' => 'gcm', - 'KeySize' => 256, - ]; + return handlePutObject($params); +}); - try { - $result = $s3ec->putObject([ - '@MaterialsProvider' => $materialProvider, - '@KmsEncryptionContext' => $encryptionContext, - '@CipherOptions' => $cipherOptions, - 'Bucket' => $bucket, - 'Key' => $key, - 'Body' => $rawBody, - ]); - - header("Content-Type: application/json"); - return json_encode([ - "bucket" => $bucket, - "key" => $key, - // "metadata" => $encryptionContext - ]); - - } catch (InvalidArgumentException $e) { - http_response_code(400); - return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); - } catch (Exception $e) { - http_response_code(500); - return json_encode(['error' => 'Server error: ' . $e->getMessage()]); - } +$router->addRoute('POST', '/client', function () { + return handleCreateClient(); }); + // Handle the request and output response $result = $router->handleRequest(); if ($result !== false) { diff --git a/test-server/php-v2-server/src/put_object.php b/test-server/php-v2-server/src/put_object.php new file mode 100644 index 00000000..a9c563fa --- /dev/null +++ b/test-server/php-v2-server/src/put_object.php @@ -0,0 +1,62 @@ + 'ClientID header is required']); + } + + # Get the S3EncryptionClient from the client_cache + $s3ecClientTuple = getCachedClient($clientId); + if ($s3ecClientTuple === null) { + error_log("No cached client found :( " . $clientId); + error_log("Creating a default client now."); + $s3ecClientTuple = createDefaultClientTuple(); + } + // Capture all Content-Metadata headers + $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; + $encryptionContext = metadataStringToMap($metadata); + error_log('Combined Encryption Context: ' . $metadata); + + // Extract bucket and key from URL parameters + $bucket = $params['bucket'] ?? null; + $key = $params['key'] ?? null; + + $s3ec = $s3ecClientTuple["encryptionClient"]; + $materialProvider = $s3ecClientTuple["materialsProvider"]; + $cipherOptions = [ + 'Cipher' => 'gcm', + 'KeySize' => 256, + ]; + + try { + $result = $s3ec->putObject([ + '@MaterialsProvider' => $materialProvider, + '@KmsEncryptionContext' => $encryptionContext, + '@CipherOptions' => $cipherOptions, + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => $rawBody, + ]); + + header("Content-Type: application/json"); + return json_encode([ + "bucket" => $bucket, + "key" => $key, + // "metadata" => $encryptionContext + ]); + + } catch (InvalidArgumentException $e) { + http_response_code(400); + return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); + } catch (Exception $e) { + http_response_code(500); + return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + } +} From a732cfb2b24f0f4c845c1f3a635e5e848a98fde6 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 14:33:34 -0700 Subject: [PATCH 07/26] fix cached clients --- test-server/cookies.txt | 5 + .../amazon/encryption/s3/RoundTripTests.java | 180 +++++++++--------- test-server/php-v2-server/cookies.txt | 2 +- test-server/php-v2-server/src/client.php | 3 + test-server/php-v2-server/src/get_object.php | 5 +- test-server/php-v2-server/src/index.php | 51 +++++ test-server/php-v2-server/src/put_object.php | 4 +- 7 files changed, 156 insertions(+), 94 deletions(-) create mode 100644 test-server/cookies.txt diff --git a/test-server/cookies.txt b/test-server/cookies.txt new file mode 100644 index 00000000..d7e13e34 --- /dev/null +++ b/test-server/cookies.txt @@ -0,0 +1,5 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +localhost FALSE / FALSE 0 PHPSESSID f01c84e1c5598a69a9378e990951b1a5 diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 7092718f..7caa475d 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -211,96 +211,96 @@ public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTar } } - @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") - @MethodSource("crossLanguageClients") - public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, LanguageServerTarget decLang) { - S3ECTestServerClient encClient = testServerClientFor(encLang); - final String objectKey = "cross-lang-test-key-kms-ec-" + encLang; - final String input = "simple-test-input"; - final Map encCtx = new HashMap<>(); - encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); - encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); - final List mdAsList = metadataMapToList(encCtx); - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String encS3ECId = encClientOutput.getClientId(); - - encClient.putObject(PutObjectInput.builder() - .clientID(encS3ECId) - .key(objectKey) - .bucket(BUCKET) - .metadata(mdAsList) - .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) - .build()); - S3ECTestServerClient decClient = testServerClientFor(decLang); - CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String decS3ECId = decClientOutput.getClientId(); - GetObjectOutput output = decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .metadata(mdAsList) - .build()); - - if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { - fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); - } - } - - @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") - @MethodSource("crossLanguageClients") - public void crossLanguageTestKmsWithEncCtxMismatchFails(LanguageServerTarget encLang, LanguageServerTarget decLang) { - if (decLang.getLanguageName().equals("PHP-V2")) { - return; - } - S3ECTestServerClient encClient = testServerClientFor(encLang); - final String objectKey = "cross-lang-test-key-kms-ec-mismatch-fails" + encLang; - final String input = "simple-test-input"; - final Map encCtx = new HashMap<>(); - encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); - encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); - final List mdAsList = metadataMapToList(encCtx); - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String encS3ECId = encClientOutput.getClientId(); - - encClient.putObject(PutObjectInput.builder() - .clientID(encS3ECId) - .key(objectKey) - .bucket(BUCKET) - .metadata(mdAsList) - .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) - .build()); - S3ECTestServerClient decClient = testServerClientFor(decLang); - CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String decS3ECId = decClientOutput.getClientId(); - try { - decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .build()); - fail("Expected exception!"); - } catch (S3EncryptionClientError e) { - assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); - } - } +// @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") +// @MethodSource("crossLanguageClients") +// public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, LanguageServerTarget decLang) { +// S3ECTestServerClient encClient = testServerClientFor(encLang); +// final String objectKey = "cross-lang-test-key-kms-ec-" + encLang; +// final String input = "simple-test-input"; +// final Map encCtx = new HashMap<>(); +// encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); +// encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); +// final List mdAsList = metadataMapToList(encCtx); +// KeyMaterial kmsKeyArn = KeyMaterial.builder() +// .kmsKeyId(KMS_KEY_ARN) +// .build(); +// CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() +// .config(S3ECConfig.builder() +// .keyMaterial(kmsKeyArn).build()) +// .build()); +// String encS3ECId = encClientOutput.getClientId(); +// +// encClient.putObject(PutObjectInput.builder() +// .clientID(encS3ECId) +// .key(objectKey) +// .bucket(BUCKET) +// .metadata(mdAsList) +// .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) +// .build()); +// S3ECTestServerClient decClient = testServerClientFor(decLang); +// CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() +// .config(S3ECConfig.builder() +// .keyMaterial(kmsKeyArn).build()) +// .build()); +// String decS3ECId = decClientOutput.getClientId(); +// GetObjectOutput output = decClient.getObject(GetObjectInput.builder() +// .clientID(decS3ECId) +// .bucket(BUCKET) +// .key(objectKey) +// .metadata(mdAsList) +// .build()); +// +// if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { +// fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); +// } +// } +// +// @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") +// @MethodSource("crossLanguageClients") +// public void crossLanguageTestKmsWithEncCtxMismatchFails(LanguageServerTarget encLang, LanguageServerTarget decLang) { +// if (decLang.getLanguageName().equals("PHP-V2")) { +// return; +// } +// S3ECTestServerClient encClient = testServerClientFor(encLang); +// final String objectKey = "cross-lang-test-key-kms-ec-mismatch-fails" + encLang; +// final String input = "simple-test-input"; +// final Map encCtx = new HashMap<>(); +// encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); +// encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); +// final List mdAsList = metadataMapToList(encCtx); +// KeyMaterial kmsKeyArn = KeyMaterial.builder() +// .kmsKeyId(KMS_KEY_ARN) +// .build(); +// CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() +// .config(S3ECConfig.builder() +// .keyMaterial(kmsKeyArn).build()) +// .build()); +// String encS3ECId = encClientOutput.getClientId(); +// +// encClient.putObject(PutObjectInput.builder() +// .clientID(encS3ECId) +// .key(objectKey) +// .bucket(BUCKET) +// .metadata(mdAsList) +// .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) +// .build()); +// S3ECTestServerClient decClient = testServerClientFor(decLang); +// CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() +// .config(S3ECConfig.builder() +// .keyMaterial(kmsKeyArn).build()) +// .build()); +// String decS3ECId = decClientOutput.getClientId(); +// try { +// decClient.getObject(GetObjectInput.builder() +// .clientID(decS3ECId) +// .bucket(BUCKET) +// .key(objectKey) +// .build()); +// fail("Expected exception!"); +// } catch (S3EncryptionClientError e) { +// assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); +// } +// } // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") // @MethodSource("clientsForTest") diff --git a/test-server/php-v2-server/cookies.txt b/test-server/php-v2-server/cookies.txt index fcb0191b..88eebe6a 100644 --- a/test-server/php-v2-server/cookies.txt +++ b/test-server/php-v2-server/cookies.txt @@ -2,4 +2,4 @@ # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. -localhost FALSE / FALSE 0 PHPSESSID ef2dbe1b5d98a271e57c7523079a04b4 +localhost FALSE / FALSE 0 PHPSESSID 0e5bc5f3120e6fd550dbdbe8e290df91 diff --git a/test-server/php-v2-server/src/client.php b/test-server/php-v2-server/src/client.php index e27f85f9..d6f8843d 100644 --- a/test-server/php-v2-server/src/client.php +++ b/test-server/php-v2-server/src/client.php @@ -53,6 +53,9 @@ function handleCreateClient() error_log("Total clients in cache: " . count($_SESSION['s3ecCache'])); error_log("ClientID: " . $clientId); + // Auto-update cookies.txt with current session ID so tests can access cached clients + writeSessionIdToCookiesFile(session_id()); + header("Content-Type: application/json"); return json_encode([ 'clientId' => $clientId, diff --git a/test-server/php-v2-server/src/get_object.php b/test-server/php-v2-server/src/get_object.php index 6ed515d2..9fcecbac 100644 --- a/test-server/php-v2-server/src/get_object.php +++ b/test-server/php-v2-server/src/get_object.php @@ -12,15 +12,16 @@ function handleGetObject($params) # Get the S3EncryptionClient from the client_cache $s3ecClientTuple = getCachedClient($clientId); - if ($s3ecClientTuple === null) { + if ($s3ecClientTuple == null) { error_log("No cached client found :( " . $clientId); error_log("Creating a default client now."); $s3ecClientTuple = createDefaultClientTuple(); + } else { + error_log("Cached Client found: " . $clientId); } $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; $encryptionContext = metadataStringToMap($metadata); - error_log("Encryption Context: " . json_encode($encryptionContext)); // Extract bucket and key from URL parameters $bucket = $params['bucket'] ?? null; diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index 90974010..0ad6bc59 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -13,7 +13,58 @@ use Ramsey\Uuid\Uuid; use GuzzleHttp\Psr7\Response; +// Function to read session ID from cookies.txt file +function getSessionIdFromCookiesFile() +{ + $cookiesFile = __DIR__ . '/../cookies.txt'; + + if (!file_exists($cookiesFile)) { + return null; + } + + $lines = file($cookiesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + foreach ($lines as $line) { + // Skip comment lines + if (strpos($line, '#') === 0) { + continue; + } + + // Parse cookie line: domain, flag, path, secure, expiration, name, value + $parts = explode("\t", $line); + if (count($parts) >= 7 && $parts[5] === 'PHPSESSID') { + return $parts[6]; // Return the session ID value + } + } + + error_log("No session Id found"); + + return null; +} + +// Function to write session ID to cookies.txt file +function writeSessionIdToCookiesFile($sessionId) +{ + $cookiesFile = __DIR__ . '/../cookies.txt'; + + // Create Netscape cookie format entry + $cookieLine = "localhost\tFALSE\t/\tFALSE\t0\tPHPSESSID\t$sessionId"; + + // Write header and cookie entry + $content = "# Netscape HTTP Cookie File\n"; + $content .= "# https://curl.se/docs/http-cookies.html\n"; + $content .= "# This file was generated by libcurl! Edit at your own risk.\n\n"; + $content .= $cookieLine . "\n"; + + file_put_contents($cookiesFile, $content); +} + // Start session to persist cache across requests +// First try to use session ID from cookies.txt if available +$sessionId = getSessionIdFromCookiesFile(); +if ($sessionId) { + session_id($sessionId); +} session_start(); // Initialize session cache if it doesn't exist diff --git a/test-server/php-v2-server/src/put_object.php b/test-server/php-v2-server/src/put_object.php index a9c563fa..9fb5f541 100644 --- a/test-server/php-v2-server/src/put_object.php +++ b/test-server/php-v2-server/src/put_object.php @@ -18,11 +18,13 @@ function handlePutObject($params) error_log("No cached client found :( " . $clientId); error_log("Creating a default client now."); $s3ecClientTuple = createDefaultClientTuple(); + } else { + error_log("Cached Client found: " . $clientId); } + // Capture all Content-Metadata headers $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; $encryptionContext = metadataStringToMap($metadata); - error_log('Combined Encryption Context: ' . $metadata); // Extract bucket and key from URL parameters $bucket = $params['bucket'] ?? null; From e9e2545ef5a23e94688df5b66d762b689631262a Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 14:38:40 -0700 Subject: [PATCH 08/26] i dont want to check in the cookies.txt file --- .gitignore | 3 ++- test-server/cookies.txt | 5 ----- test-server/php-v2-server/cookies.txt | 5 ----- test-server/php-v2-server/src/index.php | 20 +++++++++++++++++--- 4 files changed, 19 insertions(+), 14 deletions(-) delete mode 100644 test-server/cookies.txt delete mode 100644 test-server/php-v2-server/cookies.txt diff --git a/.gitignore b/.gitignore index 730f02cd..12327de5 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,5 @@ gradle-app.setting .DS_Store smithy-java-core/out -**/**/vendor/* \ No newline at end of file +**/**/vendor/* +test-server/php-v2-server/cookies.txt \ No newline at end of file diff --git a/test-server/cookies.txt b/test-server/cookies.txt deleted file mode 100644 index d7e13e34..00000000 --- a/test-server/cookies.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Netscape HTTP Cookie File -# https://curl.se/docs/http-cookies.html -# This file was generated by libcurl! Edit at your own risk. - -localhost FALSE / FALSE 0 PHPSESSID f01c84e1c5598a69a9378e990951b1a5 diff --git a/test-server/php-v2-server/cookies.txt b/test-server/php-v2-server/cookies.txt deleted file mode 100644 index 88eebe6a..00000000 --- a/test-server/php-v2-server/cookies.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Netscape HTTP Cookie File -# https://curl.se/docs/http-cookies.html -# This file was generated by libcurl! Edit at your own risk. - -localhost FALSE / FALSE 0 PHPSESSID 0e5bc5f3120e6fd550dbdbe8e290df91 diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index 0ad6bc59..ee056a2e 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -19,11 +19,17 @@ function getSessionIdFromCookiesFile() $cookiesFile = __DIR__ . '/../cookies.txt'; if (!file_exists($cookiesFile)) { + error_log("cookies.txt file does not exist, will be created when first client is created"); return null; } $lines = file($cookiesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if ($lines === false) { + error_log("Failed to read cookies.txt file"); + return null; + } + foreach ($lines as $line) { // Skip comment lines if (strpos($line, '#') === 0) { @@ -33,12 +39,12 @@ function getSessionIdFromCookiesFile() // Parse cookie line: domain, flag, path, secure, expiration, name, value $parts = explode("\t", $line); if (count($parts) >= 7 && $parts[5] === 'PHPSESSID') { + error_log("Found session ID in cookies.txt: " . $parts[6]); return $parts[6]; // Return the session ID value } } - error_log("No session Id found"); - + error_log("No PHPSESSID found in cookies.txt file"); return null; } @@ -56,7 +62,15 @@ function writeSessionIdToCookiesFile($sessionId) $content .= "# This file was generated by libcurl! Edit at your own risk.\n\n"; $content .= $cookieLine . "\n"; - file_put_contents($cookiesFile, $content); + $result = file_put_contents($cookiesFile, $content); + + if ($result === false) { + error_log("Failed to write session ID to cookies.txt file: $cookiesFile"); + return false; + } + + error_log("Successfully wrote session ID to cookies.txt: $sessionId"); + return true; } // Start session to persist cache across requests From 6c8340f80906a0cb6697e86adcd3ccd3d52692fd Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 15:28:31 -0700 Subject: [PATCH 09/26] work through legacy modes --- .../amazon/encryption/s3/RoundTripTests.java | 144 +++++++++--------- test-server/php-v2-server/src/client.php | 12 +- test-server/php-v2-server/src/get_object.php | 12 +- test-server/php-v2-server/src/index.php | 4 +- test-server/php-v2-server/src/put_object.php | 8 + 5 files changed, 99 insertions(+), 81 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 7caa475d..04a8b7d8 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -71,7 +71,7 @@ public class RoundTripTests { serverMap = new HashMap<>(2); serverMap.put("Java", new LanguageServerTarget("Java", "8080")); serverMap.put("Python", new LanguageServerTarget("Python", "8081")); - serverMap.put("PHP", new LanguageServerTarget("PHP-V2", "8087")); + serverMap.put("PHP-V2", new LanguageServerTarget("PHP-V2", "8087")); } static public class LanguageServerTarget { @@ -174,42 +174,42 @@ private List metadataMapToList(Map md) { return mdAsList; } - @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") - @MethodSource("crossLanguageClients") - public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTarget decLang) { - S3ECTestServerClient encClient = testServerClientFor(encLang); - final String objectKey = "cross-lang-test-key-" + encLang; - final String input = "simple-test-input"; - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String encS3ECId = encClientOutput.getClientId(); - encClient.putObject(PutObjectInput.builder() - .clientID(encS3ECId) - .key(objectKey) - .bucket(BUCKET) - .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) - .build()); - S3ECTestServerClient decClient = testServerClientFor(decLang); - CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String decS3ECId = decClientOutput.getClientId(); - GetObjectOutput output = decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .build()); - - if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { - fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); - } - } +// @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") +// @MethodSource("crossLanguageClients") +// public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTarget decLang) { +// S3ECTestServerClient encClient = testServerClientFor(encLang); +// final String objectKey = "cross-lang-test-key-" + encLang; +// final String input = "simple-test-input"; +// KeyMaterial kmsKeyArn = KeyMaterial.builder() +// .kmsKeyId(KMS_KEY_ARN) +// .build(); +// CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() +// .config(S3ECConfig.builder() +// .keyMaterial(kmsKeyArn).build()) +// .build()); +// String encS3ECId = encClientOutput.getClientId(); +// encClient.putObject(PutObjectInput.builder() +// .clientID(encS3ECId) +// .key(objectKey) +// .bucket(BUCKET) +// .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) +// .build()); +// S3ECTestServerClient decClient = testServerClientFor(decLang); +// CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() +// .config(S3ECConfig.builder() +// .keyMaterial(kmsKeyArn).build()) +// .build()); +// String decS3ECId = decClientOutput.getClientId(); +// GetObjectOutput output = decClient.getObject(GetObjectInput.builder() +// .clientID(decS3ECId) +// .bucket(BUCKET) +// .key(objectKey) +// .build()); +// +// if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { +// fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); +// } +// } // @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") // @MethodSource("crossLanguageClients") @@ -302,47 +302,47 @@ public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTar // } // } - // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") - // @MethodSource("clientsForTest") - // public void kmsV1Legacy(String language) { - // S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); - // final String objectKey = "test-key-kms-v1-" + language; - // final String input = "simple-test-input"; - // KeyMaterial kmsKeyArn = KeyMaterial.builder() - // .kmsKeyId(KMS_KEY_ARN) - // .build(); - // CreateClientOutput output1 = client.createClient(CreateClientInput.builder() - // .config(S3ECConfig.builder() - // .enableLegacyWrappingAlgorithms(true) - // .keyMaterial(kmsKeyArn) - // .build()) - // .build()); - // String s3ECId = output1.getClientId(); + @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + @MethodSource("clientsForTest") + public void kmsV1Legacy(String language) { + S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); + final String objectKey = "test-key-kms-v1-" + language; + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .enableLegacyWrappingAlgorithms(true) + .keyMaterial(kmsKeyArn) + .build()) + .build()); + String s3ECId = output1.getClientId(); - // // Create the object using the old client - // // V1 Client - // EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); + // Create the object using the old client + // V1 Client + EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); - // CryptoConfiguration v1Config = - // new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - // .withStorageMode(CryptoStorageMode.ObjectMetadata) - // .withAwsKmsRegion(KMS_REGION); + CryptoConfiguration v1Config = + new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.ObjectMetadata) + .withAwsKmsRegion(KMS_REGION); - // AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() - // .withCryptoConfiguration(v1Config) - // .withEncryptionMaterials(materialsProvider) - // .build(); + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(v1Config) + .withEncryptionMaterials(materialsProvider) + .build(); - // v1Client.putObject(BUCKET, objectKey, input); + v1Client.putObject(BUCKET, objectKey, input); - // GetObjectOutput output = client.getObject(GetObjectInput.builder() - // .clientID(s3ECId) - // .bucket(BUCKET) - // .key(objectKey) - // .build()); + GetObjectOutput output = client.getObject(GetObjectInput.builder() + .clientID(s3ECId) + .bucket(BUCKET) + .key(objectKey) + .build()); - // assertEquals(input, new String(output.getBody().array())); - // } + assertEquals(input, new String(output.getBody().array())); + } // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") // @MethodSource("clientsForTest") diff --git a/test-server/php-v2-server/src/client.php b/test-server/php-v2-server/src/client.php index d6f8843d..38cea3a2 100644 --- a/test-server/php-v2-server/src/client.php +++ b/test-server/php-v2-server/src/client.php @@ -13,11 +13,12 @@ function handleCreateClient() http_response_code(400); return json_encode(['error' => 'Invalid JSON in request body']); } - $config_data = $requestData['config'] ?? []; - $key_material = $config_data["keyMaterial"] ?? null; - + $configData = $requestData['config'] ?? []; + $keyMaterial = $configData["keyMaterial"] ?? null; + $legacyAlgorithms = $configData["enableLegacyWrappingAlgorithms"] ?? false; + error_log("Legacy Wrapping Algorithms: " . $legacyAlgorithms); $clientId = Uuid::uuid4()->toString(); - $kms_key_id = $key_material["kmsKeyId"] ?? null; + $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; // Store client configuration instead of objects (AWS objects can't be serialized) $_SESSION['s3ecCache'][$clientId] = [ @@ -45,7 +46,8 @@ function handleCreateClient() ] ] ], - 'kmsKeyId' => $kms_key_id, + 'kmsKeyId' => $kmsKeyId, + 'legacy' => $legacyAlgorithms, 'created' => time() ]; diff --git a/test-server/php-v2-server/src/get_object.php b/test-server/php-v2-server/src/get_object.php index 9fcecbac..e1781607 100644 --- a/test-server/php-v2-server/src/get_object.php +++ b/test-server/php-v2-server/src/get_object.php @@ -29,10 +29,20 @@ function handleGetObject($params) $s3ec = $s3ecClientTuple["encryptionClient"]; $materialProvider = $s3ecClientTuple["materialsProvider"]; + $clientConfig = $s3ecClientTuple["config"]; + $legacyConfig = $clientConfig["legacy"] ?? false; + error_log("Legacy Config from cached config: " . $legacyConfig); + $legacy = null; + if ($legacyConfig === false) { + $legacy = "V2"; + } else { + $legacy = "V2_AND_LEGACY"; + } + error_log("Using Security Profile: " . $legacy); try { $result = $s3ec->getObject([ - '@SecurityProfile' => 'V2', + '@SecurityProfile' => $legacy, '@MaterialsProvider' => $materialProvider, '@KmsEncryptionContext' => $encryptionContext, 'Bucket' => $bucket, diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index ee056a2e..e656144b 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -10,9 +10,6 @@ use Aws\S3\S3Client; use Aws\Kms\KmsClient; -use Ramsey\Uuid\Uuid; -use GuzzleHttp\Psr7\Response; - // Function to read session ID from cookies.txt file function getSessionIdFromCookiesFile() { @@ -156,6 +153,7 @@ function getCachedClient($clientId) } $config = $_SESSION['s3ecCache'][$clientId]; + error_log("Cached Config: " . json_encode($config)); // Recreate the AWS clients from stored configuration $s3Client = new S3Client($config['s3Config']); diff --git a/test-server/php-v2-server/src/put_object.php b/test-server/php-v2-server/src/put_object.php index 9fb5f541..a4e91bce 100644 --- a/test-server/php-v2-server/src/put_object.php +++ b/test-server/php-v2-server/src/put_object.php @@ -36,9 +36,17 @@ function handlePutObject($params) 'Cipher' => 'gcm', 'KeySize' => 256, ]; + $legacyConfig = $s3ecClientTuple["legacy"] ?? false; + $legacy = null; + if ($legacyConfig === false) { + $legacy = "V2"; + } else { + $legacy = "V2_AND_LEGACY"; + } try { $result = $s3ec->putObject([ + '@SecurityProfile' => $legacy, '@MaterialsProvider' => $materialProvider, '@KmsEncryptionContext' => $encryptionContext, '@CipherOptions' => $cipherOptions, From 37917f64f3b16c9a6cd327776e8fdcd04aeddd6f Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 17:06:18 -0700 Subject: [PATCH 10/26] all tests pass plus some clean up --- .../amazon/encryption/s3/RoundTripTests.java | 440 +++++++++--------- test-server/php-v2-server/src/client.php | 5 - test-server/php-v2-server/src/get_object.php | 32 +- test-server/php-v2-server/src/index.php | 1 - test-server/php-v2-server/src/put_object.php | 4 - 5 files changed, 245 insertions(+), 237 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 04a8b7d8..e84b7e7d 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -174,133 +174,133 @@ private List metadataMapToList(Map md) { return mdAsList; } -// @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") -// @MethodSource("crossLanguageClients") -// public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTarget decLang) { -// S3ECTestServerClient encClient = testServerClientFor(encLang); -// final String objectKey = "cross-lang-test-key-" + encLang; -// final String input = "simple-test-input"; -// KeyMaterial kmsKeyArn = KeyMaterial.builder() -// .kmsKeyId(KMS_KEY_ARN) -// .build(); -// CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() -// .config(S3ECConfig.builder() -// .keyMaterial(kmsKeyArn).build()) -// .build()); -// String encS3ECId = encClientOutput.getClientId(); -// encClient.putObject(PutObjectInput.builder() -// .clientID(encS3ECId) -// .key(objectKey) -// .bucket(BUCKET) -// .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) -// .build()); -// S3ECTestServerClient decClient = testServerClientFor(decLang); -// CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() -// .config(S3ECConfig.builder() -// .keyMaterial(kmsKeyArn).build()) -// .build()); -// String decS3ECId = decClientOutput.getClientId(); -// GetObjectOutput output = decClient.getObject(GetObjectInput.builder() -// .clientID(decS3ECId) -// .bucket(BUCKET) -// .key(objectKey) -// .build()); -// -// if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { -// fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); -// } -// } - -// @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") -// @MethodSource("crossLanguageClients") -// public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, LanguageServerTarget decLang) { -// S3ECTestServerClient encClient = testServerClientFor(encLang); -// final String objectKey = "cross-lang-test-key-kms-ec-" + encLang; -// final String input = "simple-test-input"; -// final Map encCtx = new HashMap<>(); -// encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); -// encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); -// final List mdAsList = metadataMapToList(encCtx); -// KeyMaterial kmsKeyArn = KeyMaterial.builder() -// .kmsKeyId(KMS_KEY_ARN) -// .build(); -// CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() -// .config(S3ECConfig.builder() -// .keyMaterial(kmsKeyArn).build()) -// .build()); -// String encS3ECId = encClientOutput.getClientId(); -// -// encClient.putObject(PutObjectInput.builder() -// .clientID(encS3ECId) -// .key(objectKey) -// .bucket(BUCKET) -// .metadata(mdAsList) -// .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) -// .build()); -// S3ECTestServerClient decClient = testServerClientFor(decLang); -// CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() -// .config(S3ECConfig.builder() -// .keyMaterial(kmsKeyArn).build()) -// .build()); -// String decS3ECId = decClientOutput.getClientId(); -// GetObjectOutput output = decClient.getObject(GetObjectInput.builder() -// .clientID(decS3ECId) -// .bucket(BUCKET) -// .key(objectKey) -// .metadata(mdAsList) -// .build()); -// -// if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { -// fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); -// } -// } -// -// @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") -// @MethodSource("crossLanguageClients") -// public void crossLanguageTestKmsWithEncCtxMismatchFails(LanguageServerTarget encLang, LanguageServerTarget decLang) { -// if (decLang.getLanguageName().equals("PHP-V2")) { -// return; -// } -// S3ECTestServerClient encClient = testServerClientFor(encLang); -// final String objectKey = "cross-lang-test-key-kms-ec-mismatch-fails" + encLang; -// final String input = "simple-test-input"; -// final Map encCtx = new HashMap<>(); -// encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); -// encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); -// final List mdAsList = metadataMapToList(encCtx); -// KeyMaterial kmsKeyArn = KeyMaterial.builder() -// .kmsKeyId(KMS_KEY_ARN) -// .build(); -// CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() -// .config(S3ECConfig.builder() -// .keyMaterial(kmsKeyArn).build()) -// .build()); -// String encS3ECId = encClientOutput.getClientId(); -// -// encClient.putObject(PutObjectInput.builder() -// .clientID(encS3ECId) -// .key(objectKey) -// .bucket(BUCKET) -// .metadata(mdAsList) -// .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) -// .build()); -// S3ECTestServerClient decClient = testServerClientFor(decLang); -// CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() -// .config(S3ECConfig.builder() -// .keyMaterial(kmsKeyArn).build()) -// .build()); -// String decS3ECId = decClientOutput.getClientId(); -// try { -// decClient.getObject(GetObjectInput.builder() -// .clientID(decS3ECId) -// .bucket(BUCKET) -// .key(objectKey) -// .build()); -// fail("Expected exception!"); -// } catch (S3EncryptionClientError e) { -// assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); -// } -// } + @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") + @MethodSource("crossLanguageClients") + public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTarget decLang) { + S3ECTestServerClient encClient = testServerClientFor(encLang); + final String objectKey = "cross-lang-test-key-" + encLang; + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String encS3ECId = encClientOutput.getClientId(); + encClient.putObject(PutObjectInput.builder() + .clientID(encS3ECId) + .key(objectKey) + .bucket(BUCKET) + .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) + .build()); + S3ECTestServerClient decClient = testServerClientFor(decLang); + CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String decS3ECId = decClientOutput.getClientId(); + GetObjectOutput output = decClient.getObject(GetObjectInput.builder() + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .build()); + + if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { + fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); + } + } + + @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") + @MethodSource("crossLanguageClients") + public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, LanguageServerTarget decLang) { + S3ECTestServerClient encClient = testServerClientFor(encLang); + final String objectKey = "cross-lang-test-key-kms-ec-" + encLang; + final String input = "simple-test-input"; + final Map encCtx = new HashMap<>(); + encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); + encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); + final List mdAsList = metadataMapToList(encCtx); + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String encS3ECId = encClientOutput.getClientId(); + + encClient.putObject(PutObjectInput.builder() + .clientID(encS3ECId) + .key(objectKey) + .bucket(BUCKET) + .metadata(mdAsList) + .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) + .build()); + S3ECTestServerClient decClient = testServerClientFor(decLang); + CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String decS3ECId = decClientOutput.getClientId(); + GetObjectOutput output = decClient.getObject(GetObjectInput.builder() + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .metadata(mdAsList) + .build()); + + if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { + fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); + } + } + + @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") + @MethodSource("crossLanguageClients") + public void crossLanguageTestKmsWithEncCtxMismatchFails(LanguageServerTarget encLang, LanguageServerTarget decLang) { + if (decLang.getLanguageName().equals("PHP-V2")) { + return; + } + S3ECTestServerClient encClient = testServerClientFor(encLang); + final String objectKey = "cross-lang-test-key-kms-ec-mismatch-fails" + encLang; + final String input = "simple-test-input"; + final Map encCtx = new HashMap<>(); + encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); + encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); + final List mdAsList = metadataMapToList(encCtx); + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String encS3ECId = encClientOutput.getClientId(); + + encClient.putObject(PutObjectInput.builder() + .clientID(encS3ECId) + .key(objectKey) + .bucket(BUCKET) + .metadata(mdAsList) + .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) + .build()); + S3ECTestServerClient decClient = testServerClientFor(decLang); + CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String decS3ECId = decClientOutput.getClientId(); + try { + decClient.getObject(GetObjectInput.builder() + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .build()); + fail("Expected exception!"); + } catch (S3EncryptionClientError e) { + assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); + } + } @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") @MethodSource("clientsForTest") @@ -344,98 +344,98 @@ public void kmsV1Legacy(String language) { assertEquals(input, new String(output.getBody().array())); } - // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") - // @MethodSource("clientsForTest") - // public void kmsV1LegacyWithEncCtx(String language) { - // S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); - // final String objectKey = "test-key-kms-v1-with-enc-ctx-" + language; - // final String input = "simple-test-input"; - // KeyMaterial kmsKeyArn = KeyMaterial.builder() - // .kmsKeyId(KMS_KEY_ARN) - // .build(); - // CreateClientOutput output1 = client.createClient(CreateClientInput.builder() - // .config(S3ECConfig.builder() - // .enableLegacyWrappingAlgorithms(true) - // .keyMaterial(kmsKeyArn) - // .build()) - // .build()); - // String s3ECId = output1.getClientId(); - - // // Create the object using the old client - // // V1 Client - // final String ecKey = "user-metadata-key"; - // final String ecValue = "user-metadata-value-v1"; - // KMSEncryptionMaterials kmsMaterials = new KMSEncryptionMaterials(KMS_KEY_ARN); - // kmsMaterials.addDescription(ecKey, ecValue); - // EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(kmsMaterials); - - // CryptoConfiguration v1Config = - // new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - // .withStorageMode(CryptoStorageMode.ObjectMetadata) - // .withAwsKmsRegion(KMS_REGION); - - // AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() - // .withCryptoConfiguration(v1Config) - // .withEncryptionMaterials(materialsProvider) - // .build(); - - // v1Client.putObject(BUCKET, objectKey, input); - - // final Map encCtx = new HashMap<>(); - // encCtx.put(ecKey, ecValue); - // GetObjectOutput output = client.getObject(GetObjectInput.builder() - // .clientID(s3ECId) - // .bucket(BUCKET) - // .key(objectKey) - // .metadata(metadataMapToList(encCtx)) - // .build()); - - // assertEquals(input, new String(output.getBody().array())); - // } - - // @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") - // @MethodSource("clientsForTest") - // public void kmsV1LegacyFailsWhenLegacyDisabled(String language) { - // S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); - // final String objectKey = "test-key-kms-v1-fails-disabled" + language; - // final String input = "simple-test-input"; - // KeyMaterial kmsKeyArn = KeyMaterial.builder() - // .kmsKeyId(KMS_KEY_ARN) - // .build(); - // CreateClientOutput output1 = client.createClient(CreateClientInput.builder() - // .config(S3ECConfig.builder() - // .enableLegacyWrappingAlgorithms(false) - // .keyMaterial(kmsKeyArn) - // .build()) - // .build()); - // String s3ECId = output1.getClientId(); - - // // Create the object using the old client - // // V1 Client - // EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); - - // CryptoConfiguration v1Config = - // new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - // .withStorageMode(CryptoStorageMode.ObjectMetadata) - // .withAwsKmsRegion(KMS_REGION); - - // AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() - // .withCryptoConfiguration(v1Config) - // .withEncryptionMaterials(materialsProvider) - // .build(); - - // v1Client.putObject(BUCKET, objectKey, input); - - // try { - // client.getObject(GetObjectInput.builder() - // .clientID(s3ECId) - // .bucket(BUCKET) - // .key(objectKey) - // .build()); - // fail("Expected Exception"); - // } catch (S3EncryptionClientError e) { - // assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms")); - // } - // } + @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + @MethodSource("clientsForTest") + public void kmsV1LegacyWithEncCtx(String language) { + S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); + final String objectKey = "test-key-kms-v1-with-enc-ctx-" + language; + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .enableLegacyWrappingAlgorithms(true) + .keyMaterial(kmsKeyArn) + .build()) + .build()); + String s3ECId = output1.getClientId(); + + // Create the object using the old client + // V1 Client + final String ecKey = "user-metadata-key"; + final String ecValue = "user-metadata-value-v1"; + KMSEncryptionMaterials kmsMaterials = new KMSEncryptionMaterials(KMS_KEY_ARN); + kmsMaterials.addDescription(ecKey, ecValue); + EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(kmsMaterials); + + CryptoConfiguration v1Config = + new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.ObjectMetadata) + .withAwsKmsRegion(KMS_REGION); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(v1Config) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + final Map encCtx = new HashMap<>(); + encCtx.put(ecKey, ecValue); + GetObjectOutput output = client.getObject(GetObjectInput.builder() + .clientID(s3ECId) + .bucket(BUCKET) + .key(objectKey) + .metadata(metadataMapToList(encCtx)) + .build()); + + assertEquals(input, new String(output.getBody().array())); + } + + @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + @MethodSource("clientsForTest") + public void kmsV1LegacyFailsWhenLegacyDisabled(String language) { + S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); + final String objectKey = "test-key-kms-v1-fails-disabled" + language; + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .enableLegacyWrappingAlgorithms(false) + .keyMaterial(kmsKeyArn) + .build()) + .build()); + String s3ECId = output1.getClientId(); + + // Create the object using the old client + // V1 Client + EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); + + CryptoConfiguration v1Config = + new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.ObjectMetadata) + .withAwsKmsRegion(KMS_REGION); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(v1Config) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + try { + client.getObject(GetObjectInput.builder() + .clientID(s3ECId) + .bucket(BUCKET) + .key(objectKey) + .build()); + fail("Expected Exception"); + } catch (S3EncryptionClientError e) { + assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms")); + } + } } diff --git a/test-server/php-v2-server/src/client.php b/test-server/php-v2-server/src/client.php index 38cea3a2..d8c0bc00 100644 --- a/test-server/php-v2-server/src/client.php +++ b/test-server/php-v2-server/src/client.php @@ -16,7 +16,6 @@ function handleCreateClient() $configData = $requestData['config'] ?? []; $keyMaterial = $configData["keyMaterial"] ?? null; $legacyAlgorithms = $configData["enableLegacyWrappingAlgorithms"] ?? false; - error_log("Legacy Wrapping Algorithms: " . $legacyAlgorithms); $clientId = Uuid::uuid4()->toString(); $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; @@ -51,10 +50,6 @@ function handleCreateClient() 'created' => time() ]; - // Debug: show all cached clients after adding - error_log("Total clients in cache: " . count($_SESSION['s3ecCache'])); - error_log("ClientID: " . $clientId); - // Auto-update cookies.txt with current session ID so tests can access cached clients writeSessionIdToCookiesFile(session_id()); diff --git a/test-server/php-v2-server/src/get_object.php b/test-server/php-v2-server/src/get_object.php index e1781607..c963b21d 100644 --- a/test-server/php-v2-server/src/get_object.php +++ b/test-server/php-v2-server/src/get_object.php @@ -13,11 +13,7 @@ function handleGetObject($params) # Get the S3EncryptionClient from the client_cache $s3ecClientTuple = getCachedClient($clientId); if ($s3ecClientTuple == null) { - error_log("No cached client found :( " . $clientId); - error_log("Creating a default client now."); $s3ecClientTuple = createDefaultClientTuple(); - } else { - error_log("Cached Client found: " . $clientId); } $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; @@ -31,16 +27,17 @@ function handleGetObject($params) $materialProvider = $s3ecClientTuple["materialsProvider"]; $clientConfig = $s3ecClientTuple["config"]; $legacyConfig = $clientConfig["legacy"] ?? false; - error_log("Legacy Config from cached config: " . $legacyConfig); $legacy = null; if ($legacyConfig === false) { $legacy = "V2"; } else { $legacy = "V2_AND_LEGACY"; } - error_log("Using Security Profile: " . $legacy); try { + // Start output buffering before the AWS call to capture any unwanted output + ob_start(); + $result = $s3ec->getObject([ '@SecurityProfile' => $legacy, '@MaterialsProvider' => $materialProvider, @@ -49,17 +46,38 @@ function handleGetObject($params) 'Key' => $key, ]); + // Capture and discard any unwanted output from AWS SDK + $unwantedOutput = ob_get_clean(); + if (!empty($unwantedOutput)) { + error_log("AWS SDK produced unexpected output: " . strlen($unwantedOutput) . " bytes"); + } + $body = $result['Body']->getContents(); $formattedMetadata = formatMetadataForResponse($result["Metadata"]); + + // Now set headers safely header("Content-Metadata: " . $formattedMetadata); header("Content-Type: application/octet-stream"); header("Content-Length: " . strlen($body)); return $body; } catch (InvalidArgumentException $e) { + // Clean up output buffer if still active + if (ob_get_level()) + ob_end_clean(); http_response_code(400); return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); } catch (Exception $e) { + // Clean up output buffer if still active + if (ob_get_level()) + ob_end_clean(); http_response_code(500); - return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + if (strpos($e->getMessage(), "@SecurityProfile=V2") !== false) { + return json_encode([ + "__type" => "software.amazon.encryption.s3#S3EncryptionClientError", + "message" => $e->getMessage() . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms", + ]); + } else { + return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + } } } diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index e656144b..06794cc0 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -153,7 +153,6 @@ function getCachedClient($clientId) } $config = $_SESSION['s3ecCache'][$clientId]; - error_log("Cached Config: " . json_encode($config)); // Recreate the AWS clients from stored configuration $s3Client = new S3Client($config['s3Config']); diff --git a/test-server/php-v2-server/src/put_object.php b/test-server/php-v2-server/src/put_object.php index a4e91bce..71580a2b 100644 --- a/test-server/php-v2-server/src/put_object.php +++ b/test-server/php-v2-server/src/put_object.php @@ -15,11 +15,7 @@ function handlePutObject($params) # Get the S3EncryptionClient from the client_cache $s3ecClientTuple = getCachedClient($clientId); if ($s3ecClientTuple === null) { - error_log("No cached client found :( " . $clientId); - error_log("Creating a default client now."); $s3ecClientTuple = createDefaultClientTuple(); - } else { - error_log("Cached Client found: " . $clientId); } // Capture all Content-Metadata headers From 57b6976e999b1dc66768cd56e3a8f45d9ee3aa70 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 17:11:03 -0700 Subject: [PATCH 11/26] add php to bad ec list --- .../it/java/software/amazon/encryption/s3/RoundTripTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 57680331..61b64d94 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -82,7 +82,7 @@ public class RoundTripTests { // these implementations will not raise an error as expected. // For now, skip tests that expect encryption context validation on decrypt. private static final Set ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED = - Set.of("Go-V3"); + Set.of("Go-V3", "PHP-V2"); static public class LanguageServerTarget { public String getLanguageName() { From f6c46f339e6a54fc010d9bf81d584e57fb634884 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 17:22:00 -0700 Subject: [PATCH 12/26] m --- .gitignore | 3 --- test-server/php-v2-server/.gitignore | 3 +++ test-server/php-v2-server/Makefile | 24 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 test-server/php-v2-server/.gitignore create mode 100644 test-server/php-v2-server/Makefile diff --git a/.gitignore b/.gitignore index 12327de5..0e29a9fb 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,3 @@ gradle-app.setting .DS_Store smithy-java-core/out - -**/**/vendor/* -test-server/php-v2-server/cookies.txt \ No newline at end of file diff --git a/test-server/php-v2-server/.gitignore b/test-server/php-v2-server/.gitignore new file mode 100644 index 00000000..3353d5bf --- /dev/null +++ b/test-server/php-v2-server/.gitignore @@ -0,0 +1,3 @@ +vendor/* +cookies.txt +server.pid \ No newline at end of file diff --git a/test-server/php-v2-server/Makefile b/test-server/php-v2-server/Makefile new file mode 100644 index 00000000..6962ce5e --- /dev/null +++ b/test-server/php-v2-server/Makefile @@ -0,0 +1,24 @@ +# Makefile for S3 Encryption Client Testing + +.PHONY: start-server stop-server wait-for-server + +PID_FILE := server.pid +PORT := 8087 + +start-server: + @echo "Starting PHP V2 server..." + AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ + AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ + AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ + AWS_REGION="us-west-2" \ + composer run start & echo $$! > $(PID_FILE) + @echo "PHP V2 server starting..." + +stop-server: + @if [ -f $(PID_FILE) ]; then \ + kill $$(cat $(PID_FILE)) 2>/dev/null || true; \ + rm $(PID_FILE); \ + fi + +wait-for-server: + $(MAKE) -C .. wait-for-port PORT=$(PORT) From 909366185484a7199fa4f9ee1f8aab89482abfa9 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 17:23:33 -0700 Subject: [PATCH 13/26] remove --- test-server/php-v3-server/README.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 test-server/php-v3-server/README.md diff --git a/test-server/php-v3-server/README.md b/test-server/php-v3-server/README.md deleted file mode 100644 index 96d55f00..00000000 --- a/test-server/php-v3-server/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# S3EC PHP V3 Test Server - -This is the PHP V3 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality. - -## Overview - -The S3ECPhpV3TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: - -- Creating S3 Encryption Clients -- Putting objects with encryption -- Getting and decrypting objects - -## Usage - -This will start the server running on port `8093`. - -The server is used as part of the testing framework to verify cross-language compatibility of the S3 Encryption Client implementations. From 1d342a1c48f080bee2b1cacb1a80ae31b0482e2f Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 17:32:52 -0700 Subject: [PATCH 14/26] increase php timeout --- test-server/php-v2-server/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json index e9c399ac..444b3e2e 100644 --- a/test-server/php-v2-server/composer.json +++ b/test-server/php-v2-server/composer.json @@ -15,7 +15,7 @@ }, "scripts": { "start": [ - "php -S 0.0.0.0:8087 src/index.php" + "php -d max_execution_time=600 -S 0.0.0.0:8087 src/index.php" ] }, "config": { From 2428edef4b753d7f6518d876a93bfe2bf2930b89 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 17:44:28 -0700 Subject: [PATCH 15/26] bump the time --- test-server/php-v2-server/composer.json | 2 +- test-server/php-v2-server/src/index.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json index 444b3e2e..e9c399ac 100644 --- a/test-server/php-v2-server/composer.json +++ b/test-server/php-v2-server/composer.json @@ -15,7 +15,7 @@ }, "scripts": { "start": [ - "php -d max_execution_time=600 -S 0.0.0.0:8087 src/index.php" + "php -S 0.0.0.0:8087 src/index.php" ] }, "config": { diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index 06794cc0..cc5dee29 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -70,6 +70,7 @@ function writeSessionIdToCookiesFile($sessionId) return true; } +set_time_limit(600); // Start session to persist cache across requests // First try to use session ID from cookies.txt if available $sessionId = getSessionIdFromCookiesFile(); From ad3a262903b2f9eb35a071805b02a856f246a81b Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 19:06:19 -0700 Subject: [PATCH 16/26] set up php --- .github/workflows/test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8025246..89b9816f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,12 @@ jobs: with: python-version: ${{ inputs.python-version || '3.11' }} + - name: Set up PHP with Composer + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + tools: composer:v2 + # Cache uv dependencies - name: Cache uv dependencies uses: actions/cache@v3 From ac0e8a8eab057601ba3407fd996da0b50a2102cb Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 19:17:23 -0700 Subject: [PATCH 17/26] install deps --- .github/workflows/test.yml | 2 ++ test-server/php-v2-server/.gitignore | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 89b9816f..c491c6c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,9 +28,11 @@ jobs: - name: Set up PHP with Composer uses: shivammathur/setup-php@v2 + working-directory: ./test-server/php-v2-server with: php-version: '8.4' tools: composer:v2 + - run: composer install # Cache uv dependencies - name: Cache uv dependencies diff --git a/test-server/php-v2-server/.gitignore b/test-server/php-v2-server/.gitignore index 3353d5bf..07108589 100644 --- a/test-server/php-v2-server/.gitignore +++ b/test-server/php-v2-server/.gitignore @@ -1,3 +1,4 @@ vendor/* cookies.txt -server.pid \ No newline at end of file +server.pid +composer.lock \ No newline at end of file From 8268b6d356deca1166d70eb626d1d4f43f21a4c4 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 18 Sep 2025 19:20:41 -0700 Subject: [PATCH 18/26] install deps for real --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c491c6c5..f72eb1f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,11 +28,14 @@ jobs: - name: Set up PHP with Composer uses: shivammathur/setup-php@v2 - working-directory: ./test-server/php-v2-server with: php-version: '8.4' tools: composer:v2 - - run: composer install + + - name: Install PHP dependencies + working-directory: ./test-server/php-v2-server + shell: bash + run: composer install # Cache uv dependencies - name: Cache uv dependencies From d7903708efe9312179164429efb9425ba2a4fd55 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 19 Sep 2025 09:32:41 -0700 Subject: [PATCH 19/26] format? --- .../amazon/encryption/s3/RoundTripTests.java | 150 +++++++++--------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 61b64d94..6586c9f9 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -221,49 +221,49 @@ public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTar } } - @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") - @MethodSource("crossLanguageClients") - public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, LanguageServerTarget decLang) { - S3ECTestServerClient encClient = testServerClientFor(encLang); - final String objectKey = "cross-lang-test-key-kms-ec-" + encLang; - final String input = "simple-test-input"; - final Map encCtx = new HashMap<>(); - encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); - encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); - final List mdAsList = metadataMapToList(encCtx); - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); - String encS3ECId = encClientOutput.getClientId(); - - encClient.putObject(PutObjectInput.builder() - .clientID(encS3ECId) - .key(objectKey) - .bucket(BUCKET) - .metadata(mdAsList) - .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) - .build()); + @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") + @MethodSource("crossLanguageClients") + public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, LanguageServerTarget decLang) { + S3ECTestServerClient encClient = testServerClientFor(encLang); + final String objectKey = "cross-lang-test-key-kms-ec-" + encLang; + final String input = "simple-test-input"; + final Map encCtx = new HashMap<>(); + encCtx.put("user-defined-enc-ctx-key", "user-defined-enc-ctx-value"); + encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); + final List mdAsList = metadataMapToList(encCtx); + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); + String encS3ECId = encClientOutput.getClientId(); + + encClient.putObject(PutObjectInput.builder() + .clientID(encS3ECId) + .key(objectKey) + .bucket(BUCKET) + .metadata(mdAsList) + .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) + .build()); S3ECTestServerClient decClient = testServerClientFor(decLang); CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() + .config(S3ECConfig.builder() .keyMaterial(kmsKeyArn).build()) - .build()); + .build()); String decS3ECId = decClientOutput.getClientId(); GetObjectOutput output = decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .metadata(mdAsList) - .build()); + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .metadata(mdAsList) + .build()); if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); } - } + } @ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}") @MethodSource("crossLanguageClients") @@ -364,47 +364,47 @@ public void crossLanguageTestKmsWithIncorrectEncCtxFails(LanguageServerTarget en } } - @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") - @MethodSource("clientsForTest") - public void kmsV1Legacy(String language) { - S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); - final String objectKey = "test-key-kms-v1-" + language; - final String input = "simple-test-input"; - KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); - CreateClientOutput output1 = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .enableLegacyWrappingAlgorithms(true) - .keyMaterial(kmsKeyArn) - .build()) - .build()); - String s3ECId = output1.getClientId(); - - // Create the object using the old client - // V1 Client - EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); - - CryptoConfiguration v1Config = - new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.ObjectMetadata) - .withAwsKmsRegion(KMS_REGION); - - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() - .withCryptoConfiguration(v1Config) - .withEncryptionMaterials(materialsProvider) - .build(); - - v1Client.putObject(BUCKET, objectKey, input); - - GetObjectOutput output = client.getObject(GetObjectInput.builder() - .clientID(s3ECId) - .bucket(BUCKET) - .key(objectKey) - .build()); - - assertEquals(input, new String(output.getBody().array())); - } + @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + @MethodSource("clientsForTest") + public void kmsV1Legacy(String language) { + S3ECTestServerClient client = testServerClientFor(serverMap.get(language)); + final String objectKey = "test-key-kms-v1-" + language; + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .enableLegacyWrappingAlgorithms(true) + .keyMaterial(kmsKeyArn) + .build()) + .build()); + String s3ECId = output1.getClientId(); + + // Create the object using the old client + // V1 Client + EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); + + CryptoConfiguration v1Config = + new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.ObjectMetadata) + .withAwsKmsRegion(KMS_REGION); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(v1Config) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + GetObjectOutput output = client.getObject(GetObjectInput.builder() + .clientID(s3ECId) + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, new String(output.getBody().array())); + } @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") @MethodSource("clientsForTest") From 8651eca1600109e20da5baf6e735bf13f30219e0 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 19 Sep 2025 09:37:12 -0700 Subject: [PATCH 20/26] more format? --- .../amazon/encryption/s3/RoundTripTests.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 6586c9f9..4c984d5a 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -232,33 +232,33 @@ public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, Languag encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2"); final List mdAsList = metadataMapToList(encCtx); KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(KMS_KEY_ARN) - .build(); + .kmsKeyId(KMS_KEY_ARN) + .build(); CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) - .build()); + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn).build()) + .build()); String encS3ECId = encClientOutput.getClientId(); encClient.putObject(PutObjectInput.builder() - .clientID(encS3ECId) - .key(objectKey) - .bucket(BUCKET) - .metadata(mdAsList) - .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) - .build()); + .clientID(encS3ECId) + .key(objectKey) + .bucket(BUCKET) + .metadata(mdAsList) + .body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))) + .build()); S3ECTestServerClient decClient = testServerClientFor(decLang); CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() + .config(S3ECConfig.builder() .keyMaterial(kmsKeyArn).build()) - .build()); + .build()); String decS3ECId = decClientOutput.getClientId(); GetObjectOutput output = decClient.getObject(GetObjectInput.builder() - .clientID(decS3ECId) - .bucket(BUCKET) - .key(objectKey) - .metadata(mdAsList) - .build()); + .clientID(decS3ECId) + .bucket(BUCKET) + .key(objectKey) + .metadata(mdAsList) + .build()); if (!input.equals(StandardCharsets.UTF_8.decode(output.getBody()).toString())) { fail(String.format("Encryption in %s failed to decrpyt in %s!", encLang, decLang)); From 58fe85a4ca8278fb28e291ad675f38a00f59f937 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 19 Sep 2025 09:57:54 -0700 Subject: [PATCH 21/26] format --- .../it/java/software/amazon/encryption/s3/RoundTripTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 4c984d5a..ad1a727d 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -236,7 +236,7 @@ public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, Languag .build(); CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder() .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn).build()) + .keyMaterial(kmsKeyArn).build()) .build()); String encS3ECId = encClientOutput.getClientId(); From 7707d07600c090b47565d8a4e03edef227e32956 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 19 Sep 2025 09:59:29 -0700 Subject: [PATCH 22/26] Untrack composer.lock --- test-server/php-v2-server/composer.lock | 1133 ----------------------- 1 file changed, 1133 deletions(-) delete mode 100644 test-server/php-v2-server/composer.lock diff --git a/test-server/php-v2-server/composer.lock b/test-server/php-v2-server/composer.lock deleted file mode 100644 index 170610bb..00000000 --- a/test-server/php-v2-server/composer.lock +++ /dev/null @@ -1,1133 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "5dc3463978ebc22921e829339c210c35", - "packages": [ - { - "name": "aws/aws-crt-php", - "version": "v1.2.7", - "source": { - "type": "git", - "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", - "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "AWS SDK Common Runtime Team", - "email": "aws-sdk-common-runtime@amazon.com" - } - ], - "description": "AWS Common Runtime for PHP", - "homepage": "https://github.com/awslabs/aws-crt-php", - "keywords": [ - "amazon", - "aws", - "crt", - "sdk" - ], - "support": { - "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" - }, - "time": "2024-10-18T22:15:13+00:00" - }, - { - "name": "aws/aws-sdk-php", - "version": "3.356.18", - "source": { - "type": "git", - "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "af1bf3dee5e66cf9b3d5d2507b08d9465636df29" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/af1bf3dee5e66cf9b3d5d2507b08d9465636df29", - "reference": "af1bf3dee5e66cf9b3d5d2507b08d9465636df29", - "shasum": "" - }, - "require": { - "aws/aws-crt-php": "^1.2.3", - "ext-json": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "guzzlehttp/guzzle": "^7.4.5", - "guzzlehttp/promises": "^2.0", - "guzzlehttp/psr7": "^2.4.5", - "mtdowling/jmespath.php": "^2.8.0", - "php": ">=8.1", - "psr/http-message": "^1.0 || ^2.0" - }, - "require-dev": { - "andrewsville/php-token-reflection": "^1.4", - "aws/aws-php-sns-message-validator": "~1.0", - "behat/behat": "~3.0", - "composer/composer": "^2.7.8", - "dms/phpunit-arraysubset-asserts": "^0.4.0", - "doctrine/cache": "~1.4", - "ext-dom": "*", - "ext-openssl": "*", - "ext-pcntl": "*", - "ext-sockets": "*", - "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^2.0 || ^3.0", - "psr/simple-cache": "^2.0 || ^3.0", - "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", - "symfony/filesystem": "^v6.4.0 || ^v7.1.0", - "yoast/phpunit-polyfills": "^2.0" - }, - "suggest": { - "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", - "doctrine/cache": "To use the DoctrineCacheAdapter", - "ext-curl": "To send requests using cURL", - "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", - "ext-sockets": "To use client-side monitoring" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Aws\\": "src/" - }, - "exclude-from-classmap": [ - "src/data/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" - } - ], - "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", - "keywords": [ - "amazon", - "aws", - "cloud", - "dynamodb", - "ec2", - "glacier", - "s3", - "sdk" - ], - "support": { - "forum": "https://github.com/aws/aws-sdk-php/discussions", - "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.356.18" - }, - "time": "2025-09-15T18:21:28+00:00" - }, - { - "name": "brick/math", - "version": "0.14.0", - "source": { - "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", - "shasum": "" - }, - "require": { - "php": "^8.2" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpstan/phpstan": "2.1.22", - "phpunit/phpunit": "^11.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Brick\\Math\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Arbitrary-precision arithmetic library", - "keywords": [ - "Arbitrary-precision", - "BigInteger", - "BigRational", - "arithmetic", - "bigdecimal", - "bignum", - "bignumber", - "brick", - "decimal", - "integer", - "math", - "mathematics", - "rational" - ], - "support": { - "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.0" - }, - "funding": [ - { - "url": "https://github.com/BenMorel", - "type": "github" - } - ], - "time": "2025-08-29T12:40:03+00:00" - }, - { - "name": "guzzlehttp/guzzle", - "version": "7.10.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", - "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" - } - ], - "time": "2025-08-23T22:36:01+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "2.3.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" - } - ], - "time": "2025-08-22T14:34:08+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2025-08-23T21:21:41+00:00" - }, - { - "name": "mtdowling/jmespath.php", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", - "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "symfony/polyfill-mbstring": "^1.17" - }, - "require-dev": { - "composer/xdebug-handler": "^3.0.3", - "phpunit/phpunit": "^8.5.33" - }, - "bin": [ - "bin/jp.php" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "files": [ - "src/JmesPath.php" - ], - "psr-4": { - "JmesPath\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Declaratively specify how to extract elements from a JSON document", - "keywords": [ - "json", - "jsonpath" - ], - "support": { - "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" - }, - "time": "2024-09-04T18:46:31+00:00" - }, - { - "name": "psr/http-client", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client" - }, - "time": "2023-09-23T14:17:50+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory" - }, - "time": "2024-04-15T12:06:14+00:00" - }, - { - "name": "psr/http-message", - "version": "2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" - }, - "time": "2023-04-04T09:54:51+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "ramsey/collection", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.45", - "fakerphp/faker": "^1.24", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^2.1", - "mockery/mockery": "^1.6", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpspec/prophecy-phpunit": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", - "ramsey/coding-standard": "^2.3", - "ramsey/conventional-commits": "^1.6", - "roave/security-advisories": "dev-latest" - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" - } - }, - "autoload": { - "psr-4": { - "Ramsey\\Collection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP library for representing and manipulating collections.", - "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" - ], - "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.1" - }, - "time": "2025-03-22T05:38:12+00:00" - }, - { - "name": "ramsey/uuid", - "version": "4.9.1", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "shasum": "" - }, - "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", - "php": "^8.0", - "ramsey/collection": "^1.2 || ^2.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "captainhook/captainhook": "^5.25", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "ergebnis/composer-normalize": "^2.47", - "mockery/mockery": "^1.6", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.6", - "php-mock/php-mock-mockery": "^1.5", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpbench/phpbench": "^1.2.14", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "slevomat/coding-standard": "^8.18", - "squizlabs/php_codesniffer": "^3.13" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.1" - }, - "time": "2025-09-04T20:59:21+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-23T08:48:59+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=7.4" - }, - "platform-dev": {}, - "plugin-api-version": "2.6.0" -} From ee5ec252e21d2ac9697a8c8aee8b372396498e2f Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 19 Sep 2025 11:10:37 -0700 Subject: [PATCH 23/26] add php-v3 --- .../amazon/encryption/s3/RoundTripTests.java | 4 +- test-server/php-v2-server/src/client.php | 12 +- test-server/php-v2-server/src/errors.php | 42 +++ test-server/php-v2-server/src/get_object.php | 28 +- test-server/php-v2-server/src/put_object.php | 20 +- test-server/php-v3-server/.gitignore | 4 + test-server/php-v3-server/Makefile | 24 ++ test-server/php-v3-server/README.md | 66 ++++ test-server/php-v3-server/composer.json | 24 ++ test-server/php-v3-server/src/client.php | 68 ++++ test-server/php-v3-server/src/errors.php | 42 +++ test-server/php-v3-server/src/get_object.php | 85 +++++ test-server/php-v3-server/src/index.php | 294 ++++++++++++++++++ test-server/php-v3-server/src/put_object.php | 72 +++++ 14 files changed, 761 insertions(+), 24 deletions(-) create mode 100644 test-server/php-v2-server/src/errors.php create mode 100644 test-server/php-v3-server/.gitignore create mode 100644 test-server/php-v3-server/Makefile create mode 100644 test-server/php-v3-server/README.md create mode 100644 test-server/php-v3-server/composer.json create mode 100644 test-server/php-v3-server/src/client.php create mode 100644 test-server/php-v3-server/src/errors.php create mode 100644 test-server/php-v3-server/src/get_object.php create mode 100644 test-server/php-v3-server/src/index.php create mode 100644 test-server/php-v3-server/src/put_object.php diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index ad1a727d..5b9eb535 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -69,12 +69,14 @@ public class RoundTripTests { serverList.add(new LanguageServerTarget("Python-V3", "8081")); serverList.add(new LanguageServerTarget("Go-V3", "8082")); serverList.add(new LanguageServerTarget("PHP-V2", "8087")); + serverList.add(new LanguageServerTarget("PHP-V3", "8093")); serverMap = new HashMap<>(14); serverMap.put("Java-V3", new LanguageServerTarget("Java-V3", "8080")); serverMap.put("Python-V3", new LanguageServerTarget("Python-V3", "8081")); serverMap.put("Go-V3", new LanguageServerTarget("Go-V3", "8082")); serverMap.put("PHP-V2", new LanguageServerTarget("PHP-V2", "8087")); + serverMap.put("PHP-V3", new LanguageServerTarget("PHP-V3", "8093")); } // These S3EC implementations do not validate encryption context provided to getObject (i.e. on decrypt). @@ -82,7 +84,7 @@ public class RoundTripTests { // these implementations will not raise an error as expected. // For now, skip tests that expect encryption context validation on decrypt. private static final Set ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED = - Set.of("Go-V3", "PHP-V2"); + Set.of("Go-V3", "PHP-V2", "PHP-V3"); static public class LanguageServerTarget { public String getLanguageName() { diff --git a/test-server/php-v2-server/src/client.php b/test-server/php-v2-server/src/client.php index d8c0bc00..44fe1b39 100644 --- a/test-server/php-v2-server/src/client.php +++ b/test-server/php-v2-server/src/client.php @@ -1,5 +1,7 @@ 'Invalid JSON in request body']); + return GenericServerError("Invalid JSON in request body", 400); } $configData = $requestData['config'] ?? []; $keyMaterial = $configData["keyMaterial"] ?? null; @@ -19,6 +20,13 @@ function handleCreateClient() $clientId = Uuid::uuid4()->toString(); $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; + if ($configData == []) { + return GenericServerError("Invalid config in request body", 400); + } + if (($keyMaterial || $kmsKeyId) === null) { + return GenericServerError("Invalid keyMaterial in config", 400); + } + // Store client configuration instead of objects (AWS objects can't be serialized) $_SESSION['s3ecCache'][$clientId] = [ 's3Config' => [ diff --git a/test-server/php-v2-server/src/errors.php b/test-server/php-v2-server/src/errors.php new file mode 100644 index 00000000..2b59861d --- /dev/null +++ b/test-server/php-v2-server/src/errors.php @@ -0,0 +1,42 @@ + 'GenericServerError', + 'message' => $message + ]; + + return json_encode($errorResponse); +} + +/** + * Used for modeled errors, e.g. errors thrown by the S3EC + * Tests SHOULD expect this error in negative tests. + * + * @param string $message The error message to include in the response + * @return string JSON-encoded error response + */ +function S3EncryptionClientError($message) +{ + http_response_code(500); + header('Content-Type: application/json'); + + $errorResponse = [ + "__type" => "software.amazon.encryption.s3#S3EncryptionClientError", + 'message' => $message + ]; + + return json_encode($errorResponse); +} diff --git a/test-server/php-v2-server/src/get_object.php b/test-server/php-v2-server/src/get_object.php index c963b21d..61bacb5b 100644 --- a/test-server/php-v2-server/src/get_object.php +++ b/test-server/php-v2-server/src/get_object.php @@ -1,19 +1,20 @@ 'ClientID header is required']); + return GenericServerError("ClientID header is required", 400); } # Get the S3EncryptionClient from the client_cache $s3ecClientTuple = getCachedClient($clientId); if ($s3ecClientTuple == null) { - $s3ecClientTuple = createDefaultClientTuple(); + return GenericServerError("No client found for ClientID: " . $clientId, 404); } $metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? ''; @@ -23,6 +24,10 @@ function handleGetObject($params) $bucket = $params['bucket'] ?? null; $key = $params['key'] ?? null; + if (is_null($bucket) || is_null($key)) { + return GenericServerError("Invalidb bucket or key parameters", 400); + } + $s3ec = $s3ecClientTuple["encryptionClient"]; $materialProvider = $s3ecClientTuple["materialsProvider"]; $clientConfig = $s3ecClientTuple["config"]; @@ -62,22 +67,19 @@ function handleGetObject($params) return $body; } catch (InvalidArgumentException $e) { // Clean up output buffer if still active - if (ob_get_level()) + if (ob_get_level()) { ob_end_clean(); - http_response_code(400); - return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); + } + return GenericServerError("Invalid argument: " . $e->getMessage(), 400); } catch (Exception $e) { // Clean up output buffer if still active - if (ob_get_level()) + if (ob_get_level()) { ob_end_clean(); - http_response_code(500); + } if (strpos($e->getMessage(), "@SecurityProfile=V2") !== false) { - return json_encode([ - "__type" => "software.amazon.encryption.s3#S3EncryptionClientError", - "message" => $e->getMessage() . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms", - ]); + return S3EncryptionClientError($e->getMessage() . " " . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"); } else { - return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + return GenericServerError("Server argument: " . $e->getMessage(), 500); } } } diff --git a/test-server/php-v2-server/src/put_object.php b/test-server/php-v2-server/src/put_object.php index 71580a2b..63058f7d 100644 --- a/test-server/php-v2-server/src/put_object.php +++ b/test-server/php-v2-server/src/put_object.php @@ -1,5 +1,7 @@ 'ClientID header is required']); + return GenericServerError("ClientID header is required", 400); } # Get the S3EncryptionClient from the client_cache $s3ecClientTuple = getCachedClient($clientId); - if ($s3ecClientTuple === null) { - $s3ecClientTuple = createDefaultClientTuple(); + if (is_null($s3ecClientTuple)) { + return GenericServerError("No client found for ClientID: " . $clientId, 404); } // Capture all Content-Metadata headers @@ -26,6 +27,10 @@ function handlePutObject($params) $bucket = $params['bucket'] ?? null; $key = $params['key'] ?? null; + if (is_null($bucket) || is_null($key)) { + return GenericServerError("Invalidb bucket or key parameters", 400); + } + $s3ec = $s3ecClientTuple["encryptionClient"]; $materialProvider = $s3ecClientTuple["materialsProvider"]; $cipherOptions = [ @@ -55,14 +60,13 @@ function handlePutObject($params) return json_encode([ "bucket" => $bucket, "key" => $key, + // php for some reason blows java's heap if we pass the metadata // "metadata" => $encryptionContext ]); } catch (InvalidArgumentException $e) { - http_response_code(400); - return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]); + return S3EncryptionClientError("Invalid arguement: " . $e->getMessage()); } catch (Exception $e) { - http_response_code(500); - return json_encode(['error' => 'Server error: ' . $e->getMessage()]); + return GenericServerError("Server error: " . $e->getMessage()); } } diff --git a/test-server/php-v3-server/.gitignore b/test-server/php-v3-server/.gitignore new file mode 100644 index 00000000..07108589 --- /dev/null +++ b/test-server/php-v3-server/.gitignore @@ -0,0 +1,4 @@ +vendor/* +cookies.txt +server.pid +composer.lock \ No newline at end of file diff --git a/test-server/php-v3-server/Makefile b/test-server/php-v3-server/Makefile new file mode 100644 index 00000000..d62be452 --- /dev/null +++ b/test-server/php-v3-server/Makefile @@ -0,0 +1,24 @@ +# Makefile for S3 Encryption Client Testing + +.PHONY: start-server stop-server wait-for-server + +PID_FILE := server.pid +PORT := 8093 + +start-server: + @echo "Starting PHP V2 server..." + AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \ + AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ + AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ + AWS_REGION="us-west-2" \ + composer run start & echo $$! > $(PID_FILE) + @echo "PHP V2 server starting..." + +stop-server: + @if [ -f $(PID_FILE) ]; then \ + kill $$(cat $(PID_FILE)) 2>/dev/null || true; \ + rm $(PID_FILE); \ + fi + +wait-for-server: + $(MAKE) -C .. wait-for-port PORT=$(PORT) diff --git a/test-server/php-v3-server/README.md b/test-server/php-v3-server/README.md new file mode 100644 index 00000000..185804fc --- /dev/null +++ b/test-server/php-v3-server/README.md @@ -0,0 +1,66 @@ +# S3EC PHP v2 Test Server + +This is the PHP V3 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality. + +## Overview + +The S3ECPhpV2TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: + +- Creating S3 Encryption Clients with session-based caching +- Putting objects with encryption +- Getting and decrypting objects + +## Starting the Server + +### Method 1: Using Composer (Recommended) +```bash +composer run start +``` + +The server will start on port `8093`. + +## Available Endpoints + +### Server Status +- **GET /** - Returns server status and available endpoints + +### Client Management +- **POST /client** - Creates an S3EncryptionClient and caches it with session persistence +- **GET /cache** - Shows current session state and cached clients (for debugging) + +### Object Operations +- **GET /object/{bucket}/{key}** - Handle GET requests using the S3EncryptionClient +- **PUT /object/{bucket}/{key}** - Handle PUT requests using the S3EncryptionClient + +## Testing with curl + +### Important: Session Cookie Management + +To properly test the server and maintain session persistence, you **must** use cookies with curl: + +#### First Request (creates session cookie): +```bash +curl -X POST http://localhost:8093/client \ + -H "Content-Type: application/json" \ + -v +``` + +#### Subsequent Requests (reuses session cookie): +```bash +curl -X POST http://localhost:8093/client \ + -H "Content-Type: application/json" \ + -v +``` + +#### Check Cache Status: +```bash +curl http://localhost:8093/cache \ + -b cookies.txt +``` + +#### Helpful Notes +- **Session Storage**: Client configurations are stored in `$_SESSION['s3ecCache']` +- **Object Recreation**: AWS SDK objects are recreated from stored configuration (they cannot be serialized) +AWS SDK obbjects cannot be serialized due to internal resources and closures. +- **Helper Function**: `getCachedClient($clientId)` retrieves and recreates clients from cache +- **Debugging**: Enhanced logging and `/cache` endpoint for troubleshooting diff --git a/test-server/php-v3-server/composer.json b/test-server/php-v3-server/composer.json new file mode 100644 index 00000000..7ed1daf3 --- /dev/null +++ b/test-server/php-v3-server/composer.json @@ -0,0 +1,24 @@ +{ + "name": "aws/s3ec-php-v2-test-server", + "description": "PHP v2 implementation of the S3EC Test Server framework", + "type": "project", + "license": "Apache-2.0", + "require": { + "php": ">=7.4", + "aws/aws-sdk-php": "^3.356", + "ramsey/uuid": "^4.9" + }, + "autoload": { + "psr-4": { + "S3EC\\PhpV2Server\\": "src/" + } + }, + "scripts": { + "start": [ + "php -S 0.0.0.0:8093 src/index.php" + ] + }, + "config": { + "optimize-autoloader": true + } +} \ No newline at end of file diff --git a/test-server/php-v3-server/src/client.php b/test-server/php-v3-server/src/client.php new file mode 100644 index 00000000..6c40f590 --- /dev/null +++ b/test-server/php-v3-server/src/client.php @@ -0,0 +1,68 @@ +toString(); + $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; + + if (empty($configData)) { + return GenericServerError("Invalid config in request body", 400); + } + if (is_null($keyMaterial) || is_null($kmsKeyId)) { + return GenericServerError("Invalid keyMaterial in config", 400); + } + + // Store client configuration instead of objects (AWS objects can't be serialized) + $_SESSION['s3ecCache'][$clientId] = [ + 's3Config' => [ + 'region' => 'us-west-2', + 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] + ], + 'kmsConfig' => [ + 'region' => 'us-west-2', + 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] + ], + 'kmsKeyId' => $kmsKeyId, + 'legacy' => $legacyAlgorithms, + 'created' => time() + ]; + + // Auto-update cookies.txt with current session ID so tests can access cached clients + writeSessionIdToCookiesFile(session_id()); + + header("Content-Type: application/json"); + return json_encode([ + 'clientId' => $clientId, + ]); +} diff --git a/test-server/php-v3-server/src/errors.php b/test-server/php-v3-server/src/errors.php new file mode 100644 index 00000000..2b59861d --- /dev/null +++ b/test-server/php-v3-server/src/errors.php @@ -0,0 +1,42 @@ + 'GenericServerError', + 'message' => $message + ]; + + return json_encode($errorResponse); +} + +/** + * Used for modeled errors, e.g. errors thrown by the S3EC + * Tests SHOULD expect this error in negative tests. + * + * @param string $message The error message to include in the response + * @return string JSON-encoded error response + */ +function S3EncryptionClientError($message) +{ + http_response_code(500); + header('Content-Type: application/json'); + + $errorResponse = [ + "__type" => "software.amazon.encryption.s3#S3EncryptionClientError", + 'message' => $message + ]; + + return json_encode($errorResponse); +} diff --git a/test-server/php-v3-server/src/get_object.php b/test-server/php-v3-server/src/get_object.php new file mode 100644 index 00000000..59e2192c --- /dev/null +++ b/test-server/php-v3-server/src/get_object.php @@ -0,0 +1,85 @@ +getObject([ + '@SecurityProfile' => $legacy, + '@MaterialsProvider' => $materialProvider, + '@KmsEncryptionContext' => $encryptionContext, + 'Bucket' => $bucket, + 'Key' => $key, + ]); + + // Capture and discard any unwanted output from AWS SDK + $unwantedOutput = ob_get_clean(); + if (!empty($unwantedOutput)) { + error_log("AWS SDK produced unexpected output: " . strlen($unwantedOutput) . " bytes"); + } + + $body = $result['Body']->getContents(); + $formattedMetadata = formatMetadataForResponse($result["Metadata"]); + + // Now set headers safely + header("Content-Metadata: " . $formattedMetadata); + header("Content-Type: application/octet-stream"); + header("Content-Length: " . strlen($body)); + return $body; + } catch (InvalidArgumentException $e) { + // Clean up output buffer if still active + if (ob_get_level()) { + ob_end_clean(); + } + return GenericServerError("Invalid argument: " . $e->getMessage(), 400); + } catch (Exception $e) { + // Clean up output buffer if still active + if (ob_get_level()) { + ob_end_clean(); + } + if (strpos($e->getMessage(), "@SecurityProfile=V2") !== false) { + return S3EncryptionClientError($e->getMessage() . " " . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"); + } else { + return GenericServerError("Server argument: " . $e->getMessage(), 500); + } + } +} diff --git a/test-server/php-v3-server/src/index.php b/test-server/php-v3-server/src/index.php new file mode 100644 index 00000000..cc5dee29 --- /dev/null +++ b/test-server/php-v3-server/src/index.php @@ -0,0 +1,294 @@ += 7 && $parts[5] === 'PHPSESSID') { + error_log("Found session ID in cookies.txt: " . $parts[6]); + return $parts[6]; // Return the session ID value + } + } + + error_log("No PHPSESSID found in cookies.txt file"); + return null; +} + +// Function to write session ID to cookies.txt file +function writeSessionIdToCookiesFile($sessionId) +{ + $cookiesFile = __DIR__ . '/../cookies.txt'; + + // Create Netscape cookie format entry + $cookieLine = "localhost\tFALSE\t/\tFALSE\t0\tPHPSESSID\t$sessionId"; + + // Write header and cookie entry + $content = "# Netscape HTTP Cookie File\n"; + $content .= "# https://curl.se/docs/http-cookies.html\n"; + $content .= "# This file was generated by libcurl! Edit at your own risk.\n\n"; + $content .= $cookieLine . "\n"; + + $result = file_put_contents($cookiesFile, $content); + + if ($result === false) { + error_log("Failed to write session ID to cookies.txt file: $cookiesFile"); + return false; + } + + error_log("Successfully wrote session ID to cookies.txt: $sessionId"); + return true; +} + +set_time_limit(600); +// Start session to persist cache across requests +// First try to use session ID from cookies.txt if available +$sessionId = getSessionIdFromCookiesFile(); +if ($sessionId) { + session_id($sessionId); +} +session_start(); + +// Initialize session cache if it doesn't exist +if (!isset($_SESSION['s3ecCache'])) { + $_SESSION['s3ecCache'] = []; +} + +// Simple router class +class SimpleRouter +{ + private $routes = []; + + public function addRoute($method, $path, $handler) + { + $this->routes[] = [ + 'method' => strtoupper($method), + 'path' => $path, + 'handler' => $handler + ]; + } + + public function handleRequest() + { + $method = $_SERVER['REQUEST_METHOD']; + $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); + + foreach ($this->routes as $route) { + if ($route['method'] === $method) { + $params = $this->matchPathWithParams($route['path'], $path); + if ($params !== false) { + return call_user_func($route['handler'], $params); + } + } + } + + // Default 404 response + http_response_code(404); + return json_encode(['error' => 'Not Found']); + } + + private function matchPathWithParams($routePath, $requestPath) + { + // Handle exact matches first (for routes without parameters) + if ($routePath === $requestPath) { + return []; + } + + // Convert route path like '/object/{bucket}/{key}' to regex + $pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $routePath); + $pattern = '/^' . str_replace('/', '\/', $pattern) . '$/'; + + if (preg_match($pattern, $requestPath, $matches)) { + array_shift($matches); // Remove full match + + // Extract parameter names + preg_match_all('/\{([^}]+)\}/', $routePath, $paramNames); + $params = []; + + foreach ($paramNames[1] as $index => $paramName) { + $params[$paramName] = $matches[$index] ?? null; + } + + return $params; + } + + return false; + } +} + +// Helper function to get cached client by ID +function getCachedClient($clientId) +{ + if (!isset($_SESSION['s3ecCache'][$clientId])) { + return null; + } + + $config = $_SESSION['s3ecCache'][$clientId]; + + // Recreate the AWS clients from stored configuration + $s3Client = new S3Client($config['s3Config']); + $encryptionClient = new S3EncryptionClientV2($s3Client); + + $kmsClient = new KmsClient($config['kmsConfig']); + $materialsProvider = new KmsMaterialsProviderV2($kmsClient, $config['kmsKeyId']); + + return [ + 'encryptionClient' => $encryptionClient, + 'materialsProvider' => $materialsProvider, + 'config' => $config + ]; +} + +function createDefaultClientTuple(): array +{ + $s3Client = new S3Client([ + 'region' => 'us-west-2', + 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] + ]); + $encryptionClient = new S3EncryptionClientV2($s3Client); + + $kmsClient = new KmsClient([ + 'region' => 'us-west-2', + 'version' => 'latest', + 'http' => [ + 'debug' => false, + 'verify' => true, + 'curl' => [ + CURLOPT_VERBOSE => false, + CURLOPT_NOPROGRESS => true + ] + ] + ]); + $materialsProvider = new KmsMaterialsProviderV2($kmsClient, 'arn:aws:kms:us-west-2:370957321024:alias/S3EC-Test-Server-Github-KMS-Key'); + + return [ + 'encryptionClient' => $encryptionClient, + 'materialsProvider' => $materialsProvider + ]; +} + +function metadataStringToMap($metadata): array +{ + $md = []; + + if (empty($metadata)) { + return $md; + } + + $mdList = explode(',', $metadata); + + foreach ($mdList as $entry) { + $parts = explode(']:[', $entry); + + if (count($parts) === 2) { + $key = substr($parts[0], 1); + $value = substr($parts[1], 0, -1); + $md[$key] = $value; + } else { + throw new InvalidArgumentException("Malformed metadata list entry: " . $entry); + } + } + + return $md; +} +function formatMetadataForResponse($metadata) +{ + $metadataList = []; + // Handle different metadata input types + if (is_array($metadata)) { + // If it's an associative array (like Python dict) + foreach ($metadata as $key => $value) { + $metadataList[] = $key . '=' . $value; + } + } elseif (is_string($metadata) && !empty($metadata)) { + // If it's already a string, assume it's in the correct format + return $metadata; + } + + // Convert array to comma-separated string + return implode(',', $metadataList); +} + +// Initialize router +$router = new SimpleRouter(); + +// Add basic routes +$router->addRoute('GET', '/', function () { + return json_encode([ + 'service' => 'S3EC PHP v2 Test Server', + 'status' => 'running', + 'port' => 8087, + 'endpoints' => [ + 'GET /' => 'Server status', + 'POST /client' => 'Create an S3EncryptionClient and cache it.', + 'GET /object/{bucket}/{key}' => 'Handle GET requests to /object/{bucket}/{key} by using the S3EncryptionClient to make a GetObject request to S3.', + 'PUT /object/{bucket}/{key}' => 'Handle PUT requests to /object/{bucket}/{key} by using the S3EncryptionClient to make a PutObject request to S3.', + ] + ]); +}); + +$router->addRoute('GET', '/cache', function () { + return json_encode([ + 'sessionId' => session_id(), + 'sessionStatus' => session_status(), + 'totalCachedClients' => count($_SESSION['s3ecCache'] ?? []), + 'allClientIds' => array_keys($_SESSION['s3ecCache'] ?? []), + 'cacheDetails' => $_SESSION['s3ecCache'] ?? [] + ]); +}); + +$router->addRoute('GET', '/object/{bucket}/{key}', function ($params) { + return handleGetObject($params); +}); + +$router->addRoute('PUT', '/object/{bucket}/{key}', function ($params) { + return handlePutObject($params); +}); + +$router->addRoute('POST', '/client', function () { + return handleCreateClient(); +}); + +// Handle the request and output response +$result = $router->handleRequest(); +if ($result !== false) { + echo $result; +} diff --git a/test-server/php-v3-server/src/put_object.php b/test-server/php-v3-server/src/put_object.php new file mode 100644 index 00000000..63058f7d --- /dev/null +++ b/test-server/php-v3-server/src/put_object.php @@ -0,0 +1,72 @@ + 'gcm', + 'KeySize' => 256, + ]; + $legacyConfig = $s3ecClientTuple["legacy"] ?? false; + $legacy = null; + if ($legacyConfig === false) { + $legacy = "V2"; + } else { + $legacy = "V2_AND_LEGACY"; + } + + try { + $result = $s3ec->putObject([ + '@SecurityProfile' => $legacy, + '@MaterialsProvider' => $materialProvider, + '@KmsEncryptionContext' => $encryptionContext, + '@CipherOptions' => $cipherOptions, + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => $rawBody, + ]); + + header("Content-Type: application/json"); + return json_encode([ + "bucket" => $bucket, + "key" => $key, + // php for some reason blows java's heap if we pass the metadata + // "metadata" => $encryptionContext + ]); + + } catch (InvalidArgumentException $e) { + return S3EncryptionClientError("Invalid arguement: " . $e->getMessage()); + } catch (Exception $e) { + return GenericServerError("Server error: " . $e->getMessage()); + } +} From 1a823fd67436157111854390c10f77936051a5e4 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 19 Sep 2025 11:15:21 -0700 Subject: [PATCH 24/26] install deps in php-v3 --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f72eb1f9..144e5e80 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,8 @@ jobs: tools: composer:v2 - name: Install PHP dependencies - working-directory: ./test-server/php-v2-server + php-directories: [php-v2-server, php-v3-server] + working-directory: ./test-server/${{php-directories}} shell: bash run: composer install From 73995edb531e1c9ba1b3e1bee130eace54b536ed Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 19 Sep 2025 11:16:55 -0700 Subject: [PATCH 25/26] oops --- .github/workflows/test.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 144e5e80..0639f45a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,9 +32,13 @@ jobs: php-version: '8.4' tools: composer:v2 - - name: Install PHP dependencies - php-directories: [php-v2-server, php-v3-server] - working-directory: ./test-server/${{php-directories}} + - name: Install PHP V2 dependencies + working-directory: ./test-server/php-v2-server + shell: bash + run: composer install + + - name: Install PHP V3 dependencies + working-directory: ./test-server/php-v3-server shell: bash run: composer install From 4a2fe4b5d605af9364d091692f2e8c6af750c02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Corella?= <39066999+josecorella@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:32:24 -0700 Subject: [PATCH 26/26] Apply suggestions from code review Co-authored-by: seebees --- test-server/php-v3-server/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-server/php-v3-server/README.md b/test-server/php-v3-server/README.md index 185804fc..284c6e97 100644 --- a/test-server/php-v3-server/README.md +++ b/test-server/php-v3-server/README.md @@ -1,10 +1,10 @@ -# S3EC PHP v2 Test Server +# S3EC PHP v3 Test Server This is the PHP V3 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality. ## Overview -The S3ECPhpV2TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: +The S3ECPhpV3TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for: - Creating S3 Encryption Clients with session-based caching - Putting objects with encryption