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..f9434c6b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java @@ -14,13 +14,16 @@ 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; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.interfaces.RSAPublicKey; import java.text.ParseException; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.Objects; @@ -29,6 +32,8 @@ public class Manifest { private static final String kAssertionHash = "assertionHash"; private static final String kAssertionSignature = "assertionSig"; + private static final Gson gson = new Gson(); + @Override public boolean equals(Object o) { if (this == o) @@ -327,7 +332,6 @@ public int hashCode() { } public String hash() throws IOException { - Gson gson = new Gson(); MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-256"); @@ -443,4 +447,17 @@ private JWSVerifier createVerifier(AssertionConfig.AssertionKey assertionKey) th public EncryptionInformation encryptionInformation; public Payload payload; public List assertions = new ArrayList<>(); + + private static Manifest readManifest(Reader reader) { + return gson.fromJson(reader, Manifest.class); + } + + static PolicyObject readPolicyObject(Reader reader) { + var manifest = readManifest(reader); + var policyBase64 = manifest.encryptionInformation.policy; + var policyBytes = Base64.getDecoder().decode(policyBase64); + var policyJson = new String(policyBytes, StandardCharsets.UTF_8); + + return gson.fromJson(policyJson, PolicyObject.class); + } } 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..ba7c5b97 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -15,9 +15,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; @@ -355,6 +357,10 @@ public void readPayload(OutputStream outputStream) throws TDFReadFailed, } } } + + public PolicyObject readPolicyObject() { + return tdfReader.readPolicyObject(); + } } private static String calculateSignature(byte[] data, byte[] secret, Config.IntegrityAlgorithm algorithm) { @@ -411,13 +417,6 @@ public TDFObject createTDF(InputStream payload, StringBuilder aggregateHash = new StringBuilder(); byte[] readBuf = new byte[tdfConfig.defaultSegmentSize]; - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new SDKException("error creating SHA-256 message digest", e); - } - tdfObject.manifest.encryptionInformation.integrityInformation.segments = new ArrayList<>(); long totalSize = 0; boolean finished; @@ -487,7 +486,7 @@ public TDFObject createTDF(InputStream payload, tdfObject.manifest.payload.isEncrypted = true; List signedAssertions = new ArrayList<>(); - ; + for (var assertionConfig : tdfConfig.assertionConfigList) { var assertion = new Manifest.Assertion(); assertion.id = assertionConfig.id; @@ -538,18 +537,6 @@ public List defaultKases(TDFConfig config) { return defk; } - private void fillInPublicKeyInfo(List kasInfoList, SDK.KAS kas) { - for (var kasInfo : kasInfoList) { - if (kasInfo.PublicKey != null && !kasInfo.PublicKey.isBlank()) { - continue; - } - logger.info("no public key provided for KAS at {}, retrieving", kasInfo.URL); - Config.KASInfo getKasInfo = kas.getPublicKey(kasInfo); - kasInfo.PublicKey = getKasInfo.PublicKey; - kasInfo.KID = getKasInfo.KID; - } - } - public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVerificationKeys... assertionVerificationKeys) throws NotValidateRootSignature, SegmentSizeMismatch, @@ -562,8 +549,8 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, String unencryptedMetadata = null; Set knownSplits = new HashSet(); - Set foundSplits = new HashSet(); - ; + Set foundSplits = new HashSet<>(); + Map skippedSplits = new HashMap<>(); boolean mixedSplits = manifest.encryptionInformation.keyAccessObj.size() > 1 && (manifest.encryptionInformation.keyAccessObj.get(0).sid != 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 c83a8bd9..24b05940 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java @@ -1,8 +1,10 @@ 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.stream.Collectors; @@ -12,7 +14,7 @@ public class TDFReader { - private final ZipReader.Entry manifest; + private final ZipReader.Entry manifestEntry; private final InputStream payload; public TDFReader(SeekableByteChannel tdf) throws IOException { @@ -27,14 +29,14 @@ public TDFReader(SeekableByteChannel tdf) throws IOException { throw new IllegalArgumentException("tdf doesn't contain a payload"); } - manifest = entries.get(TDF_MANIFEST_FILE_NAME); + manifestEntry = entries.get(TDF_MANIFEST_FILE_NAME); payload = entries.get(TDF_PAYLOAD_FILE_NAME).getData(); } public String manifest() { var out = new ByteArrayOutputStream(); try { - manifest.getData().transferTo(out); + manifestEntry.getData().transferTo(out); } catch (IOException e) { throw new SDKException("error retrieving manifest from zip file", e); } @@ -54,4 +56,12 @@ int readPayloadBytes(byte[] buf) { } return totalRead; } + + public 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); + } + } } 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..e4fed930 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -24,6 +24,7 @@ import java.util.Base64; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; @@ -120,6 +121,7 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), Config.withMetaData("here is some metadata"), + Config.withDataAttributes("https://example.org/attr/a/value/b", "https://example.org/attr/c/value/d"), Config.withAssertionConfig(assertion1)); String plainText = "this is extremely sensitive stuff!!!"; @@ -144,6 +146,11 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { .withFailMessage("extracted data does not match") .isEqualTo(plainText); assertThat(reader.getMetadata()).isEqualTo("here is some metadata"); + + var policyObject = reader.readPolicyObject(); + assertThat(policyObject).isNotNull(); + assertThat(policyObject.body.dataAttributes.stream().map(a -> a.attribute).collect(Collectors.toList())).asList() + .containsExactlyInAnyOrder("https://example.org/attr/a/value/b", "https://example.org/attr/c/value/d"); } @Test