diff --git a/examples/src/main/java/io/opentdf/platform/GetManifestInformation.java b/examples/src/main/java/io/opentdf/platform/GetManifestInformation.java new file mode 100644 index 00000000..047fd1c3 --- /dev/null +++ b/examples/src/main/java/io/opentdf/platform/GetManifestInformation.java @@ -0,0 +1,28 @@ +package io.opentdf.platform; + +import io.opentdf.platform.sdk.Manifest; +import io.opentdf.platform.sdk.PolicyObject; +import io.opentdf.platform.sdk.SDK; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +public class GetManifestInformation { + public static void main(String[] args) throws IOException { + if (args.length < 1) { + System.err.println("TDF file path must be provided as an argument."); + return; + } + + try (FileChannel tdfStream = FileChannel.open(Path.of(args[0]), StandardOpenOption.READ)) { + Manifest manifest = SDK.readManifest(tdfStream); + System.out.println("loaded a TDF with key access type: " + manifest.encryptionInformation.keyAccessType); + + PolicyObject policyObject = SDK.decodePolicyObject(manifest); + System.out.println("the policy has uuid: " + policyObject.uuid); + } + } +} + 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 d1d37264..9cd94aa1 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java @@ -27,7 +27,6 @@ import org.erdtman.jcs.JsonCanonicalizer; import java.io.IOException; -import java.io.Reader; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -500,8 +499,8 @@ public AssertionConfig.Statement deserialize(JsonElement json, Type typeOfT, Jso public EncryptionInformation encryptionInformation; public Payload payload; public List assertions = new ArrayList<>(); - protected static Manifest readManifest(Reader reader) { - Manifest result = gson.fromJson(reader, Manifest.class); + protected static Manifest readManifest(String manifestJson) { + Manifest result = gson.fromJson(manifestJson, Manifest.class); if (result.assertions == null) { result.assertions = new ArrayList<>(); } @@ -539,8 +538,7 @@ protected static Manifest readManifest(Reader reader) { return result; } - static PolicyObject readPolicyObject(Reader reader) { - var manifest = readManifest(reader); + static PolicyObject decodePolicyObject(Manifest manifest) { var policyBase64 = manifest.encryptionInformation.policy; var policyBytes = Base64.getDecoder().decode(policyBase64); var policyJson = new String(policyBytes, StandardCharsets.UTF_8); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index 9195fca5..74468ecf 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -145,6 +145,30 @@ public static boolean isTDF(SeekableByteChannel channel) { && entries.stream().anyMatch(e -> "0.payload".equals(e.getName())); } + /** + * Reads the {@link Manifest} without decrypting the TDF + * @param tdfBytes A SeekableByteChannel containing the TDF data + * @return The parsed {@link Manifest} object + * @throws SDKException if an SDK-specific error occurs + * @throws IOException if an I/O error occurs + */ + public static Manifest readManifest(SeekableByteChannel tdfBytes) throws SDKException, IOException { + TDFReader reader = new TDFReader(tdfBytes); + String manifestJson = reader.manifest(); + return Manifest.readManifest(manifestJson); + } + + /** + * Decodes a PolicyObject from the manifest. Use {@link SDK#readManifest(SeekableByteChannel)} + * to get the {@link Manifest} from a TDF. + * @param manifest The {@link Manifest} containing the policy. + * @return The decoded {@link PolicyObject}. + * @throws SDKException if there is an error during decoding. + */ + public static PolicyObject decodePolicyObject(Manifest manifest) throws SDKException { + return Manifest.decodePolicyObject(manifest); + } + public String getPlatformUrl() { return platformUrl; } 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 67f743ca..4b605c15 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.StringReader; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.security.*; @@ -607,7 +606,7 @@ Reader loadTDF(SeekableByteChannel tdf, Config.TDFReaderConfig tdfReaderConfig) TDFReader tdfReader = new TDFReader(tdf); String manifestJson = tdfReader.manifest(); // use Manifest.readManifest in order to validate the Manifest input - Manifest manifest = Manifest.readManifest(new StringReader(manifestJson)); + Manifest manifest = Manifest.readManifest(manifestJson); byte[] payloadKey = new byte[GCM_KEY_SIZE]; String unencryptedMetadata = null; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java index 0ef61d59..6e9f32d2 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java @@ -1,10 +1,8 @@ package io.opentdf.platform.sdk; -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -64,10 +62,8 @@ int readPayloadBytes(byte[] buf) { } PolicyObject readPolicyObject() { - try (var reader = new BufferedReader(new InputStreamReader(manifestEntry.getData()))){ - return Manifest.readPolicyObject(reader); - } catch (IOException e) { - throw new SDKException("error reading policy object", e); - } + String manifestJson = manifest(); + Manifest manifest = Manifest.readManifest(manifestJson); + return Manifest.decodePolicyObject(manifest); } } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ManifestTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ManifestTest.java index 98ade257..220ca6d1 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ManifestTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ManifestTest.java @@ -4,8 +4,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.StringReader; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -62,7 +61,7 @@ void testManifestMarshalAndUnMarshal() { " }\n" + "}"; - Manifest manifest = Manifest.readManifest(new StringReader(kManifestJsonFromTDF)); + Manifest manifest = Manifest.readManifest(kManifestJsonFromTDF); // Test payload assertEquals(manifest.payload.url, "0.payload"); @@ -85,7 +84,7 @@ void testManifestMarshalAndUnMarshal() { assertEquals(manifest.encryptionInformation.integrityInformation.segments.get(0).segmentSize, 1048576); var serialized = Manifest.toJson(manifest); - var deserializedAgain = Manifest.readManifest(new StringReader(serialized)); + var deserializedAgain = Manifest.readManifest(serialized); assertEquals(manifest, deserializedAgain, "something changed when we deserialized -> serialized -> deserialized"); } @@ -140,7 +139,7 @@ void testAssertionNull() { " \"assertions\": null\n"+ "}"; - Manifest manifest = Manifest.readManifest(new StringReader(kManifestJsonFromTDF)); + Manifest manifest = Manifest.readManifest(kManifestJsonFromTDF); // Test payload for sanity check assertEquals(manifest.payload.url, "0.payload"); @@ -155,7 +154,8 @@ void testReadingManifestWithObjectStatementValue() throws IOException { final Manifest manifest; try (var mStream = getClass().getResourceAsStream("/io.opentdf.platform.sdk.TestData/manifest-with-object-statement-value.json")) { assert mStream != null; - manifest = Manifest.readManifest(new InputStreamReader(mStream)) ; + var manifestJson = new String(mStream.readAllBytes(), StandardCharsets.UTF_8); + manifest = Manifest.readManifest(manifestJson); } assertThat(manifest.assertions).hasSize(2); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKTest.java index d8518ec1..5b70848d 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKTest.java @@ -77,6 +77,21 @@ public SeekableByteChannel truncate(long size) { assertThat(SDK.isTDF(chan)).isFalse(); } + @Test + void testExaminingManifest() throws IOException { + try (var tdfStream = SDKTest.class.getClassLoader().getResourceAsStream("sample.txt.tdf")) { + assertThat(tdfStream) + .withFailMessage("sample.txt.tdf not found in classpath") + .isNotNull(); + var manifest = SDK.readManifest(new SeekableInMemoryByteChannel(tdfStream.readAllBytes())); + assertThat(manifest).isNotNull(); + assertThat(manifest.encryptionInformation.integrityInformation.encryptedSegmentSizeDefault) + .isEqualTo(1048604); + var policyObject = SDK.decodePolicyObject(manifest); + assertThat(policyObject.uuid).isEqualTo("98bb8a81-5217-4a31-8852-932d29d71aac"); + } + } + @Test void testReadingRandomBytes() { var tdf = new byte[2023];