diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index d24e2275..051123ae 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -160,54 +160,6 @@ jobs: fi working-directory: cmdline - - name: Encrypt/Decrypt NanoTDF - run: | - echo 'here is some data to encrypt' > data - - java -jar target/cmdline.jar \ - --client-id=opentdf-sdk \ - --client-secret=secret \ - --platform-endpoint=http://localhost:8080 \ - -h\ - encryptnano --kas-url=http://localhost:8080 --attr https://example.com/attr/attr1/value/value1 --policy-type encrypted -f data -m 'here is some metadata' > nano.ntdf - - java -jar target/cmdline.jar \ - --client-id=opentdf-sdk \ - --client-secret=secret \ - --platform-endpoint=http://localhost:8080 \ - -h\ - decryptnano -f nano.ntdf > decrypted - - if ! diff -q data decrypted; then - printf 'decrypted data is incorrect [%s]' "$(< decrypted)" - exit 1 - fi - working-directory: cmdline - - - name: Encrypt/Decrypt NanoTDF with plain text policy type - run: | - echo 'here is some data to encrypt' > data - - java -jar target/cmdline.jar \ - --client-id=opentdf-sdk \ - --client-secret=secret \ - --platform-endpoint=http://localhost:8080 \ - -h\ - encryptnano --kas-url=http://localhost:8080 --attr https://example.com/attr/attr1/value/value1 --policy-type plaintext -f data -m 'here is some metadata' > nanopt.ntdf - - java -jar target/cmdline.jar \ - --client-id=opentdf-sdk \ - --client-secret=secret \ - --platform-endpoint=http://localhost:8080 \ - -h\ - decryptnano -f nanopt.ntdf > decrypted - - if ! diff -q data decrypted; then - printf 'decrypted data is incorrect [%s]' "$(< decrypted)" - exit 1 - fi - working-directory: cmdline - - name: Encrypt/Decrypt Assertions run: | echo "basic assertions" diff --git a/cmdline/src/main/java/io/opentdf/platform/Command.java b/cmdline/src/main/java/io/opentdf/platform/Command.java index 36b37781..c031c4a4 100644 --- a/cmdline/src/main/java/io/opentdf/platform/Command.java +++ b/cmdline/src/main/java/io/opentdf/platform/Command.java @@ -8,7 +8,6 @@ import io.opentdf.platform.sdk.Config; import io.opentdf.platform.sdk.KeyType; import io.opentdf.platform.sdk.Config.AssertionVerificationKeys; -import io.opentdf.platform.sdk.NanoTDFType; import io.opentdf.platform.sdk.SDK; import io.opentdf.platform.sdk.SDKBuilder; import nl.altindag.ssl.SSLFactory; @@ -23,7 +22,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; -import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -323,68 +321,4 @@ void readMetadata(@Option(names = { "-f", "--file" }, required = true) Path tdfP } } } - - @CommandLine.Command(name = "encryptnano") - void createNanoTDF( - @Option(names = { "-f", "--file" }, defaultValue = Option.NULL_VALUE) Optional file, - @Option(names = { "-k", "--kas-url" }, required = true) List kas, - @Option(names = { "-m", "--metadata" }, defaultValue = Option.NULL_VALUE) Optional metadata, - @Option(names = { "--policy-type" }, defaultValue = Option.NULL_VALUE, description = "how to embed the policy, either plaintext or encrypted") Optional policyType, - @Option(names = { "-a", "--attr" }, defaultValue = Option.NULL_VALUE) Optional attributes) - throws Exception { - - var sdk = buildSDK(); - var kasInfos = kas.stream().map(k -> { - var ki = new Config.KASInfo(); - ki.URL = k; - return ki; - }).toArray(Config.KASInfo[]::new); - - List> configs = new ArrayList<>(); - configs.add(Config.withNanoKasInformation(kasInfos)); - attributes.ifPresent(attr -> { - configs.add(Config.witDataAttributes(attr.split(","))); - }); - policyType.ifPresent(mode -> { - switch (mode) { - case "": - case "encrypted": - configs.add(Config.withPolicyType(NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED)); - break; - case "plaintext": - configs.add(Config.withPolicyType(NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT)); - break; - default: - throw new IllegalArgumentException("Unknown policy type: " + mode); - } - }); - - var nanoTDFConfig = Config.newNanoTDFConfig(configs.toArray(Consumer[]::new)); - try (var in = file.isEmpty() ? new BufferedInputStream(System.in) : new FileInputStream(file.get())) { - try (var out = new BufferedOutputStream(System.out)) { - sdk.createNanoTDF(ByteBuffer.wrap(in.readAllBytes()), out, nanoTDFConfig); - } - } - } - - @CommandLine.Command(name = "decryptnano") - void readNanoTDF(@Option(names = { "-f", "--file" }, required = true) Path nanoTDFPath, - @Option(names = { "--kas-allowlist" }, defaultValue = Option.NULL_VALUE) Optional kasAllowlistStr, - @Option(names = { - "--ignore-kas-allowlist" }, defaultValue = Option.NULL_VALUE) Optional ignoreAllowlist) - throws Exception { - var sdk = buildSDK(); - try (var in = FileChannel.open(nanoTDFPath, StandardOpenOption.READ)) { - try (var stdout = new BufferedOutputStream(System.out)) { - ByteBuffer buffer = ByteBuffer.allocate((int) in.size()); - in.read(buffer); - buffer.flip(); - var opts = new ArrayList>(); - ignoreAllowlist.map(Config::WithNanoIgnoreKasAllowlist).ifPresent(opts::add); - kasAllowlistStr.map(s -> s.split(",")).map(Config::WithNanoKasAllowlist).ifPresent(opts::add); - var readerConfig = Config.newNanoTDFReaderConfig(opts.toArray(new Consumer[0])); - sdk.readNanoTDF(buffer, stdout, readerConfig); - } - } - } } diff --git a/examples/src/main/java/io/opentdf/platform/DecryptCollectionExample.java b/examples/src/main/java/io/opentdf/platform/DecryptCollectionExample.java deleted file mode 100644 index 6e9bcb3e..00000000 --- a/examples/src/main/java/io/opentdf/platform/DecryptCollectionExample.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.opentdf.platform; - -import io.opentdf.platform.sdk.Config; -import io.opentdf.platform.sdk.SDK; -import io.opentdf.platform.sdk.SDKBuilder; - -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; - -public class DecryptCollectionExample { - public static void main(String[] args) throws IOException { - String clientId = "opentdf-sdk"; - String clientSecret = "secret"; - String platformEndpoint = "localhost:8080"; - - SDKBuilder builder = new SDKBuilder(); - SDK sdk = builder.platformEndpoint(platformEndpoint) - .clientSecret(clientId, clientSecret).useInsecurePlaintextConnection(true) - .build(); - - var kasInfo = new Config.KASInfo(); - kasInfo.URL = "http://localhost:8080/kas"; - - - // Convert String to InputStream - for (int i = 0; i < 50; i++) { - FileInputStream fis = new FileInputStream(String.format("out/my.%d_ciphertext", i)); - sdk.readNanoTDF(ByteBuffer.wrap(fis.readAllBytes()), System.out, Config.newNanoTDFReaderConfig()); - fis.close(); - } - - } -} diff --git a/examples/src/main/java/io/opentdf/platform/EncryptCollectionExample.java b/examples/src/main/java/io/opentdf/platform/EncryptCollectionExample.java deleted file mode 100644 index 5c060450..00000000 --- a/examples/src/main/java/io/opentdf/platform/EncryptCollectionExample.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.opentdf.platform; - -import io.opentdf.platform.sdk.Config; -import io.opentdf.platform.sdk.SDK; -import io.opentdf.platform.sdk.SDKBuilder; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -public class EncryptCollectionExample { - public static void main(String[] args) throws IOException { - String clientId = "opentdf-sdk"; - String clientSecret = "secret"; - String platformEndpoint = "localhost:8080"; - - SDKBuilder builder = new SDKBuilder(); - SDK sdk = builder.platformEndpoint(platformEndpoint) - .clientSecret(clientId, clientSecret).useInsecurePlaintextConnection(true) - .build(); - - var kasInfo = new Config.KASInfo(); - kasInfo.URL = "http://localhost:8080/kas"; - - var tdfConfig = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfo), - Config.witDataAttributes("https://example.com/attr/attr1/value/value1"), - Config.withCollection() - ); - - String str = "Hello, World!"; - - for (int i = 0; i < 50; i++) { - FileOutputStream fos = new FileOutputStream(String.format("out/my.%d_ciphertext", i)); - sdk.createNanoTDF(ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8)), fos, tdfConfig); - } - } -} diff --git a/sdk/fuzz.sh b/sdk/fuzz.sh index 88a803ae..76fff4be 100755 --- a/sdk/fuzz.sh +++ b/sdk/fuzz.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -tests=("fuzzNanoTDF", "fuzzTDF", "fuzzZipRead") +tests=("fuzzTDF", "fuzzZipRead") base_seed_dir="src/test/resources/io/opentdf/platform/sdk/FuzzingInputs/" for test in "${tests[@]}"; do diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/CollectionStore.java b/sdk/src/main/java/io/opentdf/platform/sdk/CollectionStore.java deleted file mode 100644 index 536aa268..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/CollectionStore.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.opentdf.platform.sdk; - -public interface CollectionStore { - NanoTDF.CollectionKey NO_PRIVATE_KEY = new NanoTDF.CollectionKey(null); - void store(Header header, NanoTDF.CollectionKey key); - NanoTDF.CollectionKey getKey(Header header); - - class NoOpCollectionStore implements CollectionStore { - public NoOpCollectionStore() {} - - @Override - public void store(Header header, NanoTDF.CollectionKey key) {} - - @Override - public NanoTDF.CollectionKey getKey(Header header) { - return NO_PRIVATE_KEY; - } - } -} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/CollectionStoreImpl.java b/sdk/src/main/java/io/opentdf/platform/sdk/CollectionStoreImpl.java deleted file mode 100644 index 282d6087..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/CollectionStoreImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.opentdf.platform.sdk; - -import java.nio.ByteBuffer; -import java.util.LinkedHashMap; -import java.util.Map; - -class CollectionStoreImpl extends LinkedHashMap - implements CollectionStore { - private static final int MAX_SIZE_STORE = 500; - - public CollectionStoreImpl() {} - - public synchronized void store(Header header, NanoTDF.CollectionKey key) { - ByteBuffer buf = ByteBuffer.allocate(header.getTotalSize()); - header.writeIntoBuffer(buf); - super.put(buf, key); - } - - public synchronized NanoTDF.CollectionKey getKey(Header header) { - ByteBuffer buf = ByteBuffer.allocate(header.getTotalSize()); - header.writeIntoBuffer(buf); - return super.getOrDefault(buf, NO_PRIVATE_KEY); - } - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return this.size() > MAX_SIZE_STORE; - } -} 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 ea49d074..f4dbc4f5 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -26,7 +26,6 @@ public class Config { public static final int MIN_SEGMENT_SIZE = 16 * 1024; // not currently enforced in parsing due to existing payloads in testing public static final String KAS_PUBLIC_KEY_PATH = "/kas_public_key"; public static final String DEFAULT_MIME_TYPE = "application/octet-stream"; - public static final int MAX_COLLECTION_ITERATION = (1 << 24) - 1; private static Logger logger = LoggerFactory.getLogger(Config.class); public enum TDFFormat { @@ -342,180 +341,6 @@ public static Consumer withSystemMetadataAssertion() { return (TDFConfig config) -> config.systemMetadataAssertion = true; } - public static class NanoTDFConfig { - public ECCMode eccMode; - public NanoTDFType.Cipher cipher; - public SymmetricAndPayloadConfig config; - public List attributes; - public List kasInfoList; - public CollectionConfig collectionConfig; - public NanoTDFType.PolicyType policyType; - - public NanoTDFConfig() { - this.eccMode = new ECCMode(); - this.eccMode.setEllipticCurve(NanoTDFType.ECCurve.SECP256R1); - this.eccMode.setECDSABinding(false); - - this.cipher = NanoTDFType.Cipher.AES_256_GCM_96_TAG; - - this.config = new SymmetricAndPayloadConfig(); - this.config.setHasSignature(false); - this.config.setSymmetricCipherType(NanoTDFType.Cipher.AES_256_GCM_96_TAG); - - this.attributes = new ArrayList<>(); - this.kasInfoList = new ArrayList<>(); - this.collectionConfig = new CollectionConfig(false); - this.policyType = NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED; - } - } - - public static NanoTDFConfig newNanoTDFConfig(Consumer... options) { - NanoTDFConfig config = new NanoTDFConfig(); - for (Consumer option : options) { - option.accept(config); - } - return config; - } - - public static Consumer withCollection() { - return (NanoTDFConfig config) -> { - config.collectionConfig = new CollectionConfig(true); - }; - } - - public static Consumer witDataAttributes(String... attributes) { - return (NanoTDFConfig config) -> { - Collections.addAll(config.attributes, attributes); - }; - } - - public static Consumer withNanoKasInformation(KASInfo... kasInfoList) { - return (NanoTDFConfig config) -> { - Collections.addAll(config.kasInfoList, kasInfoList); - }; - } - - public static Consumer withEllipticCurve(String curve) { - NanoTDFType.ECCurve ecCurve; - if (curve == null || curve.isEmpty()) { - ecCurve = NanoTDFType.ECCurve.SECP256R1; // default curve - } else if (curve.compareToIgnoreCase(NanoTDFType.ECCurve.SECP384R1.toString()) == 0) { - ecCurve = NanoTDFType.ECCurve.SECP384R1; - } else if (curve.compareToIgnoreCase(NanoTDFType.ECCurve.SECP521R1.toString()) == 0) { - ecCurve = NanoTDFType.ECCurve.SECP521R1; - } else if (curve.compareToIgnoreCase(NanoTDFType.ECCurve.SECP256R1.toString()) == 0) { - ecCurve = NanoTDFType.ECCurve.SECP256R1; - } else { - throw new IllegalArgumentException("The supplied curve string " + curve + " is not recognized."); - } - return (NanoTDFConfig config) -> config.eccMode.setEllipticCurve(ecCurve); - } - - public static Consumer WithECDSAPolicyBinding() { - return (NanoTDFConfig config) -> config.eccMode.setECDSABinding(true); - } - - public static Consumer WithECDSAPolicyBinding(boolean enable) { - return (NanoTDFConfig config) -> config.eccMode.setECDSABinding(enable); - } - - public static Consumer withPolicyType(NanoTDFType.PolicyType policyType) { - return (NanoTDFConfig config) -> config.policyType = policyType; - } - - public static class NanoTDFReaderConfig { - Set kasAllowlist; - boolean ignoreKasAllowlist; - } - - public static NanoTDFReaderConfig newNanoTDFReaderConfig(Consumer... options) { - NanoTDFReaderConfig config = new NanoTDFReaderConfig(); - for (Consumer option : options) { - option.accept(config); - } - return config; - } - - public static Consumer WithNanoKasAllowlist(String... kasAllowlist) { - return (NanoTDFReaderConfig config) -> { - // apply getKasAddress to each kasAllowlist entry and add to hashset - config.kasAllowlist = Arrays.stream(kasAllowlist) - .map(Config::getKasAddress) - .collect(Collectors.toSet()); - }; - } - - public static Consumer withNanoKasAllowlist(Set kasAllowlist) { - return (NanoTDFReaderConfig config) -> { - config.kasAllowlist = kasAllowlist; - }; - } - - public static Consumer WithNanoIgnoreKasAllowlist(boolean ignore) { - return (NanoTDFReaderConfig config) -> config.ignoreKasAllowlist = ignore; - } - - public static class HeaderInfo { - private final Header header; - private final AesGcm key; - private final int iteration; - - public HeaderInfo(Header header,AesGcm key, int iteration) { - this.header = header; - this.key = key; - this.iteration = iteration; - } - - public Header getHeader() { - return header; - } - - public int getIteration() { - return iteration; - } - - public AesGcm getKey() { - return key; - } - } - - public static class CollectionConfig { - private int iterationCounter; - private HeaderInfo headerInfo; - public final boolean useCollection; - private Boolean updatedHeaderInfo; - - - public CollectionConfig(boolean useCollection) { - this.useCollection = useCollection; - } - - public synchronized HeaderInfo getHeaderInfo() throws SDKException { - int iteration = iterationCounter; - iterationCounter = (iterationCounter + 1) % MAX_COLLECTION_ITERATION; - - if (iteration == 0) { - updatedHeaderInfo = false; - return null; - } - while (!updatedHeaderInfo) { - try { - this.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new SDKException("interrupted while waiting for header info", e); - } - } - return new HeaderInfo(headerInfo.getHeader(), headerInfo.getKey(), iteration); - } - - public synchronized void updateHeaderInfo(HeaderInfo headerInfo) { - this.headerInfo = headerInfo; - updatedHeaderInfo = true; - this.notifyAll(); - } - } - public static String getKasAddress(String kasURL) throws SDKException { // Prepend "https://" if no scheme is provided if (!kasURL.contains("://")) { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ECCMode.java b/sdk/src/main/java/io/opentdf/platform/sdk/ECCMode.java deleted file mode 100644 index 7eeb2775..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECCMode.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.opentdf.platform.sdk; - -import javax.annotation.Nonnull; - -public class ECCMode { - private ECCModeStruct data; - - public ECCMode() { - data = new ECCModeStruct(); - data.curveMode = 0x00; // SECP256R1 - data.unused = 0; // fill with zero(unused) - data.useECDSABinding = 0; // enable ECDSA binding - } - - public ECCMode(byte value) { - data = new ECCModeStruct(); - int curveMode = value & 0x07; // first 3 bits - setEllipticCurve(NanoTDFType.ECCurve.fromCurveMode(curveMode)); - int useECDSABinding = (value >> 7) & 0x01; // most significant bit - data.useECDSABinding = useECDSABinding; - } - - public void setECDSABinding(boolean flag) { - if (flag) { - data.useECDSABinding = 1; - } else { - data.useECDSABinding = 0; - } - } - - public void setEllipticCurve(NanoTDFType.ECCurve curve) { - switch (curve) { - case SECP256R1: - data.curveMode = 0x00; - break; - case SECP384R1: - data.curveMode = 0x01; - break; - case SECP521R1: - data.curveMode = 0x02; - break; - case SECP256K1: - throw new RuntimeException("SDK doesn't support 'secp256k1' curve"); - default: - throw new RuntimeException("Unsupported ECC algorithm."); - } - } - - public boolean isECDSABindingEnabled() { - return data.useECDSABinding == 1; - } - - public byte getECCModeAsByte() { - int value = (data.useECDSABinding << 7) | data.curveMode; - return (byte) value; - } - - @Nonnull - public NanoTDFType.ECCurve getCurve() { - return NanoTDFType.ECCurve.fromCurveMode(data.curveMode); - } - - private class ECCModeStruct { - int curveMode; - int unused; - int useECDSABinding; - } -} \ No newline at end of file diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ECCurve.java b/sdk/src/main/java/io/opentdf/platform/sdk/ECCurve.java new file mode 100644 index 00000000..79edbec0 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECCurve.java @@ -0,0 +1,84 @@ +package io.opentdf.platform.sdk; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Optional; + +/** + * Elliptic curve definitions for EC key operations. + */ +public enum ECCurve { + SECP256R1("secp256r1", 32, 33, 0x00), + SECP384R1("secp384r1", 48, 49, 0x01), + SECP521R1("secp521r1", 66, 67, 0x02), + SECP256K1("secp256k1", -1, -1, -1, false); // Note: SECP256K1 is not supported by the SDK + + private static final Logger log = LoggerFactory.getLogger(ECCurve.class); + + private final int curveMode; + private final int keySize; + // compressedPubKeySize is a byte bigger since it encodes the X coordinate plus a byte that tells + // if the Y coordinate is positive or negative + private final int compressedPubKeySize; + private final String curveName; + private final boolean isSupported; + + ECCurve(String curveName, int keySize, int compressedPubKeySize, int curveMode) { + this(curveName, keySize, compressedPubKeySize, curveMode, true); + } + + ECCurve(String curveName, int keySize, int compressedPubKeySize, int curveMode, boolean isSupported) { + this.curveName = curveName; + this.keySize = keySize; + this.compressedPubKeySize = compressedPubKeySize; + this.curveMode = curveMode; + this.isSupported = isSupported; + } + + @Nonnull + public static ECCurve fromCurveMode(int curveMode) { + for (ECCurve curve : ECCurve.values()) { + if (curve.getCurveMode() == curveMode) { + return curve; + } + } + throw new IllegalArgumentException("No enum constant for curve mode: " + curveMode); + } + + public static Optional fromAlgorithm(String platformAlgorithm) { + log.debug("looking for platformAlgorithm [{}]", platformAlgorithm); + if (platformAlgorithm == null) { + return Optional.empty(); + } + return Arrays.stream(ECCurve.values()) + .filter(v -> v.getPlatformCurveName().equals(platformAlgorithm)) + .findAny(); + } + + public int getCurveMode() { + return curveMode; + } + + public int getKeySize() { + return keySize; + } + + public int getCompressedPubKeySize() { + return compressedPubKeySize; + } + + public String getCurveName() { + return curveName; + } + + public String getPlatformCurveName() { + return String.format("ec:%s", curveName); + } + + public boolean isSupported() { + return isSupported; + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java index c434cd75..36110853 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -35,7 +35,7 @@ public class ECKeyPair { Security.addProvider(new BouncyCastleProvider()); } - private final NanoTDFType.ECCurve curve; + private final ECCurve curve; public enum ECAlgorithm { ECDH, @@ -47,10 +47,10 @@ public enum ECAlgorithm { private KeyPair keyPair; public ECKeyPair() { - this(NanoTDFType.ECCurve.SECP256R1, ECAlgorithm.ECDH); + this(ECCurve.SECP256R1, ECAlgorithm.ECDH); } - public ECKeyPair(NanoTDFType.ECCurve curve, ECAlgorithm algorithm) { + public ECKeyPair(ECCurve curve, ECAlgorithm algorithm) { this.curve = Objects.requireNonNull(curve); KeyPairGenerator generator; @@ -83,7 +83,7 @@ public ECPrivateKey getPrivateKey() { return (ECPrivateKey) this.keyPair.getPrivate(); } - NanoTDFType.ECCurve getCurve() { + ECCurve getCurve() { return this.curve; } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Header.java b/sdk/src/main/java/io/opentdf/platform/sdk/Header.java deleted file mode 100644 index 69a93a60..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Header.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.opentdf.platform.sdk; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -public class Header { - private ResourceLocator kasLocator; - private ECCMode eccMode; - private SymmetricAndPayloadConfig payloadConfig; - private PolicyInfo policyInfo; - private byte[] ephemeralKey; - - public Header() { - } - - public Header(ByteBuffer buffer) { - - byte[] magicNumberAndVersion = new byte[3]; - buffer.get(magicNumberAndVersion); - if (!Arrays.equals(magicNumberAndVersion, NanoTDF.MAGIC_NUMBER_AND_VERSION)) { - throw new RuntimeException("Invalid magic number and version in nano tdf."); - } - - this.kasLocator = new ResourceLocator(buffer); - this.eccMode = new ECCMode(buffer.get()); - this.payloadConfig = new SymmetricAndPayloadConfig(buffer.get()); - this.policyInfo = new PolicyInfo(buffer, this.eccMode); - - int compressedPubKeySize = this.eccMode.getCurve().getCompressedPubKeySize(); - this.ephemeralKey = new byte[compressedPubKeySize]; - buffer.get(this.ephemeralKey); - } - - public byte[] getMagicNumberAndVersion() { - return Arrays.copyOf(NanoTDF.MAGIC_NUMBER_AND_VERSION, NanoTDF.MAGIC_NUMBER_AND_VERSION.length); - } - - public void setMagicNumberAndVersion(byte[] magicNumberAndVersion) { - if (magicNumberAndVersion.length != NanoTDF.MAGIC_NUMBER_AND_VERSION.length) { - throw new IllegalArgumentException("Invalid magic number and version length."); - } - if (!Arrays.equals(magicNumberAndVersion, NanoTDF.MAGIC_NUMBER_AND_VERSION)) { - throw new IllegalArgumentException("Invalid magic number and version. It must be {0x4C, 0x31, 0x4C}."); - } - System.arraycopy(magicNumberAndVersion, 0, NanoTDF.MAGIC_NUMBER_AND_VERSION, 0, - NanoTDF.MAGIC_NUMBER_AND_VERSION.length); - } - - public void setKasLocator(ResourceLocator kasLocator) { - this.kasLocator = kasLocator; - } - - public ResourceLocator getKasLocator() { - return kasLocator; - } - - public void setECCMode(ECCMode eccMode) { - this.eccMode = eccMode; - } - - public ECCMode getECCMode() { - return eccMode; - } - - public void setPayloadConfig(SymmetricAndPayloadConfig payloadConfig) { - this.payloadConfig = payloadConfig; - } - - public SymmetricAndPayloadConfig getPayloadConfig() { - return payloadConfig; - } - - public void setPolicyInfo(PolicyInfo policyInfo) { - this.policyInfo = policyInfo; - } - - public PolicyInfo getPolicyInfo() { - return policyInfo; - } - - public void setEphemeralKey(byte[] bytes) { - if (bytes.length < eccMode.getCurve().getCompressedPubKeySize()) { - throw new IllegalArgumentException("Failed to read ephemeral key - invalid buffer size."); - } - ephemeralKey = Arrays.copyOf(bytes, eccMode.getCurve().getCompressedPubKeySize()); - } - - public byte[] getEphemeralKey() { - return Arrays.copyOf(ephemeralKey, ephemeralKey.length); - } - - public int getTotalSize() { - int totalSize = 0; - totalSize += NanoTDF.MAGIC_NUMBER_AND_VERSION.length; - totalSize += kasLocator.getTotalSize(); - totalSize += 1; // size of ECC mode - totalSize += 1; // size of payload config - totalSize += policyInfo.getTotalSize(); - totalSize += ephemeralKey.length; - return totalSize; - } - - public int writeIntoBuffer(ByteBuffer buffer) { - if (buffer.remaining() < getTotalSize()) { - throw new IllegalArgumentException("Failed to write header - invalid buffer size."); - } - - int totalBytesWritten = 0; - buffer.put(NanoTDF.MAGIC_NUMBER_AND_VERSION); - totalBytesWritten += NanoTDF.MAGIC_NUMBER_AND_VERSION.length; - - int kasLocatorSize = kasLocator.writeIntoBuffer(buffer); - totalBytesWritten += kasLocatorSize; - - buffer.put(eccMode.getECCModeAsByte()); - totalBytesWritten += 1; - - buffer.put(payloadConfig.getSymmetricAndPayloadConfigAsByte()); - totalBytesWritten += 1; - - int policyInfoSize = policyInfo.writeIntoBuffer(buffer); - totalBytesWritten += policyInfoSize; - - buffer.put(ephemeralKey); - totalBytesWritten += ephemeralKey.length; - - return totalBytesWritten; - } -} \ No newline at end of file diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 7ab09283..b1c00085 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -2,7 +2,6 @@ import com.connectrpc.Code; import com.connectrpc.ConnectException; -import com.connectrpc.ResponseMessageKt; import com.connectrpc.impl.ProtocolClient; import com.google.gson.Gson; import com.nimbusds.jose.JOSEException; @@ -24,8 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; import java.util.Collections; @@ -34,7 +31,6 @@ import java.util.function.BiFunction; import static io.opentdf.platform.sdk.TDF.GLOBAL_KEY_SALT; -import static java.lang.String.format; /** * A client implementation that communicates with a Key Access Service (KAS). @@ -71,24 +67,6 @@ class KASClient implements SDK.KAS { this.kasKeyCache = new KASKeyCache(); } - @Override - public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { - log.debug("retrieving public key with kasinfo = [{}]", kasInfo); - - var req = PublicKeyRequest.newBuilder().setAlgorithm(curve.getPlatformCurveName()).build(); - var r = getStub(kasInfo.URL).publicKeyBlocking(req, Collections.emptyMap()).execute(); - PublicKeyResponse res; - try { - res = ResponseMessageKt.getOrThrow(r); - } catch (Exception e) { - throw new SDKException("error getting public key", e); - } - var k2 = kasInfo.clone(); - k2.KID = res.getKid(); - k2.PublicKey = res.getPublicKey(); - return k2; - } - @Override public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { Config.KASInfo cachedValue = this.kasKeyCache.get(kasInfo.URL, kasInfo.Algorithm, kasInfo.KID); @@ -135,19 +113,6 @@ static class RewrapRequestBody { Manifest.KeyAccess keyAccess; } - static class NanoTDFKeyAccess { - String header; - String type; - String url; - String protocol; - } - - static class NanoTDFRewrapRequestBody { - String algorithm; - String clientPublicKey; - NanoTDFKeyAccess keyAccess; - } - private static final Gson gson = new Gson(); @Override @@ -223,68 +188,6 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi } } - public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL) { - ECKeyPair keyPair = new ECKeyPair(curve, ECKeyPair.ECAlgorithm.ECDH); - - NanoTDFKeyAccess keyAccess = new NanoTDFKeyAccess(); - keyAccess.header = header; - keyAccess.type = "remote"; - keyAccess.url = kasURL; - keyAccess.protocol = "kas"; - - NanoTDFRewrapRequestBody body = new NanoTDFRewrapRequestBody(); - body.algorithm = format("ec:%s", curve.getCurveName()); - body.clientPublicKey = keyPair.publicKeyInPEMFormat(); - body.keyAccess = keyAccess; - - var requestBody = gson.toJson(body); - var claims = new JWTClaimsSet.Builder() - .claim("requestBody", requestBody) - .issueTime(Date.from(Instant.now())) - .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(1)))) - .build(); - - var jws = new JWSHeader.Builder(JWSAlgorithm.RS256).build(); - SignedJWT jwt = new SignedJWT(jws, claims); - try { - jwt.sign(signer); - } catch (JOSEException e) { - throw new SDKException("error signing KAS request", e); - } - - var req = RewrapRequest - .newBuilder() - .setSignedRequestToken(jwt.serialize()) - .build(); - - var request = getStub(keyAccess.url).rewrapBlocking(req, Collections.emptyMap()).execute(); - RewrapResponse response; - try { - response = RequestHelper.getOrThrow(request); - } catch (ConnectException e) { - throw new SDKException("error rewrapping key", e); - } - var wrappedKey = response.getEntityWrappedKey().toByteArray(); - - // Generate symmetric key - byte[] symmetricKey = ECKeyPair.computeECDHKey(ECKeyPair.publicKeyFromPem(response.getSessionPublicKey()), - keyPair.getPrivateKey()); - - // Generate HKDF key - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new SDKException("error creating SHA-256 message digest", e); - } - byte[] hashOfSalt = digest.digest(NanoTDF.MAGIC_NUMBER_AND_VERSION); - byte[] key = ECKeyPair.calculateHKDF(hashOfSalt, symmetricKey); - - AesGcm gcm = new AesGcm(key); - AesGcm.Encrypted encrypted = new AesGcm.Encrypted(wrappedKey); - return gcm.decrypt(encrypted); - } - private final HashMap stubs = new HashMap<>(); // make this protected so we can test the address normalization logic diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KeyType.java b/sdk/src/main/java/io/opentdf/platform/sdk/KeyType.java index 3f7973a6..373bf33e 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KeyType.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KeyType.java @@ -5,9 +5,9 @@ import javax.annotation.Nonnull; -import static io.opentdf.platform.sdk.NanoTDFType.ECCurve.SECP256R1; -import static io.opentdf.platform.sdk.NanoTDFType.ECCurve.SECP384R1; -import static io.opentdf.platform.sdk.NanoTDFType.ECCurve.SECP521R1; +import static io.opentdf.platform.sdk.ECCurve.SECP256R1; +import static io.opentdf.platform.sdk.ECCurve.SECP384R1; +import static io.opentdf.platform.sdk.ECCurve.SECP521R1; public enum KeyType { RSA2048Key("rsa:2048"), @@ -16,9 +16,9 @@ public enum KeyType { EC521Key("ec:secp521r1", SECP521R1); private final String keyType; - private final NanoTDFType.ECCurve curve; + private final ECCurve curve; - KeyType(String keyType, NanoTDFType.ECCurve ecCurve) { + KeyType(String keyType, ECCurve ecCurve) { this.keyType = keyType; this.curve = ecCurve; } @@ -28,7 +28,7 @@ public enum KeyType { } @Nonnull - NanoTDFType.ECCurve getECCurve() { + ECCurve getECCurve() { if (!isEc()) { throw new IllegalStateException("This key type does not have an ECCurve associated with it: " + keyType); } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java deleted file mode 100644 index 2fe3d70d..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ /dev/null @@ -1,377 +0,0 @@ -package io.opentdf.platform.sdk; - -import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; -import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; -import io.opentdf.platform.sdk.SDK.KasAllowlistException; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.security.*; -import java.util.*; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import io.opentdf.platform.wellknownconfiguration.WellKnownServiceClientInterface; -import org.bouncycastle.jce.interfaces.ECPublicKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The NanoTDF class provides methods to create and read NanoTDF (Tiny Data Format) files. - * The NanoTDF format is intended for securely encrypting small data payloads using elliptic-curve cryptography - * and authenticated encryption. - */ -class NanoTDF { - - public static Logger logger = LoggerFactory.getLogger(NanoTDF.class); - - public static final byte[] MAGIC_NUMBER_AND_VERSION = new byte[] { 0x4C, 0x31, 0x4C }; - private static final int kMaxTDFSize = ((16 * 1024 * 1024) - 3 - 32); // 16 mb - 3(iv) - 32(max auth tag) - private static final int kNanoTDFGMACLength = 8; - private static final int kIvPadding = 9; - private static final int kNanoTDFIvSize = 3; - private static final byte[] kEmptyIV = new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; - private final SDK.Services services; - private final CollectionStore collectionStore; - - NanoTDF(SDK.Services services) { - this(services, new CollectionStore.NoOpCollectionStore()); - } - - NanoTDF(SDK.Services services, CollectionStore collectionStore) { - this.services = services; - this.collectionStore = collectionStore; - } - - public static class NanoTDFMaxSizeLimit extends SDKException { - public NanoTDFMaxSizeLimit(String errorMessage) { - super(errorMessage); - } - } - - public static class UnsupportedNanoTDFFeature extends SDKException { - public UnsupportedNanoTDFFeature(String errorMessage) { - super(errorMessage); - } - } - - public static class InvalidNanoTDFConfig extends SDKException { - public InvalidNanoTDFConfig(String errorMessage) { - super(errorMessage); - } - } - - private static Optional getBaseKey(WellKnownServiceClientInterface wellKnownService) { - return Planner.fetchBaseKey(wellKnownService).map(k -> { - if (!KeyType.fromAlgorithm(k.getPublicKey().getAlgorithm()).isEc()) { - throw new SDKException(String.format("base key is not an EC key, cannot create NanoTDF using a key of type %s", - k.getPublicKey().getAlgorithm())); - } - return Config.KASInfo.fromSimpleKasKey(k); - }); - } - - private Optional getKasInfo(Config.NanoTDFConfig nanoTDFConfig) { - if (nanoTDFConfig.kasInfoList.isEmpty()) { - logger.debug("no kas info provided in NanoTDFConfig"); - return Optional.empty(); - } - return Optional.of(nanoTDFConfig.kasInfoList.get(0)); - } - - private Config.HeaderInfo getHeaderInfo(Config.NanoTDFConfig nanoTDFConfig) throws InvalidNanoTDFConfig, UnsupportedNanoTDFFeature { - if (nanoTDFConfig.collectionConfig.useCollection) { - Config.HeaderInfo headerInfo = nanoTDFConfig.collectionConfig.getHeaderInfo(); - if (headerInfo != null) { - return headerInfo; - } - } - - Gson gson = new GsonBuilder().create(); - Config.KASInfo kasInfo = getKasInfo(nanoTDFConfig) - .or(() -> NanoTDF.getBaseKey(services.wellknown())) - .orElseThrow(() -> new SDKException("no KAS info provided and couldn't get base key, cannot create NanoTDF")); - - String url = kasInfo.URL; - if (kasInfo.PublicKey == null || kasInfo.PublicKey.isEmpty()) { - logger.info("no public key provided for KAS at {}, retrieving", url); - kasInfo = services.kas().getECPublicKey(kasInfo, nanoTDFConfig.eccMode.getCurve()); - } - - // Kas url resource locator - ResourceLocator kasURL = new ResourceLocator(kasInfo.URL, kasInfo.KID); - assert kasURL.getIdentifier() != null : "Identifier in ResourceLocator cannot be null"; - - NanoTDFType.ECCurve ecCurve = getEcCurve(nanoTDFConfig, kasInfo); - ECKeyPair keyPair = new ECKeyPair(ecCurve, ECKeyPair.ECAlgorithm.ECDSA); - - // Generate symmetric key - ECPublicKey kasPublicKey = ECKeyPair.publicKeyFromPem(kasInfo.PublicKey); - byte[] symmetricKey = ECKeyPair.computeECDHKey(kasPublicKey, keyPair.getPrivateKey()); - - // Generate HKDF key - MessageDigest digest = null; - try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new SDKException("error getting instance of SHA-256 digest", e); - } - byte[] hashOfSalt = digest.digest(MAGIC_NUMBER_AND_VERSION); - byte[] key = ECKeyPair.calculateHKDF(hashOfSalt, symmetricKey); - - // Encrypt policy - PolicyObject policyObject = createPolicyObject(nanoTDFConfig.attributes); - String policyObjectAsStr = gson.toJson(policyObject); - - logger.debug("createNanoTDF policy object - {}", policyObjectAsStr); - - // Set policy body and embed in header, either as plain text or encrypted - final byte[] policyBody; - PolicyInfo policyInfo = new PolicyInfo(); - AesGcm gcm = new AesGcm(key); - if (nanoTDFConfig.policyType == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT) { - policyBody = policyObjectAsStr.getBytes(StandardCharsets.UTF_8); - policyInfo.setEmbeddedPlainTextPolicy(policyBody); - } else { - byte[] policyObjectAsBytes = policyObjectAsStr.getBytes(StandardCharsets.UTF_8); - int authTagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(nanoTDFConfig.config.getCipherType()); - byte[] encryptedPolicy = gcm.encrypt(kEmptyIV, authTagSize, policyObjectAsBytes, 0, policyObjectAsBytes.length); - policyBody = Arrays.copyOfRange(encryptedPolicy, kEmptyIV.length, encryptedPolicy.length); - policyInfo.setEmbeddedEncryptedTextPolicy(policyBody); - } - - // Set policy binding (GMAC) - if (nanoTDFConfig.eccMode.isECDSABindingEnabled()) { - throw new UnsupportedNanoTDFFeature("ECDSA policy binding is not support"); - } else { - byte[] hash = digest.digest(policyBody); - byte[] gmac = Arrays.copyOfRange(hash, hash.length - kNanoTDFGMACLength, hash.length); - policyInfo.setPolicyBinding(gmac); - } - - // Create header - byte[] compressedPubKey = keyPair.compressECPublickey(); - Header header = new Header(); - ECCMode mode; - if (nanoTDFConfig.eccMode.getCurve() != keyPair.getCurve()) { - mode = new ECCMode(nanoTDFConfig.eccMode.getECCModeAsByte()); - mode.setEllipticCurve(keyPair.getCurve()); - } else { - mode = nanoTDFConfig.eccMode; - } - header.setECCMode(mode); - header.setPayloadConfig(nanoTDFConfig.config); - header.setEphemeralKey(compressedPubKey); - header.setKasLocator(kasURL); - header.setPolicyInfo(policyInfo); - - Config.HeaderInfo headerInfo = new Config.HeaderInfo(header, gcm, 0); - if (nanoTDFConfig.collectionConfig.useCollection) { - nanoTDFConfig.collectionConfig.updateHeaderInfo(headerInfo); - } - - return headerInfo; - } - - private static NanoTDFType.ECCurve getEcCurve(Config.NanoTDFConfig nanoTDFConfig, Config.KASInfo kasInfo) { - // it might be better to pull the curve from the OIDC in the PEM but it looks like we - // are just taking the Algorithm as correct - Optional specifiedCurve = NanoTDFType.ECCurve.fromAlgorithm(kasInfo.Algorithm); - NanoTDFType.ECCurve ecCurve; - if (specifiedCurve.isEmpty()) { - logger.info("no curve specified in KASInfo, using the curve from config [{}]", nanoTDFConfig.eccMode.getCurve()); - ecCurve = nanoTDFConfig.eccMode.getCurve(); - } else { - if (specifiedCurve.get() != nanoTDFConfig.eccMode.getCurve()) { - logger.warn("ECCurve in NanoTDFConfig [{}] does not match the curve in KASInfo, using KASInfo curve [{}]", nanoTDFConfig.eccMode.getCurve(), specifiedCurve.get()); - } - ecCurve = specifiedCurve.get(); - } - return ecCurve; - } - - public int createNanoTDF(ByteBuffer data, OutputStream outputStream, - Config.NanoTDFConfig nanoTDFConfig) throws SDKException, IOException { - int nanoTDFSize = 0; - - int dataSize = data.limit(); - if (dataSize > kMaxTDFSize) { - throw new NanoTDFMaxSizeLimit("exceeds max size for nano tdf"); - } - - // check the policy type, support only embedded policy - if (nanoTDFConfig.policyType != NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT && - nanoTDFConfig.policyType != NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { - throw new UnsupportedNanoTDFFeature("unsupported policy type"); - } - - Config.HeaderInfo headerKeyPair = getHeaderInfo(nanoTDFConfig); - Header header = headerKeyPair.getHeader(); - AesGcm gcm = headerKeyPair.getKey(); - int iteration = headerKeyPair.getIteration(); - - int headerSize = header.getTotalSize(); - ByteBuffer bufForHeader = ByteBuffer.allocate(headerSize); - header.writeIntoBuffer(bufForHeader); - - // Write header - outputStream.write(bufForHeader.array()); - nanoTDFSize += headerSize; - logger.debug("createNanoTDF header length {}", headerSize); - - int authTagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(nanoTDFConfig.config.getCipherType()); - // Encrypt the data - byte[] actualIV = new byte[kIvPadding + kNanoTDFIvSize]; - if (nanoTDFConfig.collectionConfig.useCollection) { - ByteBuffer b = ByteBuffer.allocate(4); - b.order(ByteOrder.LITTLE_ENDIAN); - b.putInt(iteration); - System.arraycopy(b.array(), 0, actualIV, kIvPadding, kNanoTDFIvSize); - } else { - do { - byte[] iv = new byte[kNanoTDFIvSize]; - try { - SecureRandom.getInstanceStrong().nextBytes(iv); - } catch (NoSuchAlgorithmException e) { - throw new SDKException("error getting instance of strong SecureRandom", e); - } - System.arraycopy(iv, 0, actualIV, kIvPadding, iv.length); - } while (Arrays.equals(actualIV, kEmptyIV)); // if match, we need to retry to prevent key + iv reuse with the policy - } - - byte[] cipherData = gcm.encrypt(actualIV, authTagSize, data.array(), data.arrayOffset(), dataSize); - - // Write the length of the payload as int24 - int cipherDataLengthWithoutPadding = cipherData.length - kIvPadding; - byte[] bgIntAsBytes = ByteBuffer.allocate(4).putInt(cipherDataLengthWithoutPadding).array(); - outputStream.write(bgIntAsBytes, 1, 3); - nanoTDFSize += 3; - - logger.debug("createNanoTDF payload length {}", cipherDataLengthWithoutPadding); - - // Write the payload - outputStream.write(cipherData, kIvPadding, cipherDataLengthWithoutPadding); - nanoTDFSize += cipherDataLengthWithoutPadding; - - return nanoTDFSize; - } - - public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream) throws IOException { - readNanoTDF(nanoTDF, outputStream, Config.newNanoTDFReaderConfig()); - } - - public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, String platformUrl) throws IOException { - readNanoTDF(nanoTDF, outputStream, Config.newNanoTDFReaderConfig(), platformUrl); - } - - - public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, - Config.NanoTDFReaderConfig nanoTdfReaderConfig, String platformUrl) throws IOException { - if (!nanoTdfReaderConfig.ignoreKasAllowlist && (nanoTdfReaderConfig.kasAllowlist == null || nanoTdfReaderConfig.kasAllowlist.isEmpty())) { - ListKeyAccessServersRequest request = ListKeyAccessServersRequest.newBuilder() - .build(); - ListKeyAccessServersResponse response = ResponseMessageKt.getOrThrow(services.kasRegistry().listKeyAccessServersBlocking(request, Collections.emptyMap()).execute()); - nanoTdfReaderConfig.kasAllowlist = new HashSet<>(); - var kases = response.getKeyAccessServersList(); - - for (var entry : kases) { - nanoTdfReaderConfig.kasAllowlist.add(Config.getKasAddress(entry.getUri())); - } - - nanoTdfReaderConfig.kasAllowlist.add(Config.getKasAddress(platformUrl)); - } - readNanoTDF(nanoTDF, outputStream, nanoTdfReaderConfig); - } - - - public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, - Config.NanoTDFReaderConfig nanoTdfReaderConfig) throws IOException { - - Header header = new Header(nanoTDF); - CollectionKey cachedKey = collectionStore.getKey(header); - byte[] key = cachedKey.getKey(); - - // perform unwrap is not in collectionStore; - if (key == null) { - // create base64 encoded - byte[] headerData = new byte[header.getTotalSize()]; - header.writeIntoBuffer(ByteBuffer.wrap(headerData)); - String base64HeaderData = Base64.getEncoder().encodeToString(headerData); - - logger.debug("readNanoTDF header length {}", headerData.length); - - String kasUrl = header.getKasLocator().getResourceUrl(); - - var realAddress = Config.getKasAddress(kasUrl); - if (nanoTdfReaderConfig.ignoreKasAllowlist) { - logger.warn("Ignoring KasAllowlist for url {}", realAddress); - } else if (nanoTdfReaderConfig.kasAllowlist == null || nanoTdfReaderConfig.kasAllowlist.isEmpty()) { - logger.error("KasAllowlist: No KAS allowlist provided and no KeyAccessServerRegistry available, {} is not allowed", realAddress); - throw new KasAllowlistException("No KAS allowlist provided and no KeyAccessServerRegistry available"); - } else if (!nanoTdfReaderConfig.kasAllowlist.contains(realAddress)) { - logger.error("KasAllowlist: kas url {} is not allowed", realAddress); - throw new KasAllowlistException("KasAllowlist: kas url "+realAddress+" is not allowed"); - } - - - key = services.kas().unwrapNanoTDF(header.getECCMode().getCurve(), - base64HeaderData, - kasUrl); - collectionStore.store(header, new CollectionKey(key)); - } - - byte[] payloadLengthBuf = new byte[4]; - nanoTDF.get(payloadLengthBuf, 1, 3); - int payloadLength = ByteBuffer.wrap(payloadLengthBuf).getInt(); - - logger.debug("readNanoTDF payload length {}, retrieving", payloadLength); - - // Read iv - byte[] iv = new byte[kNanoTDFIvSize]; - nanoTDF.get(iv); - - // pad the IV with zero's - byte[] ivPadded = new byte[AesGcm.GCM_NONCE_LENGTH]; - System.arraycopy(iv, 0, ivPadded, kIvPadding, iv.length); - - byte[] cipherData = new byte[payloadLength - kNanoTDFIvSize]; - nanoTDF.get(cipherData); - - int authTagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(header.getPayloadConfig().getCipherType()); - AesGcm gcm = new AesGcm(key); - byte[] plainData = gcm.decrypt(ivPadded, authTagSize, cipherData); - - outputStream.write(plainData); - } - - PolicyObject createPolicyObject(List attributes) { - PolicyObject policyObject = new PolicyObject(); - policyObject.body = new PolicyObject.Body(); - policyObject.uuid = UUID.randomUUID().toString(); - policyObject.body.dataAttributes = new ArrayList<>(attributes.size()); - policyObject.body.dissem = new ArrayList<>(); - - for (String attribute : attributes) { - PolicyObject.AttributeObject attributeObject = new PolicyObject.AttributeObject(); - attributeObject.attribute = attribute; - policyObject.body.dataAttributes.add(attributeObject); - } - return policyObject; - } - - public static class CollectionKey { - private final byte[] key; - - public CollectionKey(byte[] key) { - this.key = key; - } - protected byte[] getKey() { - return key; - } - } -} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDFECDSAStruct.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDFECDSAStruct.java deleted file mode 100644 index 84e26586..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDFECDSAStruct.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.opentdf.platform.sdk; - -public class NanoTDFECDSAStruct { - - public static class IncorrectNanoTDFECDSASignatureSize extends Exception { - public IncorrectNanoTDFECDSASignatureSize(String errorMessage) { - super(errorMessage); - } - } - - final int[] array = new int[3]; - - private final byte[] rLength = new byte[1]; - private byte[] rValue; - private final byte[] sLength = new byte[1]; - private byte[] sValue; - - NanoTDFECDSAStruct(byte[] ecdsaSignatureValue, int keySize) throws IncorrectNanoTDFECDSASignatureSize { - if (ecdsaSignatureValue.length != (2 * keySize) + 2) { - throw new IncorrectNanoTDFECDSASignatureSize("Invalid signature buffer size"); - } - - // Copy value of rLength to signature struct - int index = 0; - System.arraycopy(ecdsaSignatureValue, index , this.rLength, 0, 1); - - // Copy the contents of rValue to signature struct - index += 1; - int rlen = this.rLength[0]; - this.rValue = new byte[keySize]; - System.arraycopy(ecdsaSignatureValue, index, this.rValue, 0, rlen); - - // Copy value of sLength to signature struct - index += keySize; - System.arraycopy(ecdsaSignatureValue, index , this.sLength, 0, 1); - - // Copy value of sValue - index += 1; - int slen = this.sLength[0]; - this.sValue = new byte[keySize]; - System.arraycopy(ecdsaSignatureValue, index , this.sValue, 0, slen); - } - - public byte[] asBytes() { - byte[] signature = new byte[this.rLength[0] + this.rValue.length + this.sLength[0] + this.sValue.length]; - - // Copy value of rLength - int index = 0; - System.arraycopy(this.rLength, 0, signature, index, this.rLength.length); - - // Copy the contents of rValue - index += this.rLength.length; - System.arraycopy(this.rValue, 0, signature, index, this.rValue.length); - - // Copy value of sLength - index += this.rValue.length; - System.arraycopy(this.sLength, 0, signature, index, this.sLength.length); - - // Copy value of sValue - index += this.sLength.length; - System.arraycopy(this.sValue, 0, signature, index, this.sValue.length); - - return signature; - } - - public byte[] getsValue() { - return sValue; - } - - public void setsValue(byte[] sValue) { - this.sValue = sValue; - } - - public byte getsLength() { - return sLength[0]; - } - - public void setsLength(byte sLength) { - this.sLength[0] = sLength; - } - - public byte[] getrValue() { - return rValue; - } - - public void setrValue(byte[] rValue) { - this.rValue = rValue; - } - - public byte getrLength() { - return rLength[0]; - } - - public void setrLength(byte rLength) { - this.rLength[0] = rLength; - } -} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDFType.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDFType.java deleted file mode 100644 index 46743383..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDFType.java +++ /dev/null @@ -1,119 +0,0 @@ -package io.opentdf.platform.sdk; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import java.util.Arrays; -import java.util.Optional; - -public class NanoTDFType { - private static final Logger log = LoggerFactory.getLogger(NanoTDFType.class); - enum ECCurve { - SECP256R1("secp256r1", 32, 33, 0x00), - SECP384R1("secp384r1", 48, 49, 0x01), - SECP521R1("secp521r1", 66, 67, 0x02), - SECP256K1("secp256k1",-1, -1, -1, false); // Note: SECP256K1 is not supported by the SDK - - private final int curveMode; - private final int keySize; - // compressedPubKeySize is a byte bigger since it encodes the X coordinate plus a byte that tells - // if the Y coordinate is positive or negative - private final int compressedPubKeySize; - private final String curveName; - private final boolean isSupported; - - - ECCurve(String curveName, int keySize, int compressedPubKeySize, int curveMode) { - this(curveName, keySize, compressedPubKeySize, curveMode, true); - } - - ECCurve(String curveName, int keySize, int compressedPubKeySize, int curveMode, boolean isSupported) { - this.curveName = curveName ; - this.keySize = keySize; - this.compressedPubKeySize = compressedPubKeySize; - this.curveMode = curveMode; - this.isSupported = isSupported; - } - - @Nonnull - static ECCurve fromCurveMode(int curveMode) { - for (ECCurve curve : ECCurve.values()) { - if (curve.getCurveMode() == curveMode) { - return curve; - } - } - throw new IllegalArgumentException("No enum constant for curve mode: " + curveMode); - } - - static Optional fromAlgorithm(String platformAlgorithm) { - log.debug("looking for platformAlgorithm [{}]", platformAlgorithm); - if (platformAlgorithm == null) { - return Optional.empty(); - } - return Arrays.stream(ECCurve.values()) - .filter(v -> v.getPlatformCurveName().equals(platformAlgorithm)) - .findAny(); - } - - int getCurveMode() { - return curveMode; - } - - int getKeySize() { - return keySize; - } - - int getCompressedPubKeySize() { - return compressedPubKeySize; - } - - String getCurveName() { - return curveName; - } - - String getPlatformCurveName() { - return String.format("ec:%s", curveName); - } - - boolean isSupported() { - return isSupported; - } - } - // ResourceLocator Protocol - public enum Protocol { - HTTP, - HTTPS - } - // ResourceLocator Identifier - public enum IdentifierType { - NONE(0), - TWO_BYTES(2), - EIGHT_BYTES(8), - THIRTY_TWO_BYTES(32); - private final int length; - IdentifierType(int length) { - this.length = length; - } - public int getLength() { - return length; - } - } - - public enum PolicyType { - REMOTE_POLICY, - EMBEDDED_POLICY_PLAIN_TEXT, - EMBEDDED_POLICY_ENCRYPTED, - EMBEDDED_POLICY_ENCRYPTED_POLICY_KEY_ACCESS - } - - public enum Cipher { - AES_256_GCM_64_TAG , - AES_256_GCM_96_TAG , - AES_256_GCM_104_TAG , - AES_256_GCM_112_TAG , - AES_256_GCM_120_TAG , - AES_256_GCM_128_TAG , - EAD_AES_256_HMAC_SHA_256 - } -} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/PolicyInfo.java b/sdk/src/main/java/io/opentdf/platform/sdk/PolicyInfo.java deleted file mode 100644 index d09a8f3c..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/PolicyInfo.java +++ /dev/null @@ -1,210 +0,0 @@ -package io.opentdf.platform.sdk; - -import java.nio.ByteBuffer; - -public class PolicyInfo { - private static final int DEFAULT_BINDING_SIZE = 8; - private NanoTDFType.PolicyType type; - private byte[] body; - private byte[] binding; - - public PolicyInfo() { - } - - public PolicyInfo(ByteBuffer buffer, ECCMode eccMode) { - this.type = NanoTDFType.PolicyType.values()[buffer.get()]; - - if (this.type == NanoTDFType.PolicyType.REMOTE_POLICY) { - - byte[] oneByte = new byte[1]; - buffer.get(oneByte); - - ResourceLocator policyUrl = new ResourceLocator(ByteBuffer.wrap(oneByte)); - int policyUrlSize = policyUrl.getTotalSize(); - this.body = new byte[policyUrlSize]; - buffer = ByteBuffer.wrap(this.body); - policyUrl.writeIntoBuffer(buffer); - } else { - byte[] policyLengthBuf = new byte[Short.BYTES]; - buffer.get(policyLengthBuf); - - // read short value into int to prevent possible overflow resulting in negative length - int policyLength = ByteBuffer.wrap(policyLengthBuf).getShort(); - - if (this.type == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT || - this.type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { - - // Copy the policy data. - this.body = new byte[policyLength]; - buffer.get(this.body); - } else if (this.type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED_POLICY_KEY_ACCESS) { - throw new RuntimeException("Embedded policy with key access is not supported."); - } else { - throw new RuntimeException("Invalid policy type."); - } - } - - 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); - } - - 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() { - - int totalSize = 0; - - if (this.type == NanoTDFType.PolicyType.REMOTE_POLICY) { - totalSize = (1 + body.length + binding.length); - } else { - if (type == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT || - type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { - - totalSize = (1 + Short.BYTES + body.length + binding.length); - } else { - throw new RuntimeException("Embedded policy with key access is not supported."); - } - } - return totalSize; - } - - public int writeIntoBuffer(ByteBuffer buffer) { - - if (buffer.remaining() < getTotalSize()) { - throw new RuntimeException("Failed to write policy info - invalid buffer size."); - } - - if (binding.length == 0) { - throw new RuntimeException("Policy binding is not set"); - } - - int totalBytesWritten = 0; - - // Write the policy info type. - buffer.put((byte) type.ordinal()); - totalBytesWritten += 1; // size of byte - - // Remote policy - The body is resource locator. - if (type == NanoTDFType.PolicyType.REMOTE_POLICY) { - buffer.put(body); - totalBytesWritten += body.length; - } else { // Embedded Policy - - // Embedded policy layout - // 1 - Length of the policy; - // 2 - policy bytes itself - // 3 - policy key access( ONLY for EMBEDDED_POLICY_ENCRYPTED_POLICY_KEY_ACCESS) - // 1 - resource locator - // 2 - ephemeral public key, the size depends on ECC mode. - - // Write the length of the policy - - short policyLength = (short) body.length; - buffer.putShort(policyLength); - totalBytesWritten += Short.BYTES; - - if (type == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT || - type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { - - // Copy the policy data. - buffer.put(body); - totalBytesWritten += policyLength; - } else if (type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED_POLICY_KEY_ACCESS) { - throw new RuntimeException("Embedded policy with key access is not supported."); - } else { - throw new RuntimeException("Invalid policy type."); - } - } - - // Write the binding. - buffer.put(binding); - totalBytesWritten += binding.length; - - return totalBytesWritten; - } - - public NanoTDFType.PolicyType getPolicyType() { - return this.type; - } - - public void setRemotePolicy(String policyUrl) { - ResourceLocator remotePolicyUrl = new ResourceLocator(policyUrl); - int size = remotePolicyUrl.getTotalSize(); - this.body = new byte[size]; - remotePolicyUrl.writeIntoBuffer(ByteBuffer.wrap(this.body)); - this.type = NanoTDFType.PolicyType.REMOTE_POLICY; - } - - public String getRemotePolicyUrl() { - if (this.type != NanoTDFType.PolicyType.REMOTE_POLICY) { - throw new RuntimeException("Policy is not remote type."); - } - ResourceLocator policyUrl = new ResourceLocator(ByteBuffer.wrap(this.body)); - return policyUrl.getResourceUrl(); - } - - public void setEmbeddedPlainTextPolicy(byte[] bytes) { - this.body = new byte[bytes.length]; - System.arraycopy(bytes, 0, this.body, 0, bytes.length); - this.type = NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT; - } - - public byte[] getEmbeddedPlainTextPolicy() { - if (this.type != NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT) { - throw new RuntimeException("Policy is not embedded plain text type."); - } - return this.body; - } - - public void setEmbeddedEncryptedTextPolicy(byte[] bytes) { - this.body = new byte[bytes.length]; - System.arraycopy(bytes, 0, this.body, 0, bytes.length); - this.type = NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED; - } - - public byte[] getEmbeddedEncryptedTextPolicy() { - if (this.type != NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { - throw new RuntimeException("Policy is not embedded encrypted text type."); - } - return this.body; - } - - public void setPolicyBinding(byte[] bytes) { - this.binding = new byte[bytes.length]; - System.arraycopy(bytes, 0, this.binding, 0, bytes.length); - } - - public byte[] getPolicyBinding() { - return this.binding; - } -} \ No newline at end of file diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ResourceLocator.java b/sdk/src/main/java/io/opentdf/platform/sdk/ResourceLocator.java deleted file mode 100644 index b73438fc..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ResourceLocator.java +++ /dev/null @@ -1,234 +0,0 @@ -package io.opentdf.platform.sdk; - -import java.nio.ByteBuffer; -import java.util.Objects; - -/** - * The ResourceLocator class represents a resource locator containing a - * protocol, body, and identifier. It provides methods to set and retrieve - * the protocol, body, and identifier, as well as to get the resource URL and - * the total size of the resource locator. It also provides methods to write - * the resource locator into a ByteBuffer and obtain the identifier. - */ -public class ResourceLocator { - private static final String HTTP = "http://"; - private static final String HTTPS = "https://"; - - private NanoTDFType.Protocol protocol; - private int bodyLength; - private byte[] body; - private NanoTDFType.IdentifierType identifierType; - private byte[] identifier; - - public ResourceLocator() { - } - - public ResourceLocator(final String resourceUrl) { - this(resourceUrl, null); - } - - /** - * ResourceLocator represents a locator for a resource. - * It takes a resource URL and an identifier as parameters and initializes the object. - * The resource URL is used to determine the protocol and the body. - * The identifier is used to determine the identifier type and the identifier value. - * - * @param resourceUrl the URL of the resource - * @param identifier the identifier of the resource (optional, can be null) - * @throws IllegalArgumentException if the resource URL has an unsupported protocol or if the identifier length is unsupported - */ - public ResourceLocator(final String resourceUrl, final String identifier) { - if (resourceUrl.startsWith(HTTP)) { - this.protocol = NanoTDFType.Protocol.HTTP; - } else if (resourceUrl.startsWith(HTTPS)) { - this.protocol = NanoTDFType.Protocol.HTTPS; - } else { - throw new IllegalArgumentException("Unsupported protocol for resource locator"); - } - // body - this.body = resourceUrl.substring(resourceUrl.indexOf("://") + 3).getBytes(); - this.bodyLength = this.body.length; - // identifier - if (identifier == null) { - this.identifierType = NanoTDFType.IdentifierType.NONE; - this.identifier = new byte[NanoTDFType.IdentifierType.NONE.getLength()]; - } else { - int identifierLen = identifier.getBytes().length; - if (identifierLen == 0) { - this.identifierType = NanoTDFType.IdentifierType.NONE; - this.identifier = new byte[NanoTDFType.IdentifierType.NONE.getLength()]; - } else if (identifierLen <= 2) { - this.identifierType = NanoTDFType.IdentifierType.TWO_BYTES; - this.identifier = new byte[NanoTDFType.IdentifierType.TWO_BYTES.getLength()]; - System.arraycopy(identifier.getBytes(), 0, this.identifier, 0, identifierLen); - } else if (identifierLen <= 8) { - this.identifierType = NanoTDFType.IdentifierType.EIGHT_BYTES; - this.identifier = new byte[NanoTDFType.IdentifierType.EIGHT_BYTES.getLength()]; - System.arraycopy(identifier.getBytes(), 0, this.identifier, 0, identifierLen); - } else if (identifierLen <= 32) { - this.identifierType = NanoTDFType.IdentifierType.THIRTY_TWO_BYTES; - this.identifier = new byte[NanoTDFType.IdentifierType.THIRTY_TWO_BYTES.getLength()]; - System.arraycopy(identifier.getBytes(), 0, this.identifier, 0, identifierLen); - } else { - throw new IllegalArgumentException("Unsupported identifier length: " + identifierLen); - } - } - } - - public ResourceLocator(ByteBuffer buffer) { - // Get the first byte and mask it with 0xF to keep only the first four bits - final byte protocolWithIdentifier = buffer.get(); - int protocolNibble = protocolWithIdentifier & 0x0F; - int identifierNibble = ((protocolWithIdentifier & 0xFF) & 0xF0) >> 4; - this.protocol = NanoTDFType.Protocol.values()[protocolNibble]; - // body - this.bodyLength = buffer.get(); - this.body = new byte[this.bodyLength]; - buffer.get(this.body); - // identifier - this.identifierType = NanoTDFType.IdentifierType.values()[identifierNibble]; - switch (this.identifierType) { - case NONE: - this.identifier = new byte[0]; - break; - case TWO_BYTES: - this.identifier = new byte[2]; - buffer.get(this.identifier); - break; - case EIGHT_BYTES: - this.identifier = new byte[8]; - buffer.get(this.identifier); - break; - case THIRTY_TWO_BYTES: - this.identifier = new byte[32]; - buffer.get(this.identifier); - break; - default: - throw new IllegalArgumentException("Unexpected identifier type: " + identifierType); - } - } - - public void setIdentifier(String identifier) { - if (identifier == null) { - this.identifierType = NanoTDFType.IdentifierType.NONE; - this.identifier = new byte[0]; - } else { - byte[] identifierBytes = identifier.getBytes(); - int identifierLen = identifierBytes.length; - - if (identifierLen == 0) { - this.identifierType = NanoTDFType.IdentifierType.NONE; - this.identifier = new byte[0]; - } else if (identifierLen <= 2) { - this.identifierType = NanoTDFType.IdentifierType.TWO_BYTES; - this.identifier = new byte[2]; - System.arraycopy(identifierBytes, 0, this.identifier, 0, identifierLen); - } else if (identifierLen <= 8) { - this.identifierType = NanoTDFType.IdentifierType.EIGHT_BYTES; - this.identifier = new byte[8]; - System.arraycopy(identifierBytes, 0, this.identifier, 0, identifierLen); - } else if (identifierLen <= 32) { - this.identifierType = NanoTDFType.IdentifierType.THIRTY_TWO_BYTES; - this.identifier = new byte[32]; - System.arraycopy(identifierBytes, 0, this.identifier, 0, identifierLen); - } else { - throw new IllegalArgumentException("Unsupported identifier length: " + identifierLen); - } - } - } - - public void setProtocol(NanoTDFType.Protocol protocol) { - this.protocol = protocol; - } - - public void setBodyLength(int bodyLength) { - this.bodyLength = bodyLength; - } - - public void setBody(byte[] body) { - this.body = body; - } - - public String getResourceUrl() { - StringBuilder sb = new StringBuilder(); - - if (Objects.requireNonNull(this.protocol) == NanoTDFType.Protocol.HTTP) { - sb.append(HTTP); - } else if (this.protocol == NanoTDFType.Protocol.HTTPS) { - sb.append(HTTPS); - } - - sb.append(new String(this.body)); - - return sb.toString(); - } - - public int getTotalSize() { - return 1 + 1 + this.body.length + this.identifier.length; - } - - /** - * Writes the resource locator into the provided ByteBuffer. - * - * @param buffer the ByteBuffer to write into - * @return the number of bytes written - * @throws RuntimeException if the buffer size is insufficient to write the resource locator - */ - public int writeIntoBuffer(ByteBuffer buffer) { - int totalSize = getTotalSize(); - if (buffer.remaining() < totalSize) { - throw new RuntimeException("Failed to write resource locator - invalid buffer size."); - } - - int totalBytesWritten = 0; - - // Write the protocol type. - switch (identifierType) { - case NONE: - buffer.put((byte) protocol.ordinal()); - break; - case TWO_BYTES: - buffer.put((byte) ((0b0001 << 4) | protocol.ordinal())); - break; - case EIGHT_BYTES: - buffer.put((byte) ((0b0010 << 4) | protocol.ordinal())); - break; - case THIRTY_TWO_BYTES: - buffer.put((byte) ((0b0011 << 4) | protocol.ordinal())); - break; - } - totalBytesWritten += 1; // size of byte - - // Write the url body length - buffer.put((byte)bodyLength); - totalBytesWritten += 1; - - // Write the url body - buffer.put(body); - totalBytesWritten += body.length; - - // Write the identifier - if (identifierType != NanoTDFType.IdentifierType.NONE) { - buffer.put(identifier); - totalBytesWritten += identifier.length; - } - - return totalBytesWritten; - } - - public byte[] getIdentifier() { - return this.identifier; - } - - // getIdentifierString removes potential padding - public String getIdentifierString() { - int actualLength = 0; - for (int i = 0; i < this.identifier.length; i++) { - if (this.identifier[i] != 0) { - actualLength = i + 1; - } - } - String identifierPadded = new String(this.identifier, 0, actualLength); - return identifierPadded.trim(); - } -} \ No newline at end of file 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 ba1c8082..3f0325f9 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.util.Optional; @@ -49,13 +48,9 @@ public void close() throws Exception { public interface KAS extends AutoCloseable { Config.KASInfo getPublicKey(Config.KASInfo kasInfo); - Config.KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve); - byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessionKeyType); - byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL); - KASKeyCache getKeyCache(); } @@ -113,16 +108,6 @@ public Manifest createTDF(InputStream payload, OutputStream outputStream, Config return tdf.createTDF(payload, outputStream, config).getManifest(); } - public int createNanoTDF(ByteBuffer payload, OutputStream outputStream, Config.NanoTDFConfig config) throws SDKException, IOException { - var ntdf = new NanoTDF(services); - return ntdf.createNanoTDF(payload, outputStream, config); - } - - public void readNanoTDF(ByteBuffer nanoTDF, OutputStream out, Config.NanoTDFReaderConfig config) throws SDKException, IOException { - var ntdf = new NanoTDF(services); - ntdf.readNanoTDF(nanoTDF, out, config, platformUrl); - } - public ProtocolClient getPlatformServicesClient() { return this.platformServicesClient; } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SymmetricAndPayloadConfig.java b/sdk/src/main/java/io/opentdf/platform/sdk/SymmetricAndPayloadConfig.java deleted file mode 100644 index 8d4a8b3d..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SymmetricAndPayloadConfig.java +++ /dev/null @@ -1,108 +0,0 @@ -package io.opentdf.platform.sdk; - -public class SymmetricAndPayloadConfig { - private Data data; - - public SymmetricAndPayloadConfig() { - data = new Data(); - data.symmetricCipherEnum = 0x0; // AES_256_GCM_64_TAG - data.signatureECCMode = 0x00; // SECP256R1 - data.hasSignature = 1; - } - - public SymmetricAndPayloadConfig(byte value) { - data = new Data(); - - int cipherType = value & 0x0F; // first 4 bits - setSymmetricCipherType(NanoTDFType.Cipher.values()[cipherType]); - - int signatureECCMode = (value >> 4) & 0x07; - data.signatureECCMode = signatureECCMode; - - int hasSignature = (value >> 7) & 0x01; // most significant bit - data.hasSignature = hasSignature; - } - - public void setHasSignature(boolean flag) { - data.hasSignature = flag ? 1 : 0; - } - - public void setSignatureECCMode(NanoTDFType.ECCurve eccCurve) { - if (!eccCurve.isSupported()) { - throw new SDKException(String.format("Unsupported ECC algorithm: %s", eccCurve.getCurveName())); - } - data.signatureECCMode = eccCurve.getCurveMode(); - } - - public void setSymmetricCipherType(NanoTDFType.Cipher cipherType) { - switch (cipherType) { - case AES_256_GCM_64_TAG: - data.symmetricCipherEnum = 0x00; - break; - case AES_256_GCM_96_TAG: - data.symmetricCipherEnum = 0x01; - break; - case AES_256_GCM_104_TAG: - data.symmetricCipherEnum = 0x02; - break; - case AES_256_GCM_112_TAG: - data.symmetricCipherEnum = 0x03; - break; - case AES_256_GCM_120_TAG: - data.symmetricCipherEnum = 0x04; - break; - case AES_256_GCM_128_TAG: - data.symmetricCipherEnum = 0x05; - break; - case EAD_AES_256_HMAC_SHA_256: - data.symmetricCipherEnum = 0x06; - break; - default: - throw new RuntimeException("Unsupported symmetric cipher for signature."); - } - } - - public boolean hasSignature() { - return data.hasSignature == 1; - } - - public NanoTDFType.Cipher getCipherType() { - return NanoTDFType.Cipher.values()[data.symmetricCipherEnum]; - } - - public byte getSymmetricAndPayloadConfigAsByte() { - int value = data.hasSignature << 7 | data.signatureECCMode << 4 | data.symmetricCipherEnum; - return (byte) value; - } - - static public int sizeOfAuthTagForCipher(NanoTDFType.Cipher cipherType) { - switch (cipherType) { - case AES_256_GCM_64_TAG: - return 8; - case AES_256_GCM_96_TAG: - return 12; - case AES_256_GCM_104_TAG: - return 13; - case AES_256_GCM_112_TAG: - return 14; - case AES_256_GCM_120_TAG: - return 15; - case AES_256_GCM_128_TAG: - return 16; - case EAD_AES_256_HMAC_SHA_256: - return 32; - default: - throw new IllegalArgumentException("Unsupported symmetric cipher for signature."); - } - } - - public NanoTDFType.ECCurve getSignatureECCMode() { - return NanoTDFType.ECCurve.fromCurveMode(this.data.signatureECCMode); - } - - private static class Data { - int symmetricCipherEnum; - int signatureECCMode; - int hasSignature; - } -} \ No newline at end of file diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java index 22293281..d454b6c6 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java @@ -12,7 +12,7 @@ import java.util.Arrays; import java.util.Base64; -import static io.opentdf.platform.sdk.NanoTDFType.ECCurve.SECP256R1; +import static io.opentdf.platform.sdk.ECCurve.SECP256R1; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -124,8 +124,8 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch @Test void createSymmetricKeysWithOtherCurves() { - ECKeyPair pubPair = new ECKeyPair(NanoTDFType.ECCurve.SECP384R1, ECKeyPair.ECAlgorithm.ECDH); - ECKeyPair keyPair = new ECKeyPair(NanoTDFType.ECCurve.SECP384R1, ECKeyPair.ECAlgorithm.ECDH); + ECKeyPair pubPair = new ECKeyPair(ECCurve.SECP384R1, ECKeyPair.ECAlgorithm.ECDH); + ECKeyPair keyPair = new ECKeyPair(ECCurve.SECP384R1, ECKeyPair.ECAlgorithm.ECDH); byte[] sharedSecret = ECKeyPair.computeECDHKey(pubPair.getPublicKey(), keyPair.getPrivateKey()); byte[] encryptionKey = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), sharedSecret); @@ -168,7 +168,7 @@ void testECDH() { void testECDSA() { String plainText = "Virtru!"; - for (var curve: NanoTDFType.ECCurve.values()) { + for (var curve: ECCurve.values()) { ECKeyPair keyPair = new ECKeyPair(curve, ECKeyPair.ECAlgorithm.ECDSA); byte[] signature = ECKeyPair.computeECDSASig(plainText.getBytes(), keyPair.getPrivateKey()); boolean verify = ECKeyPair.verifyECDSAig(plainText.getBytes(), signature, keyPair.getPublicKey()); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java b/sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java index fd9f9afc..72ce61d0 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.OutputStream; -import java.nio.ByteBuffer; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; @@ -26,13 +25,6 @@ public void write(byte[] b, int off, int len) { } }; - @FuzzTest(maxDuration=TEST_DURATION) - public void fuzzNanoTDF(FuzzedDataProvider data) throws IOException { - byte[] fuzzBytes = data.consumeRemainingAsBytes(); - NanoTDF nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(NanoTDFTest.kas).build()); - nanoTDF.readNanoTDF(ByteBuffer.wrap(fuzzBytes), IGNORE_OUTPUT_STREAM); - } - @FuzzTest(maxDuration=TEST_DURATION) public void fuzzTDF(FuzzedDataProvider data) { byte[] fuzzBytes = data.consumeRemainingAsBytes(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/HeaderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/HeaderTest.java deleted file mode 100644 index 8fd231a3..00000000 --- a/sdk/src/test/java/io/opentdf/platform/sdk/HeaderTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.opentdf.platform.sdk; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.*; - -class HeaderTest { - private Header header; - - @BeforeEach - void setUp() { - header = new Header(); - } - - @Test - void settingAndGettingMagicNumberAndVersion() { - byte[] expected = header.getMagicNumberAndVersion(); - assertArrayEquals(expected, new byte[] { 0x4C, 0x31, 0x4C }); - } - - @Test - void settingAndGettingKasLocator() { - ResourceLocator locator = new ResourceLocator("http://test.com"); - header.setKasLocator(locator); - assertEquals(locator, header.getKasLocator()); - } - - @Test - void settingAndGettingECCMode() { - ECCMode mode = new ECCMode((byte) 1); - header.setECCMode(mode); - assertEquals(mode, header.getECCMode()); - } - - @Test - void settingAndGettingPayloadConfig() { - SymmetricAndPayloadConfig config = new SymmetricAndPayloadConfig((byte) 1); - header.setPayloadConfig(config); - assertEquals(config, header.getPayloadConfig()); - } - - @Test - void settingAndGettingEphemeralKey() { - ECCMode mode = new ECCMode((byte) 1); // Initialize the ECCMode object - header.setECCMode(mode); // Set the ECCMode object - - int keySize = mode.getCurve().getCompressedPubKeySize(); - byte[] key = new byte[keySize]; // Ensure the key size is correct - header.setEphemeralKey(key); - assertArrayEquals(key, header.getEphemeralKey()); - } - - @Test - void settingEphemeralKeyWithInvalidSize() { - byte[] key = new byte[] { 1, 2, 3 }; - header.setECCMode(new ECCMode((byte) 1)); // ECC mode with key size > 3 - assertThrows(IllegalArgumentException.class, () -> header.setEphemeralKey(key)); - } - - @Test - void writingIntoBufferWithInsufficientSize() { - ByteBuffer buffer = ByteBuffer.allocate(1); // Buffer with insufficient size - assertThrows(NullPointerException.class, () -> header.writeIntoBuffer(buffer)); - } -} \ No newline at end of file diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFECDSAStructTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFECDSAStructTest.java deleted file mode 100644 index a2e504ed..00000000 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFECDSAStructTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.opentdf.platform.sdk; - -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.util.Arrays; -import java.util.Random; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -public class NanoTDFECDSAStructTest { - @Test - void testECDSASigStruct() throws NanoTDFECDSAStruct.IncorrectNanoTDFECDSASignatureSize { - int keySizeBytes = 32; - byte[] l = {(byte) keySizeBytes}; - byte[] b = new byte[keySizeBytes]; - - new Random().nextBytes(b); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); - outputStream.writeBytes(l); - new Random().nextBytes(b); - byte[] rValue = Arrays.copyOf(b, b.length); - outputStream.writeBytes(b); - outputStream.writeBytes(l); - new Random().nextBytes(b); - byte[] sValue = Arrays.copyOf(b, b.length); - outputStream.writeBytes(b); - - NanoTDFECDSAStruct nanoTDFECDSAStruct = new NanoTDFECDSAStruct(outputStream.toByteArray(), keySizeBytes); - assertArrayEquals(nanoTDFECDSAStruct.getrValue(), rValue); - assertArrayEquals(nanoTDFECDSAStruct.getsValue(), sValue); - } -} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFHeaderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFHeaderTest.java deleted file mode 100644 index 5019c198..00000000 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFHeaderTest.java +++ /dev/null @@ -1,273 +0,0 @@ -package io.opentdf.platform.sdk; - -import org.junit.jupiter.api.Test; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.*; -import java.nio.ByteBuffer; -import java.security.*; -import java.security.cert.CertificateException; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; - -public class NanoTDFHeaderTest { - - byte[] binding = new byte[] { (byte) 0x33, (byte) 0x31, (byte) 0x63, (byte) 0x31, - (byte) 0x66, (byte) 0x35, (byte) 0x35, (byte) 0x00 }; - String kasUrl = "https://api.example.com/kas"; - String remotePolicyUrl = "https://api-develop01.develop.virtru.com/acm/api/policies/1a1d5e42-bf91-45c7-a86a-61d5331c1f55"; - - // Curve - "prime256v1" - String sdkPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1HjFYV8D16BQszNW\n" + - "6Hx/JxTE53oqk5/bWaIj4qV5tOyhRANCAAQW1Hsq0tzxN6ObuXqV+JoJN0f78Em/\n" + - "PpJXUV02Y6Ex3WlxK/Oaebj8ATsbfaPaxrhyCWB3nc3w/W6+lySlLPn5\n" + - "-----END PRIVATE KEY-----"; - - String sdkPublicKey = "-----BEGIN PUBLIC KEY-----\n" + - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFtR7KtLc8Tejm7l6lfiaCTdH+/BJ\n" + - "vz6SV1FdNmOhMd1pcSvzmnm4/AE7G32j2sa4cglgd53N8P1uvpckpSz5+Q==\n" + - "-----END PUBLIC KEY-----"; - - String kasPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgu2Hmm80uUzQB1OfB\n" + - "PyMhWIyJhPA61v+j0arvcLjTwtqhRANCAASHCLUHY4szFiVV++C9+AFMkEL2gG+O\n" + - "byN4Hi7Ywl8GMPOAPcQdIeUkoTd9vub9PcuSj23I8/pLVzs23qhefoUf\n" + - "-----END PRIVATE KEY-----"; - - String kasPublicKey = "-----BEGIN PUBLIC KEY-----\n" + - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhwi1B2OLMxYlVfvgvfgBTJBC9oBv\n" + - "jm8jeB4u2MJfBjDzgD3EHSHlJKE3fb7m/T3Lko9tyPP6S1c7Nt6oXn6FHw==\n" + - "-----END PUBLIC KEY-----"; - - byte[] compressedPubKey = new byte[] { - (byte) 0x03, (byte) 0x16, (byte) 0xd4, (byte) 0x7b, (byte) 0x2a, (byte) 0xd2, (byte) 0xdc, - (byte) 0xf1, - (byte) 0x37, (byte) 0xa3, (byte) 0x9b, (byte) 0xb9, (byte) 0x7a, (byte) 0x95, (byte) 0xf8, - (byte) 0x9a, - (byte) 0x09, (byte) 0x37, (byte) 0x47, (byte) 0xfb, (byte) 0xf0, (byte) 0x49, (byte) 0xbf, - (byte) 0x3e, - (byte) 0x92, (byte) 0x57, (byte) 0x51, (byte) 0x5d, (byte) 0x36, (byte) 0x63, (byte) 0xa1, - (byte) 0x31, - (byte) 0xdd - }; - - byte[] expectedHeader = new byte[] { - (byte) 0x4c, (byte) 0x31, (byte) 0x4c, (byte) 0x01, (byte) 0x12, (byte) 0x61, (byte) 0x70, - (byte) 0x69, (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, - (byte) 0x6c, (byte) 0x2e, - (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x2f, (byte) 0x6b, (byte) 0x61, (byte) 0x73, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x56, (byte) 0x61, (byte) 0x70, - (byte) 0x69, (byte) 0x2d, - (byte) 0x64, (byte) 0x65, (byte) 0x76, (byte) 0x65, (byte) 0x6c, (byte) 0x6f, (byte) 0x70, - (byte) 0x30, (byte) 0x31, (byte) 0x2e, (byte) 0x64, (byte) 0x65, (byte) 0x76, (byte) 0x65, - (byte) 0x6c, (byte) 0x6f, - (byte) 0x70, (byte) 0x2e, (byte) 0x76, (byte) 0x69, (byte) 0x72, (byte) 0x74, (byte) 0x72, - (byte) 0x75, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x2f, (byte) 0x61, - (byte) 0x63, (byte) 0x6d, - (byte) 0x2f, (byte) 0x61, (byte) 0x70, (byte) 0x69, (byte) 0x2f, (byte) 0x70, (byte) 0x6f, - (byte) 0x6c, (byte) 0x69, (byte) 0x63, (byte) 0x69, (byte) 0x65, (byte) 0x73, (byte) 0x2f, - (byte) 0x31, (byte) 0x61, - (byte) 0x31, (byte) 0x64, (byte) 0x35, (byte) 0x65, (byte) 0x34, (byte) 0x32, (byte) 0x2d, - (byte) 0x62, (byte) 0x66, (byte) 0x39, (byte) 0x31, (byte) 0x2d, (byte) 0x34, (byte) 0x35, - (byte) 0x63, (byte) 0x37, - (byte) 0x2d, (byte) 0x61, (byte) 0x38, (byte) 0x36, (byte) 0x61, (byte) 0x2d, (byte) 0x36, - (byte) 0x31, (byte) 0x64, (byte) 0x35, (byte) 0x33, (byte) 0x33, (byte) 0x31, (byte) 0x63, - (byte) 0x31, (byte) 0x66, - (byte) 0x35, (byte) 0x35, (byte) 0x33, (byte) 0x31, (byte) 0x63, (byte) 0x31, (byte) 0x66, - (byte) 0x35, (byte) 0x35, (byte) 0x00, (byte) 0x03, (byte) 0x16, (byte) 0xd4, (byte) 0x7b, - (byte) 0x2a, (byte) 0xd2, - (byte) 0xdc, (byte) 0xf1, (byte) 0x37, (byte) 0xa3, (byte) 0x9b, (byte) 0xb9, (byte) 0x7a, - (byte) 0x95, (byte) 0xf8, (byte) 0x9a, (byte) 0x09, (byte) 0x37, (byte) 0x47, (byte) 0xfb, - (byte) 0xf0, (byte) 0x49, - (byte) 0xbf, (byte) 0x3e, (byte) 0x92, (byte) 0x57, (byte) 0x51, (byte) 0x5d, (byte) 0x36, - (byte) 0x63, (byte) 0xa1, (byte) 0x31, (byte) 0xdd - }; - - // TODO: Need to update the static data to fix this test - @Test - public void testNanoTDFHeaderRemotePolicy() throws IOException { - byte[] headerData = new byte[155]; - - // Construct empty header - encrypt use case - Header header = new Header(); - - ResourceLocator kasLocator = new ResourceLocator("https://api.exampl.com/kas"); - header.setKasLocator(kasLocator); - - ECCMode eccMode = new ECCMode((byte) 0x0); // no ecdsa binding and 'secp256r1' - header.setECCMode(eccMode); - - SymmetricAndPayloadConfig payloadConfig = new SymmetricAndPayloadConfig((byte) 0x0); // no signature and - // AES_256_GCM_64_TAG - header.setPayloadConfig(payloadConfig); - - PolicyInfo policyInfo = new PolicyInfo(); - policyInfo.setRemotePolicy(remotePolicyUrl); - policyInfo.setPolicyBinding(binding); - - header.setPolicyInfo(policyInfo); - header.setEphemeralKey(compressedPubKey); - - int headerSize = header.writeIntoBuffer(ByteBuffer.wrap(headerData)); - assertEquals(headerData.length, headerSize); - assertTrue(Arrays.equals(headerData, expectedHeader)); - } - - // TODO: Need to update the static data to fix this test - @Test - public void testNanoTDFReader() - throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchProviderException, CertificateException, InvalidKeyException, InvalidKeySpecException, - NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException { - - Header header2 = new Header(); - FileInputStream fileIn = new FileInputStream("src/test/resources/javasdknanotdf.ntdf"); - DataInputStream dataIn = new DataInputStream(fileIn); - - // Read each field of the Header object from the file - byte[] magicNumberAndVersion = new byte[3]; // size of magic number and version - dataIn.readFully(magicNumberAndVersion); - header2.setMagicNumberAndVersion(magicNumberAndVersion); - - NanoTDFType.Protocol protocol = NanoTDFType.Protocol.values()[dataIn.readByte()]; - - // Read the body length - int bodyLength = dataIn.readByte(); - - // Read the body - byte[] body = new byte[bodyLength]; - dataIn.readFully(body); - - // Create a new ResourceLocator object - ResourceLocator resourceLocator = new ResourceLocator(); - resourceLocator.setProtocol(protocol); - resourceLocator.setBodyLength(bodyLength); - resourceLocator.setBody(body); - header2.setKasLocator(resourceLocator); - - ECCMode eccMode2 = new ECCMode(dataIn.readByte()); - header2.setECCMode(eccMode2); - - SymmetricAndPayloadConfig payloadConfig2 = new SymmetricAndPayloadConfig(dataIn.readByte()); - header2.setPayloadConfig(payloadConfig2); - - // Read the policy type - int remainingBytes = dataIn.available(); - - // Create a byte array to hold the remaining bytes - byte[] remainingBytesArray = new byte[remainingBytes]; - - // Read the remaining bytes into the byte array - dataIn.readFully(remainingBytesArray); - PolicyInfo policyInfo = new PolicyInfo(ByteBuffer.wrap(remainingBytesArray), header2.getECCMode()); - header2.setPolicyInfo(policyInfo); - - int sizeToRead = policyInfo.getTotalSize(); - int compressedPubKeySize = header2.getECCMode().getCurve().getCompressedPubKeySize(); - byte[] ephemeralKey = new byte[compressedPubKeySize]; // size of compressed public key - System.arraycopy(remainingBytesArray, sizeToRead, ephemeralKey, 0, ephemeralKey.length); - header2.setEphemeralKey(ephemeralKey); - - dataIn.close(); - fileIn.close(); - - assertEquals(kasUrl, header2.getKasLocator().getResourceUrl()); - } - - @Test - public void testNanoTDFEncryption() - throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchProviderException, CertificateException, InvalidKeyException, InvalidKeySpecException, - NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException { - final int kGmacPayloadLength = 8; - final int nanoTDFIvSize = 3; - String policy = "{\"body\":{\"dataAttributes\":[],\"dissem\":[\"cn=virtru-user\",\"user@example.com\"]},\"uuid\":\"1a84b9c7-d59c-45ed-b092-c7ed7de73a07\"}"; - - // Some buffers for compare. - byte[] compressedPubKey; - byte[] headerBuffer; - byte[] encryptedPayLoad; - byte[] policyBinding; - byte[] encryptKey; - - SymmetricAndPayloadConfig payloadConfig = new SymmetricAndPayloadConfig((byte) 0x0); - int tagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(payloadConfig.getCipherType()); - byte[] tag = new byte[tagSize]; - - ECCMode eccMode = new ECCMode((byte) 0x0); // no ecdsa binding and 'secp256r1' - ECKeyPair sdkECKeyPair = new ECKeyPair(eccMode.getCurve(), ECKeyPair.ECAlgorithm.ECDH); - String sdkPrivateKeyForEncrypt = sdkECKeyPair.privateKeyInPEMFormat(); - String sdkPublicKeyForEncrypt = sdkECKeyPair.publicKeyInPEMFormat(); - - ECKeyPair kasECKeyPair = new ECKeyPair(eccMode.getCurve(), ECKeyPair.ECAlgorithm.ECDH); - String kasPublicKey = kasECKeyPair.publicKeyInPEMFormat(); - // Encrypt - Header header = new Header(); - - ResourceLocator kasLocator = new ResourceLocator("https://test.com"); - header.setKasLocator(kasLocator); - - header.setECCMode(eccMode); - header.setPayloadConfig(payloadConfig); - - byte[] secret = ECKeyPair.computeECDHKey(ECKeyPair.publicKeyFromPem(kasPublicKey), - ECKeyPair.privateKeyFromPem(sdkPrivateKeyForEncrypt)); - byte[] saltValue = { 'V', 'I', 'R', 'T', 'R', 'U' }; - encryptKey = ECKeyPair.calculateHKDF(saltValue, secret); - - // Encrypt the policy with key from KDF - int encryptedPayLoadSize = policy.length() + nanoTDFIvSize + tagSize; - encryptedPayLoad = new byte[encryptedPayLoadSize]; - - SecureRandom secureRandom = new SecureRandom(); - byte[] iv = new byte[nanoTDFIvSize]; - secureRandom.nextBytes(iv); - - // Adjust the span to add the IV vector at the start of the buffer - byte[] encryptBufferSpan = Arrays.copyOfRange(encryptedPayLoad, nanoTDFIvSize, encryptedPayLoad.length); - - AesGcm encoder = new AesGcm(encryptKey); - encoder.encrypt(encryptBufferSpan); - - byte[] authTag = new byte[tag.length]; - // encoder.finish(authTag); - - // Copy IV at start - System.arraycopy(iv, 0, encryptedPayLoad, 0, iv.length); - - // Copy tag at end - System.arraycopy(tag, 0, encryptedPayLoad, nanoTDFIvSize + policy.length(), tag.length); - - // Create an encrypted policy. - PolicyInfo encryptedPolicy = new PolicyInfo(); - encryptedPolicy.setEmbeddedEncryptedTextPolicy(encryptedPayLoad); - - byte[] digest = encryptedPayLoad; - if (eccMode.isECDSABindingEnabled()) { - // Calculate the ecdsa binding. - policyBinding = ECKeyPair.computeECDSASig(digest, - ECKeyPair.privateKeyFromPem(sdkPrivateKeyForEncrypt)); - encryptedPolicy.setPolicyBinding(policyBinding); - } else { - // Calculate the gmac binding - byte[] gmac = Arrays.copyOfRange(digest, digest.length - kGmacPayloadLength, digest.length); - encryptedPolicy.setPolicyBinding(gmac); - } - - header.setPolicyInfo(encryptedPolicy); - - compressedPubKey = ECKeyPair.compressECPublickey(sdkPublicKeyForEncrypt); - header.setEphemeralKey(compressedPubKey); - - int headerSize = header.getTotalSize(); - headerBuffer = new byte[headerSize]; - int sizeWritten = header.writeIntoBuffer(ByteBuffer.wrap(headerBuffer)); - assertEquals(sizeWritten, headerSize); - } -} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java deleted file mode 100644 index 599a52a5..00000000 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java +++ /dev/null @@ -1,414 +0,0 @@ -package io.opentdf.platform.sdk; - -import com.connectrpc.ResponseMessage; -import com.connectrpc.UnaryBlockingCall; -import com.google.protobuf.Struct; -import com.google.protobuf.Value; -import io.opentdf.platform.policy.KeyAccessServer; -import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; -import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; -import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; -import io.opentdf.platform.sdk.Config.KASInfo; -import io.opentdf.platform.sdk.Config.NanoTDFReaderConfig; - -import java.nio.charset.StandardCharsets; - -import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; -import io.opentdf.platform.wellknownconfiguration.WellKnownServiceClientInterface; -import org.apache.commons.io.output.ByteArrayOutputStream; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.security.KeyPair; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Random; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -public class NanoTDFTest { - - public static final String kasPublicKey = "-----BEGIN PUBLIC KEY-----\n" + - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC4Wmdb7smRiIeA/Zkua2TNj9kySE\n" + - "8Q2MaJ0kQX9GFePqi5KNDVnjBxQrkHXSTGB7Z/SrRny9vxgo86FT+1aXMQ==\n" + - "-----END PUBLIC KEY-----"; - - public static final String kasPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg2Wgo3sPikn/fj9uU\n" + - "/cU+F4I2rRyOit9/s3fNjHVLxgugCgYIKoZIzj0DAQehRANCAAQLhaZ1vuyZGIh4\n" + - "D9mS5rZM2P2TJITxDYxonSRBf0YV4+qLko0NWeMHFCuQddJMYHtn9KtGfL2/GCjz\n" + - "oVP7Vpcx\n" + - "-----END PRIVATE KEY-----"; - - private static final String BASE_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/NawR/F7RJfX/odyOLPjl+5Ce1Br\n" + - "QZ/MBCIerHe26HzlBSbpa7HQHZx9PYVamHTw9+iJCY3dm8Uwp4Ab2uehnA==\n" + - "-----END PUBLIC KEY-----"; - - private static final String BASE_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" + - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgB3YtAvS7lctHlPsq\n" + - "bZI8OX1B9W1c4GAIxzwKzD6iPkqhRANCAAT81rBH8XtEl9f+h3I4s+OX7kJ7UGtB\n" + - "n8wEIh6sd7bofOUFJulrsdAdnH09hVqYdPD36IkJjd2bxTCngBva56Gc\n" + - "-----END PRIVATE KEY-----"; - - private static final String KID = "r1"; - private static final String BASE_KID = "basekid"; - - protected static KeyAccessServerRegistryServiceClient kasRegistryService; - protected static List registeredKases = List.of( - "https://api.example.com/kas", - "https://other.org/kas2", - "http://localhost:8181/kas", - "https://localhost:8383/kas", - "https://api.kaswithbasekey.example.com" - ); - protected static String platformUrl = "http://localhost:8080"; - - protected static SDK.KAS kas = new SDK.KAS() { - @Override - public void close() { - } - - @Override - public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { - Config.KASInfo returnKI = new Config.KASInfo(); - returnKI.PublicKey = kasPublicKey; - return returnKI; - } - - @Override - public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { - if (kasInfo.Algorithm != null && !"ec:secp256r1".equals(kasInfo.Algorithm)) { - throw new IllegalArgumentException("Unexpected algorithm: " + kasInfo); - } - var k2 = kasInfo.clone(); - k2.KID = KID; - k2.PublicKey = kasPublicKey; - k2.Algorithm = "ec:secp256r1"; - return k2; - } - - @Override - public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessionKeyType) { - throw new UnsupportedOperationException("no unwrapping ZTDFs here"); - } - - @Override - public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL) { - String key = Objects.equals(kasURL, "https://api.kaswithbasekey.example.com") - ? BASE_PRIVATE_KEY - : kasPrivateKey; - byte[] headerAsBytes = Base64.getDecoder().decode(header); - Header nTDFHeader = new Header(ByteBuffer.wrap(headerAsBytes)); - byte[] ephemeralKey = nTDFHeader.getEphemeralKey(); - - String publicKeyAsPem = ECKeyPair.publicKeyFromECPoint(ephemeralKey, nTDFHeader.getECCMode().getCurve().getCurveName()); - - // Generate symmetric key - byte[] symmetricKey = ECKeyPair.computeECDHKey(ECKeyPair.publicKeyFromPem(publicKeyAsPem), - ECKeyPair.privateKeyFromPem(key)); - - // Generate HKDF key - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new SDKException("error creating SHA-256 message digest", e); - } - byte[] hashOfSalt = digest.digest(NanoTDF.MAGIC_NUMBER_AND_VERSION); - return ECKeyPair.calculateHKDF(hashOfSalt, symmetricKey); - } - - @Override - public KASKeyCache getKeyCache(){ - return new KASKeyCache(); - } - }; - - @BeforeAll - static void setupMocks() { - kasRegistryService = mock(KeyAccessServerRegistryServiceClient.class); - List kasRegEntries = new ArrayList<>(); - for (String kasUrl : registeredKases ) { - kasRegEntries.add(KeyAccessServer.newBuilder() - .setUri(kasUrl).build()); - } - ListKeyAccessServersResponse mockResponse = ListKeyAccessServersResponse.newBuilder() - .addAllKeyAccessServers(kasRegEntries) - .build(); - - // Stub the listKeyAccessServers method - when(kasRegistryService.listKeyAccessServersBlocking(any(ListKeyAccessServersRequest.class), any())) - .thenReturn(new UnaryBlockingCall<>() { - @Override - public ResponseMessage execute() { - return new ResponseMessage.Success<>(mockResponse, Collections.emptyMap(), Collections.emptyMap()); - } - - @Override - public void cancel() { - // this never happens in tests - } - }); - } - - private static ArrayList keypairs = new ArrayList<>(); - - @Test - void encryptionAndDecryptionWithValidKey() throws Exception { - var kasInfos = new ArrayList<>(); - var kasInfo = new Config.KASInfo(); - kasInfo.URL = "https://api.example.com/kas"; - kasInfo.PublicKey = null; - kasInfo.KID = KID; - kasInfos.add(kasInfo); - - Config.NanoTDFConfig config = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfos.toArray(new Config.KASInfo[0])), - Config.withEllipticCurve("secp384r1"), - Config.witDataAttributes("https://example.com/attr/Classification/value/S", - "https://example.com/attr/Classification/value/X") - ); - - String plainText = "Virtru!!"; - ByteBuffer byteBuffer = ByteBuffer.wrap(plainText.getBytes()); - ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); - - NanoTDF nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()); - nanoTDF.createNanoTDF(byteBuffer, tdfOutputStream, config); - - byte[] nanoTDFBytes = tdfOutputStream.toByteArray(); - ByteArrayOutputStream plainTextStream = new ByteArrayOutputStream(); - nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()); - nanoTDF.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, platformUrl); - - String out = new String(plainTextStream.toByteArray(), StandardCharsets.UTF_8); - assertThat(out).isEqualTo(plainText); - // KAS KID - assertThat(new String(nanoTDFBytes, StandardCharsets.UTF_8)).contains(KID); - - - int[] nanoTDFSize = { 0, 1, 100*1024, 1024*1024, 4*1024*1024, 12*1024*1024, 15*1024,1024, ((16 * 1024 * 1024) - 3 - 32) }; - for (int size: nanoTDFSize) { - byte[] data = new byte[size]; - new Random().nextBytes(data); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - NanoTDF nTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()); - nTDF.createNanoTDF(ByteBuffer.wrap(data), outputStream, config); - - byte[] nTDFBytes = outputStream.toByteArray(); - ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); - nanoTDF.readNanoTDF(ByteBuffer.wrap(nTDFBytes), dataStream, platformUrl); - assertThat(dataStream.toByteArray()).isEqualTo(data); - } - } - - @Test - void encryptionAndDecryptWithBaseKey() throws Exception { - var baseKeyJson = "{\"kas_url\":\"https://api.kaswithbasekey.example.com\",\"public_key\":{\"algorithm\":\"ALGORITHM_EC_P256\",\"kid\":\"" + BASE_KID + "\",\"pem\": \"" + BASE_PUBLIC_KEY + "\"}}"; - var val = Value.newBuilder().setStringValue(baseKeyJson).build(); - var config = Struct.newBuilder().putFields("base_key", val).build(); - WellKnownServiceClientInterface wellknown = mock(WellKnownServiceClientInterface.class); - GetWellKnownConfigurationResponse response = GetWellKnownConfigurationResponse.newBuilder().setConfiguration(config).build(); - when(wellknown.getWellKnownConfigurationBlocking(any(), any())).thenReturn(TestUtil.successfulUnaryCall(response)); - Config.NanoTDFConfig nanoConfig = Config.newNanoTDFConfig( - Config.witDataAttributes("https://example.com/attr/Classification/value/S", - "https://example.com/attr/Classification/value/X") - ); - - String plainText = "Virtru!!"; - ByteBuffer byteBuffer = ByteBuffer.wrap(plainText.getBytes()); - ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); - NanoTDF nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).setWellknownService(wellknown).build()); - nanoTDF.createNanoTDF(byteBuffer, tdfOutputStream, nanoConfig); - - byte[] nanoTDFBytes = tdfOutputStream.toByteArray(); - ByteArrayOutputStream plainTextStream = new ByteArrayOutputStream(); - nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()); - nanoTDF.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, platformUrl); - String out = new String(plainTextStream.toByteArray(), StandardCharsets.UTF_8); - assertThat(out).isEqualTo(plainText); - // KAS KID - assertThat(new Header(ByteBuffer.wrap(nanoTDFBytes)).getKasLocator().getIdentifierString()).isEqualTo(BASE_KID); - } - - @Test - void testWithDifferentConfigAndKeyValues() throws Exception { - var kasInfos = new ArrayList<>(); - var kasInfo = new Config.KASInfo(); - kasInfo.URL = "https://api.example.com/kas"; - kasInfo.PublicKey = null; - kasInfos.add(kasInfo); - var config = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfos.toArray(new Config.KASInfo[0])), - Config.withEllipticCurve("secp384r1"), - Config.witDataAttributes("https://example.com/attr/Classification/value/S", "https://example.com/attr/Classification/value/X") - ); - runBasicTest(null, true, kasRegistryService, null, config); - } - - void runBasicTest(String kasUrl, boolean allowed, KeyAccessServerRegistryServiceClient kasReg, NanoTDFReaderConfig decryptConfig, Config.NanoTDFConfig writerConfig) throws Exception { - Config.NanoTDFConfig config; - if (writerConfig == null) { - var kasInfos = new ArrayList<>(); - var kasInfo = new Config.KASInfo(); - kasInfo.URL = kasUrl; - kasInfo.PublicKey = null; - kasInfos.add(kasInfo); - config = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfos.toArray(new Config.KASInfo[0])), - Config.witDataAttributes("https://example.com/attr/Classification/value/S", "https://example.com/attr/Classification/value/X") - ); - } else { - config = writerConfig; - } - - String plainText = "Virtru!!"; - ByteBuffer byteBuffer = ByteBuffer.wrap(plainText.getBytes()); - ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); - - NanoTDF nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasReg).build()); - nanoTDF.createNanoTDF(byteBuffer, tdfOutputStream, config); - - byte[] nanoTDFBytes = tdfOutputStream.toByteArray(); - ByteArrayOutputStream plainTextStream = new ByteArrayOutputStream(); - nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasReg).build()); - if (allowed) { - if (decryptConfig != null) { - nanoTDF.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, decryptConfig); - } else { - nanoTDF.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, platformUrl); - } - String out = new String(plainTextStream.toByteArray(), StandardCharsets.UTF_8); - assertThat(out).isEqualTo(plainText); - } else { - try { - if (decryptConfig != null) { - nanoTDF.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, decryptConfig); - } else { - nanoTDF.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, platformUrl); - } - assertThat(false).isTrue(); - } catch (SDKException e) { - assertThat(e.getMessage()).contains("KasAllowlist"); - } - } - } - - @Test - void kasAllowlistTests() throws Exception { - var kasUrlsSuccess = List.of( - "https://api.example.com/kas", - "https://other.org/kas2", - "http://localhost:8181/kas", - "https://localhost:8383/kas", - platformUrl+"/kas" - ); - var kasUrlsFail = List.of( - "http://api.example.com/kas", - "http://other.org/kas", - "https://localhost:8181/kas2", - "https://localhost:8282/kas2", - "https://localhost:8080/kas" - ); - for (String kasUrl : kasUrlsSuccess) { - runBasicTest(kasUrl, true, kasRegistryService, null, null); - } - for (String kasUrl : kasUrlsFail) { - runBasicTest(kasUrl, false, kasRegistryService, null, null); - } - - // test with kasAllowlist - runBasicTest("http://api.example.com/kas", true, null, Config.newNanoTDFReaderConfig(Config.WithNanoKasAllowlist("http://api.example.com/kas")), null); - runBasicTest(platformUrl+"/kas", false, null, Config.newNanoTDFReaderConfig(Config.WithNanoKasAllowlist("http://api.example.com/kas")), null); - - // test ignore kasAllowlist - runBasicTest(platformUrl+"/kas", true, null, Config.newNanoTDFReaderConfig(Config.WithNanoKasAllowlist("http://api.example.com/kas"), Config.WithNanoIgnoreKasAllowlist(true)), null); - } - - @Test - void collection() throws Exception { - var kasInfos = new ArrayList<>(); - var kasInfo = new Config.KASInfo(); - kasInfo.URL = "https://api.example.com/kas"; - kasInfo.PublicKey = null; - kasInfo.KID = KID; - kasInfos.add(kasInfo); - - Config.NanoTDFConfig config = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfos.toArray(new Config.KASInfo[0])), - Config.witDataAttributes("https://example.com/attr/Classification/value/S", - "https://example.com/attr/Classification/value/X"), - Config.withCollection() - ); - - ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[]{}); - - NanoTDF nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).build()); - ByteBuffer header = getHeaderBuffer(byteBuffer,nanoTDF, config); - for (int i = 0; i < Config.MAX_COLLECTION_ITERATION - 10; i++) { - config.collectionConfig.getHeaderInfo(); - - } - for (int i = 1; i < 10; i++) { - ByteBuffer newHeader = getHeaderBuffer(byteBuffer,nanoTDF, config); - assertThat(header).isEqualTo(newHeader); - } - - ByteBuffer newHeader = getHeaderBuffer(byteBuffer,nanoTDF, config); - assertThat(header).isNotEqualTo(newHeader); - } - - @Test - public void testNanoTDFWithPlainTextPolicy() throws Exception { - List sampleAttributes = List.of("https://example.com/attr/Classification/value/S"); - String sampleKasUrl = "https://api.example.com/kas"; - byte[] sampleData = "test-policy".getBytes(StandardCharsets.UTF_8); - - var kasInfos = new ArrayList(); - var kasInfo = new Config.KASInfo(); - kasInfo.URL = sampleKasUrl; - kasInfo.PublicKey = kasPublicKey; - kasInfo.KID = KID; - kasInfo.Algorithm = "ec:secp256r1"; - kasInfos.add(kasInfo); - - Config.NanoTDFConfig config = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfos.toArray(new Config.KASInfo[0])), - Config.witDataAttributes(sampleAttributes.toArray(new String[0])), - Config.withPolicyType(NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT) - ); - - ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); - NanoTDF nanoTDF = new NanoTDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()); - nanoTDF.createNanoTDF(ByteBuffer.wrap(sampleData), tdfOutputStream, config); - - byte[] tdfData = tdfOutputStream.toByteArray(); - Header header = new Header(ByteBuffer.wrap(tdfData)); - String policyJson = new String(header.getPolicyInfo().getEmbeddedPlainTextPolicy(), StandardCharsets.UTF_8); - - assertThat(policyJson) - .as("Policy JSON should contain the expected attribute") - .contains(sampleAttributes.get(0)); - } - - private ByteBuffer getHeaderBuffer(ByteBuffer input, NanoTDF nanoTDF, Config.NanoTDFConfig config) throws Exception { - ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); - nanoTDF.createNanoTDF(input, tdfOutputStream, config); - ByteBuffer tdf = ByteBuffer.wrap(tdfOutputStream.toByteArray()); - Header header = new Header(tdf); - return tdf.position(0).slice().limit(header.getTotalSize()); - } -} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/PolicyInfoTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/PolicyInfoTest.java deleted file mode 100644 index 8df3a0e7..00000000 --- a/sdk/src/test/java/io/opentdf/platform/sdk/PolicyInfoTest.java +++ /dev/null @@ -1,142 +0,0 @@ -package io.opentdf.platform.sdk; - -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 { - private PolicyInfo policyInfo; - - @BeforeEach - void setUp() { - policyInfo = new PolicyInfo(); - } - - - @Test - void settingAndGettingRemotePolicyUrl() { - String url = "http://test.com"; - policyInfo.setRemotePolicy(url); - assertEquals(url, policyInfo.getRemotePolicyUrl()); - } - - @Test - void gettingRemotePolicyUrlWhenPolicyIsNotRemote() { - policyInfo.setEmbeddedPlainTextPolicy(new byte[]{1, 2, 3}); - assertThrows(RuntimeException.class, () -> policyInfo.getRemotePolicyUrl()); - } - - @Test - void settingAndGettingEmbeddedPlainTextPolicy() { - byte[] policy = new byte[]{1, 2, 3}; - policyInfo.setEmbeddedPlainTextPolicy(policy); - assertArrayEquals(policy, policyInfo.getEmbeddedPlainTextPolicy()); - } - - @Test - void gettingEmbeddedPlainTextPolicyWhenPolicyIsNotPlainText() { - policyInfo.setRemotePolicy("http://test.com"); - assertThrows(RuntimeException.class, () -> policyInfo.getEmbeddedPlainTextPolicy()); - } - - @Test - void settingAndGettingEmbeddedEncryptedTextPolicy() { - byte[] policy = new byte[]{1, 2, 3}; - policyInfo.setEmbeddedEncryptedTextPolicy(policy); - assertArrayEquals(policy, policyInfo.getEmbeddedEncryptedTextPolicy()); - } - - @Test - void gettingEmbeddedEncryptedTextPolicyWhenPolicyIsNotEncrypted() { - policyInfo.setRemotePolicy("http://test.com"); - assertThrows(RuntimeException.class, () -> policyInfo.getEmbeddedEncryptedTextPolicy()); - } - - @Test - void settingAndGettingPolicyBinding() { - byte[] binding = new byte[]{1, 2, 3}; - 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 diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ResourceLocatorTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ResourceLocatorTest.java deleted file mode 100644 index 18142810..00000000 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ResourceLocatorTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package io.opentdf.platform.sdk; - -import java.nio.ByteBuffer; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class ResourceLocatorTest { - private ResourceLocator locator; - - @BeforeEach - void setUp() { - locator = new ResourceLocator(); - } - - @Test - void creatingResourceLocatorWithHttpUrl() { - String url = "http://test.com"; - locator = new ResourceLocator(url); - assertEquals(url, locator.getResourceUrl()); - } - - @Test - void creatingResourceLocatorWithHttpsUrl() { - String url = "https://test.com"; - locator = new ResourceLocator(url); - assertEquals(url, locator.getResourceUrl()); - } - - @Test - void creatingResourceLocatorWithUnsupportedProtocol() { - String url = "ftp://test.com"; - assertThrows(RuntimeException.class, () -> new ResourceLocator(url)); - } - - @Test - void creatingResourceLocatorFromBytes() { - String url = "http://test.com"; - ResourceLocator original = new ResourceLocator(url); - byte[] buffer = new byte[original.getTotalSize()]; - original.writeIntoBuffer(ByteBuffer.wrap(buffer)); - locator = new ResourceLocator(ByteBuffer.wrap(buffer)); - assertEquals(url, locator.getResourceUrl()); - } - - @Test - void writingResourceLocatorIntoBufferWithInsufficientSize() { - String url = "http://test.com"; - locator = new ResourceLocator(url); - ByteBuffer buffer = ByteBuffer.allocate(1); // Buffer with insufficient size - assertThrows(RuntimeException.class, () -> locator.writeIntoBuffer(buffer)); - } - - @ParameterizedTest - @MethodSource("provideUrlsAndIdentifiers") - void creatingResourceLocatorWithDifferentIdentifiers(String url, String identifier, int expectedLength, byte[] goldenIdentifier) { - locator = new ResourceLocator(url, identifier); - assertEquals(url, locator.getResourceUrl()); - assertEquals(identifier, locator.getIdentifierString()); - assertArrayEquals(goldenIdentifier, locator.getIdentifier()); - assertEquals(expectedLength, locator.getIdentifier().length); - } - - private static Stream provideUrlsAndIdentifiers() { - return Stream.of( - Arguments.of("http://test.com", "F", 2, new byte[]{70, 0}), - Arguments.of("http://test.com", "e0", 2, new byte[]{101,48}), - Arguments.of("http://test.com", "12345", 8, new byte[]{49,50,51,52,53,0,0,0}), - Arguments.of("http://test.com", "e0e0e0e0", 8, new byte[]{101, 48, 101, 48, 101, 48, 101, 48}), - Arguments.of("http://test.com", "e0e0e0e0e0e0e0e0", 32, new byte[]{101,48,101,48,101,48,101,48,101,48,101,48,101,48,101,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}), - Arguments.of("https://test.com", "e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0",32, new byte[]{101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48, 101, 48}) - ); - } - - @Test - void creatingResourceLocatorUnexpectedIdentifierType() { - String url = "http://test.com"; - String identifier = "unexpectedIdentifierunexpectedIdentifier"; - assertThrows(IllegalArgumentException.class, () -> new ResourceLocator(url, identifier)); - } - - @Test - void creatingResourceLocatorFromBufferWithIdentifier() { - String url = "http://test.com"; - String identifier = "e0"; - ResourceLocator original = new ResourceLocator(url, identifier); - byte[] buffer = new byte[original.getTotalSize()]; - original.writeIntoBuffer(ByteBuffer.wrap(buffer)); - locator = new ResourceLocator(ByteBuffer.wrap(buffer)); - assertEquals(url, locator.getResourceUrl()); - assertArrayEquals(identifier.getBytes(), locator.getIdentifier()); - } -} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SymmetricAndPayloadConfigTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SymmetricAndPayloadConfigTest.java deleted file mode 100644 index bd22820c..00000000 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SymmetricAndPayloadConfigTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.opentdf.platform.sdk; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - - -import static org.junit.jupiter.api.Assertions.*; - -class SymmetricAndPayloadConfigTest { - private SymmetricAndPayloadConfig config; - - @BeforeEach - void setUp() { - config = new SymmetricAndPayloadConfig(); - } - - @Test - void settingAndGettingSignatureFlag() { - config.setHasSignature(true); - assertTrue(config.hasSignature()); - config.setHasSignature(false); - assertFalse(config.hasSignature()); - } - - @Test - void settingAndGettingSignatureECCMode() { - for (NanoTDFType.ECCurve curve : NanoTDFType.ECCurve.values()) { - if (curve != NanoTDFType.ECCurve.SECP256K1) { // SDK doesn't support 'secp256k1' curve - config.setSignatureECCMode(curve); - assertEquals(curve, config.getSignatureECCMode()); - } - } - } - - @Test - void settingUnsupportedSignatureECCMode() { - assertThrows(RuntimeException.class, () -> config.setSignatureECCMode(NanoTDFType.ECCurve.SECP256K1)); - } - - @Test - void settingAndGettingCipherType() { - for (NanoTDFType.Cipher cipher : NanoTDFType.Cipher.values()) { - config.setSymmetricCipherType(cipher); - assertEquals(cipher, config.getCipherType()); - } - } - - @Test - void gettingSymmetricAndPayloadConfigAsByte() { - config.setHasSignature(true); - config.setSignatureECCMode(NanoTDFType.ECCurve.SECP256R1); - config.setSymmetricCipherType(NanoTDFType.Cipher.AES_256_GCM_64_TAG); - byte expected = (byte) (1 << 7 | 0x00 << 4 | 0x00); - assertEquals(expected, config.getSymmetricAndPayloadConfigAsByte()); - } -} \ No newline at end of file diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java index 08c6b8aa..d46eb6ac 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java @@ -7,7 +7,6 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; @@ -63,42 +62,4 @@ public void createAndDecryptTdfIT() throws Exception { assertThat(unwrappedData.toString(StandardCharsets.UTF_8)).isEqualTo("text"); } } - - @Test @Disabled("this needs the backend services running to work") - public void createAndDecryptNanoTDF() throws Exception { - var sdk = SDKBuilder - .newBuilder() - .clientSecret("opentdf-sdk", "secret") - .useInsecurePlaintextConnection(true) - .platformEndpoint("localhost:8080") - .buildServices() - .services; - - var kasInfo = new Config.KASInfo(); - kasInfo.URL = "http://localhost:8080"; - - for (NanoTDFType.PolicyType policyType : List.of( - NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT, - NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED)) { - - Config.NanoTDFConfig config = Config.newNanoTDFConfig( - Config.withNanoKasInformation(kasInfo), - Config.withPolicyType(policyType) - ); - - String plainText = "text"; - ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream(); - - NanoTDF ntdf = new NanoTDF(sdk); - ntdf.createNanoTDF(ByteBuffer.wrap(plainText.getBytes()), tdfOutputStream, config); - - byte[] nanoTDFBytes = tdfOutputStream.toByteArray(); - ByteArrayOutputStream plainTextStream = new ByteArrayOutputStream(); - ntdf.readNanoTDF(ByteBuffer.wrap(nanoTDFBytes), plainTextStream, - Config.newNanoTDFReaderConfig(Config.WithNanoIgnoreKasAllowlist(true))); - - String out = new String(plainTextStream.toByteArray(), StandardCharsets.UTF_8); - assertThat(out).isEqualTo("text"); - } - } } \ No newline at end of file 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 c28bd2bd..3c58de4d 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -106,16 +106,6 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessio } } - @Override - public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { - return null; - } - - @Override - public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL) { - return null; - } - @Override public KASKeyCache getKeyCache() { return new KASKeyCache(); diff --git a/sdk/src/test/resources/javasdknanotdf.ntdf b/sdk/src/test/resources/javasdknanotdf.ntdf deleted file mode 100644 index 4c51f514..00000000 Binary files a/sdk/src/test/resources/javasdknanotdf.ntdf and /dev/null differ