Skip to content

Commit 1bd8b7a

Browse files
authored
chore: add client specification and Duvet annotations (#481)
* use implication/implementation/test annotation types instead of just comments for everything
1 parent 59027c4 commit 1bd8b7a

File tree

9 files changed

+240
-9
lines changed

9 files changed

+240
-9
lines changed

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "specification"]
2+
path = specification
3+
url = [email protected]:awslabs/aws-encryption-sdk-specification.git
4+
branch = kessplas/s3-ec-v3

Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Used for misc supporting functions like Duvet and prettier. Builds, tests, etc. should use the usual Java/Maven tooling.
2+
3+
duvet: | duvet_extract duvet_report
4+
5+
duvet_extract:
6+
rm -rf compliance
7+
$(foreach file, $(shell find specification/s3-encryption -name '*.md'), duvet extract -o compliance -f MARKDOWN $(file);)
8+
9+
duvet_report:
10+
duvet \
11+
report \
12+
--spec-pattern "compliance/**/*.toml" \
13+
--source-pattern "src/**/*.java" \
14+
--html specification_compliance_report.html

specification

Submodule specification added at 616da1e

src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java

Lines changed: 131 additions & 5 deletions
Large diffs are not rendered by default.

src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,13 @@ private DecryptionMaterials prepareMaterialsFromRequest(final GetObjectRequest g
7575
}
7676

7777
AlgorithmSuite algorithmSuite = contentMetadata.algorithmSuite();
78+
//= specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
79+
//= type=implication
80+
//# When enabled, the S3EC MUST be able to decrypt objects encrypted with all content encryption algorithms (both legacy and fully supported).
7881
if (!_enableLegacyUnauthenticatedModes && algorithmSuite.isLegacy()) {
82+
//= specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
83+
//= type=exception
84+
//# When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy content encryption algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy content encryption algorithm.
7985
throw new S3EncryptionClientException("Enable legacy unauthenticated modes to use legacy content decryption: " + algorithmSuite.cipherName());
8086
}
8187

@@ -146,11 +152,17 @@ public void onStream(SdkPublisher<ByteBuffer> ciphertextPublisher) {
146152
if (algorithmSuite.equals(AlgorithmSuite.ALG_AES_256_CBC_IV16_NO_KDF)
147153
|| algorithmSuite.equals(AlgorithmSuite.ALG_AES_256_CTR_IV16_TAG16_NO_KDF)
148154
|| _enableDelayedAuthentication) {
155+
//= specification/s3-encryption/client.md#enable-delayed-authentication
156+
//= type=implication
157+
//# When enabled, the S3EC MAY release plaintext from a stream which has not been authenticated.
149158
// CBC and GCM with delayed auth enabled use a standard publisher
150159
CipherPublisher plaintextPublisher = new CipherPublisher(ciphertextPublisher,
151160
getObjectResponse.contentLength(), desiredRange, contentMetadata.contentRange(), algorithmSuite.cipherTagLengthBits(), materials, iv);
152161
wrappedAsyncResponseTransformer.onStream(plaintextPublisher);
153162
} else {
163+
//= specification/s3-encryption/client.md#enable-delayed-authentication
164+
//= type=implication
165+
//# When disabled the S3EC MUST NOT release plaintext from a stream which has not been authenticated.
154166
// Use buffered publisher for GCM when delayed auth is not enabled
155167
BufferedCipherPublisher plaintextPublisher = new BufferedCipherPublisher(ciphertextPublisher,
156168
getObjectResponse.contentLength(), materials, iv, _bufferSize);

src/main/java/software/amazon/encryption/s3/internal/MultipartUploadObjectPipeline.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUpload
7474
.overrideConfiguration(API_NAME_INTERCEPTOR)
7575
.build();
7676

77+
//= specification/s3-encryption/client.md#api-operations
78+
//= type=implication
79+
//# If implemented, CreateMultipartUpload MUST initiate a multipart upload.
7780
CreateMultipartUploadResponse response = _s3AsyncClient.createMultipartUpload(request).join();
7881

7982
MultipartUploadMaterials mpuMaterials = MultipartUploadMaterials.builder()
@@ -133,13 +136,21 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ
133136
throw new S3EncryptionClientException("No client-side information available on upload ID " + uploadId);
134137
}
135138
final UploadPartResponse response;
136-
// Checks the parts are uploaded in series
139+
//= specification/s3-encryption/client.md#api-operations
140+
//= type=implication
141+
//# Each part MUST be encrypted in sequence.
137142
materials.beginPartUpload(actualRequest.partNumber(), partContentLength);
143+
//= specification/s3-encryption/client.md#api-operations
144+
//= type=implication
145+
//# Each part MUST be encrypted using the same cipher instance for each part.
138146
Cipher cipher = materials.getCipher(materials.getIv());
139147

140148
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
141149

142150
try {
151+
//= specification/s3-encryption/client.md#api-operations
152+
//= type=implication
153+
//# UploadPart MUST encrypt each part.
143154
final AsyncRequestBody cipherAsyncRequestBody = new CipherAsyncRequestBody(
144155
AsyncRequestBody.fromInputStream(
145156
requestBody.contentStreamProvider().newStream(),
@@ -159,6 +170,8 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ
159170
}
160171
// Ensures parts are not retried to avoid corrupting ciphertext
161172
AsyncRequestBody noRetryBody = new NoRetriesAsyncRequestBody(cipherAsyncRequestBody);
173+
//= specification/s3-encryption/client.md#api-operations
174+
//= type=implication
162175
response = _s3AsyncClient.uploadPart(actualRequest, noRetryBody).join();
163176
} finally {
164177
materials.endPartUpload();
@@ -187,6 +200,9 @@ public CompleteMultipartUploadResponse completeMultipartUpload(CompleteMultipart
187200
.overrideConfiguration(API_NAME_INTERCEPTOR)
188201
.build();
189202

203+
//= specification/s3-encryption/client.md#api-operations
204+
//= type=implication
205+
//# CompleteMultipartUpload MUST complete the multipart upload.
190206
CompleteMultipartUploadResponse response = _s3AsyncClient.completeMultipartUpload(actualRequest).join();
191207

192208
_multipartUploadMaterials.remove(uploadId);
@@ -198,6 +214,9 @@ public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadReq
198214
AbortMultipartUploadRequest actualRequest = request.toBuilder()
199215
.overrideConfiguration(API_NAME_INTERCEPTOR)
200216
.build();
217+
//= specification/s3-encryption/client.md#api-operations
218+
//= type=implication
219+
//# AbortMultipartUpload MUST abort the multipart upload.
201220
return _s3AsyncClient.abortMultipartUpload(actualRequest).join();
202221
}
203222

src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
66
import software.amazon.encryption.s3.S3EncryptionClientException;
77

8+
import javax.crypto.SecretKey;
89
import java.nio.charset.StandardCharsets;
910
import java.security.GeneralSecurityException;
1011
import java.security.SecureRandom;
1112
import java.util.ArrayList;
1213
import java.util.List;
1314
import java.util.Map;
14-
import javax.crypto.SecretKey;
1515

1616
/**
1717
* This serves as the base class for all the keyrings in the S3 encryption client.
@@ -115,7 +115,13 @@ public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List<E
115115
throw new S3EncryptionClientException("The keyring does not support the object's key wrapping algorithm: " + keyProviderInfo);
116116
}
117117

118+
//= specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
119+
//= type=implication
120+
//# When enabled, the S3EC MUST be able to decrypt objects encrypted with all supported wrapping algorithms (both legacy and fully supported).
118121
if (decryptStrategy.isLegacy() && !_enableLegacyWrappingAlgorithms) {
122+
//= specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
123+
//= type=exception
124+
//# When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy wrapping algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy wrapping algorithm.
119125
throw new S3EncryptionClientException("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: " + keyProviderInfo);
120126
}
121127

src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,9 @@ public void AesCbcV1toV3FailsWhenUnauthencticateModeDisabled() {
836836
final String input = "AesCbcV1toV3";
837837
v1Client.putObject(BUCKET, objectKey, input);
838838

839+
//= specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
840+
//= type=test
841+
//# When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy wrapping algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy wrapping algorithm.
839842
assertThrows(S3EncryptionClientException.class, () -> v3Client.getObjectAsBytes(builder -> builder
840843
.bucket(BUCKET)
841844
.key(objectKey)));
@@ -870,6 +873,9 @@ public void AesCbcV1toV3FailsWhenLegacyKeyringDisabled() {
870873
final String input = "AesCbcV1toV3";
871874
v1Client.putObject(BUCKET, objectKey, input);
872875

876+
//= specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
877+
//= type=test
878+
//# When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy wrapping algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy wrapping algorithm.
873879
assertThrows(S3EncryptionClientException.class, () -> v3Client.getObjectAsBytes(builder -> builder
874880
.bucket(BUCKET)
875881
.key(objectKey)));

src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import software.amazon.encryption.s3.materials.CryptographicMaterialsManager;
4242
import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager;
4343
import software.amazon.encryption.s3.materials.KmsKeyring;
44+
import software.amazon.encryption.s3.materials.PartialRsaKeyPair;
45+
import software.amazon.encryption.s3.materials.RsaKeyring;
4446
import software.amazon.encryption.s3.utils.BoundedInputStream;
4547
import software.amazon.encryption.s3.utils.S3EncryptionClientTestResources;
4648

@@ -98,6 +100,9 @@ public static void setUp() throws NoSuchAlgorithmException {
98100
RSA_KEY_PAIR = keyPairGen.generateKeyPair();
99101
}
100102

103+
//= specification/s3-encryption/client.md#aws-sdk-compatibility
104+
//= type=test
105+
//# The S3EC SHOULD support invoking operations unrelated to client-side encryption e.g. CopyObject as the conventional AWS SDK S3 client would.
101106
@Test
102107
public void copyObjectTransparently() {
103108
final String objectKey = appendTestSuffix("copy-object-from-here");
@@ -161,10 +166,15 @@ public void deleteObjectWithInstructionFileSuccess() {
161166
v3Client.deleteObject(builder -> builder.bucket(BUCKET).key(objectKey));
162167

163168
S3Client s3Client = S3Client.builder().build();
164-
// Assert throw NoSuchKeyException when getObject for objectKey
169+
//= specification/s3-encryption/client.md#api-operations
170+
//= type=test
171+
//# DeleteObject MUST delete the given object key.
165172
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
166173
.bucket(BUCKET)
167174
.key(objectKey)));
175+
//= specification/s3-encryption/client.md#api-operations
176+
//= type=test
177+
//# DeleteObject MUST delete the associated instruction file using the default instruction file suffix.
168178
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
169179
.bucket(BUCKET)
170180
.key(objectKey + ".instruction")));
@@ -208,10 +218,15 @@ public void deleteObjectsWithInstructionFilesSuccess() {
208218
.delete(builder1 -> builder1.objects(objects)));
209219

210220
S3Client s3Client = S3Client.builder().build();
211-
// Assert throw NoSuchKeyException when getObject for any of objectKeys
221+
//= specification/s3-encryption/client.md#api-operations
222+
//= type=test
223+
//# DeleteObjects MUST delete each of the given objects.
212224
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
213225
.bucket(BUCKET)
214226
.key(objectKeys[0])));
227+
//= specification/s3-encryption/client.md#api-operations
228+
//= type=test
229+
//# DeleteObjects MUST delete each of the corresponding instruction files using the default instruction file suffix.
215230
assertThrows(S3Exception.class, () -> s3Client.getObject(builder -> builder
216231
.bucket(BUCKET)
217232
.key(objectKeys[0] + ".instruction")));
@@ -292,6 +307,9 @@ public void getNonExistentObject() {
292307
v3Client.close();
293308
}
294309

310+
//= specification/s3-encryption/client.md#cryptographic-materials
311+
//= type=test
312+
//# The S3EC MUST accept either one CMM or one Keyring instance upon initialization.
295313
@Test
296314
public void s3EncryptionClientWithMultipleKeyringsFails() {
297315
assertThrows(S3EncryptionClientException.class, () -> S3EncryptionClient.builder()
@@ -300,6 +318,22 @@ public void s3EncryptionClientWithMultipleKeyringsFails() {
300318
.build());
301319
}
302320

321+
//= specification/s3-encryption/client.md#cryptographic-materials
322+
//= type=test
323+
//# If both a CMM and a Keyring are provided, the S3EC MUST throw an exception.
324+
@Test
325+
public void s3EncryptionClientWithCMMAndKeyringFails() {
326+
CryptographicMaterialsManager defaultCMM = DefaultCryptoMaterialsManager.builder()
327+
.keyring(RsaKeyring.builder()
328+
.wrappingKeyPair(new PartialRsaKeyPair(RSA_KEY_PAIR))
329+
.build())
330+
.build();
331+
assertThrows(S3EncryptionClientException.class, () -> S3EncryptionClient.builder()
332+
.aesKey(AES_KEY)
333+
.cryptoMaterialsManager(defaultCMM)
334+
.build());
335+
}
336+
303337
@Test
304338
public void s3EncryptionClientWithNoKeyringsFails() {
305339
assertThrows(S3EncryptionClientException.class, () -> S3EncryptionClient.builder()
@@ -440,6 +474,9 @@ public void s3EncryptionClientWithCmmFromKmsKeyIdSucceeds() {
440474
v3Client.close();
441475
}
442476

477+
//= specification/s3-encryption/client.md#wrapped-s3-client-s
478+
//= type=test
479+
//# The S3EC MUST support the option to provide an SDK S3 client instance during its initialization.
443480
@Test
444481
public void s3EncryptionClientWithWrappedS3ClientSucceeds() {
445482
final String objectKey = appendTestSuffix("wrapped-s3-client-with-kms-key-id");
@@ -462,6 +499,9 @@ public void s3EncryptionClientWithWrappedS3ClientSucceeds() {
462499
wrappingClient.close();
463500
}
464501

502+
//= specification/s3-encryption/client.md#wrapped-s3-client-s
503+
//= type=test
504+
//# The S3EC MUST NOT support use of S3EC as the provided S3 client during its initialization; it MUST throw an exception in this case.
465505
/**
466506
* S3EncryptionClient implements S3Client, so it can be passed into the builder as a wrappedClient.
467507
* However, is not a supported use case, and the builder should throw an exception if this happens.
@@ -854,6 +894,9 @@ public void s3EncryptionClientTopLevelCredentialsNullCreds() {
854894
}
855895
}
856896

897+
//= specification/s3-encryption/client.md#inherited-sdk-configuration
898+
//= type=test
899+
//# If the S3EC accepts SDK client configuration, the configuration MUST be applied to all wrapped SDK clients including the KMS client.
857900
@Test
858901
public void s3EncryptionClientTopLevelAlternateCredentials() {
859902
final String objectKey = appendTestSuffix("wrapped-s3-client-with-top-level-credentials");

0 commit comments

Comments
 (0)