Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c0cfcd4
net v4 improved
rishav-karanjit Oct 29, 2025
7a4ce1a
rm redundant sln
rishav-karanjit Oct 29, 2025
32bfe1b
Deleted wrong dir
rishav-karanjit Oct 29, 2025
7067ef9
v4 server
rishav-karanjit Oct 29, 2025
d707266
auto commit
rishav-karanjit Oct 29, 2025
874e5b7
auto commit
rishav-karanjit Oct 29, 2025
d9d7b41
auto commit
rishav-karanjit Oct 29, 2025
2bf1d6b
auto commit
rishav-karanjit Oct 29, 2025
7f31a74
auto commit
rishav-karanjit Oct 29, 2025
96cc190
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Oct 29, 2025
58b09e7
auto commit
rishav-karanjit Oct 29, 2025
70dc049
Merge branch 'rishav/add-net-v4-server' of https://github.com/aws/ama…
rishav-karanjit Oct 29, 2025
f93212f
auto commit
rishav-karanjit Oct 29, 2025
153b7fc
auto commit
rishav-karanjit Oct 29, 2025
1ce4191
Update S3ECJavaTestServer.java
rishav-karanjit Oct 29, 2025
79bb08e
bump
rishav-karanjit Oct 29, 2025
b3e82ec
Merge branch 'rishav/add-net-v4-server' of https://github.com/aws/ama…
rishav-karanjit Oct 29, 2025
a23050b
auto commit
rishav-karanjit Oct 30, 2025
00ac5e2
auto commit
rishav-karanjit Oct 30, 2025
060bb4c
auto commit
rishav-karanjit Oct 30, 2025
fdc388c
auto commit
rishav-karanjit Oct 30, 2025
8aaaa99
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Oct 30, 2025
5a87717
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 6, 2025
8c3f1db
duvet
rishav-karanjit Nov 6, 2025
9ac5804
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 7, 2025
e7bb8d3
bump
rishav-karanjit Nov 7, 2025
ce142fc
Merge branch 'rishav/add-net-v4-server' of https://github.com/aws/ama…
rishav-karanjit Nov 7, 2025
d6c6c6d
auto commit
rishav-karanjit Nov 7, 2025
1894b91
auto commit
rishav-karanjit Nov 7, 2025
4eb4396
bump
rishav-karanjit Nov 7, 2025
ff1ae1c
gitmodules
rishav-karanjit Nov 7, 2025
aa38086
auto commit
rishav-karanjit Nov 7, 2025
7fa3b45
merge
rishav-karanjit Nov 7, 2025
2aecedd
Add JsonStringEnumConverter
rishav-karanjit Nov 10, 2025
35f5c9f
merge
rishav-karanjit Nov 10, 2025
a4864bb
auto commit
rishav-karanjit Nov 10, 2025
c35f978
auto commit
rishav-karanjit Nov 10, 2025
62e7743
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 10, 2025
9f69a14
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 10, 2025
2cfbfb9
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 10, 2025
f2f5713
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 11, 2025
8b4b6cf
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 11, 2025
7cd1e9c
Merge branch 'fireegg-test-servers' into rishav/add-net-v4-server
rishav-karanjit Nov 12, 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
9 changes: 9 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ jobs:
ref: rishav/key-commitment
path: test-server/net-v3-transition-server/s3ec-v3-transition-branch

- name: Checkout .NET V4 (Improved) code
uses: actions/checkout@v5
with:
token: ${{ secrets.PAT_FOR_DOTNET }}
repository: aws/private-amazon-s3-encryption-client-dotnet-staging
# This is the branch for S3EC .NET V4 (improved)
ref: s3ec-v4-WIP
path: test-server/net-v4-server/s3ec-net-v4-improved

- name: Set up Python
uses: actions/setup-python@v5
with:
Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
path = test-server/net-v2-v3-server/s3ec-net-v3
url = https://github.com/aws/private-amazon-s3-encryption-client-dotnet-staging.git
branch = s3ec-v3
[submodule "test-server/net-v4-server/s3ec-net-v4-improved"]
path = test-server/net-v4-server/s3ec-net-v4-improved
url = https://github.com/aws/private-amazon-s3-encryption-client-dotnet-staging.git
branch = s3ec-v4-WIP
[submodule "test-server/go-v3-transition-server/local-go-s3ec"]
path = test-server/go-v3-transition-server/local-go-s3ec
url = https://github.com/aws/private-amazon-s3-encryption-client-go-staging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public void kmsV1LegacyFailsWhenLegacyDisabled(TestUtils.LanguageServerTarget la
.build());
fail("Expected Exception");
} catch (S3EncryptionClientError e) {
if (language.getLanguageName().equals(NET_V3_CURRENT) || language.getLanguageName().equals(NET_V2_CURRENT) || language.getLanguageName().equals(NET_V3_TRANSITION)
if (language.getLanguageName().equals(NET_V3_CURRENT) || language.getLanguageName().equals(NET_V2_CURRENT) || language.getLanguageName().equals(NET_V3_TRANSITION) || language.getLanguageName().equals(NET_V4)
|| language.getLanguageName().equals(CPP_V2_CURRENT) || language.getLanguageName().equals(CPP_V2_TRANSITION) || language.getLanguageName().equals(CPP_V3)) {
assertTrue(e.getMessage().contains(
"The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ public class TestUtils {

// Sets of unsupported features by language
public static final Set<String> ENCRYPTION_CONTEXT_ON_DECRYPT_UNSUPPORTED =
Set.of(GO_V3_CURRENT, PHP_V2_CURRENT, PHP_V2_TRANSITION, PHP_V3, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION);
Set.of(GO_V3_CURRENT, PHP_V2_CURRENT, PHP_V2_TRANSITION, PHP_V3, NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4);

public static final Set<String> ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED =
Set.of(NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION);
Set.of(NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4);

// For now, only .NET and Java have RSA support
public static final Set<String> RAW_SUPPORTED =
Expand Down Expand Up @@ -148,7 +148,7 @@ public class TestUtils {
JAVA_V4,
// PYTHON_V3,
GO_V4,
// NET_V4,
NET_V4,
CPP_V3,
PHP_V3,
RUBY_V3
Expand All @@ -169,6 +169,7 @@ public class TestUtils {
// servers.put(RUBY_V2_CURRENT, new LanguageServerTarget(RUBY_V2_CURRENT, "8086"));
servers.put(PHP_V2_CURRENT, new LanguageServerTarget(PHP_V2_CURRENT, "8087"));
servers.put(GO_V4, new LanguageServerTarget(GO_V4, "8089"));
servers.put(NET_V4, new LanguageServerTarget(NET_V4, "8090"));
servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092"));
servers.put(PHP_V3, new LanguageServerTarget(PHP_V3, "8093"));
// TODO: Create and add transition servers
Expand Down
3 changes: 3 additions & 0 deletions test-server/net-v4-server/.duvet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
reports/
requirements/
specification/
27 changes: 27 additions & 0 deletions test-server/net-v4-server/.duvet/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'$schema' = "https://awslabs.github.io/duvet/config/v0.4.0.json"

[[source]]
pattern = "**/*.cs"

# Include required specifications here
[[specification]]
source = "../specification/s3-encryption/client.md"
[[specification]]
source = "../specification/s3-encryption/decryption.md"
[[specification]]
source = "../specification/s3-encryption/encryption.md"
[[specification]]
source = "../specification/s3-encryption/key-commitment.md"
[[specification]]
source = "../specification/s3-encryption/key-derivation.md"
[[specification]]
source = "../specification/s3-encryption/data-format/content-metadata.md"
[[specification]]
source = "../specification/s3-encryption/data-format/metadata-strategy.md"

[report.html]
enabled = true

# Enable snapshots to prevent requirement coverage regressions
[report.snapshot]
enabled = false
44 changes: 44 additions & 0 deletions test-server/net-v4-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
113 changes: 113 additions & 0 deletions test-server/net-v4-server/Controllers/ClientController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Text.Json;
using Amazon.Extensions.S3.Encryption;
using Amazon.Extensions.S3.Encryption.Primitives;
using Microsoft.AspNetCore.Mvc;
using NetV4Server.Models;
using NetV4Server.Services;

namespace NetV4Server.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 ?? false)
return StatusCode(501, new GenericServerError { Message = "[NET-V4] EnableDelayedAuthenticationMode not supported" });
if (request.Config.SetBufferSize.HasValue)
return StatusCode(501, new GenericServerError { Message = "[NET-V4] SetBufferSize not supported" });
if (request.Config.KeyMaterial.RsaKey != null)
return StatusCode(501, new GenericServerError { Message = "[NET-V4] RsaKey not supported" });
if (request.Config.KeyMaterial.AesKey != null)
return StatusCode(501, new GenericServerError { Message = "[NET-V4] AesKey not supported" });

try
{
var kmsKeyId = request.Config.KeyMaterial.KmsKeyId;
var enableLegacyUnauthenticatedModes = request.Config.EnableLegacyUnauthenticatedModes ?? false;
var enableLegacyWrappingAlgorithms = request.Config.EnableLegacyWrappingAlgorithms ?? false;
var commitmentPolicy = MapCommitmentPolicy(request.Config.CommitmentPolicy);
var isSecurityProfileProvided = request.Config.EnableLegacyUnauthenticatedModes.HasValue || request.Config.EnableLegacyWrappingAlgorithms.HasValue;
var isCommitmentPolicyProvided = request.Config.CommitmentPolicy.HasValue;
var useDefaultConf = !isCommitmentPolicyProvided;

logger.LogInformation("[NET-V4] isSecurityProfileProvided: {isSecurityProfileProvided}, isCommitmentPolicyProvided: {isCommitmentPolicyProvided}, useDefaultConf: {useDefaultConf}", isSecurityProfileProvided, isCommitmentPolicyProvided, useDefaultConf);

// 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 EncryptionMaterialsV4(kmsKeyId, KmsType.KmsContext, encryptionContext);
logger.LogInformation(
"[NET-V4] Created EncryptionMaterialsV4: KMS={KmsKeyId}",
kmsKeyId);

// SecurityProfile V4AndLegacy can decrypt from legacy S3EC but V4 cannot
var enableLegacyMode = enableLegacyUnauthenticatedModes || enableLegacyWrappingAlgorithms;
var securityProfile = enableLegacyMode ? SecurityProfile.V4AndLegacy : SecurityProfile.V4;

var encryptionAlgorithm = MapEncryptionAlgorithm(request.Config.EncryptionAlgorithm);

if (!useDefaultConf)
{
logger.LogInformation("[NET-V4] Created securityProfile= {securityProfile}", securityProfile.ToString());
logger.LogInformation("[NET-V4] Created commitmentPolicy= {commitmentPolicy}", commitmentPolicy);
logger.LogInformation("[NET-V4] Created encryptionAlgorithm= {encryptionAlgorithm}", encryptionAlgorithm);
} else
{
logger.LogInformation("[NET-V4] Using default configuration for securityProfile, commitmentPolicy and encryptionAlgorithm");
}

var configuration = useDefaultConf
? new AmazonS3CryptoConfigurationV4()
: new AmazonS3CryptoConfigurationV4(securityProfile, commitmentPolicy, encryptionAlgorithm);

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

logger.LogInformation("[NET-V4] 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, "[NET-V4] Failed to create S3EC client");
return StatusCode(500, new S3EncryptionClientError
{
Message = $"[NET-V4] Failed to create client: {ex.Message}"
});
}
}

private static Amazon.Extensions.S3.Encryption.CommitmentPolicy MapCommitmentPolicy(Models.CommitmentPolicy? policy)
{
return policy switch
{
Models.CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT => Amazon.Extensions.S3.Encryption.CommitmentPolicy.RequireEncryptRequireDecrypt,
Models.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT => Amazon.Extensions.S3.Encryption.CommitmentPolicy.RequireEncryptAllowDecrypt,
Models.CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT => Amazon.Extensions.S3.Encryption.CommitmentPolicy.ForbidEncryptAllowDecrypt,
_ => Amazon.Extensions.S3.Encryption.CommitmentPolicy.RequireEncryptRequireDecrypt
};
}

private static ContentEncryptionAlgorithm MapEncryptionAlgorithm(Models.EncryptionAlgorithm? algorithm)
{
return algorithm switch
{
Models.EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF => ContentEncryptionAlgorithm.AesGcm,
Models.EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY => ContentEncryptionAlgorithm.AesGcmWithCommitment,
_ => ContentEncryptionAlgorithm.AesGcmWithCommitment
};
}
}
105 changes: 105 additions & 0 deletions test-server/net-v4-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 NetV4Server.Models;
using NetV4Server.Services;

namespace NetV4Server.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 = "[NET-V4] ClientID header is required" });

var client = clientCacheService.GetClient(clientId);
if (client == null)
return NotFound(new GenericServerError { Message = $"[NET-V4] 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(
"[NET-V4] 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, "[NET-V4] 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("[NET-V4] Starting GetObject");
var clientId = Request.Headers["clientId"].FirstOrDefault();
if (string.IsNullOrEmpty(clientId))
return BadRequest(new GenericServerError { Message = "[NET-V4] ClientID header is required" });

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

try
{
var getRequest = new GetObjectRequest
{
BucketName = bucket,
Key = key
};
var response = await client.GetObjectAsync(getRequest);
logger.LogInformation("[NET-V4] 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, "[NET-V4] Failed to get object from S3 for bucket={bucket}, key={key}", bucket, key);
return StatusCode(500, new S3EncryptionClientError { Message = ex.Message });
}
}
}
Loading
Loading