Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ name: "Checks"

on:
pull_request:
branches:
- main
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove, just needed to trigger ci on this pr

push:
branches:
- main
Expand Down Expand Up @@ -139,7 +137,7 @@ jobs:
--client-secret=secret \
--platform-endpoint=localhost:8080 \
-i \
encrypt --kas-url=localhost:8080 --mime-type=text/plain --attr https://example.com/attr/attr1/value/value1 -f data -m 'here is some metadata' > test.tdf
encrypt --kas-url=localhost:8080 --mime-type=text/plain --attr https://example.com/attr/attr1/value/value1 --autoconfigure=false -f data -m 'here is some metadata' > test.tdf

java -jar target/cmdline.jar \
--client-id=opentdf-sdk \
Expand Down
16 changes: 8 additions & 8 deletions cmdline/src/main/java/io/opentdf/platform/Command.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@
import javax.crypto.NoSuchPaddingException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
Expand All @@ -31,7 +29,6 @@
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;

@CommandLine.Command(name = "tdf")
class Command {
Expand All @@ -55,6 +52,7 @@ void encrypt(
@Option(names = {"-m", "--metadata"}, defaultValue = Option.NULL_VALUE) Optional<String> metadata,
// cant split on optional parameters
@Option(names = {"-a", "--attr"}, defaultValue = Option.NULL_VALUE) Optional<String> attributes,
@Option(names = {"-c", "--autoconfigure"}, defaultValue = Option.NULL_VALUE) Optional<Boolean> autoconfigure,
@Option(names = {"--mime-type"}, defaultValue = Option.NULL_VALUE) Optional<String> mimeType) throws
IOException, JOSEException {

Expand All @@ -68,15 +66,17 @@ void encrypt(
List<Consumer<Config.TDFConfig>> configs = new ArrayList<>();
configs.add(Config.withKasInformation(kasInfos));
metadata.map(Config::withMetaData).ifPresent(configs::add);
autoconfigure.map(Config::withAutoconfigure).ifPresent(configs::add);
mimeType.map(Config::withMimeType).ifPresent(configs::add);
attributes.ifPresent(attr -> {
configs.add(Config.withDataAttributes(attr.split(",")));
});

if (attributes.isPresent()){
configs.add(Config.withDataAttributes(attributes.get().split(",")));
}
var tdfConfig = Config.newTDFConfig(configs.toArray(Consumer[]::new));
try (var in = file.isEmpty() ? new BufferedInputStream(System.in) : new FileInputStream(file.get())) {
try (var out = new BufferedOutputStream(System.out)) {
new TDF().createTDF(in, out, tdfConfig, sdk.getServices().kas());
new TDF().createTDF(in, out, tdfConfig,
sdk.getServices().kas(),
sdk.getServices().attributes());
}
}
}
Expand Down
29 changes: 22 additions & 7 deletions sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.opentdf.platform.sdk;

import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -31,7 +32,7 @@
import io.opentdf.platform.policy.AttributeRuleTypeEnum;

// Error handling class
class AutoConfigureException extends Exception {
class AutoConfigureException extends IOException {
public AutoConfigureException(String message) {
super(message);
}
Expand Down Expand Up @@ -165,6 +166,20 @@ public AttributeValueFQN(String url) throws AutoConfigureException {
public String toString() {
return url;
}

public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof AttributeValueFQN)) {
return false;
}
AttributeValueFQN afqn = (AttributeValueFQN) obj;
if ((this.url.equals(afqn.url)) && (this.key.equals(afqn.key))){
return true;
}
return false;
}

public String getKey() {
return key;
Expand Down Expand Up @@ -273,7 +288,7 @@ public interface StringOperator {
}


public List<SplitStep> plan(List<String> defaultKas, Supplier<String> genSplitID) throws Exception {
public List<SplitStep> plan(List<String> defaultKas, Supplier<String> genSplitID) throws AutoConfigureException {
AttributeBooleanExpression b = constructAttributeBoolean();
BooleanKeyExpression k = insertKeysForAttribute(b);
if (k == null) {
Expand Down Expand Up @@ -307,7 +322,7 @@ public List<SplitStep> plan(List<String> defaultKas, Supplier<String> genSplitID
return steps;
}

public BooleanKeyExpression insertKeysForAttribute(AttributeBooleanExpression e) throws Exception {
public BooleanKeyExpression insertKeysForAttribute(AttributeBooleanExpression e) throws AutoConfigureException {
List<KeyClause> kcs = new ArrayList<>(e.must.size());

for (SingleAttributeClause clause : e.must) {
Expand All @@ -316,7 +331,7 @@ public BooleanKeyExpression insertKeysForAttribute(AttributeBooleanExpression e)
for (AttributeValueFQN term : clause.values) {
KeyAccessGrant grant = byAttribute(term);
if (grant == null) {
throw new Exception(String.format("no definition or grant found for [%s]", term));
throw new AutoConfigureException(String.format("no definition or grant found for [%s]", term));
}

List<String> kases = grant.kases;
Expand Down Expand Up @@ -660,7 +675,7 @@ public static String ruleToOperator(AttributeRuleTypeEnum e) {
}

// Gets a list of directory of KAS grants for a list of attribute FQNs
public static Granter newGranterFromService(SDK.AttributesService as, AttributeValueFQN... fqns) throws Exception {
public static Granter newGranterFromService(SDK.AttributesService as, AttributeValueFQN... fqns) throws AutoConfigureException {
String[] fqnsStr = new String[fqns.length];
for (int i = 0; i < fqns.length; i++) {
fqnsStr[i] = fqns[i].toString();
Expand Down Expand Up @@ -704,7 +719,7 @@ public static Granter newGranterFromService(SDK.AttributesService as, AttributeV
// Given a policy (list of data attributes or tags),
// get a set of grants from attribute values to KASes.
// Unlike `NewGranterFromService`, this works offline.
public static Granter newGranterFromAttributes(Value... attrs) throws Exception {
public static Granter newGranterFromAttributes(Value... attrs) throws AutoConfigureException {
List<AttributeValueFQN> policyList = new ArrayList<>(attrs.length);
Map<String, KeyAccessGrant> grantsMap = new HashMap<>();

Expand All @@ -721,7 +736,7 @@ public static Granter newGranterFromAttributes(Value... attrs) throws Exception
grants.policy.add(fqn);
Attribute def = v.getAttribute();
if (def == null) {
throw new Exception("No associated definition with value [" + fqn.toString() + "]");
throw new AutoConfigureException("No associated definition with value [" + fqn.toString() + "]");
}

grants.addAllGrants(fqn, def.getGrantsList(), def);
Expand Down
64 changes: 61 additions & 3 deletions sdk/src/main/java/io/opentdf/platform/sdk/Config.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.opentdf.platform.sdk;

import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN;
import io.opentdf.platform.sdk.nanotdf.ECCMode;
import io.opentdf.platform.sdk.nanotdf.NanoTDFType;
import io.opentdf.platform.sdk.nanotdf.SymmetricAndPayloadConfig;

import io.opentdf.platform.policy.Value;

import java.util.*;
import java.util.function.Consumer;

Expand All @@ -30,6 +33,7 @@ public static class KASInfo {
public String URL;
public String PublicKey;
public String KID;
public Boolean Default;
}


Expand Down Expand Up @@ -70,6 +74,7 @@ public static Consumer<TDFReaderConfig> withAssertionVerificationKeys(AssertionV
}

public static class TDFConfig {
public Boolean autoconfigure;
public int defaultSegmentSize;
public boolean enableEncryption;
public TDFFormat tdfFormat;
Expand All @@ -78,13 +83,15 @@ public static class TDFConfig {
public String metaData;
public IntegrityAlgorithm integrityAlgorithm;
public IntegrityAlgorithm segmentIntegrityAlgorithm;
public List<String> attributes;
public List<Autoconfigure.AttributeValueFQN> attributes;
public List<Value> attributeValues;
public List<KASInfo> kasInfoList;
public List<io.opentdf.platform.sdk.AssertionConfig> assertionConfigList;
public String mimeType;
public List<Autoconfigure.SplitStep> splitPlan;

public TDFConfig() {
this.autoconfigure = true;
this.defaultSegmentSize = DEFAULT_SEGMENT_SIZE;
this.enableEncryption = true;
this.tdfFormat = TDFFormat.JSONFormat;
Expand All @@ -107,9 +114,46 @@ public static TDFConfig newTDFConfig(Consumer<TDFConfig>... options) {
return config;
}

public static Consumer<TDFConfig> withDataAttributes(String... attributes) {
public static Consumer<TDFConfig> withDataAttributes(String... attributes) throws AutoConfigureException {
List<Autoconfigure.AttributeValueFQN> attrValFqns = new ArrayList<Autoconfigure.AttributeValueFQN>();
for (String a : attributes){
Autoconfigure.AttributeValueFQN attrValFqn = new Autoconfigure.AttributeValueFQN(a);
attrValFqns.add(attrValFqn);
}
return (TDFConfig config) -> {
Collections.addAll(config.attributes, attributes);
config.attributeValues = null;
config.attributes.addAll(attrValFqns);
};
}

public static Consumer<TDFConfig> withDataAttributeValues(String... attributes) throws AutoConfigureException {
List<Autoconfigure.AttributeValueFQN> attrValFqns = new ArrayList<Autoconfigure.AttributeValueFQN>();
for (String a : attributes){
Autoconfigure.AttributeValueFQN attrValFqn = new Autoconfigure.AttributeValueFQN(a);
attrValFqns.add(attrValFqn);
}
return (TDFConfig config) -> {
config.attributeValues = null;
config.attributes.addAll(attrValFqns);
};
}

// WithDataAttributeValues appends the given data attributes to the bound policy.
// Unlike `WithDataAttributes`, this will not trigger an attribute definition lookup
// during autoconfigure. That is, to use autoconfigure in an 'offline' context,
// you must first store the relevant attribute information locally and load
// it to the `CreateTDF` method with this option.
public static Consumer<TDFConfig> withDataAttributeValues(Value... attributes) throws AutoConfigureException {
List<Autoconfigure.AttributeValueFQN> attrValFqns = new ArrayList<Autoconfigure.AttributeValueFQN>();
List<Value> attrVals = new ArrayList<Value>();
for (Value a : attributes) {
attrVals.add(a);
AttributeValueFQN afqn = new Autoconfigure.AttributeValueFQN(a.getFqn());
attrValFqns.add(afqn);
}
return (TDFConfig config) -> {
config.attributes.addAll(attrValFqns);
config.attributeValues.addAll(attrVals);
};
}

Expand All @@ -119,6 +163,13 @@ public static Consumer<TDFConfig> withKasInformation(KASInfo... kasInfoList) {
};
}

public static Consumer<TDFConfig> withSplitPlan(Autoconfigure.SplitStep... p) {
return (TDFConfig config) -> {
config.splitPlan = new ArrayList<>(Arrays.asList(p));
config.autoconfigure = false;
};
}

public static Consumer<TDFConfig> withAssertionConfig(io.opentdf.platform.sdk.AssertionConfig... assertionList) {
return (TDFConfig config) -> {
Collections.addAll(config.assertionConfigList, assertionList);
Expand All @@ -133,6 +184,13 @@ public static Consumer<TDFConfig> withSegmentSize(int size) {
return (TDFConfig config) -> config.defaultSegmentSize = size;
}

public static Consumer<TDFConfig> withAutoconfigure(boolean enable) {
return (TDFConfig config) -> {
config.autoconfigure = enable;
config.splitPlan = null;
};
}

// public static Consumer<TDFConfig> withDisableEncryption() {
// return (TDFConfig config) -> config.enableEncryption = false;
// }
Expand Down
59 changes: 52 additions & 7 deletions sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

import io.opentdf.platform.policy.Value;

import io.opentdf.platform.sdk.Config.TDFConfig;
import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN;
import io.opentdf.platform.sdk.Config.KASInfo;

import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.MACSigner;
import org.apache.commons.codec.DecoderException;
Expand Down Expand Up @@ -148,16 +154,16 @@ public TDFObject() {
this.size = 0;
}

PolicyObject createPolicyObject(List<String> attributes) {
PolicyObject createPolicyObject(List<Autoconfigure.AttributeValueFQN> attributes) {
PolicyObject policyObject = new PolicyObject();
policyObject.body = new PolicyObject.Body();
policyObject.uuid = UUID.randomUUID().toString();
policyObject.body.dataAttributes = new ArrayList<>();
policyObject.body.dissem = new ArrayList<>();

for (String attribute: attributes) {
for (Autoconfigure.AttributeValueFQN attribute: attributes) {
PolicyObject.AttributeObject attributeObject = new PolicyObject.AttributeObject();
attributeObject.attribute = attribute;
attributeObject.attribute = attribute.toString();
policyObject.body.dataAttributes.add(attributeObject);
}
return policyObject;
Expand All @@ -172,7 +178,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) {
String base64PolicyObject = encoder.encodeToString(gson.toJson(policyObject).getBytes(StandardCharsets.UTF_8));
List<byte[]> symKeys = new ArrayList<>();
Map<String, Config.KASInfo> latestKASInfo = new HashMap<>();
if (tdfConfig.splitPlan.isEmpty()) {
if (tdfConfig.splitPlan == null || tdfConfig.splitPlan.isEmpty()) {
// Default split plan: Split keys across all KASes
List<Autoconfigure.SplitStep> splitPlan = new ArrayList<>(tdfConfig.kasInfoList.size());
int i = 0;
Expand Down Expand Up @@ -267,6 +273,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) {
keyAccess.policyBinding = policyBinding;
keyAccess.wrappedKey = encoder.encodeToString(wrappedKey);
keyAccess.encryptedMetadata = encryptedMetadata;
keyAccess.sid = splitID;

manifest.encryptionInformation.keyAccessObj.add(keyAccess);
}
Expand Down Expand Up @@ -369,9 +376,30 @@ private static String calculateSignature(byte[] data, byte[] secret, Config.Inte

public TDFObject createTDF(InputStream payload,
OutputStream outputStream,
Config.TDFConfig tdfConfig, SDK.KAS kas) throws IOException, JOSEException {
if (tdfConfig.kasInfoList.isEmpty()) {
throw new KasInfoMissing("kas information is missing");
Config.TDFConfig tdfConfig, SDK.KAS kas, SDK.AttributesService attrService) throws IOException, JOSEException {

if (tdfConfig.autoconfigure) {
Autoconfigure.Granter granter = new Autoconfigure.Granter(new ArrayList<>());
if (tdfConfig.attributeValues != null && !tdfConfig.attributeValues.isEmpty()) {
granter = Autoconfigure.newGranterFromAttributes(tdfConfig.attributeValues.toArray(new Value[0]));
} else if (tdfConfig.attributes != null && !tdfConfig.attributes.isEmpty()) {
granter = Autoconfigure.newGranterFromService(attrService, tdfConfig.attributes.toArray(new AttributeValueFQN[0]));
}

if (granter == null) {
throw new AutoConfigureException("Failed to create Granter"); // Replace with appropriate error handling
}

List<String> dk = defaultKases(tdfConfig);
tdfConfig.splitPlan = granter.plan(dk, () -> UUID.randomUUID().toString());

if (tdfConfig.splitPlan == null) {
throw new AutoConfigureException("Failed to generate Split Plan"); // Replace with appropriate error handling
}
}

if (tdfConfig.kasInfoList.isEmpty() && (tdfConfig.splitPlan==null || tdfConfig.splitPlan.isEmpty())) {
throw new KasInfoMissing("kas information is missing, no key access template specified or inferred");
}

TDFObject tdfObject = new TDFObject();
Expand Down Expand Up @@ -495,6 +523,23 @@ public TDFObject createTDF(InputStream payload,
return tdfObject;
}

public List<String> defaultKases(TDFConfig config) {
List<String> allk = new ArrayList<>();
List<String> defk = new ArrayList<>();

for (KASInfo kasInfo : config.kasInfoList) {
if (kasInfo.Default != null && kasInfo.Default) {
defk.add(kasInfo.URL);
} else if (defk.isEmpty()) {
allk.add(kasInfo.URL);
}
}
if (defk.isEmpty()) {
return allk;
}
return defk;
}

private void fillInPublicKeyInfo(List<Config.KASInfo> kasInfoList, SDK.KAS kas) {
for (var kasInfo: kasInfoList) {
if (kasInfo.PublicKey != null && !kasInfo.PublicKey.isBlank()) {
Expand Down
Loading