From f07e54406781657690fb6f558b44619acaa70d66 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 3 Apr 2025 15:19:05 -0400 Subject: [PATCH 01/14] add version class --- .../java/io/opentdf/platform/sdk/Version.java | 66 +++++++++++++++++++ .../io/opentdf/platform/sdk/VersionTest.java | 14 ++++ 2 files changed, 80 insertions(+) create mode 100644 sdk/src/main/java/io/opentdf/platform/sdk/Version.java create mode 100644 sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Version.java b/sdk/src/main/java/io/opentdf/platform/sdk/Version.java new file mode 100644 index 00000000..fb892774 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Version.java @@ -0,0 +1,66 @@ +package io.opentdf.platform.sdk; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Optional; +import java.util.regex.Pattern; + +class Version implements Comparable { + private final int major; + private final Integer minor; + private final Integer patch; + private final String prerelease; + private static final Logger log = LoggerFactory.getLogger(Version.class); + + Pattern SEMVER_PATTERN = Pattern.compile("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$\n"); + + @Override + public String toString() { + return "Version{" + + "major=" + major + + ", minor=" + minor + + ", patch=" + patch + + ", prerelease='" + prerelease + '\'' + + '}'; + } + + public Version(String semver) { + var matcher = SEMVER_PATTERN.matcher(semver); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid version format: " + semver); + } + this.major = Integer.parseInt(matcher.group("major")); + this.minor = Optional.ofNullable(matcher.group("minor")).map(Integer::parseInt).orElse(0); + this.patch = Optional.ofNullable(matcher.group("patch")).map(Integer::parseInt).orElse(0); + this.prerelease = matcher.group("prerelease"); + } + + public Version(int major, @Nullable Integer minor, @Nullable Integer patch, @Nullable String prerelease) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.prerelease = prerelease; + } + + @Override + public int compareTo(@Nonnull Version o) { + if (this.major != o.major) { + return Integer.compare(this.major, o.major); + } + int thisMinor = this.minor == null ? 0 : this.minor; + int otherMinor = o.minor == null ? 0 : o.minor; + if (thisMinor != otherMinor) { + return Integer.compare(thisMinor, otherMinor); + } + int thisPatch = this.patch == null ? 0 : this.patch; + int otherPatch = o.patch == null ? 0 : o.patch; + if (thisPatch != otherPatch) { + return Integer.compare(thisPatch, otherPatch); + } + log.debug("ignoring prerelease version during comparision this = {} o = {}", this, o); + return 0; + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java new file mode 100644 index 00000000..938374dc --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java @@ -0,0 +1,14 @@ +package io.opentdf.platform.sdk; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class VersionTest { + + @Test + public void testParsingVersions() { + assertThat(new Version("1.0.0")).isEqualTo(new Version(1, 0, 0, null)); + } + +} \ No newline at end of file From f3b77ac0aaf9e63c1cd1939b7cfffffcd2536e05 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Fri, 4 Apr 2025 15:17:45 -0400 Subject: [PATCH 02/14] add features for hash compatibility settings --- .../java/io/opentdf/platform/sdk/Config.java | 13 +++++++ .../java/io/opentdf/platform/sdk/TDF.java | 16 ++++++-- .../java/io/opentdf/platform/sdk/Version.java | 38 ++++++++++++------- .../java/io/opentdf/platform/sdk/TDFTest.java | 26 +++++++++++++ .../io/opentdf/platform/sdk/VersionTest.java | 18 ++++++++- 5 files changed, 92 insertions(+), 19 deletions(-) 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 a06daaf0..293e6d05 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -140,6 +140,8 @@ public static class TDFConfig { public String mimeType; public List splitPlan; public KeyType wrappingKeyType; + public boolean hexEncodeSegmentHashes; + public boolean renderVersionInfoInManifest; public TDFConfig() { this.autoconfigure = true; @@ -154,6 +156,8 @@ public TDFConfig() { this.mimeType = DEFAULT_MIME_TYPE; this.splitPlan = new ArrayList<>(); this.wrappingKeyType = KeyType.RSA2048Key; + this.hexEncodeSegmentHashes = false; + this.renderVersionInfoInManifest = true; } } @@ -251,6 +255,15 @@ public static Consumer withAutoconfigure(boolean enable) { }; } + public static Consumer withTargetMode(String targetVersion) { + Version version = new Version(targetVersion); + return (TDFConfig config) -> { + var legacyTDF = version.compareTo(new Version("4.3.0")) < 0; + config.renderVersionInfoInManifest = !legacyTDF; + config.hexEncodeSegmentHashes = legacyTDF; + }; + } + public static Consumer WithWrappingKeyAlg(KeyType keyType) { return (TDFConfig config) -> config.wrappingKeyType = keyType; } 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 5862f00e..2b528eb0 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -13,7 +13,6 @@ import io.opentdf.platform.sdk.nanotdf.ECKeyPair; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; -import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.jce.interfaces.ECPublicKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -222,7 +221,7 @@ PolicyObject createPolicyObject(List attributes private static final Base64.Encoder encoder = Base64.getEncoder(); private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { - manifest.tdfVersion = TDF_VERSION; + manifest.tdfVersion = tdfConfig.renderVersionInfoInManifest ? TDF_VERSION : null; manifest.encryptionInformation.keyAccessType = kSplitKeyType; manifest.encryptionInformation.keyAccessObj = new ArrayList<>(); @@ -541,6 +540,9 @@ public TDFObject createTDF(InputStream payload, payloadOutput.write(cipherData); segmentSig = calculateSignature(cipherData, tdfObject.payloadKey, tdfConfig.segmentIntegrityAlgorithm); + if (tdfConfig.hexEncodeSegmentHashes) { + segmentSig = Hex.encodeHexString(segmentSig).getBytes(StandardCharsets.UTF_8); + } segmentInfo.hash = Base64.getEncoder().encodeToString(segmentSig); aggregateHash.write(segmentSig); @@ -553,9 +555,15 @@ public TDFObject createTDF(InputStream payload, Manifest.RootSignature rootSignature = new Manifest.RootSignature(); - byte[] rootSig = calculateSignature(aggregateHash.toByteArray(), + byte[] encodedAggregateHash = tdfConfig.hexEncodeSegmentHashes + ? aggregateHash.toString(StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8) + : aggregateHash.toByteArray(); + byte[] rootSig = calculateSignature(encodedAggregateHash, tdfObject.payloadKey, tdfConfig.integrityAlgorithm); - rootSignature.signature = Base64.getEncoder().encodeToString(rootSig); + byte[] encodedRootSig = tdfConfig.hexEncodeSegmentHashes + ? Hex.encodeHexString(rootSig).getBytes(StandardCharsets.UTF_8) + : rootSig; + rootSignature.signature = Base64.getEncoder().encodeToString(encodedRootSig); String alg = kGmacIntegrityAlgorithm; if (tdfConfig.integrityAlgorithm == Config.IntegrityAlgorithm.HS256) { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Version.java b/sdk/src/main/java/io/opentdf/platform/sdk/Version.java index fb892774..064b23bf 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Version.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Version.java @@ -5,17 +5,19 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; class Version implements Comparable { private final int major; - private final Integer minor; - private final Integer patch; + private final int minor; + private final int patch; private final String prerelease; private static final Logger log = LoggerFactory.getLogger(Version.class); + private final String buildmetadata; - Pattern SEMVER_PATTERN = Pattern.compile("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$\n"); + Pattern SEMVER_PATTERN = Pattern.compile("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); @Override public String toString() { @@ -36,13 +38,15 @@ public Version(String semver) { this.minor = Optional.ofNullable(matcher.group("minor")).map(Integer::parseInt).orElse(0); this.patch = Optional.ofNullable(matcher.group("patch")).map(Integer::parseInt).orElse(0); this.prerelease = matcher.group("prerelease"); + this.buildmetadata = matcher.group("buildmetadata"); } - public Version(int major, @Nullable Integer minor, @Nullable Integer patch, @Nullable String prerelease) { + public Version(int major, int minor, int patch, @Nullable String prerelease, @Nullable String buildmetadata) { this.major = major; this.minor = minor; this.patch = patch; this.prerelease = prerelease; + this.buildmetadata = buildmetadata; } @Override @@ -50,17 +54,25 @@ public int compareTo(@Nonnull Version o) { if (this.major != o.major) { return Integer.compare(this.major, o.major); } - int thisMinor = this.minor == null ? 0 : this.minor; - int otherMinor = o.minor == null ? 0 : o.minor; - if (thisMinor != otherMinor) { - return Integer.compare(thisMinor, otherMinor); + if (this.minor != o.minor) { + return Integer.compare(this.minor, o.minor); } - int thisPatch = this.patch == null ? 0 : this.patch; - int otherPatch = o.patch == null ? 0 : o.patch; - if (thisPatch != otherPatch) { - return Integer.compare(thisPatch, otherPatch); + if (this.patch != o.patch) { + return Integer.compare(this.patch, o.patch); } - log.debug("ignoring prerelease version during comparision this = {} o = {}", this, o); + log.debug("ignoring prerelease and buildmetadata during comparision this = {} o = {}", this, o); return 0; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Version version = (Version) o; + return major == version.major && minor == version.minor && patch == version.patch; + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch); + } } 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 0c1ed084..d7a7d65b 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -5,6 +5,7 @@ import io.opentdf.platform.sdk.TDF.Reader; import io.opentdf.platform.sdk.nanotdf.ECKeyPair; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; +import org.apache.commons.codec.DecoderException; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import org.bouncycastle.jce.interfaces.ECPrivateKey; import org.junit.jupiter.api.BeforeAll; @@ -13,15 +14,19 @@ import javax.annotation.Nonnull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.text.ParseException; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.Random; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -492,6 +497,27 @@ public void testCreateTDFWithMimeType() throws Exception { assertThat(reader.getManifest().payload.mimeType).isEqualTo(mimeType); } + @Test + public void legacyTDFRoundTrips() throws DecoderException, IOException, ExecutionException, JOSEException, InterruptedException, ParseException, NoSuchAlgorithmException { + final String mimeType = "application/pdf"; + + Config.TDFConfig config = Config.newTDFConfig( + Config.withAutoconfigure(false), + Config.withKasInformation(getRSAKASInfos()), + Config.withTargetMode("4.2.1"), + Config.withMimeType(mimeType)); + + 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, null); + + var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas); + assertThat(reader.getManifest().payload.mimeType).isEqualTo(mimeType); + } + @Nonnull private static Config.KASInfo[] getKASInfos(Predicate filter) { var kasInfos = new ArrayList(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java index 938374dc..b416d033 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java @@ -2,13 +2,27 @@ import org.junit.jupiter.api.Test; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import java.util.Vector; + +import static org.assertj.core.api.Assertions.assertThat; class VersionTest { @Test public void testParsingVersions() { - assertThat(new Version("1.0.0")).isEqualTo(new Version(1, 0, 0, null)); + assertThat(new Version("1.0.0")).isEqualTo(new Version(1, 0, 0, null, null)); + assertThat(new Version("1.2.1-alpha")).isEqualTo(new Version(1, 2, 1, "alpha", "a build")); + assertThat(new Version("1.2.1-alpha+build.123")).isEqualTo(new Version(1, 2, 1, "beta", "build.1234")); } + @Test + public void testComparingVersions() { + assertThat(new Version("1.0.0")).isLessThan(new Version("1.0.1")); + assertThat(new Version("1.0.1")).isGreaterThan(new Version("1.0.0")); + + assertThat(new Version("500.0.1")).isLessThan(new Version("500.1.1")); + assertThat(new Version("500.1.1")).isGreaterThan(new Version("500.0.1")); + + assertThat(new Version("1.0.1-alpha+thisbuild")).isEqualByComparingTo(new Version("1.0.1-beta+thatbuild")); + } } \ No newline at end of file From f00fbf45c6a08d6cf375025962da61fea137b3aa Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Fri, 4 Apr 2025 15:21:36 -0400 Subject: [PATCH 03/14] fix up test --- sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 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 d7a7d65b..d3ef5f5b 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -33,6 +33,7 @@ import static io.opentdf.platform.sdk.TDF.GLOBAL_KEY_SALT; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class TDFTest { @@ -507,15 +508,20 @@ public void legacyTDFRoundTrips() throws DecoderException, IOException, Executio Config.withTargetMode("4.2.1"), Config.withMimeType(mimeType)); - String plainText = "this is extremely sensitive stuff!!!"; - InputStream plainTextInputStream = new ByteArrayInputStream(plainText.getBytes()); + byte[] data = new byte[129]; + new Random().nextBytes(data); + InputStream plainTextInputStream = new ByteArrayInputStream(data); ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); TDF tdf = new TDF(); tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, null); + var dataOutputStream = new ByteArrayOutputStream(); + var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas); + reader.readPayload(dataOutputStream); assertThat(reader.getManifest().payload.mimeType).isEqualTo(mimeType); + assertArrayEquals(data, dataOutputStream.toByteArray(), "extracted data does not match"); } @Nonnull From ce1bc6970d525aceaf7b556364a6832289f92e52 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Fri, 4 Apr 2025 15:30:17 -0400 Subject: [PATCH 04/14] add comment --- sdk/src/main/java/io/opentdf/platform/sdk/Config.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 293e6d05..b4782270 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -255,8 +255,11 @@ public static Consumer withAutoconfigure(boolean enable) { }; } + // specify a version for the TDF. Versions less that 4.3.0 will add a layer of + // hex encoding to their segment hashes and will not include version informration + // in their manifests. public static Consumer withTargetMode(String targetVersion) { - Version version = new Version(targetVersion); + Version version = new Version(targetVersion == null ? "0.0.0" : targetVersion); return (TDFConfig config) -> { var legacyTDF = version.compareTo(new Version("4.3.0")) < 0; config.renderVersionInfoInManifest = !legacyTDF; From dd95db15158d107bad60108194877380fd8a954a Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Fri, 4 Apr 2025 15:34:43 -0400 Subject: [PATCH 05/14] add option to command line --- cmdline/src/main/java/io/opentdf/platform/Command.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmdline/src/main/java/io/opentdf/platform/Command.java b/cmdline/src/main/java/io/opentdf/platform/Command.java index a739c600..30f267cb 100644 --- a/cmdline/src/main/java/io/opentdf/platform/Command.java +++ b/cmdline/src/main/java/io/opentdf/platform/Command.java @@ -149,7 +149,8 @@ void encrypt( @Option(names = { "--encap-key-type" }, defaultValue = Option.NULL_VALUE, description = "Preferred key access key wrap algorithm, one of ${COMPLETION-CANDIDATES}") Optional encapKeyType, @Option(names = { "--mime-type" }, defaultValue = Option.NULL_VALUE) Optional mimeType, - @Option(names = { "--with-assertions" }, defaultValue = Option.NULL_VALUE) Optional assertion) + @Option(names = { "--with-assertions" }, defaultValue = Option.NULL_VALUE) Optional assertion, + @Option(names = { "--with-target-mode" }, defaultValue = Option.NULL_VALUE) Optional targetMode) throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException, DecoderException { @@ -201,9 +202,8 @@ void encrypt( configs.add(Config.withAssertionConfig(assertionConfigs)); } - if (attributes.isPresent()) { - configs.add(Config.withDataAttributes(attributes.get().split(","))); - } + attributes.ifPresent(s -> configs.add(Config.withDataAttributes(s.split(",")))); + targetMode.map(Config::withTargetMode).ifPresent(configs::add); var tdfConfig = Config.newTDFConfig(configs.toArray(Consumer[]::new)); try (var in = file.isEmpty() ? new BufferedInputStream(System.in) : new FileInputStream(file.get())) { try (var out = new BufferedOutputStream(System.out)) { From b35f03799ce59063c78c690139c09c933359f44b Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Fri, 4 Apr 2025 15:35:44 -0400 Subject: [PATCH 06/14] Update Config.java --- sdk/src/main/java/io/opentdf/platform/sdk/Config.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b4782270..f5554582 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -255,7 +255,7 @@ public static Consumer withAutoconfigure(boolean enable) { }; } - // specify a version for the TDF. Versions less that 4.3.0 will add a layer of + // specify TDF version for TDF creation to target. Versions less than 4.3.0 will add a layer of // hex encoding to their segment hashes and will not include version informration // in their manifests. public static Consumer withTargetMode(String targetVersion) { @@ -409,4 +409,4 @@ public synchronized void updateHeaderInfo(HeaderInfo headerInfo) { this.notifyAll(); } } -} \ No newline at end of file +} From 51fa2282d3a857dbaee50bef937565cc6a6f629f Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Fri, 4 Apr 2025 15:37:07 -0400 Subject: [PATCH 07/14] Update Config.java --- sdk/src/main/java/io/opentdf/platform/sdk/Config.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f5554582..19879beb 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -255,8 +255,8 @@ public static Consumer withAutoconfigure(boolean enable) { }; } - // specify TDF version for TDF creation to target. Versions less than 4.3.0 will add a layer of - // hex encoding to their segment hashes and will not include version informration + // specify TDF version for TDF creation to target. Versions less than 4.3.0 will add a + // layer of hex encoding to their segment hashes and will not include version information // in their manifests. public static Consumer withTargetMode(String targetVersion) { Version version = new Version(targetVersion == null ? "0.0.0" : targetVersion); From 252c9096ca68efbad7668ae0bac5e7f9831c5b27 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Fri, 4 Apr 2025 15:56:40 -0400 Subject: [PATCH 08/14] Update VersionTest.java --- sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java index b416d033..34a4cba7 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java @@ -25,4 +25,4 @@ public void testComparingVersions() { assertThat(new Version("1.0.1-alpha+thisbuild")).isEqualByComparingTo(new Version("1.0.1-beta+thatbuild")); } -} \ No newline at end of file +} From 084422551f5ffe28dfa863076f04ee822f449698 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 7 Apr 2025 11:55:33 -0400 Subject: [PATCH 09/14] assertions on manifest and hex chars --- .../java/io/opentdf/platform/sdk/TDFTest.java | 21 ++++++++++++++++++- 1 file changed, 20 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 d3ef5f5b..e4031bcb 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -7,7 +7,6 @@ import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import org.apache.commons.codec.DecoderException; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; -import org.bouncycastle.jce.interfaces.ECPrivateKey; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -519,6 +518,22 @@ public void legacyTDFRoundTrips() throws DecoderException, IOException, Executio var dataOutputStream = new ByteArrayOutputStream(); var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas); + var integrityInformation = reader.getManifest().encryptionInformation.integrityInformation; + assertThat(reader.getManifest().tdfVersion).isNull(); + var decodedSignature = Base64.getDecoder().decode(integrityInformation.rootSignature.signature); + for (var b: decodedSignature) { + assertThat(isHexChar(b)) + .withFailMessage("non-hex byte in signature: " + b) + .isTrue(); + } + for (var s: integrityInformation.segments) { + var decodedSegmentSignature = Base64.getDecoder().decode(s.hash); + for (var b: decodedSegmentSignature) { + assertThat(isHexChar(b)) + .withFailMessage("non-hex byte in segment signature: " + b) + .isTrue(); + } + } reader.readPayload(dataOutputStream); assertThat(reader.getManifest().payload.mimeType).isEqualTo(mimeType); assertArrayEquals(data, dataOutputStream.toByteArray(), "extracted data does not match"); @@ -547,4 +562,8 @@ private static Config.KASInfo[] getRSAKASInfos() { private static Config.KASInfo[] getECKASInfos() { return getKASInfos(i -> i % 2 != 0); } + + private static boolean isHexChar(byte b) { + return (b >= 'a' && b <= 'f') || (b >= '0' && b <= '9'); + } } From afa676eaf3385e3ebeea56118d542c2cccc4cb84 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 7 Apr 2025 12:00:37 -0400 Subject: [PATCH 10/14] rename and config test --- .../main/java/io/opentdf/platform/sdk/Config.java | 6 +++--- sdk/src/main/java/io/opentdf/platform/sdk/TDF.java | 6 +++--- .../java/io/opentdf/platform/sdk/ConfigTest.java | 12 ++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) 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 19879beb..3e46649d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -140,7 +140,7 @@ public static class TDFConfig { public String mimeType; public List splitPlan; public KeyType wrappingKeyType; - public boolean hexEncodeSegmentHashes; + public boolean hexEncodeRootAndSegmentHashes; public boolean renderVersionInfoInManifest; public TDFConfig() { @@ -156,7 +156,7 @@ public TDFConfig() { this.mimeType = DEFAULT_MIME_TYPE; this.splitPlan = new ArrayList<>(); this.wrappingKeyType = KeyType.RSA2048Key; - this.hexEncodeSegmentHashes = false; + this.hexEncodeRootAndSegmentHashes = false; this.renderVersionInfoInManifest = true; } } @@ -263,7 +263,7 @@ public static Consumer withTargetMode(String targetVersion) { return (TDFConfig config) -> { var legacyTDF = version.compareTo(new Version("4.3.0")) < 0; config.renderVersionInfoInManifest = !legacyTDF; - config.hexEncodeSegmentHashes = legacyTDF; + config.hexEncodeRootAndSegmentHashes = legacyTDF; }; } 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 2b528eb0..4bbab02d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -540,7 +540,7 @@ public TDFObject createTDF(InputStream payload, payloadOutput.write(cipherData); segmentSig = calculateSignature(cipherData, tdfObject.payloadKey, tdfConfig.segmentIntegrityAlgorithm); - if (tdfConfig.hexEncodeSegmentHashes) { + if (tdfConfig.hexEncodeRootAndSegmentHashes) { segmentSig = Hex.encodeHexString(segmentSig).getBytes(StandardCharsets.UTF_8); } segmentInfo.hash = Base64.getEncoder().encodeToString(segmentSig); @@ -555,12 +555,12 @@ public TDFObject createTDF(InputStream payload, Manifest.RootSignature rootSignature = new Manifest.RootSignature(); - byte[] encodedAggregateHash = tdfConfig.hexEncodeSegmentHashes + byte[] encodedAggregateHash = tdfConfig.hexEncodeRootAndSegmentHashes ? aggregateHash.toString(StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8) : aggregateHash.toByteArray(); byte[] rootSig = calculateSignature(encodedAggregateHash, tdfObject.payloadKey, tdfConfig.integrityAlgorithm); - byte[] encodedRootSig = tdfConfig.hexEncodeSegmentHashes + byte[] encodedRootSig = tdfConfig.hexEncodeRootAndSegmentHashes ? Hex.encodeHexString(rootSig).getBytes(StandardCharsets.UTF_8) : rootSig; rootSignature.signature = Base64.getEncoder().encodeToString(encodedRootSig); 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 b3bc3f77..93c58198 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -18,6 +20,8 @@ void newTDFConfig_shouldCreateDefaultConfig() { assertEquals(Config.IntegrityAlgorithm.GMAC, config.segmentIntegrityAlgorithm); assertTrue(config.attributes.isEmpty()); assertTrue(config.kasInfoList.isEmpty()); + assertTrue(config.renderVersionInfoInManifest); + assertFalse(config.hexEncodeRootAndSegmentHashes); } @Test @@ -61,6 +65,14 @@ void withSegmentSize_shouldIgnoreSegmentSize() { } } + @Test + void withCompatibilityModeShouldSetFieldsCorrectly() { + Config.TDFConfig config = Config.newTDFConfig(Config.withTargetMode("1.0.1")); + assertThat(config.renderVersionInfoInManifest).isFalse(); + assertThat(config.hexEncodeRootAndSegmentHashes).isTrue(); + } + + @Test void withMimeType_shouldSetMimeType() { final String mimeType = "application/pdf"; From ec3d1a0b2151f507692ad87576952ba5468b188e Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 7 Apr 2025 12:01:37 -0400 Subject: [PATCH 11/14] one more --- .../test/java/io/opentdf/platform/sdk/ConfigTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 93c58198..70527133 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ConfigTest.java @@ -67,9 +67,13 @@ void withSegmentSize_shouldIgnoreSegmentSize() { @Test void withCompatibilityModeShouldSetFieldsCorrectly() { - Config.TDFConfig config = Config.newTDFConfig(Config.withTargetMode("1.0.1")); - assertThat(config.renderVersionInfoInManifest).isFalse(); - assertThat(config.hexEncodeRootAndSegmentHashes).isTrue(); + Config.TDFConfig oldConfig = Config.newTDFConfig(Config.withTargetMode("1.0.1")); + assertThat(oldConfig.renderVersionInfoInManifest).isFalse(); + assertThat(oldConfig.hexEncodeRootAndSegmentHashes).isTrue(); + + Config.TDFConfig newConfig = Config.newTDFConfig(Config.withTargetMode("100.0.1")); + assertThat(newConfig.renderVersionInfoInManifest).isTrue(); + assertThat(newConfig.hexEncodeRootAndSegmentHashes).isFalse(); } From 2715a7175127c86bb66d282a11688b1599355708 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 7 Apr 2025 12:15:56 -0400 Subject: [PATCH 12/14] we do not need this encoding --- sdk/src/main/java/io/opentdf/platform/sdk/TDF.java | 6 +----- 1 file changed, 1 insertion(+), 5 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 4bbab02d..faa082e6 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -555,11 +555,7 @@ public TDFObject createTDF(InputStream payload, Manifest.RootSignature rootSignature = new Manifest.RootSignature(); - byte[] encodedAggregateHash = tdfConfig.hexEncodeRootAndSegmentHashes - ? aggregateHash.toString(StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8) - : aggregateHash.toByteArray(); - byte[] rootSig = calculateSignature(encodedAggregateHash, - tdfObject.payloadKey, tdfConfig.integrityAlgorithm); + byte[] rootSig = calculateSignature(aggregateHash.toByteArray(), tdfObject.payloadKey, tdfConfig.integrityAlgorithm); byte[] encodedRootSig = tdfConfig.hexEncodeRootAndSegmentHashes ? Hex.encodeHexString(rootSig).getBytes(StandardCharsets.UTF_8) : rootSig; From 345c440b9a19fdb910f01d8b76aaf1495d22c6d0 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 7 Apr 2025 12:17:00 -0400 Subject: [PATCH 13/14] code review suggestions --- sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java index 34a4cba7..507e6e4e 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java @@ -12,6 +12,7 @@ class VersionTest { public void testParsingVersions() { assertThat(new Version("1.0.0")).isEqualTo(new Version(1, 0, 0, null, null)); assertThat(new Version("1.2.1-alpha")).isEqualTo(new Version(1, 2, 1, "alpha", "a build")); + // ignore anything but the version assertThat(new Version("1.2.1-alpha+build.123")).isEqualTo(new Version(1, 2, 1, "beta", "build.1234")); } @@ -23,6 +24,7 @@ public void testComparingVersions() { assertThat(new Version("500.0.1")).isLessThan(new Version("500.1.1")); assertThat(new Version("500.1.1")).isGreaterThan(new Version("500.0.1")); + // ignore anything but the version assertThat(new Version("1.0.1-alpha+thisbuild")).isEqualByComparingTo(new Version("1.0.1-beta+thatbuild")); } } From 458626f5e9ae660c4273477508dd9164f1a0c42b Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 7 Apr 2025 12:25:05 -0400 Subject: [PATCH 14/14] update regex so there is no backtracking --- .../java/io/opentdf/platform/sdk/Version.java | 15 ++++++--------- .../java/io/opentdf/platform/sdk/VersionTest.java | 8 +++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Version.java b/sdk/src/main/java/io/opentdf/platform/sdk/Version.java index 064b23bf..f0d5f809 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Version.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Version.java @@ -13,11 +13,10 @@ class Version implements Comparable { private final int major; private final int minor; private final int patch; - private final String prerelease; + private final String prereleaseAndMetadata; private static final Logger log = LoggerFactory.getLogger(Version.class); - private final String buildmetadata; - Pattern SEMVER_PATTERN = Pattern.compile("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + Pattern SEMVER_PATTERN = Pattern.compile("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?\\D.*)?$"); @Override public String toString() { @@ -25,7 +24,7 @@ public String toString() { "major=" + major + ", minor=" + minor + ", patch=" + patch + - ", prerelease='" + prerelease + '\'' + + ", prereleaseAndMetadata='" + prereleaseAndMetadata + '\'' + '}'; } @@ -37,16 +36,14 @@ public Version(String semver) { this.major = Integer.parseInt(matcher.group("major")); this.minor = Optional.ofNullable(matcher.group("minor")).map(Integer::parseInt).orElse(0); this.patch = Optional.ofNullable(matcher.group("patch")).map(Integer::parseInt).orElse(0); - this.prerelease = matcher.group("prerelease"); - this.buildmetadata = matcher.group("buildmetadata"); + this.prereleaseAndMetadata = matcher.group("prereleaseAndMetadata"); } - public Version(int major, int minor, int patch, @Nullable String prerelease, @Nullable String buildmetadata) { + public Version(int major, int minor, int patch, @Nullable String prereleaseAndMetadata) { this.major = major; this.minor = minor; this.patch = patch; - this.prerelease = prerelease; - this.buildmetadata = buildmetadata; + this.prereleaseAndMetadata = prereleaseAndMetadata; } @Override diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java index 507e6e4e..05b88211 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/VersionTest.java @@ -2,18 +2,16 @@ import org.junit.jupiter.api.Test; -import java.util.Vector; - import static org.assertj.core.api.Assertions.assertThat; class VersionTest { @Test public void testParsingVersions() { - assertThat(new Version("1.0.0")).isEqualTo(new Version(1, 0, 0, null, null)); - assertThat(new Version("1.2.1-alpha")).isEqualTo(new Version(1, 2, 1, "alpha", "a build")); + assertThat(new Version("1.0.0")).isEqualTo(new Version(1, 0, 0, null)); + assertThat(new Version("1.2.1-alpha")).isEqualTo(new Version(1, 2, 1, "alpha a build")); // ignore anything but the version - assertThat(new Version("1.2.1-alpha+build.123")).isEqualTo(new Version(1, 2, 1, "beta", "build.1234")); + assertThat(new Version("1.2.1-alpha+build.123")).isEqualTo(new Version(1, 2, 1, "beta build.1234")); } @Test