From d70d463d5681ad4baa8160db6b9825d5119913bf Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 8 Oct 2024 10:58:28 -0400 Subject: [PATCH 1/8] assertion error --- sdk/src/main/java/io/opentdf/platform/sdk/TDF.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 47aaa188..76ec821c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -128,6 +128,12 @@ public TDFReadFailed(String errorMessage) { } } + public static class AssertionException extends RuntimeException { + public AssertionException(String errorMessage, String id) { + super("assertion id: "+ id + "; " + errorMessage); + } + } + public static class EncryptedMetadata { private String ciphertext; private String iv; @@ -689,11 +695,11 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, var encodeSignature = Base64.getEncoder().encodeToString(signature.getBytes()); if (!Objects.equals(hashOfAssertion, hashValues.getAssertionHash())) { - throw new SDKException("assertion hash mismatch"); + throw new AssertionException("assertion hash mismatch", assertion.id); } if (!Objects.equals(encodeSignature, hashValues.getSignature())) { - throw new SDKException("failed integrity check on assertion signature"); + throw new AssertionException("failed integrity check on assertion signature", assertion.id); } } From b5b971ce4d2de9caa3b9f9ba83cf8943f286e1f1 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 8 Oct 2024 11:24:26 -0400 Subject: [PATCH 2/8] create parent tamper class --- cmdline/sensitive.txt | 1 + .../java/io/opentdf/platform/sdk/TDF.java | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 cmdline/sensitive.txt diff --git a/cmdline/sensitive.txt b/cmdline/sensitive.txt new file mode 100644 index 00000000..31e0fce5 --- /dev/null +++ b/cmdline/sensitive.txt @@ -0,0 +1 @@ +helloworld diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 76ec821c..ce5e9c9f 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -104,31 +104,37 @@ public FailedToCreateGMAC(String errorMessage) { } } - public static class NotValidateRootSignature extends RuntimeException { - public NotValidateRootSignature(String errorMessage) { + public static class TDFReadFailed extends RuntimeException { + public TDFReadFailed(String errorMessage) { super(errorMessage); } } - public static class SegmentSizeMismatch extends RuntimeException { - public SegmentSizeMismatch(String errorMessage) { + public static class TamperException extends RuntimeException { + public TamperException(String errorMessage) { super(errorMessage); } } - public static class SegmentSignatureMismatch extends RuntimeException { - public SegmentSignatureMismatch(String errorMessage) { + public static class RootSignatureValidationException extends TamperException { + public RootSignatureValidationException(String errorMessage) { super(errorMessage); } } - public static class TDFReadFailed extends RuntimeException { - public TDFReadFailed(String errorMessage) { + public static class SegmentSizeMismatch extends TamperException { + public SegmentSizeMismatch(String errorMessage) { + super(errorMessage); + } + } + + public static class SegmentSignatureMismatch extends TamperException { + public SegmentSignatureMismatch(String errorMessage) { super(errorMessage); } } - public static class AssertionException extends RuntimeException { + public static class AssertionException extends TamperException { public AssertionException(String errorMessage, String id) { super("assertion id: "+ id + "; " + errorMessage); } @@ -558,7 +564,7 @@ private void fillInPublicKeyInfo(List kasInfoList, SDK.KAS kas) public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVerificationKeys... assertionVerificationKeys) - throws NotValidateRootSignature, SegmentSizeMismatch, + throws RootSignatureValidationException, SegmentSizeMismatch, IOException, FailedToCreateGMAC, JOSEException, ParseException, NoSuchAlgorithmException, DecoderException { TDFReader tdfReader = new TDFReader(tdf); @@ -666,7 +672,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, } if (rootSignature.compareTo(rootSigValue) != 0) { - throw new NotValidateRootSignature("root signature validation failed"); + throw new RootSignatureValidationException("root signature validation failed"); } int segmentSize = manifest.encryptionInformation.integrityInformation.segmentSizeDefault; From a6aeec9b5556343ee4704f3cbc9cbee42188ff41 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 8 Oct 2024 11:25:29 -0400 Subject: [PATCH 3/8] remove file --- cmdline/sensitive.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 cmdline/sensitive.txt diff --git a/cmdline/sensitive.txt b/cmdline/sensitive.txt deleted file mode 100644 index 31e0fce5..00000000 --- a/cmdline/sensitive.txt +++ /dev/null @@ -1 +0,0 @@ -helloworld From 4027d9fa7eb8298f242c341a8da1744346ec2d77 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Wed, 9 Oct 2024 02:03:20 -0400 Subject: [PATCH 4/8] tamper exception and test --- .../io/opentdf/platform/sdk/Manifest.java | 9 ++- .../java/io/opentdf/platform/sdk/TDF.java | 4 +- .../java/io/opentdf/platform/sdk/TDFTest.java | 58 +++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java index 831274e2..4c7fe118 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java @@ -10,6 +10,9 @@ import com.nimbusds.jose.crypto.RSASSAVerifier; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; + +import io.opentdf.platform.sdk.TDF.AssertionException; + import org.apache.commons.codec.binary.Hex; import org.erdtman.jcs.JsonCanonicalizer; @@ -373,7 +376,7 @@ public void sign(final HashValues hashValues, final AssertionConfig.AssertionKey public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey) throws ParseException, JOSEException { if (binding == null) { - throw new SDKException("Binding is null in assertion"); + throw new AssertionException("Binding is null in assertion", this.id); } String signatureString = binding.signature; @@ -383,7 +386,7 @@ public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey) JWSVerifier verifier = createVerifier(assertionKey); if (!signedJWT.verify(verifier)) { - throw new SDKException("Unable to verify assertion signature"); + throw new AssertionException("Unable to verify assertion signature", this.id); } JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); @@ -435,7 +438,7 @@ private JWSVerifier createVerifier(AssertionConfig.AssertionKey assertionKey) th case HS256: return new MACVerifier((byte[]) assertionKey.key); default: - throw new SDKException("Unknown verify key, unable to verify assertion signature"); + throw new AssertionException("Unknown verify key, unable to verify assertion signature", this.id); } } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index ce5e9c9f..0713ed56 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -110,9 +110,9 @@ public TDFReadFailed(String errorMessage) { } } - public static class TamperException extends RuntimeException { + public static class TamperException extends SDKException { public TamperException(String errorMessage) { - super(errorMessage); + super("[tamper detected] "+errorMessage); } } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index 1dcea5ce..1cf38fa7 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -6,6 +6,9 @@ import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; import io.opentdf.platform.sdk.Config.KASInfo; +import io.opentdf.platform.sdk.TDF.AssertionException; +import io.opentdf.platform.sdk.TDF.TamperException; +import io.opentdf.platform.sdk.TDF.Reader; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import org.junit.jupiter.api.BeforeAll; @@ -265,6 +268,61 @@ void testSimpleTDFWithAssertionWithHS256() throws Exception { } } + @Test + void testSimpleTDFWithAssertionWithHS256Failure() throws Exception { + + ListenableFuture resp1 = mock(ListenableFuture.class); + lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenReturn(resp1); + + // var keypair = CryptoUtils.generateRSAKeypair(); + SecureRandom secureRandom = new SecureRandom(); + byte[] key = new byte[32]; + secureRandom.nextBytes(key); + + String assertion1Id = "assertion1"; + var assertionConfig1 = new AssertionConfig(); + assertionConfig1.id = assertion1Id; + assertionConfig1.type = AssertionConfig.Type.BaseAssertion; + assertionConfig1.scope = AssertionConfig.Scope.TrustedDataObj; + assertionConfig1.appliesToState = AssertionConfig.AppliesToState.Unencrypted; + assertionConfig1.statement = new AssertionConfig.Statement(); + assertionConfig1.statement.format = "base64binary"; + assertionConfig1.statement.schema = "text"; + assertionConfig1.statement.value = "ICAgIDxlZGoOkVkaD4="; + assertionConfig1.assertionKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, key); + + Config.TDFConfig config = Config.newTDFConfig( + Config.withAutoconfigure(false), + Config.withKasInformation(getKASInfos()), + Config.withAssertionConfig(assertionConfig1)); + + String plainText = "this is extremely sensitive stuff!!!"; + InputStream plainTextInputStream = new ByteArrayInputStream(plainText.getBytes()); + ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); + + TDF tdf = new TDF(); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + + byte[] notkey = new byte[32]; + secureRandom.nextBytes(notkey); + var assertionVerificationKeys = new Config.AssertionVerificationKeys(); + assertionVerificationKeys.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, + notkey); + + var unwrappedData = new ByteArrayOutputStream(); + Reader reader; + try { + reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, assertionVerificationKeys); + throw new RuntimeException("no tamper error thrown"); + + } catch (TamperException e) { + assertThat(e) + .isInstanceOf(AssertionException.class); + } + } + @Test public void testCreatingTDFWithMultipleSegments() throws Exception { From 78f8089edbcce833e18f689fc942980814146964 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Fri, 11 Oct 2024 11:29:30 -0400 Subject: [PATCH 5/8] update exposed errors --- .../io/opentdf/platform/sdk/KASClient.java | 22 ++++++++++++++++--- .../io/opentdf/platform/sdk/Manifest.java | 4 ++-- .../java/io/opentdf/platform/sdk/TDF.java | 6 +++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 4732865b..1b9b857f 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -9,13 +9,17 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import io.grpc.Status; import io.opentdf.platform.kas.AccessServiceGrpc; import io.opentdf.platform.kas.PublicKeyRequest; import io.opentdf.platform.kas.PublicKeyResponse; import io.opentdf.platform.kas.RewrapRequest; +import io.opentdf.platform.kas.RewrapResponse; import io.opentdf.platform.sdk.Config.KASInfo; import io.opentdf.platform.sdk.nanotdf.ECKeyPair; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; +import io.opentdf.platform.sdk.TDF.KasBadRequestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -177,9 +181,21 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy) { .newBuilder() .setSignedRequestToken(jwt.serialize()) .build(); - var response = getStub(keyAccess.url).rewrap(request); - var wrappedKey = response.getEntityWrappedKey().toByteArray(); - return decryptor.decrypt(wrappedKey); + RewrapResponse response; + try { + response = getStub(keyAccess.url).rewrap(request); + var wrappedKey = response.getEntityWrappedKey().toByteArray(); + return decryptor.decrypt(wrappedKey); + } catch (StatusRuntimeException e) { + if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) { + // 400 Bad Request + throw new KasBadRequestException("rewrap request 400: " + e.toString()); + } else { + // Other errors + throw e; + } + } + } public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL) { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java index 4c7fe118..d5780cb5 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java @@ -386,7 +386,7 @@ public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey) JWSVerifier verifier = createVerifier(assertionKey); if (!signedJWT.verify(verifier)) { - throw new AssertionException("Unable to verify assertion signature", this.id); + throw new SDKException("Unable to verify assertion signature"); } JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); @@ -438,7 +438,7 @@ private JWSVerifier createVerifier(AssertionConfig.AssertionKey assertionKey) th case HS256: return new MACVerifier((byte[]) assertionKey.key); default: - throw new AssertionException("Unknown verify key, unable to verify assertion signature", this.id); + throw new SDKException("Unknown verify key, unable to verify assertion signature"); } } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 0713ed56..6c37e240 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -134,6 +134,12 @@ public SegmentSignatureMismatch(String errorMessage) { } } + public static class KasBadRequestException extends TamperException { + public KasBadRequestException(String errorMessage) { + super(errorMessage); + } + } + public static class AssertionException extends TamperException { public AssertionException(String errorMessage, String id) { super("assertion id: "+ id + "; " + errorMessage); From 35d8b3e0a90485655566d2e6269edff9bedc3d19 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Fri, 11 Oct 2024 11:34:28 -0400 Subject: [PATCH 6/8] update tests --- sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index 1cf38fa7..79859f90 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -6,8 +6,6 @@ import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; import io.opentdf.platform.sdk.Config.KASInfo; -import io.opentdf.platform.sdk.TDF.AssertionException; -import io.opentdf.platform.sdk.TDF.TamperException; import io.opentdf.platform.sdk.TDF.Reader; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; @@ -31,7 +29,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; - import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -315,11 +312,10 @@ void testSimpleTDFWithAssertionWithHS256Failure() throws Exception { Reader reader; try { reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, assertionVerificationKeys); - throw new RuntimeException("no tamper error thrown"); + throw new RuntimeException("assertion verify key error thrown"); - } catch (TamperException e) { - assertThat(e) - .isInstanceOf(AssertionException.class); + } catch (SDKException e) { + assertThat(e).hasMessageContaining("verify"); } } From 16325241920dd4796e796f066f30df6222ab418c Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 29 Oct 2024 15:33:21 -0400 Subject: [PATCH 7/8] fix test --- sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index ae4746b2..acd1fd15 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -375,11 +375,13 @@ void testSimpleTDFWithAssertionWithHS256Failure() throws Exception { var assertionVerificationKeys = new Config.AssertionVerificationKeys(); assertionVerificationKeys.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, notkey); + Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig( + Config.withAssertionVerificationKeys(assertionVerificationKeys)); var unwrappedData = new ByteArrayOutputStream(); Reader reader; try { - reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, assertionVerificationKeys); + reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, readerConfig); throw new RuntimeException("assertion verify key error thrown"); } catch (SDKException e) { From a5e7a200f3ea7a9712db272e0ff96bcb10f37ff1 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy <35498075+elizabethhealy@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:34:49 -0400 Subject: [PATCH 8/8] update throw Co-authored-by: Dave Mihalcik --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 4a3a9392..806d48d0 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -195,10 +195,8 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy) { if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) { // 400 Bad Request throw new KasBadRequestException("rewrap request 400: " + e.toString()); - } else { - // Other errors - throw e; } + throw e; } }