diff --git a/sdk/fuzz.sh b/sdk/fuzz.sh new file mode 100755 index 00000000..88a803ae --- /dev/null +++ b/sdk/fuzz.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +tests=("fuzzNanoTDF", "fuzzTDF", "fuzzZipRead") +base_seed_dir="src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/" + +for test in "${tests[@]}"; do + seed_dir="${base_seed_dir}${test}" + echo "Running $test fuzzing with seeds from $seed_dir" + mvn verify -P fuzz -Djazzer.testDir=$seed_dir +done diff --git a/sdk/pom.xml b/sdk/pom.xml index 678c78b0..6685f513 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -9,6 +9,10 @@ 0.7.5 jar + + 0.22.1 + https://github.com/CodeIntelligenceTesting/jazzer/releases/download/v${jazzer.version} + @@ -121,6 +125,18 @@ 4.13.2 test + + com.code-intelligence + jazzer-api + ${jazzer.version} + test + + + com.code-intelligence + jazzer-junit + ${jazzer.version} + test + org.apache.commons commons-compress @@ -307,4 +323,110 @@ - \ No newline at end of file + + + + fuzz + + false + + + true + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + download-and-unpack-jazzer + process-test-classes + + + + + + + + + + + + + + + + + + + run + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.4.0 + + + copy-dependencies + process-test-classes + + copy-dependencies + + + ${project.build.directory}/dependency-jars + test + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + run-jazzer-fuzzing + verify + + + + + + + + + + + + + + + + + + + + + + + + run + + + + + + + + + diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java index 7b7d824f..a8b58c4b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -21,6 +21,8 @@ public class Config { public static final int TDF3_KEY_SIZE = 2048; public static final int DEFAULT_SEGMENT_SIZE = 2 * 1024 * 1024; // 2mb + public static final int MAX_SEGMENT_SIZE = DEFAULT_SEGMENT_SIZE * 2; + public static final int MIN_SEGMENT_SIZE = 16 * 1024; // not currently enforced in parsing due to existing payloads in testing public static final String KAS_PUBLIC_KEY_PATH = "/kas_public_key"; public static final String DEFAULT_MIME_TYPE = "application/octet-stream"; public static final int MAX_COLLECTION_ITERATION = (1 << 24) - 1; @@ -228,6 +230,12 @@ public static Consumer withMetaData(String metaData) { } public static Consumer withSegmentSize(int size) { + if (size > MAX_SEGMENT_SIZE) { + throw new IllegalArgumentException("Segment size " + size + " exceeds the maximum " + MAX_SEGMENT_SIZE); + } else if (size < MIN_SEGMENT_SIZE) { + throw new IllegalArgumentException("Segment size " + size + " is under the minimum " + MIN_SEGMENT_SIZE); + } + return (TDFConfig config) -> config.defaultSegmentSize = size; } 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 479d7b0e..3f7fd8e8 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java @@ -260,7 +260,7 @@ static public class Payload { public String url; public String protocol; public String mimeType; - public Boolean isEncrypted; + public boolean isEncrypted; @Override public boolean equals(Object o) { @@ -473,8 +473,41 @@ public Manifest deserialize(JsonElement json, Type typeOfT, JsonDeserializationC } } - private static Manifest readManifest(Reader reader) { - return gson.fromJson(reader, Manifest.class); + protected static Manifest readManifest(Reader reader) { + Manifest result = gson.fromJson(reader, Manifest.class); + if (result == null) { + throw new IllegalArgumentException("Manifest is null"); + } else if (result.payload == null) { + throw new IllegalArgumentException("Manifest with null payload"); + } else if (result.encryptionInformation == null) { + throw new IllegalArgumentException("Manifest with null encryptionInformation"); + } else if (result.encryptionInformation.integrityInformation == null) { + throw new IllegalArgumentException("Manifest with null integrityInformation"); + } else if (result.encryptionInformation.integrityInformation.rootSignature == null) { + throw new IllegalArgumentException("Manifest with null rootSignature"); + } else if (result.encryptionInformation.integrityInformation.rootSignature.algorithm == null + || result.encryptionInformation.integrityInformation.rootSignature.signature == null) { + throw new IllegalArgumentException("Manifest with invalid rootSignature"); + } else if (result.encryptionInformation.integrityInformation.segments == null) { + throw new IllegalArgumentException("Manifest with null segments"); + } else if (result.encryptionInformation.keyAccessObj == null) { + throw new IllegalArgumentException("Manifest with null keyAccessObj"); + } else if (result.encryptionInformation.policy == null) { + throw new IllegalArgumentException("Manifest with null policy"); + } + + for (Manifest.Segment segment : result.encryptionInformation.integrityInformation.segments) { + if (segment == null || segment.hash == null) { + throw new IllegalArgumentException("Invalid integrity segment"); + } + } + for (Manifest.KeyAccess keyAccess : result.encryptionInformation.keyAccessObj) { + if (keyAccess == null) { + throw new IllegalArgumentException("Invalid null KeyAccess in manifest"); + } + } + + return result; } static PolicyObject readPolicyObject(Reader reader) { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java index f893762a..5f92069c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -252,7 +252,7 @@ PolicyObject createPolicyObject(List attributes) { PolicyObject policyObject = new PolicyObject(); policyObject.body = new PolicyObject.Body(); policyObject.uuid = UUID.randomUUID().toString(); - policyObject.body.dataAttributes = new ArrayList<>(); + policyObject.body.dataAttributes = new ArrayList<>(attributes.size()); policyObject.body.dissem = new ArrayList<>(); for (String attribute : attributes) { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 8c6b1804..9f4e2cd9 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -74,7 +74,7 @@ public SDKBuilder sslFactoryFromDirectory(String certsDirPath) throws Exception File certsDir = new File(certsDirPath); File[] certFiles = certsDir.listFiles((dir, name) -> name.endsWith(".pem") || name.endsWith(".crt")); logger.info("Loading certificates from: " + certsDir.getAbsolutePath()); - List certStreams = new ArrayList<>(); + List certStreams = new ArrayList<>(certFiles.length); for (File certFile : certFiles) { certStreams.add(new FileInputStream(certFile)); } 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 124ebe0f..4bbd8008 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.StringReader; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.security.*; @@ -188,7 +189,7 @@ PolicyObject createPolicyObject(List attributes PolicyObject policyObject = new PolicyObject(); policyObject.body = new PolicyObject.Body(); policyObject.uuid = UUID.randomUUID().toString(); - policyObject.body.dataAttributes = new ArrayList<>(); + policyObject.body.dataAttributes = new ArrayList<>(attributes.size()); policyObject.body.dissem = new ArrayList<>(); for (Autoconfigure.AttributeValueFQN attribute : attributes) { @@ -208,7 +209,6 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { PolicyObject policyObject = createPolicyObject(tdfConfig.attributes); String base64PolicyObject = encoder .encodeToString(gson.toJson(policyObject).getBytes(StandardCharsets.UTF_8)); - List symKeys = new ArrayList<>(); Map latestKASInfo = new HashMap<>(); if (tdfConfig.splitPlan == null || tdfConfig.splitPlan.isEmpty()) { // Default split plan: Split keys across all KASes @@ -261,6 +261,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { } } + List symKeys = new ArrayList<>(splitIDs.size()); for (String splitID : splitIDs) { // Symmetric key byte[] symKey = new byte[GCM_KEY_SIZE]; @@ -358,6 +359,10 @@ public void readPayload(OutputStream outputStream) throws TDFReadFailed, MessageDigest digest = MessageDigest.getInstance("SHA-256"); for (Manifest.Segment segment : manifest.encryptionInformation.integrityInformation.segments) { + if (segment.encryptedSegmentSize > Config.MAX_SEGMENT_SIZE) { + throw new IllegalStateException("Segment size " + segment.encryptedSegmentSize + " exceeded limit " + Config.MAX_SEGMENT_SIZE); + } // MIN_SEGMENT_SIZE NOT validated out due to tests needing small segment sizes with existing payloads + byte[] readBuf = new byte[(int) segment.encryptedSegmentSize]; int bytesRead = tdfReader.readPayloadBytes(readBuf); @@ -520,8 +525,7 @@ public TDFObject createTDF(InputStream payload, tdfObject.manifest.payload.url = TDFWriter.TDF_PAYLOAD_FILE_NAME; tdfObject.manifest.payload.isEncrypted = true; - List signedAssertions = new ArrayList<>(); - + List signedAssertions = new ArrayList<>(tdfConfig.assertionConfigList.size()); for (var assertionConfig : tdfConfig.assertionConfigList) { var assertion = new Manifest.Assertion(); assertion.id = assertionConfig.id; @@ -585,7 +589,8 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, TDFReader tdfReader = new TDFReader(tdf); String manifestJson = tdfReader.manifest(); - Manifest manifest = gson.fromJson(manifestJson, Manifest.class); + // use Manifest.readManifest in order to validate the Manifest input + Manifest manifest = Manifest.readManifest(new StringReader(manifestJson)); byte[] payloadKey = new byte[GCM_KEY_SIZE]; String unencryptedMetadata = null; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java b/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java index cb6d5ffd..cf0b5772 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java @@ -3,6 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; @@ -25,17 +26,17 @@ public class ZipReader { public static final int ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE = 20; final ByteBuffer longBuf = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN); - private Long readLong() throws IOException { + private long readLong() throws IOException { longBuf.clear(); if (this.zipChannel.read(longBuf) != 8) { - return null; + throw new InvalidZipException("Expected long value"); } longBuf.flip(); return longBuf.getLong(); } final ByteBuffer intBuf = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); - private Integer readInt() throws IOException { + private Integer readInteger() throws IOException { intBuf.clear(); if (this.zipChannel.read(intBuf) != 4) { return null; @@ -43,13 +44,20 @@ private Integer readInt() throws IOException { intBuf.flip(); return intBuf.getInt(); } + private int readInt() throws IOException { + Integer result = readInteger(); + if (result == null) { + throw new InvalidZipException("Expected int value"); + } + return result.intValue(); + } final ByteBuffer shortBuf = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.LITTLE_ENDIAN); - private Short readShort() throws IOException { + private short readShort() throws IOException { shortBuf.clear(); if (this.zipChannel.read(shortBuf) != 2) { - return null; + throw new InvalidZipException("Expected short value"); } shortBuf.flip(); return shortBuf.getShort(); @@ -79,8 +87,8 @@ CentralDirectoryRecord readEndOfCentralDirectory() throws IOException { while (eoCDRStart >= 0) { zipChannel.position(eoCDRStart); - int signature = readInt(); - if (signature == END_OF_CENTRAL_DIRECTORY_SIGNATURE) { + Integer signature = readInteger(); + if (signature == null || signature == END_OF_CENTRAL_DIRECTORY_SIGNATURE) { if (logger.isDebugEnabled()) { logger.debug("Found end of central directory signature at {}", zipChannel.position() - Integer.BYTES); } @@ -113,8 +121,8 @@ CentralDirectoryRecord readEndOfCentralDirectory() throws IOException { private CentralDirectoryRecord extractZIP64CentralDirectoryInfo() throws IOException { // buffer's position at the start of the Central Directory - int signature = readInt(); - if (signature != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE) { + Integer signature = readInteger(); + if (signature == null || signature != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE) { throw new InvalidZipException("Invalid Zip64 End of Central Directory Record Signature"); } @@ -157,7 +165,8 @@ public String getName() { public InputStream getData() throws IOException { zipChannel.position(offsetToLocalHeader); - if (readInt() != LOCAL_FILE_HEADER_SIGNATURE) { + Integer signature = readInteger(); + if (signature == null || signature != LOCAL_FILE_HEADER_SIGNATURE) { throw new InvalidZipException("Invalid Local Header Signature"); } zipChannel.position(zipChannel.position() @@ -184,8 +193,8 @@ public int read() throws IOException { return -1; } setChannelPosition(); - while (buf.position() != buf.capacity()) { - if (zipChannel.read(buf) < 0) { + while (buf.hasRemaining()) { + if (zipChannel.read(buf) <= 0) { return -1; } } @@ -222,8 +231,8 @@ public int read(byte[] b, int off, int len) throws IOException { } } public Entry readCentralDirectoryFileHeader() throws IOException { - int signature = readInt(); - if (signature != CENTRAL_FILE_HEADER_SIGNATURE) { + Integer signature = readInteger(); + if (signature == null || signature != CENTRAL_FILE_HEADER_SIGNATURE) { throw new InvalidZipException("Invalid Central Directory File Header Signature"); } short versionMadeBy = readShort(); @@ -244,8 +253,10 @@ public Entry readCentralDirectoryFileHeader() throws IOException { long relativeOffsetOfLocalHeader = readInt(); ByteBuffer fileName = ByteBuffer.allocate(fileNameLength); - while (fileName.position() != fileName.capacity()) { - zipChannel.read(fileName); + while (fileName.hasRemaining()) { + if (zipChannel.read(fileName) <= 0) { + throw new EOFException("Unexpected EOF when reading filename of length: " + fileNameLength); + } } // Parse the extra field @@ -277,7 +288,6 @@ public Entry readCentralDirectoryFileHeader() throws IOException { return new Entry(fileName.array(), relativeOffsetOfLocalHeader, uncompressedSize); } - public ZipReader(SeekableByteChannel channel) throws IOException { zipChannel = channel; var centralDirectoryRecord = readEndOfCentralDirectory(); @@ -286,11 +296,11 @@ public ZipReader(SeekableByteChannel channel) throws IOException { entries.add(readCentralDirectoryFileHeader()); } } - + final SeekableByteChannel zipChannel; final ArrayList entries = new ArrayList<>(); public List getEntries() { return entries; } -} \ No newline at end of file +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java index c4b93f34..78b96aea 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java @@ -29,7 +29,8 @@ public PolicyInfo(ByteBuffer buffer, ECCMode eccMode) { byte[] policyLengthBuf = new byte[Short.BYTES]; buffer.get(policyLengthBuf); - short policyLength = ByteBuffer.wrap(policyLengthBuf).getShort(); + // read short value into int to prevent possible overflow resulting in negative length + int policyLength = ByteBuffer.wrap(policyLengthBuf).getShort(); if (this.type == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT || this.type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java index cae23434..b3bc3f77 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; class ConfigTest { @@ -46,8 +47,18 @@ void withMetaData_shouldSetMetaData() { @Test void withSegmentSize_shouldSetSegmentSize() { - Config.TDFConfig config = Config.newTDFConfig(Config.withSegmentSize(1024)); - assertEquals(1024, config.defaultSegmentSize); + Config.TDFConfig config = Config.newTDFConfig(Config.withSegmentSize(Config.MIN_SEGMENT_SIZE)); + assertEquals(Config.MIN_SEGMENT_SIZE, config.defaultSegmentSize); + } + + @Test + void withSegmentSize_shouldIgnoreSegmentSize() { + try { + Config.withSegmentSize(1024); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + // expected + } } @Test diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java b/sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java new file mode 100644 index 00000000..5fcb90fc --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java @@ -0,0 +1,69 @@ +package io.opentdf.platform.sdk; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.junit.FuzzTest; +import com.google.gson.JsonParseException; +import com.nimbusds.jose.JOSEException; + +import io.opentdf.platform.sdk.TDF.FailedToCreateGMAC; +import io.opentdf.platform.sdk.TDF.Reader; + +public class Fuzzing { + private static final String TEST_DURATION = "600s"; + private static final OutputStream IGNORE_OUTPUT_STREAM = new OutputStream() { + @Override + public void write(int b) { + // ignored + } + + @Override + public void write(byte[] b, int off, int len) { + // ignored + } + }; + + @FuzzTest(maxDuration=TEST_DURATION) + public void fuzzNanoTDF(FuzzedDataProvider data) throws IOException { + byte[] fuzzBytes = data.consumeRemainingAsBytes(); + NanoTDF nanoTDF = new NanoTDF(); + nanoTDF.readNanoTDF(ByteBuffer.wrap(fuzzBytes), IGNORE_OUTPUT_STREAM, NanoTDFTest.kas); + } + + @FuzzTest(maxDuration=TEST_DURATION) + public void fuzzTDF(FuzzedDataProvider data) throws FailedToCreateGMAC, NoSuchAlgorithmException, IOException, JOSEException, ParseException, DecoderException { + byte[] fuzzBytes = data.consumeRemainingAsBytes(); + byte[] key = new byte[32]; // use consistent zero key for performance and so fuzz can relate to seed + var assertionVerificationKeys = new Config.AssertionVerificationKeys(); + assertionVerificationKeys.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, key); + Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig( + Config.withAssertionVerificationKeys(assertionVerificationKeys)); + TDF tdf = new TDF(); + + try { + Reader reader = tdf.loadTDF(new SeekableInMemoryByteChannel(fuzzBytes), TDFTest.kas, readerConfig); + + reader.readPayload(IGNORE_OUTPUT_STREAM); + } catch (SDKException | InvalidZipException | JsonParseException | IOException | IllegalArgumentException e) { + // expected failure cases + } + } + + @FuzzTest(maxDuration=TEST_DURATION) + public void fuzzZipRead(FuzzedDataProvider data) { + byte[] fuzzBytes = data.consumeRemainingAsBytes(); + try { + ZipReaderTest.testReadingZipChannel(new SeekableInMemoryByteChannel(fuzzBytes), false); + } catch (InvalidZipException | IllegalArgumentException | JsonParseException | IOException e) { + // cases which are expected with invalid fuzzed inputs + } + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java index 87bae33b..cd4c056d 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java @@ -37,7 +37,7 @@ public class NanoTDFTest { private static final String KID = "r1"; - private static SDK.KAS kas = new SDK.KAS() { + protected static SDK.KAS kas = new SDK.KAS() { @Override public void close() throws Exception { } 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 acd1fd15..326809f3 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -1,11 +1,6 @@ package io.opentdf.platform.sdk; -import com.google.common.util.concurrent.ListenableFuture; - import com.nimbusds.jose.JOSEException; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; -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.Reader; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; @@ -35,13 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class TDFTest { - - @BeforeEach - public void setup() { - attributeGrpcStub = mock(AttributesServiceGrpc.AttributesServiceFutureStub.class); - } - - private static SDK.KAS kas = new SDK.KAS() { + protected static SDK.KAS kas = new SDK.KAS() { @Override public void close() { } @@ -84,8 +73,6 @@ public KASKeyCache getKeyCache() { } }; - AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub; - private static ArrayList keypairs = new ArrayList<>(); @BeforeAll @@ -97,12 +84,6 @@ static void createKeypairs() { @Test void testSimpleTDFEncryptAndDecrypt() throws Exception { - - ListenableFuture resp1 = mock(ListenableFuture.class); - lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) - .thenReturn(resp1); - SecureRandom secureRandom = new SecureRandom(); byte[] key = new byte[32]; secureRandom.nextBytes(key); @@ -130,7 +111,7 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); TDF tdf = new TDF(); - tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); var assertionVerificationKeys = new Config.AssertionVerificationKeys(); assertionVerificationKeys.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, @@ -139,6 +120,7 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { var unwrappedData = new ByteArrayOutputStream(); Config.TDFReaderConfig readerConfig = Config.newTDFReaderConfig( Config.withAssertionVerificationKeys(assertionVerificationKeys)); + var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, readerConfig); assertThat(reader.getManifest().payload.mimeType).isEqualTo("application/octet-stream"); @@ -158,12 +140,6 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { @Test void testSimpleTDFWithAssertionWithRS256() throws Exception { - - ListenableFuture resp1 = mock(ListenableFuture.class); - lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) - .thenReturn(resp1); - String assertion1Id = "assertion1"; var keypair = CryptoUtils.generateRSAKeypair(); var assertionConfig = new AssertionConfig(); @@ -188,7 +164,7 @@ void testSimpleTDFWithAssertionWithRS256() throws Exception { ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); TDF tdf = new TDF(); - tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); var assertionVerificationKeys = new Config.AssertionVerificationKeys(); assertionVerificationKeys.keys.put(assertion1Id, @@ -208,12 +184,6 @@ void testSimpleTDFWithAssertionWithRS256() throws Exception { @Test void testWithAssertionVerificationDisabled() throws Exception { - - ListenableFuture resp1 = mock(ListenableFuture.class); - lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) - .thenReturn(resp1); - String assertion1Id = "assertion1"; var keypair = CryptoUtils.generateRSAKeypair(); var assertionConfig = new AssertionConfig(); @@ -238,7 +208,7 @@ void testWithAssertionVerificationDisabled() throws Exception { ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); TDF tdf = new TDF(); - tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); var assertionVerificationKeys = new Config.AssertionVerificationKeys(); assertionVerificationKeys.keys.put(assertion1Id, @@ -263,12 +233,6 @@ void testWithAssertionVerificationDisabled() throws Exception { } @Test void testSimpleTDFWithAssertionWithHS256() throws Exception { - - ListenableFuture resp1 = mock(ListenableFuture.class); - lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) - .thenReturn(resp1); - String assertion1Id = "assertion1"; var assertionConfig1 = new AssertionConfig(); assertionConfig1.id = assertion1Id; @@ -301,7 +265,7 @@ void testSimpleTDFWithAssertionWithHS256() throws Exception { ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); TDF tdf = new TDF(); - tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); var unwrappedData = new ByteArrayOutputStream(); var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), @@ -335,12 +299,6 @@ 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]; @@ -368,7 +326,7 @@ void testSimpleTDFWithAssertionWithHS256Failure() throws Exception { ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); TDF tdf = new TDF(); - tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); byte[] notkey = new byte[32]; secureRandom.nextBytes(notkey); @@ -391,27 +349,20 @@ void testSimpleTDFWithAssertionWithHS256Failure() throws Exception { @Test public void testCreatingTDFWithMultipleSegments() 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 random = new Random(); Config.TDFConfig config = Config.newTDFConfig( Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), - // use a random segment size that makes sure that we will use multiple segments - Config.withSegmentSize(1 + random.nextInt(20))); + Config.withSegmentSize(Config.MIN_SEGMENT_SIZE)); - // data should be bigger than the largest segment - var data = new byte[21 + random.nextInt(2048)]; + // data should be large enough to have multiple complete and a partial segment + var data = new byte[(int)(Config.MIN_SEGMENT_SIZE * 2.8)]; random.nextBytes(data); var plainTextInputStream = new ByteArrayInputStream(data); var tdfOutputStream = new ByteArrayOutputStream(); var tdf = new TDF(); - tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); var unwrappedData = new ByteArrayOutputStream(); var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas); reader.readPayload(unwrappedData); @@ -424,11 +375,6 @@ public void testCreatingTDFWithMultipleSegments() throws Exception { @Test public void testCreatingTooLargeTDF() 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 random = new Random(); var maxSize = random.nextInt(1024); var numReturned = new AtomicInteger(0); @@ -466,9 +412,9 @@ public void write(byte[] b, int off, int len) { var tdfConfig = Config.newTDFConfig( Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), - Config.withSegmentSize(1 + random.nextInt(128))); + Config.withSegmentSize(Config.MIN_SEGMENT_SIZE)); assertThrows(TDF.DataSizeNotSupported.class, - () -> tdf.createTDF(is, os, tdfConfig, kas, attributeGrpcStub), + () -> tdf.createTDF(is, os, tdfConfig, kas, null), "didn't throw an exception when we created TDF that was too large"); assertThat(numReturned.get()) .withFailMessage("test returned the wrong number of bytes") @@ -477,12 +423,6 @@ public void write(byte[] b, int off, int len) { @Test public void testCreateTDFWithMimeType() throws Exception { - - ListenableFuture resp1 = mock(ListenableFuture.class); - lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) - .thenReturn(resp1); - final String mimeType = "application/pdf"; Config.TDFConfig config = Config.newTDFConfig( @@ -495,7 +435,7 @@ public void testCreateTDFWithMimeType() throws Exception { ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); TDF tdf = new TDF(); - tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); + tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas); assertThat(reader.getManifest().payload.mimeType).isEqualTo(mimeType); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java index 5c47710c..f9819193 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java @@ -1,5 +1,4 @@ package io.opentdf.platform.sdk; -import com.google.gson.Gson; import com.google.gson.GsonBuilder; import io.opentdf.platform.sdk.Manifest.ManifestDeserializer; @@ -14,6 +13,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -32,21 +32,31 @@ public class ZipReaderTest { public void testReadingExistingZip() throws Exception { try (RandomAccessFile raf = new RandomAccessFile("src/test/resources/sample.txt.tdf", "r")) { var fileChannel = raf.getChannel(); - var zipReader = new ZipReader(fileChannel); - var entries = zipReader.getEntries(); + ZipReaderTest.testReadingZipChannel(fileChannel, true); + } + } + + protected static void testReadingZipChannel(SeekableByteChannel fileChannel, boolean test) throws IOException { + var zipReader = new ZipReader(fileChannel); + var entries = zipReader.getEntries(); + if (test) { assertThat(entries.size()).isEqualTo(2); - for (var entry: entries) { - var stream = new ByteArrayOutputStream(); - if (entry.getName().endsWith(".json")) { - entry.getData().transferTo(stream); - var data = stream.toString(StandardCharsets.UTF_8); - var gson = new GsonBuilder() - .registerTypeAdapter(Manifest.class, new ManifestDeserializer()) - .create(); - var map = gson.fromJson(data, Map.class); - + } + for (var entry: entries) { + var stream = new ByteArrayOutputStream(); + if (entry.getName().endsWith(".json")) { + entry.getData().transferTo(stream); + var data = stream.toString(StandardCharsets.UTF_8); + var gson = new GsonBuilder() + .registerTypeAdapter(Manifest.class, new ManifestDeserializer()) + .create(); + var map = gson.fromJson(data, Map.class); + + if (test) { assertThat(map.get("encryptionInformation")).isNotNull(); } + } else if (!test) { + entry.getData().transferTo(stream); // still invoke getData logic } } } diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzNanoTDF/sample.ntdf b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzNanoTDF/sample.ntdf new file mode 100644 index 00000000..7dd6dcbb Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzNanoTDF/sample.ntdf differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-1 b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-1 new file mode 100644 index 00000000..5e0b5c99 Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-1 differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-2 b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-2 new file mode 100644 index 00000000..de7914cd Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-2 differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-3 b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-3 new file mode 100644 index 00000000..bfe79806 Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-3 differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-4 b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-4 new file mode 100644 index 00000000..da6463a9 Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-4 differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-NullKeyAccessObj b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-NullKeyAccessObj new file mode 100644 index 00000000..d55d907b Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-NullKeyAccessObj differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-NullSegment b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-NullSegment new file mode 100644 index 00000000..4537beda Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/crash-InvalidManifest-NullSegment differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/sample.tdf b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/sample.tdf new file mode 100644 index 00000000..cc27081a Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzTDF/sample.tdf differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/crash-NullSignature b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/crash-NullSignature new file mode 100644 index 00000000..24ea0b1c Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/crash-NullSignature differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/crash-f39ad8416aef7cf275f84683aaa0efd15f24272a b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/crash-f39ad8416aef7cf275f84683aaa0efd15f24272a new file mode 100644 index 00000000..2fc1da22 Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/crash-f39ad8416aef7cf275f84683aaa0efd15f24272a differ diff --git a/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/sample.txt.tdf b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/sample.txt.tdf new file mode 100644 index 00000000..2bb81265 Binary files /dev/null and b/sdk/src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/fuzzZipRead/sample.txt.tdf differ