Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0d652b9
docs
rishav-karanjit Sep 15, 2025
fe1e1ff
init server
rishav-karanjit Sep 15, 2025
833e20a
auto commit
rishav-karanjit Sep 16, 2025
139b88a
Add context for q
rishav-karanjit Sep 16, 2025
7ed605b
auto commit
rishav-karanjit Sep 16, 2025
c2826e0
auto commit
rishav-karanjit Sep 16, 2025
cecf9aa
auto commit
rishav-karanjit Sep 17, 2025
2263f16
auto commit
rishav-karanjit Sep 17, 2025
e7806a4
clean up
rishav-karanjit Sep 17, 2025
4e39960
:rename
rishav-karanjit Sep 17, 2025
b106aba
readme
rishav-karanjit Sep 17, 2025
bf1d6fd
auto commit
rishav-karanjit Sep 17, 2025
62e9383
remove temp
rishav-karanjit Sep 17, 2025
d9924f0
logs
rishav-karanjit Sep 17, 2025
2312865
auto commit
rishav-karanjit Sep 17, 2025
a039186
Merge branch 'fireegg-test-servers' of https://github.com/aws/amazon-…
rishav-karanjit Sep 18, 2025
a142eab
logs
rishav-karanjit Sep 18, 2025
f82b4ab
legacy
rishav-karanjit Sep 18, 2025
d01506e
makefile
rishav-karanjit Sep 18, 2025
8b19cdf
auto commit
rishav-karanjit Sep 18, 2025
43514e0
auto commit
rishav-karanjit Sep 18, 2025
24637c5
Merge remote-tracking branch 'origin/fireegg-test-servers' into risha…
rishav-karanjit Sep 18, 2025
86146f5
auto commit
rishav-karanjit Sep 18, 2025
b9acf93
auto commit
rishav-karanjit Sep 18, 2025
6255978
Error model
rishav-karanjit Sep 18, 2025
69fdd20
auto commit
rishav-karanjit Sep 18, 2025
4738803
auto commit
rishav-karanjit Sep 18, 2025
a3baf9d
auto commit
rishav-karanjit Sep 18, 2025
a37a232
remove redundant import
rishav-karanjit Sep 18, 2025
8d07ee4
auto commit
rishav-karanjit Sep 19, 2025
a89ce29
validation
rishav-karanjit Sep 19, 2025
f9ade30
Update test-server/net-v2-v3-server/Controllers/ObjectController.cs
rishav-karanjit Sep 19, 2025
ac556cb
Add not implemented feature
rishav-karanjit Sep 19, 2025
8c95c21
Merge branch 'rishav/dotnet/testserver' of https://github.com/aws/ama…
rishav-karanjit Sep 19, 2025
8ed7ee4
Merge branch 'fireegg-test-servers' into rishav/dotnet/testserver
rishav-karanjit Sep 19, 2025
46ae023
Update test-server/java-tests/src/it/java/software/amazon/encryption/…
rishav-karanjit Sep 19, 2025
0b18711
Update test-server/net-v2-v3-server/Controllers/ClientController.cs
rishav-karanjit Sep 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;

public class RoundTripTests {
private static final String JAVA_V3 = "Java-V3";
private static final String PYTHON_V3 = "Python-V3";
private static final String GO_V3 = "Go-V3";
private static final String NET_V2 = "NET-V2";
private static final String NET_V3 = "NET-V3";
private static final String PHP_V2 = "PHP-V2";
private static final String PHP_V3 = "PHP-V3";

private static final List<LanguageServerTarget> serverList;
private static final Map<String, LanguageServerTarget> serverMap;

Expand All @@ -65,26 +73,39 @@ public class RoundTripTests {

static {
serverList = new ArrayList<>(14);
serverList.add(new LanguageServerTarget("Java-V3", "8080"));
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"));
serverList.add(new LanguageServerTarget(JAVA_V3, "8080"));
serverList.add(new LanguageServerTarget(PYTHON_V3, "8081"));
serverList.add(new LanguageServerTarget(GO_V3, "8082"));
serverList.add(new LanguageServerTarget(NET_V2, "8083"));
serverList.add(new LanguageServerTarget(NET_V3, "8084"));
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"));
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(NET_V2, new LanguageServerTarget(NET_V2, "8083"));
serverMap.put(NET_V3, new LanguageServerTarget(NET_V3, "8084"));
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).
// Encryption context validation behavior varies by implementation:
// - Go: Does not validate encryption context on decrypt operations
// - .NET: Only validates against encryption context stored in the object metadata
// If the encryption context provided to getObject does not match the encryption context on the stored object,
// these implementations will not raise an error as expected.
// For now, skip tests that expect encryption context validation on decrypt.
private static final Set<String> ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED =
Set.of("Go-V3", "PHP-V2", "PHP-V3");
Set.of(GO_V3, PHP_V2, PHP_V3, NET_V2, NET_V3);

// S3EC .NET implementations does not accept encryption context (EC) during putObject operations.
// These tests are not configured to pass encryption context at client level but at encrypt,
// So, for .NET EC is not passed.
// For now, skip tests that expect encryption context validation on decrypt.
Comment thread
texastony marked this conversation as resolved.
private static final Set<String> ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED =
Set.of(NET_V2, NET_V3);

static public class LanguageServerTarget {
public String getLanguageName() {
Expand Down Expand Up @@ -226,6 +247,9 @@ public void crossLanguageTestKms(LanguageServerTarget encLang, LanguageServerTar
@ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}")
@MethodSource("crossLanguageClients")
public void crossLanguageTestKmsWithEncCtx(LanguageServerTarget encLang, LanguageServerTarget decLang) {
if (ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED.contains(encLang.getLanguageName())) {
return;
}
S3ECTestServerClient encClient = testServerClientFor(encLang);
final String objectKey = "cross-lang-test-key-kms-ec-" + encLang;
final String input = "simple-test-input";
Expand Down Expand Up @@ -273,6 +297,9 @@ public void crossLanguageTestKmsWithSubsetEncCtxFails(LanguageServerTarget encLa
if (ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED.contains(decLang.getLanguageName())) {
return;
}
if (ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED.contains(encLang.getLanguageName())) {
return;
}
S3ECTestServerClient encClient = testServerClientFor(encLang);
final String objectKey = "cross-lang-test-key-kms-ec-subset-fails" + encLang;
final String input = "simple-test-input";
Expand Down Expand Up @@ -498,7 +525,13 @@ public void kmsV1LegacyFailsWhenLegacyDisabled(String language) {
.build());
fail("Expected Exception");
} catch (S3EncryptionClientError e) {
assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"));
if (language.equals(NET_V3) || language.equals(NET_V2)) {
assertTrue(e.getMessage().contains(
"The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration V2."
));
} else {
assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"));
}
}
}

Expand Down
44 changes: 44 additions & 0 deletions test-server/net-v2-v3-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/

# Visual Studio 2015/2017 cache/options directory
.vs/

# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates

# NuGet Packages
*.nupkg
*.snupkg
packages/

# JetBrains Rider
.idea/
*.sln.iml

# VS Code
.vscode/

# macOS
.DS_Store

# Temporary files
*.tmp
*.temp
72 changes: 72 additions & 0 deletions test-server/net-v2-v3-server/Controllers/ClientController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Text.Json;
using Amazon.Extensions.S3.Encryption;
using Amazon.Extensions.S3.Encryption.Primitives;
using Microsoft.AspNetCore.Mvc;
using NetV2V3Server.Models;
using NetV2V3Server.Services;

namespace NetV2V3Server.Controllers;

[ApiController]
[Route("[controller]")]
public class ClientController(IClientCacheService clientCacheService, ILogger<ClientController> logger) : ControllerBase
{
[HttpPost]
public IActionResult CreateClient([FromBody] ClientRequest request)
{
// Return 501 for not implemented features by the server
if (request.Config.EnableDelayedAuthenticationMode)
return StatusCode(501, new GenericServerError { Message = "EnableDelayedAuthenticationMode not supported" });
if (request.Config.SetBufferSize.HasValue)
return StatusCode(501, new GenericServerError { Message = "SetBufferSize not supported" });
if (request.Config.KeyMaterial.RsaKey != null)
return StatusCode(501, new GenericServerError { Message = "RsaKey not supported" });
if (request.Config.KeyMaterial.AesKey != null)
return StatusCode(501, new GenericServerError { Message = "AesKey not supported" });

var kmsKeyId = request.Config.KeyMaterial.KmsKeyId;
var enableLegacyUnauthenticatedModes = request.Config.EnableLegacyUnauthenticatedModes;
var enableLegacyWrappingAlgorithms = request.Config.EnableLegacyWrappingAlgorithms;

try
{
// The POST request does not contain encryption context.
// However, encryption context is a required field when using KMS.
// So, we are passing empty dictionary.
var encryptionContext = new Dictionary<string, string>();
var encryptionMaterial = new EncryptionMaterialsV2(kmsKeyId, KmsType.KmsContext, encryptionContext);
logger.LogInformation(
"Created EncryptionMaterialsV2: KMS={KmsKeyId}",
kmsKeyId);
// SecurityProfile V2AndLegacy can decrypt from legacy S3EC but V2 cannot
var enableLegacyMode = enableLegacyUnauthenticatedModes || enableLegacyWrappingAlgorithms;
var securityProfile = enableLegacyMode ? SecurityProfile.V2AndLegacy : SecurityProfile.V2;

logger.LogInformation("Created securityProfile= {securityProfile}", securityProfile.ToString());

var configuration = new AmazonS3CryptoConfigurationV2(securityProfile);
// Create S3 encryption client
var encryptionClient = new AmazonS3EncryptionClientV2(configuration, encryptionMaterial);
// Add to cache and return client ID
var clientId = clientCacheService.AddClient(encryptionClient);
var response = new ClientResponse { ClientId = clientId };

logger.LogInformation("Created S3EC client with ID: {clientId}", clientId);

return new ContentResult
{
Content = JsonSerializer.Serialize(response),
ContentType = "application/json",
StatusCode = 200
};
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to create S3EC client");
return StatusCode(500, new S3EncryptionClientError
{
Message = $"Failed to create client: {ex.Message}"
});
}
}
}
105 changes: 105 additions & 0 deletions test-server/net-v2-v3-server/Controllers/ObjectController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Text.Json;
using Amazon.S3.Model;
using Microsoft.AspNetCore.Mvc;
using NetV2V3Server.Models;
using NetV2V3Server.Services;

namespace NetV2V3Server.Controllers;

[ApiController]
[Route("[controller]")]
public class ObjectController(IClientCacheService clientCacheService, ILogger<ObjectController> logger) : ControllerBase
{
[HttpPut("{bucket}/{key}")]
public async Task<IActionResult> PutObject(string bucket, string key)
{
logger.LogInformation("Starting PutObject");
var clientId = Request.Headers["clientId"].FirstOrDefault();
if (string.IsNullOrEmpty(clientId))
return BadRequest(new GenericServerError { Message = "ClientID header is required" });

var client = clientCacheService.GetClient(clientId);
if (client == null)
return NotFound(new GenericServerError { Message = $"No client found for ClientID: {clientId}" });

try
{
// Read raw body data
using var memoryStream = new MemoryStream();
// Request is the HTTP request this method is currently handling
await Request.Body.CopyToAsync(memoryStream);
var bodyBytes = memoryStream.ToArray();

// Create put request
var putRequest = new PutObjectRequest
{
BucketName = bucket,
Key = key,
InputStream = new MemoryStream(bodyBytes)
};

await client.PutObjectAsync(putRequest);

var response = new { bucket, key };

logger.LogInformation(
"Put object succeeded for bucket={bucket}, key={key} and clientId = {clientId}",
bucket, key, clientId);
return new ContentResult
{
Content = JsonSerializer.Serialize(response),
ContentType = "application/json",
StatusCode = 200
};
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to put object from S3 for bucket={bucket}, key={key}", bucket, key);
return StatusCode(500, new S3EncryptionClientError { Message = $"Failed to put object: {ex.Message}" });
}
}

[HttpGet("{bucket}/{key}")]
public async Task<IActionResult> GetObject(string bucket, string key)
{
logger.LogInformation("Starting GetObject");
var clientId = Request.Headers["clientId"].FirstOrDefault();
if (string.IsNullOrEmpty(clientId))
return BadRequest(new GenericServerError { Message = "ClientID header is required" });

var client = clientCacheService.GetClient(clientId);
if (client == null)
return NotFound(new GenericServerError { Message = $"No client found for ClientID: {clientId}" });

try
{
var getRequest = new GetObjectRequest
{
BucketName = bucket,
Key = key
};
var response = await client.GetObjectAsync(getRequest);
logger.LogInformation("Got object from S3 for bucket={bucket}, key={key}", bucket, key);
// Read response body
using var memoryStream = new MemoryStream();
await response.ResponseStream.CopyToAsync(memoryStream);
var bodyBytes = memoryStream.ToArray();

// Convert metadata to content-metadata header format
var metadataList = response.Metadata.Keys
.Select(metaDataKey => $"{metaDataKey}={response.Metadata[metaDataKey]}")
.ToList();
var metadataStr = string.Join(",", metadataList);

// Set response headers
Response.Headers["Content-Metadata"] = metadataStr;

return File(bodyBytes, "application/octet-stream");
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get object from S3 for bucket={bucket}, key={key}", bucket, key);
return StatusCode(500, new S3EncryptionClientError { Message = ex.Message });
}
}
}
55 changes: 55 additions & 0 deletions test-server/net-v2-v3-server/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Makefile for S3 Encryption Client .NET Testing

.PHONY: start-server stop-server wait-for-server

PID_FILE_NET_V2 := net-v2-server.pid
PID_FILE_NET_V3 := net-v3-server.pid
PORT_NET_V2 := 8083
PORT_NET_V3 := 8084

start-server:
$(MAKE) start-net-v2-server; \
$(MAKE) start-net-v3-server;

stop-server:
@if [ -f $(PID_FILE_NET_V2) ]; then \
kill $$(cat $(PID_FILE_NET_V2)) 2>/dev/null || true; \
rm $(PID_FILE_NET_V2); \
fi
@if [ -f $(PID_FILE_NET_V3) ]; then \
kill $$(cat $(PID_FILE_NET_V3)) 2>/dev/null || true; \
rm $(PID_FILE_NET_V3); \
fi

# Start .NET V2 server in background
# This builds first into bin/v2 and runs through dll
# to avoid simultaneous dotnet run conflict
start-net-v2-server:
@echo "Starting .NET 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" \
rm -rf obj/v2 bin/v2 && \
dotnet build -p:S3EncryptionVersion=v2 -o bin/v2 -p:BaseIntermediateOutputPath=obj/v2/ && \
dotnet bin/v2/NetV2V3Server.dll > net-v2-server.log 2>&1 & echo $$! > net-v2-server.pid
@echo ".NET V2 server starting..."


# Start .NET V3 server in background
# This builds first into bin/v3 and runs through dll
# to avoid simultaneous dotnet run conflict
start-net-v3-server:
@echo "Starting .NET V3 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" \
rm -rf obj/v3 bin/v3 && \
dotnet build -p:S3EncryptionVersion=v3 -o bin/v3 -p:BaseIntermediateOutputPath=obj/v3/ && \
dotnet bin/v3/NetV2V3Server.dll > net-v3-server.log 2>&1 & echo $$! > net-v3-server.pid
@echo ".NET V3 server starting..."

wait-for-server:
$(MAKE) -C .. wait-for-port PORT=$(PORT_NET_V2) \
$(MAKE) -C .. wait-for-port PORT=$(PORT_NET_V3)
Loading