Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ gradle-app.setting

.DS_Store
smithy-java-core/out

# test server
*.pid
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -174,40 +176,40 @@ public void crossLanguageTestKmsWithSubsetEncCtxFails(LanguageServerTarget encLa
encCtx.put("user-defined-enc-ctx-key-2", "user-defined-enc-ctx-value-2");
final List<String> 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)
.commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF)
.build())
.build());
.config(S3ECConfig.builder()
.keyMaterial(kmsKeyArn)
.commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF)
.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()
.keyMaterial(kmsKeyArn)
.commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF)
.build()
)
.build());
.config(S3ECConfig.builder()
.keyMaterial(kmsKeyArn)
.commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF)
.build()
)
.build());
String decS3ECId = decClientOutput.getClientId();
try {
decClient.getObject(GetObjectInput.builder()
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());
fail("Expected exception!");
} catch (S3EncryptionClientError e) {
if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_CURRENT) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) {
Expand Down Expand Up @@ -275,9 +277,9 @@ public void crossLanguageTestKmsWithIncorrectEncCtxFails(LanguageServerTarget en
fail("Expected exception!");
} catch (S3EncryptionClientError e) {
if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_CURRENT) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) {
assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context"));
assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context"));
} else {
assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3"));
assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3"));
}
}
}
Expand Down Expand Up @@ -426,15 +428,62 @@ public void kmsV1LegacyFailsWhenLegacyDisabled(TestUtils.LanguageServerTarget la
"The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration"
));
} else if (language.getLanguageName().equals(RUBY_V3) || language.getLanguageName().equals(RUBY_V2_CURRENT) || language.getLanguageName().equals(RUBY_V2_TRANSITION)) {
assertTrue(e.getMessage().contains(
"The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration security_profile = :v2. Retry with :v2_and_legacy or re-encrypt the object."
assertTrue(e.getMessage().contains(
"The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration security_profile = :v2. Retry with :v2_and_legacy or re-encrypt the object."
), "Actual error:" + e.getMessage());
} else {
assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"));
assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"));
}
}
}

@ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}")
@MethodSource("software.amazon.encryption.s3.TestUtils#crossLanguageClients")
public void rsaRoundTrip(LanguageServerTarget encLang, LanguageServerTarget decLang) throws Exception {
if (!RAW_SUPPORTED.contains(encLang.getLanguageName())) {
throw new TestAbortedException("not encrypting raw keyrings with: " + encLang.getLanguageName());
}
if (!RAW_SUPPORTED.contains(decLang.getLanguageName())) {
throw new TestAbortedException("not decrypting raw keyrings with: " + decLang.getLanguageName());
}
S3ECTestServerClient encClient = testServerClientFor(encLang);
S3ECTestServerClient decClient = testServerClientFor(decLang);
final String objectKey = appendTestSuffix(String.format("rsa-write-%s-read-%s", encLang.getLanguageName(), decLang.getLanguageName()));
final String input = "simple-test-input-rsa";
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
KeyPair RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair();

KeyMaterial rsaKeyOne = KeyMaterial.builder()
.rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded()))
.build();
CreateClientOutput encClientOutput = encClient.createClient(CreateClientInput.builder()
.config(S3ECConfig.builder()
// TODO: use this for now to satisfy current. think about long term soln for this
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF)
.commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
.keyMaterial(rsaKeyOne).build())
.build());
String encS3ECId = encClientOutput.getClientId();
CreateClientOutput decClientOutput = decClient.createClient(CreateClientInput.builder()
.config(S3ECConfig.builder()
.keyMaterial(rsaKeyOne).build())
.build());
String decS3ECId = decClientOutput.getClientId();
encClient.putObject(PutObjectInput.builder()
.clientID(encS3ECId)
.key(objectKey)
.bucket(BUCKET)
.body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8)))
.build());
GetObjectOutput output = decClient.getObject(GetObjectInput.builder()
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());
assertEquals(input, new String(output.getBody().array()));
}

@ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}")
@MethodSource("software.amazon.encryption.s3.TestUtils#clientsForTest")
public void instructionFileReadV2Format(TestUtils.LanguageServerTarget language) {
Expand Down Expand Up @@ -545,16 +594,15 @@ public void instructionFileWriteAndRead(LanguageServerTarget encLang, LanguageSe
if (!encLang.getLanguageName().startsWith("Ruby") && !encLang.getLanguageName().startsWith("PHP")) {
// Ruby and PHP do not include it :(
assertTrue(ptInstFile.response().metadata().containsKey("x-amz-crypto-instr-file"));
}
assertFalse(ptInstFile.asUtf8String().isEmpty());

// Read should be enabled by default
GetObjectOutput output = decClient.getObject(GetObjectInput.builder()
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());
assertFalse(ptInstFile.asUtf8String().isEmpty());
// Read should be enabled by default
GetObjectOutput output = decClient.getObject(GetObjectInput.builder()
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());

assertEquals(input, new String(output.getBody().array()));
assertEquals(input, new String(output.getBody().array()));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ public class TestUtils {
public static final Set<String> ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED =
Set.of(NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION);

// For now, only .NET and Java have RSA support
public static final Set<String> RAW_SUPPORTED =
Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4
, NET_V2_CURRENT, NET_V3_CURRENT
// , NET_V3_TRANSITION
);

// .NET only supports decrypting instruction files using AES and RSA.
// Python MUST support decrypting KMS instruction files, but does not yet.
public static final Set<String> KMS_INSTRUCTION_FILE_UNSUPPORTED =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
import java.io.StringWriter;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -74,13 +77,25 @@ public CreateClientOutput createClient(CreateClientInput input, RequestContext c
key.getRsaKey().get(keyBytes);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec);
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(
privateKey.getModulus(),
privateKey.getPublicExponent()
);

// Generate public key
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

keyring = RsaKeyring.builder()
.enableLegacyWrappingAlgorithms(input.getConfig().isEnableLegacyWrappingAlgorithms())
.wrappingKeyPair(PartialRsaKeyPair.builder()
.privateKey(keyFactory.generatePrivate(keySpec)).build())
.publicKey(publicKey)
.privateKey(privateKey).build())
.build();
} catch (NoSuchAlgorithmException | InvalidKeySpecException nse) {
throw new RuntimeException(nse);
throw GenericServerError.builder()
.message(nse.getMessage())
.build();
}
} else if (key.getKmsKeyId() != null) {
keyring = KmsKeyring.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import java.io.StringWriter;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Map;
import java.util.UUID;
Expand Down Expand Up @@ -74,13 +77,25 @@ public CreateClientOutput createClient(CreateClientInput input, RequestContext c
key.getRsaKey().get(keyBytes);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec);
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(
privateKey.getModulus(),
privateKey.getPublicExponent()
);

// Generate public key
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

keyring = RsaKeyring.builder()
.enableLegacyWrappingAlgorithms(input.getConfig().isEnableLegacyWrappingAlgorithms())
.wrappingKeyPair(PartialRsaKeyPair.builder()
.privateKey(keyFactory.generatePrivate(keySpec)).build())
.publicKey(publicKey)
.privateKey(privateKey).build())
.build();
} catch (NoSuchAlgorithmException | InvalidKeySpecException nse) {
throw new RuntimeException(nse);
throw GenericServerError.builder()
.message(nse.getMessage())
.build();
}
} else if (key.getKmsKeyId() != null) {
keyring = KmsKeyring.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import java.io.StringWriter;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Map;
import java.util.UUID;
Expand Down Expand Up @@ -75,13 +78,25 @@ public CreateClientOutput createClient(CreateClientInput input, RequestContext c
key.getRsaKey().get(keyBytes);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec);
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(
privateKey.getModulus(),
privateKey.getPublicExponent()
);

// Generate public key
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

keyring = RsaKeyring.builder()
.enableLegacyWrappingAlgorithms(input.getConfig().isEnableLegacyWrappingAlgorithms())
.wrappingKeyPair(PartialRsaKeyPair.builder()
.privateKey(keyFactory.generatePrivate(keySpec)).build())
.publicKey(publicKey)
.privateKey(privateKey).build())
.build();
} catch (NoSuchAlgorithmException | InvalidKeySpecException nse) {
throw new RuntimeException(nse);
throw GenericServerError.builder()
.message(nse.getMessage())
.build();
}
} else if (key.getKmsKeyId() != null) {
keyring = KmsKeyring.builder()
Expand Down
43 changes: 30 additions & 13 deletions test-server/net-v2-v3-server/Controllers/ClientController.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Net;
using System.Security.Cryptography;
using System.Text.Json;
using Amazon.Extensions.S3.Encryption;
using Amazon.Extensions.S3.Encryption.Primitives;
Expand All @@ -19,25 +21,40 @@ public IActionResult CreateClient([FromBody] ClientRequest request)
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}",
EncryptionMaterialsV2 encryptionMaterial;
if (request.Config.KeyMaterial.KmsKeyId != null)
{
// 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 kmsKeyId = request.Config.KeyMaterial.KmsKeyId;
encryptionMaterial = new EncryptionMaterialsV2(kmsKeyId, KmsType.KmsContext, encryptionContext);
logger.LogInformation(
"Created EncryptionMaterialsV2: KMS={KmsKeyId}",
kmsKeyId);
}
else if (request.Config.KeyMaterial.RsaKey != null)
{
var rsaKeyBytes = request.Config.KeyMaterial.RsaKey;
var rsaKey = RSA.Create();
rsaKey.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(rsaKeyBytes), out _);
encryptionMaterial = new EncryptionMaterialsV2(rsaKey, AsymmetricAlgorithmType.RsaOaepSha1);
logger.LogInformation(
"Created EncryptionMaterialsV2: RSA");
} else
{
return StatusCode(501, new GenericServerError { Message = "Unknown or missing key material!" });
}

var enableLegacyUnauthenticatedModes = request.Config.EnableLegacyUnauthenticatedModes;
var enableLegacyWrappingAlgorithms = request.Config.EnableLegacyWrappingAlgorithms;

// SecurityProfile V2AndLegacy can decrypt from legacy S3EC but V2 cannot
var enableLegacyMode = enableLegacyUnauthenticatedModes || enableLegacyWrappingAlgorithms;
var securityProfile = enableLegacyMode ? SecurityProfile.V2AndLegacy : SecurityProfile.V2;
Expand Down
4 changes: 1 addition & 3 deletions test-server/net-v2-v3-server/Models/ClientRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,5 @@ public class KeyMaterial
{
public byte[]? RsaKey { get; set; }
public byte[]? AesKey { get; set; }

[Required]
public string KmsKeyId { get; set; } = string.Empty;
public string? KmsKeyId { get; set; }
}
Loading
Loading