diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ECCMode.java b/sdk/src/main/java/io/opentdf/platform/sdk/ECCMode.java index 98d89feb..7eeb2775 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECCMode.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECCMode.java @@ -55,12 +55,6 @@ public byte getECCModeAsByte() { return (byte) value; } - public static int getECDSASignatureStructSize(NanoTDFType.ECCurve curve) { - int keySize = curve.getKeySize(); - return (1 + keySize + 1 + keySize); - } - - @Nonnull public NanoTDFType.ECCurve getCurve() { return NanoTDFType.ECCurve.fromCurveMode(data.curveMode); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/PolicyInfo.java b/sdk/src/main/java/io/opentdf/platform/sdk/PolicyInfo.java index f181a817..d09a8f3c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/PolicyInfo.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/PolicyInfo.java @@ -3,8 +3,8 @@ import java.nio.ByteBuffer; public class PolicyInfo { + private static final int DEFAULT_BINDING_SIZE = 8; private NanoTDFType.PolicyType type; - private boolean hasECDSABinding; private byte[] body; private byte[] binding; @@ -13,7 +13,6 @@ public PolicyInfo() { public PolicyInfo(ByteBuffer buffer, ECCMode eccMode) { this.type = NanoTDFType.PolicyType.values()[buffer.get()]; - this.hasECDSABinding = eccMode.isECDSABindingEnabled(); if (this.type == NanoTDFType.PolicyType.REMOTE_POLICY) { @@ -45,13 +44,40 @@ public PolicyInfo(ByteBuffer buffer, ECCMode eccMode) { } } - int bindingBytesSize = 8; // GMAC length - if (this.hasECDSABinding) { // ECDSA - The size of binding depends on the curve. - bindingBytesSize = ECCMode.getECDSASignatureStructSize(eccMode.getCurve()); + this.binding = readBinding(buffer, eccMode); + } + + static byte[] readBinding(ByteBuffer buffer, ECCMode eccMode) { + byte[] binding; + if (eccMode.isECDSABindingEnabled()) { // ECDSA - The size of binding depends on the curve. + int rSize = getSize(buffer.get(), eccMode.getCurve()); + byte[] rBytes = new byte[rSize]; + buffer.get(rBytes); + int sSize = getSize(buffer.get(), eccMode.getCurve()); + byte[] sBytes = new byte[sSize]; + buffer.get(sBytes); + binding = ByteBuffer.allocate(rSize + sSize + 2) + .put((byte) rSize) + .put(rBytes) + .put((byte) sSize) + .put(sBytes) + .array(); + } else { + binding = new byte[DEFAULT_BINDING_SIZE]; + buffer.get(binding); } - this.binding = new byte[bindingBytesSize]; - buffer.get(this.binding); + return binding; + } + + private static int getSize(byte size, NanoTDFType.ECCurve curve) { + int elementSize = Byte.toUnsignedInt(size); + if (elementSize > curve.getKeySize()) { + throw new SDK.MalformedTDFException( + String.format("Invalid ECDSA binding size. Expected signature components to be at most %d bytes but got (%d) bytes for curve %s.", + curve.getKeySize(), elementSize, curve.getCurveName())); + } + return elementSize; } public int getTotalSize() { @@ -64,7 +90,6 @@ public int getTotalSize() { if (type == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT || type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { - int policySize = body.length; totalSize = (1 + Short.BYTES + body.length + binding.length); } else { throw new RuntimeException("Embedded policy with key access is not supported."); 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 f6009ed6..d1c91fcc 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -166,7 +166,7 @@ public static Manifest readManifest(SeekableByteChannel tdfBytes) throws SDKExce * 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. + * @throws {@link SDKException} if there is an error during decoding. */ public static PolicyObject decodePolicyObject(Manifest manifest) throws SDKException { return Manifest.decodePolicyObject(manifest); @@ -176,6 +176,15 @@ public String getPlatformUrl() { return platformUrl; } + /** + * Indicates that the TDF is malformed in some way + */ + public static class MalformedTDFException extends SDKException { + public MalformedTDFException(String errorMessage) { + super(errorMessage); + } + } + /** * {@link SplitKeyException} is thrown when the SDK encounters an error related to * the inability to reconstruct a split key during decryption. diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/PolicyInfoTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/PolicyInfoTest.java index 8a2c2e31..8df3a0e7 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/PolicyInfoTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/PolicyInfoTest.java @@ -2,6 +2,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.*; class PolicyInfoTest { @@ -58,4 +65,78 @@ void settingAndGettingPolicyBinding() { policyInfo.setPolicyBinding(binding); assertArrayEquals(binding, policyInfo.getPolicyBinding()); } + + + BigInteger getRandomBigInteger(Random rand, int byteLength) { + return new BigInteger((1+rand.nextInt(byteLength-1))*8, rand); + } + + @Test + void testReadingSignatureWithComponentSizes() { + var rand = new Random(); + var curve = NanoTDFType.ECCurve.SECP256R1; + for (var i = 0; i < 100; i++) { + var rBytes = getRandomBigInteger(rand, curve.getKeySize()).toByteArray(); + var sBytes = getRandomBigInteger(rand, curve.getKeySize()) .toByteArray(); + var buffer = ByteBuffer.allocate(rBytes.length + sBytes.length + 2); + buffer.put((byte)rBytes.length); + buffer.put(rBytes); + buffer.put((byte) sBytes.length); + buffer.put(sBytes); + + var originalSig = Arrays.copyOf(buffer.array(), buffer.position()); + + buffer.flip(); + + ECCMode eccMode = new ECCMode(); + eccMode.setECDSABinding(true); + eccMode.setEllipticCurve(curve); + + byte[] signature = PolicyInfo.readBinding(buffer, eccMode); + assertThat(signature).containsExactly(originalSig); + // make sure we read all bytes so that reading continues after us in the TDF + assertThat(buffer.position()).isEqualTo(buffer.capacity()); + } + } + + @Test + void testParsingTooBigSignatureComponents() { + { + var rand = new Random(); + var curve = NanoTDFType.ECCurve.SECP256R1; + var rBytes = new BigInteger((curve.getKeySize() + 1) * 8, rand).toByteArray(); + var sBytes = getRandomBigInteger(rand, curve.getKeySize()).toByteArray(); + var buffer = ByteBuffer.allocate(rBytes.length + sBytes.length + 2); + buffer.put((byte) rBytes.length); + buffer.put(rBytes); + buffer.put((byte) sBytes.length); + buffer.put(sBytes); + + buffer.flip(); + + ECCMode eccMode = new ECCMode(); + eccMode.setECDSABinding(true); + eccMode.setEllipticCurve(curve); + assertThrows(SDK.MalformedTDFException.class, () -> PolicyInfo.readBinding(buffer, eccMode)); + } + + { + var rand = new Random(); + var curve = NanoTDFType.ECCurve.SECP256R1; + var rBytes = getRandomBigInteger(rand, curve.getKeySize()).toByteArray(); + var sBytes = new BigInteger((curve.getKeySize() + 1) * 8, rand).toByteArray(); + var buffer = ByteBuffer.allocate(rBytes.length + sBytes.length + 2); + buffer.put((byte) rBytes.length); + buffer.put(rBytes); + buffer.put((byte) sBytes.length); + buffer.put(sBytes); + + buffer.flip(); + + ECCMode eccMode = new ECCMode(); + eccMode.setECDSABinding(true); + eccMode.setEllipticCurve(curve); + assertThrows(SDK.MalformedTDFException.class, () -> PolicyInfo.readBinding(buffer, eccMode)); + } + } } \ No newline at end of file