diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 818c5cb..e3146c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,10 @@ jobs: gcloud-auth: ${{ secrets.GCLOUD_AUTH }} env-file-path: .env.integration - name: integration test - run: env $(cat .env.integration) ./test-suites/integrationTest.sh + run: env $(cat .env.integration) ./test-suites/allTest.sh + env: + TENANT_ID: INTEGRATION-TEST-GCP + NEW_TENANT_ID: INTEGRATION-TEST-AWS build_examples: runs-on: ubuntu-22.04 diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/BatchDocumentKeys.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/BatchDocumentKeys.java index 9e6bde6..7f45c64 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/BatchDocumentKeys.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/BatchDocumentKeys.java @@ -1,14 +1,13 @@ package com.ironcorelabs.tenantsecurity.kms.v1; import java.util.Map; - import com.google.api.client.util.Key; /** * A map from a document ID to a either the wrapped or unwrapped version of a documents keys. Also * includes a map of failures if any problems occurred when performing the batch wrap operation. */ -public class BatchDocumentKeys { +public class BatchDocumentKeys extends NullParsingValidator { @Key private Map keys; @@ -22,4 +21,11 @@ public Map getKeys() { public Map getFailures() { return this.failures; } + + @Override + void ensureNoNullsOrThrow() throws IllegalArgumentException { + if (keys == null || failures == null) + throw new IllegalArgumentException( + "Batch response from the Tenant Security Proxy was not valid."); + } } diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeriveKeyResponse.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeriveKeyResponse.java index 5a96573..97435ef 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeriveKeyResponse.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeriveKeyResponse.java @@ -5,7 +5,7 @@ import com.google.api.client.util.Key; import com.ironcorelabs.tenantsecurity.kms.v1.exception.TspServiceException; -public final class DeriveKeyResponse { +public final class DeriveKeyResponse extends NullParsingValidator { @Key private boolean hasPrimaryConfig; @Key @@ -37,4 +37,10 @@ CompletableFuture getDerivedKeys(String secretPath, String derivat } return CompletableFuture.completedFuture(derivedKeys); } + + @Override + void ensureNoNullsOrThrow() throws IllegalArgumentException { + if (derivedKeys == null) + throw new IllegalArgumentException("TSP failed to derive keys."); + } } diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeterministicCryptoUtils.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeterministicCryptoUtils.java index dbee8cb..6715ea3 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeterministicCryptoUtils.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeterministicCryptoUtils.java @@ -106,7 +106,7 @@ static CompletableFuture decryptField( return key; }).thenCompose(key -> decryptBytes(parts.getEncryptedBytes(), key.getDerivedKeyBytes()))) .thenApply(decrypted -> new DeterministicPlaintextField(decrypted, - encryptedField.getDerivationPath(), encryptedField.getSecretPath())); + encryptedField.getSecretPath(), encryptedField.getDerivationPath())); } diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/NullParsingValidator.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/NullParsingValidator.java new file mode 100644 index 0000000..dfa5fe7 --- /dev/null +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/NullParsingValidator.java @@ -0,0 +1,8 @@ +package com.ironcorelabs.tenantsecurity.kms.v1; + +abstract class NullParsingValidator { + /** + * Throws an IllegalArgumentException if any of the fields were parsed as null. + */ + abstract void ensureNoNullsOrThrow() throws IllegalArgumentException; +} diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyedDocumentKey.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyedDocumentKey.java index 1cdfae7..48a1005 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyedDocumentKey.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyedDocumentKey.java @@ -5,11 +5,18 @@ /** * An EDEK made by wrapping an existing encrypted document with a tenant's KMS, in Base64 format. */ -public class RekeyedDocumentKey { +public class RekeyedDocumentKey extends NullParsingValidator { @Key private String edek; public String getEdek() { return this.edek; } + + @Override + void ensureNoNullsOrThrow() throws IllegalArgumentException { + if (edek == null) + throw new IllegalArgumentException( + "Rekeyed document key response from the Tenant Security Proxy was not valid base64."); + } } diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityClient.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityClient.java index 0074447..8bf9d28 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityClient.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityClient.java @@ -189,7 +189,7 @@ public Builder allowInsecureHttp(boolean allow) { } /** - * Construct the TenantSecurityClient fron the builder. + * Construct the TenantSecurityClient from the builder. * * @return The newly constructed TenantSecurityClient. * @throws Exception If the tsp url isn't valid or if HTTPS is required and not provided. diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityRequest.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityRequest.java index e30c5c3..ec88381 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityRequest.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityRequest.java @@ -153,13 +153,42 @@ enum SecretType { * Generic method for making a request to the provided URL with the provided post data. Returns an * instance of the provided generic JSON class or an error message with the provided error. */ - private CompletableFuture makeRequestAndParseFailure(GenericUrl url, - Map postData, Class jsonType, String errorMessage) { + private CompletableFuture makeRequestAndParseFailure( + GenericUrl url, Map postData, Class jsonType, String errorMessage) { return CompletableFuture.supplyAsync(() -> { try { HttpResponse resp = this.getApiRequest(postData, url).execute(); if (resp.isSuccessStatusCode()) { - return resp.parseAs(jsonType); + T parsed = resp.parseAs(jsonType); + parsed.ensureNoNullsOrThrow(); + return parsed; + } + throw parseFailureFromRequest(resp); + } catch (Exception cause) { + if (cause instanceof TenantSecurityException) { + throw new CompletionException(cause); + } else if (cause instanceof IllegalArgumentException) { + throw new CompletionException(new TspServiceException( + TenantSecurityErrorCodes.UNKNOWN_ERROR, 0, errorMessage, cause)); + } + throw new CompletionException(new TspServiceException( + TenantSecurityErrorCodes.UNABLE_TO_MAKE_REQUEST, 0, errorMessage, cause)); + } + }, webRequestExecutor); + } + + /** + * Overload for generic method for making a request to the provided URL with the provided post + * data. Returns a CompletableFuture because it does not try to parse a successful result. + * In the case of an error, it does try to parse the provided error. + */ + private CompletableFuture makeRequestAndParseFailure(GenericUrl url, + Map postData, String errorMessage) { + return CompletableFuture.supplyAsync(() -> { + try { + HttpResponse resp = this.getApiRequest(postData, url).execute(); + if (resp.isSuccessStatusCode()) { + return null; } throw parseFailureFromRequest(resp); } catch (Exception cause) { @@ -261,7 +290,7 @@ CompletableFuture logSecurityEvent(SecurityEvent event, EventMetadata meta String error = String.format( "Unable to make request to Tenant Security Proxy security event endpoint. Endpoint requested: %s", this.securityEventEndpoint); - return this.makeRequestAndParseFailure(this.securityEventEndpoint, postData, Void.class, error); + return this.makeRequestAndParseFailure(this.securityEventEndpoint, postData, error); } /** diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/UnwrappedDocumentKey.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/UnwrappedDocumentKey.java index a2c58b7..def5965 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/UnwrappedDocumentKey.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/UnwrappedDocumentKey.java @@ -7,7 +7,7 @@ /** * Represents the JSON response object from the document/unwrap endpoint which includes the dek. */ -public class UnwrappedDocumentKey { +public class UnwrappedDocumentKey extends NullParsingValidator { @Key private String dek; @@ -19,4 +19,11 @@ public byte[] getDekBytes() { "Unwrap DEK response from the Tenant Security Proxy was not valid base64."); } } + + @Override + void ensureNoNullsOrThrow() throws IllegalArgumentException { + if (dek == null) + throw new IllegalArgumentException( + "Unwrap DEK response from the Tenant Security Proxy was not valid base64."); + } } diff --git a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/WrappedDocumentKey.java b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/WrappedDocumentKey.java index 63f0250..e21601e 100644 --- a/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/WrappedDocumentKey.java +++ b/src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/WrappedDocumentKey.java @@ -7,7 +7,7 @@ /** * A new DEK wrapped by the tenant's KMS and its encrypted form (EDEK), both in Base64 format. */ -public class WrappedDocumentKey { +public class WrappedDocumentKey extends NullParsingValidator { @Key private String dek; @@ -26,4 +26,11 @@ public byte[] getDekBytes() { public String getEdek() { return this.edek; } + + @Override + void ensureNoNullsOrThrow() throws IllegalArgumentException { + if (edek == null || dek == null) + throw new IllegalArgumentException( + "Wrapped document key response from the Tenant Security Proxy was not valid base64."); + } } diff --git a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/JsonParsingTest.java b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/JsonParsingTest.java new file mode 100644 index 0000000..ad6accd --- /dev/null +++ b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/JsonParsingTest.java @@ -0,0 +1,52 @@ +package com.ironcorelabs.tenantsecurity.kms.v1; + +import java.io.StringReader; +import org.testng.annotations.Test; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; + +@Test(groups = {"unit"}) +public class JsonParsingTest { + static JsonObjectParser parser = new JsonObjectParser(new GsonFactory()); + + @Test(expectedExceptions = IllegalArgumentException.class) + void batchKeysErrors() throws Exception { + String json = "{}"; + BatchWrappedDocumentKeys type = parser.parseAndClose( + new StringReader(json), BatchWrappedDocumentKeys.class); + type.ensureNoNullsOrThrow(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + void derivedKeyResponseErrors() throws Exception { + String json = "{}"; + DeriveKeyResponse type = + parser.parseAndClose(new StringReader(json), DeriveKeyResponse.class); + type.ensureNoNullsOrThrow(); + + } + + @Test(expectedExceptions = IllegalArgumentException.class) + void unwrappedDocumentKeyErrors() throws Exception { + String json = "{}"; + UnwrappedDocumentKey type = parser.parseAndClose(new StringReader(json), + UnwrappedDocumentKey.class); + type.ensureNoNullsOrThrow(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + void wrappedDocumentKeyErrors() throws Exception { + String json = "{}"; + WrappedDocumentKey type = + parser.parseAndClose(new StringReader(json), WrappedDocumentKey.class); + type.ensureNoNullsOrThrow(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + void rekeyedDocumentKeyErrors() throws Exception { + String json = "{}"; + RekeyedDocumentKey type = + parser.parseAndClose(new StringReader(json), RekeyedDocumentKey.class); + type.ensureNoNullsOrThrow(); + } +} diff --git a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalBatch.java b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalBatch.java index c2185a5..7621312 100644 --- a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalBatch.java +++ b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalBatch.java @@ -60,7 +60,7 @@ public void batchRoundtrip() throws Exception { TenantSecurityClient client = new TenantSecurityClient.Builder(TestSettings.TSP_ADDRESS + TestSettings.TSP_PORT, - this.API_KEY).build(); + this.API_KEY).allowInsecureHttp(true).build(); int batchSize = 25; int batchRepetitions = 50; diff --git a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalDeterministic.java b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalDeterministic.java index f158131..4cc6b01 100644 --- a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalDeterministic.java +++ b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalDeterministic.java @@ -22,8 +22,8 @@ private void assertEqualBytes(byte[] one, byte[] two) throws Exception { } private DeterministicPlaintextField getRoundtripDataToEncrypt() { - return new DeterministicPlaintextField("Encrypt these bytes!".getBytes(), "deriv_path", - "secret_path"); + return new DeterministicPlaintextField("Encrypt these bytes!".getBytes(), "secret_path", + "deriv_path"); } private CompletableFuture createClient() { diff --git a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalRoundTrip.java b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalRoundTrip.java index 2930ee9..4d8ed4c 100644 --- a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalRoundTrip.java +++ b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/LocalRoundTrip.java @@ -12,7 +12,6 @@ import com.ironcorelabs.tenantsecurity.kms.v1.exception.TenantSecurityException; import com.ironcorelabs.tenantsecurity.logdriver.v1.EventMetadata; import com.ironcorelabs.tenantsecurity.logdriver.v1.UserEvent; -import com.ironcorelabs.tenantsecurity.utils.CompletableFutures; import org.testng.annotations.Test; @Test(groups = {"local-integration"}) diff --git a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/NotPrimaryAndDisabledConfigs.java b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/NotPrimaryAndDisabledConfigs.java index 7ac8037..3ba5214 100644 --- a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/NotPrimaryAndDisabledConfigs.java +++ b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/NotPrimaryAndDisabledConfigs.java @@ -9,7 +9,6 @@ import java.util.concurrent.ExecutionException; import com.ironcorelabs.tenantsecurity.TestUtils; import com.ironcorelabs.tenantsecurity.kms.v1.exception.TenantSecurityException; -import com.ironcorelabs.tenantsecurity.utils.CompletableFutures; import org.testng.annotations.Test; @Test(groups = {"dev-integration"}) diff --git a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyRoundTrip.java b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyRoundTrip.java index 89c7e28..c801c9a 100644 --- a/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyRoundTrip.java +++ b/src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/RekeyRoundTrip.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import com.ironcorelabs.tenantsecurity.kms.v1.exception.TenantSecurityException; import org.testng.annotations.Test; @@ -50,24 +49,21 @@ public void roundtripTest() throws Exception { new DocumentMetadata(tenant_id, "integrationTest", "sample", customFields, "customRayID"); Map documentMap = getRoundtripDataToEncrypt(); - CompletableFuture roundtrip = - TenantSecurityClient.create(tsp_address + tsp_port, api_key).thenCompose(client -> { - try { - return client.encrypt(documentMap, metadata).thenCompose(encryptedDocument -> { - return client.rekeyEdek(encryptedDocument.getEdek(), metadata, new_tenant_id) - .thenCompose(rekeyedEdek -> { - DocumentMetadata newMetadata = new DocumentMetadata(new_tenant_id, - "integrationTest", "sample", customFields, "customRayID"); - EncryptedDocument newDocument = - new EncryptedDocument(encryptedDocument.getEncryptedFields(), rekeyedEdek); - return client.decrypt(newDocument, newMetadata); - }); - }); - } catch (Exception e) { - throw new CompletionException(e); - } - }); try { + TenantSecurityClient client = + new TenantSecurityClient.Builder(tsp_address + tsp_port, api_key).allowInsecureHttp(true) + .build(); + CompletableFuture roundtrip = + client.encrypt(documentMap, metadata).thenCompose(encryptedDocument -> { + return client.rekeyEdek(encryptedDocument.getEdek(), metadata, new_tenant_id) + .thenCompose(rekeyedEdek -> { + DocumentMetadata newMetadata = new DocumentMetadata(new_tenant_id, + "integrationTest", "sample", customFields, "customRayID"); + EncryptedDocument newDocument = + new EncryptedDocument(encryptedDocument.getEncryptedFields(), rekeyedEdek); + return client.decrypt(newDocument, newMetadata); + }); + }); Map decryptedValuesMap = roundtrip.get().getDecryptedFields(); assertEqualBytes(decryptedValuesMap.get("doc1"), documentMap.get("doc1")); assertEqualBytes(decryptedValuesMap.get("doc2"), documentMap.get("doc2")); diff --git a/test-suites/allTest.sh b/test-suites/allTest.sh new file mode 100755 index 0000000..9d01814 --- /dev/null +++ b/test-suites/allTest.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Be sure to set API_KEY, TENANT_ID, and NEW_TENANT_ID env vars +cd "${0%/*/*}" # set the current directory to the one above this script +mvn -Dsuite=test-suites/test-all test diff --git a/test-suites/test-all.xml b/test-suites/test-all.xml new file mode 100644 index 0000000..dc89c6b --- /dev/null +++ b/test-suites/test-all.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +