Skip to content

Commit ee5ec25

Browse files
committed
add php-v3
1 parent 7707d07 commit ee5ec25

14 files changed

Lines changed: 761 additions & 24 deletions

File tree

test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,22 @@ public class RoundTripTests {
6969
serverList.add(new LanguageServerTarget("Python-V3", "8081"));
7070
serverList.add(new LanguageServerTarget("Go-V3", "8082"));
7171
serverList.add(new LanguageServerTarget("PHP-V2", "8087"));
72+
serverList.add(new LanguageServerTarget("PHP-V3", "8093"));
7273

7374
serverMap = new HashMap<>(14);
7475
serverMap.put("Java-V3", new LanguageServerTarget("Java-V3", "8080"));
7576
serverMap.put("Python-V3", new LanguageServerTarget("Python-V3", "8081"));
7677
serverMap.put("Go-V3", new LanguageServerTarget("Go-V3", "8082"));
7778
serverMap.put("PHP-V2", new LanguageServerTarget("PHP-V2", "8087"));
79+
serverMap.put("PHP-V3", new LanguageServerTarget("PHP-V3", "8093"));
7880
}
7981

8082
// These S3EC implementations do not validate encryption context provided to getObject (i.e. on decrypt).
8183
// If the encryption context provided to getObject does not match the encryption context on the stored object,
8284
// these implementations will not raise an error as expected.
8385
// For now, skip tests that expect encryption context validation on decrypt.
8486
private static final Set<String> ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED =
85-
Set.of("Go-V3", "PHP-V2");
87+
Set.of("Go-V3", "PHP-V2", "PHP-V3");
8688

8789
static public class LanguageServerTarget {
8890
public String getLanguageName() {

test-server/php-v2-server/src/client.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
require_once __DIR__ . '/errors.php';
4+
35
use Ramsey\Uuid\Uuid;
46

57
function handleCreateClient()
@@ -10,15 +12,21 @@ function handleCreateClient()
1012
// Parse JSON if the body contains JSON
1113
$requestData = json_decode($rawBody, true);
1214
if (json_last_error() !== JSON_ERROR_NONE) {
13-
http_response_code(400);
14-
return json_encode(['error' => 'Invalid JSON in request body']);
15+
return GenericServerError("Invalid JSON in request body", 400);
1516
}
1617
$configData = $requestData['config'] ?? [];
1718
$keyMaterial = $configData["keyMaterial"] ?? null;
1819
$legacyAlgorithms = $configData["enableLegacyWrappingAlgorithms"] ?? false;
1920
$clientId = Uuid::uuid4()->toString();
2021
$kmsKeyId = $keyMaterial["kmsKeyId"] ?? null;
2122

23+
if ($configData == []) {
24+
return GenericServerError("Invalid config in request body", 400);
25+
}
26+
if (($keyMaterial || $kmsKeyId) === null) {
27+
return GenericServerError("Invalid keyMaterial in config", 400);
28+
}
29+
2230
// Store client configuration instead of objects (AWS objects can't be serialized)
2331
$_SESSION['s3ecCache'][$clientId] = [
2432
's3Config' => [
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/**
4+
* Used for "internal" errors, e.g. problems with the test server itself
5+
* Tests MUST NOT expect this error in negative tests.
6+
*
7+
* @param string $message The error message to include in the response
8+
* @param int $code The error code to set in the reponse
9+
* @return string JSON-encoded error response
10+
*/
11+
function GenericServerError($message, $code = 500)
12+
{
13+
http_response_code(500);
14+
header('Content-Type: application/json');
15+
16+
$errorResponse = [
17+
'error' => 'GenericServerError',
18+
'message' => $message
19+
];
20+
21+
return json_encode($errorResponse);
22+
}
23+
24+
/**
25+
* Used for modeled errors, e.g. errors thrown by the S3EC
26+
* Tests SHOULD expect this error in negative tests.
27+
*
28+
* @param string $message The error message to include in the response
29+
* @return string JSON-encoded error response
30+
*/
31+
function S3EncryptionClientError($message)
32+
{
33+
http_response_code(500);
34+
header('Content-Type: application/json');
35+
36+
$errorResponse = [
37+
"__type" => "software.amazon.encryption.s3#S3EncryptionClientError",
38+
'message' => $message
39+
];
40+
41+
return json_encode($errorResponse);
42+
}

test-server/php-v2-server/src/get_object.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
<?php
22

3+
require_once __DIR__ . '/errors.php';
4+
35
function handleGetObject($params)
46
{
57
// Get ClientID from HTTP header
68
$clientId = $_SERVER['HTTP_X_CLIENT_ID'] ?? $_SERVER['HTTP_CLIENTID'] ?? null;
79

810
if (empty($clientId)) {
9-
http_response_code(400);
10-
return json_encode(['error' => 'ClientID header is required']);
11+
return GenericServerError("ClientID header is required", 400);
1112
}
1213

1314
# Get the S3EncryptionClient from the client_cache
1415
$s3ecClientTuple = getCachedClient($clientId);
1516
if ($s3ecClientTuple == null) {
16-
$s3ecClientTuple = createDefaultClientTuple();
17+
return GenericServerError("No client found for ClientID: " . $clientId, 404);
1718
}
1819

1920
$metadata = $_SERVER['HTTP_CONTENT_METADATA'] ?? '';
@@ -23,6 +24,10 @@ function handleGetObject($params)
2324
$bucket = $params['bucket'] ?? null;
2425
$key = $params['key'] ?? null;
2526

27+
if (is_null($bucket) || is_null($key)) {
28+
return GenericServerError("Invalidb bucket or key parameters", 400);
29+
}
30+
2631
$s3ec = $s3ecClientTuple["encryptionClient"];
2732
$materialProvider = $s3ecClientTuple["materialsProvider"];
2833
$clientConfig = $s3ecClientTuple["config"];
@@ -62,22 +67,19 @@ function handleGetObject($params)
6267
return $body;
6368
} catch (InvalidArgumentException $e) {
6469
// Clean up output buffer if still active
65-
if (ob_get_level())
70+
if (ob_get_level()) {
6671
ob_end_clean();
67-
http_response_code(400);
68-
return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]);
72+
}
73+
return GenericServerError("Invalid argument: " . $e->getMessage(), 400);
6974
} catch (Exception $e) {
7075
// Clean up output buffer if still active
71-
if (ob_get_level())
76+
if (ob_get_level()) {
7277
ob_end_clean();
73-
http_response_code(500);
78+
}
7479
if (strpos($e->getMessage(), "@SecurityProfile=V2") !== false) {
75-
return json_encode([
76-
"__type" => "software.amazon.encryption.s3#S3EncryptionClientError",
77-
"message" => $e->getMessage() . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms",
78-
]);
80+
return S3EncryptionClientError($e->getMessage() . " " . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms");
7981
} else {
80-
return json_encode(['error' => 'Server error: ' . $e->getMessage()]);
82+
return GenericServerError("Server argument: " . $e->getMessage(), 500);
8183
}
8284
}
8385
}

test-server/php-v2-server/src/put_object.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
require_once __DIR__ . '/errors.php';
4+
35
function handlePutObject($params)
46
{
57
// Get the raw request body
@@ -8,14 +10,13 @@ function handlePutObject($params)
810
$clientId = $_SERVER['HTTP_X_CLIENT_ID'] ?? $_SERVER['HTTP_CLIENTID'] ?? null;
911

1012
if (empty($clientId)) {
11-
http_response_code(400);
12-
return json_encode(['error' => 'ClientID header is required']);
13+
return GenericServerError("ClientID header is required", 400);
1314
}
1415

1516
# Get the S3EncryptionClient from the client_cache
1617
$s3ecClientTuple = getCachedClient($clientId);
17-
if ($s3ecClientTuple === null) {
18-
$s3ecClientTuple = createDefaultClientTuple();
18+
if (is_null($s3ecClientTuple)) {
19+
return GenericServerError("No client found for ClientID: " . $clientId, 404);
1920
}
2021

2122
// Capture all Content-Metadata headers
@@ -26,6 +27,10 @@ function handlePutObject($params)
2627
$bucket = $params['bucket'] ?? null;
2728
$key = $params['key'] ?? null;
2829

30+
if (is_null($bucket) || is_null($key)) {
31+
return GenericServerError("Invalidb bucket or key parameters", 400);
32+
}
33+
2934
$s3ec = $s3ecClientTuple["encryptionClient"];
3035
$materialProvider = $s3ecClientTuple["materialsProvider"];
3136
$cipherOptions = [
@@ -55,14 +60,13 @@ function handlePutObject($params)
5560
return json_encode([
5661
"bucket" => $bucket,
5762
"key" => $key,
63+
// php for some reason blows java's heap if we pass the metadata
5864
// "metadata" => $encryptionContext
5965
]);
6066

6167
} catch (InvalidArgumentException $e) {
62-
http_response_code(400);
63-
return json_encode(['error' => 'Invalid argument: ' . $e->getMessage()]);
68+
return S3EncryptionClientError("Invalid arguement: " . $e->getMessage());
6469
} catch (Exception $e) {
65-
http_response_code(500);
66-
return json_encode(['error' => 'Server error: ' . $e->getMessage()]);
70+
return GenericServerError("Server error: " . $e->getMessage());
6771
}
6872
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
vendor/*
2+
cookies.txt
3+
server.pid
4+
composer.lock

test-server/php-v3-server/Makefile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Makefile for S3 Encryption Client Testing
2+
3+
.PHONY: start-server stop-server wait-for-server
4+
5+
PID_FILE := server.pid
6+
PORT := 8093
7+
8+
start-server:
9+
@echo "Starting PHP V2 server..."
10+
AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID" \
11+
AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \
12+
AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \
13+
AWS_REGION="us-west-2" \
14+
composer run start & echo $$! > $(PID_FILE)
15+
@echo "PHP V2 server starting..."
16+
17+
stop-server:
18+
@if [ -f $(PID_FILE) ]; then \
19+
kill $$(cat $(PID_FILE)) 2>/dev/null || true; \
20+
rm $(PID_FILE); \
21+
fi
22+
23+
wait-for-server:
24+
$(MAKE) -C .. wait-for-port PORT=$(PORT)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# S3EC PHP v2 Test Server
2+
3+
This is the PHP V3 implementation of the S3ECTestServer framework. It provides a server implementation for testing S3 Encryption Client functionality.
4+
5+
## Overview
6+
7+
The S3ECPhpV2TestServer implements the S3ECTestServer service defined in the shared Smithy model. It provides endpoints for:
8+
9+
- Creating S3 Encryption Clients with session-based caching
10+
- Putting objects with encryption
11+
- Getting and decrypting objects
12+
13+
## Starting the Server
14+
15+
### Method 1: Using Composer (Recommended)
16+
```bash
17+
composer run start
18+
```
19+
20+
The server will start on port `8093`.
21+
22+
## Available Endpoints
23+
24+
### Server Status
25+
- **GET /** - Returns server status and available endpoints
26+
27+
### Client Management
28+
- **POST /client** - Creates an S3EncryptionClient and caches it with session persistence
29+
- **GET /cache** - Shows current session state and cached clients (for debugging)
30+
31+
### Object Operations
32+
- **GET /object/{bucket}/{key}** - Handle GET requests using the S3EncryptionClient
33+
- **PUT /object/{bucket}/{key}** - Handle PUT requests using the S3EncryptionClient
34+
35+
## Testing with curl
36+
37+
### Important: Session Cookie Management
38+
39+
To properly test the server and maintain session persistence, you **must** use cookies with curl:
40+
41+
#### First Request (creates session cookie):
42+
```bash
43+
curl -X POST http://localhost:8093/client \
44+
-H "Content-Type: application/json" \
45+
-v
46+
```
47+
48+
#### Subsequent Requests (reuses session cookie):
49+
```bash
50+
curl -X POST http://localhost:8093/client \
51+
-H "Content-Type: application/json" \
52+
-v
53+
```
54+
55+
#### Check Cache Status:
56+
```bash
57+
curl http://localhost:8093/cache \
58+
-b cookies.txt
59+
```
60+
61+
#### Helpful Notes
62+
- **Session Storage**: Client configurations are stored in `$_SESSION['s3ecCache']`
63+
- **Object Recreation**: AWS SDK objects are recreated from stored configuration (they cannot be serialized)
64+
AWS SDK obbjects cannot be serialized due to internal resources and closures.
65+
- **Helper Function**: `getCachedClient($clientId)` retrieves and recreates clients from cache
66+
- **Debugging**: Enhanced logging and `/cache` endpoint for troubleshooting
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "aws/s3ec-php-v2-test-server",
3+
"description": "PHP v2 implementation of the S3EC Test Server framework",
4+
"type": "project",
5+
"license": "Apache-2.0",
6+
"require": {
7+
"php": ">=7.4",
8+
"aws/aws-sdk-php": "^3.356",
9+
"ramsey/uuid": "^4.9"
10+
},
11+
"autoload": {
12+
"psr-4": {
13+
"S3EC\\PhpV2Server\\": "src/"
14+
}
15+
},
16+
"scripts": {
17+
"start": [
18+
"php -S 0.0.0.0:8093 src/index.php"
19+
]
20+
},
21+
"config": {
22+
"optimize-autoloader": true
23+
}
24+
}

0 commit comments

Comments
 (0)