Skip to content

fix(sdk): add methods to examine Manifest and Policy #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 21, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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 {
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);
}
}

8 changes: 3 additions & 5 deletions sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -500,8 +499,8 @@ public AssertionConfig.Statement deserialize(JsonElement json, Type typeOfT, Jso
public EncryptionInformation encryptionInformation;
public Payload payload;
public List<Assertion> 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<>();
}
Expand Down Expand Up @@ -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);
Expand Down
24 changes: 24 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/SDK.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,30 @@ public static boolean isTDF(SeekableByteChannel channel) {
&& entries.stream().anyMatch(e -> "0.payload".equals(e.getName()));
}

/**
* Reads the manifest without decrypting the TDF
* @param tdfBytes A SeekableByteChannel containing the TDF data
* @return
* @throws SDKException
* @throws IOException
*/
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#decodePolicyObject(Manifest)}
* to get the manifest from a TDF.
* @param manifest
* @return
* @throws SDKException
*/
public static PolicyObject decodePolicyObject(Manifest manifest) throws SDKException {
return Manifest.decodePolicyObject(manifest);
}

public String getPlatformUrl() {
return platformUrl;
}
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,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;

Expand Down
8 changes: 3 additions & 5 deletions sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,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);
}
}
9 changes: 5 additions & 4 deletions sdk/src/test/java/io/opentdf/platform/sdk/ManifestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ void testManifestMarshalAndUnMarshal() {
" }\n" +
"}";

Manifest manifest = Manifest.readManifest(new StringReader(kManifestJsonFromTDF));
Manifest manifest = Manifest.readManifest(kManifestJsonFromTDF);

// Test payload
assertEquals(manifest.payload.url, "0.payload");
Expand All @@ -85,7 +85,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");
}
Expand Down Expand Up @@ -140,7 +140,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");
Expand All @@ -155,7 +155,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());
manifest = Manifest.readManifest(manifestJson);
}

assertThat(manifest.assertions).hasSize(2);
Expand Down
15 changes: 15 additions & 0 deletions sdk/src/test/java/io/opentdf/platform/sdk/SDKTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down