Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
01cd832
add it to the manifest
elizabethhealy Aug 13, 2024
35f0a71
add split logic on decrypt
elizabethhealy Aug 13, 2024
ec5e91b
update cli exception handling
elizabethhealy Aug 13, 2024
484dcaa
make custom exception type
elizabethhealy Aug 13, 2024
4552d07
null check
elizabethhealy Aug 13, 2024
8b006dd
check for null pointer
elizabethhealy Aug 13, 2024
fb889a3
encrypt w key splits
elizabethhealy Aug 14, 2024
03fae78
try to fix test, exception fix, sets
elizabethhealy Aug 14, 2024
ee193e3
fix test
elizabethhealy Aug 14, 2024
93bae72
Merge branch 'main' into split-key-implementation
elizabethhealy Aug 14, 2024
44a8d96
set up attributes client
elizabethhealy Aug 16, 2024
895592e
add autoconfigure
elizabethhealy Aug 19, 2024
7828af8
Merge branch 'main' into add-attributes-client
elizabethhealy Aug 19, 2024
caccf55
Merge branch 'autoconfiguration-key-splitting' into add-multikas-auto…
elizabethhealy Aug 19, 2024
3dfb9c3
Merge branch 'main' into add-multikas-autoconfigure
elizabethhealy Aug 19, 2024
3610633
most tests working
elizabethhealy Aug 19, 2024
8d2ea13
tests passing
elizabethhealy Aug 20, 2024
dc8a1ba
multikas e2e (#122)
elizabethhealy Aug 20, 2024
b17950c
fix ci trigger
elizabethhealy Aug 20, 2024
8d512f7
Merge branch 'main' into add-multikas-autoconfigure
elizabethhealy Aug 20, 2024
d879fff
addressing some comments
elizabethhealy Aug 20, 2024
d337c92
resolving more suggestions
elizabethhealy Aug 20, 2024
1ac7a20
hashcode
elizabethhealy Aug 20, 2024
42d05e9
Merge branch 'main' into add-multikas-autoconfigure
elizabethhealy Aug 20, 2024
06aedff
reverting attributes client change
elizabethhealy Aug 20, 2024
ac005f9
adding key cache
elizabethhealy Aug 21, 2024
744f660
add tests, get keys from grants
elizabethhealy Aug 21, 2024
1a25269
Merge branch 'main' into add-kas-key-cache
elizabethhealy Aug 21, 2024
7644218
more tests
elizabethhealy Aug 22, 2024
64c76c3
change to 5 mins
elizabethhealy Aug 22, 2024
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
61 changes: 50 additions & 11 deletions sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse.AttributeAndValue;
import io.opentdf.platform.policy.Attribute;
import io.opentdf.platform.policy.Value;
import io.opentdf.platform.policy.KasPublicKey;
import io.opentdf.platform.policy.AttributeValueSelector;
import io.opentdf.platform.policy.AttributeRuleTypeEnum;
import io.opentdf.platform.policy.KasPublicKeyAlgEnum;

// Attribute rule types: operators!
class RuleType {
Expand All @@ -48,24 +50,29 @@ public class Autoconfigure {

public static Logger logger = LoggerFactory.getLogger(Autoconfigure.class);

public static class SplitStep {
public static class KeySplitStep {
public String kas;
public String splitID;

public SplitStep(String kas, String splitId) {
public KeySplitStep(String kas, String splitId) {
this.kas = kas;
this.splitID = splitId;
}

@Override
public String toString() {
return "KeySplitStep{kas=" + this.kas + ", splitID=" + this.splitID + "}";
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof SplitStep)) {
if (obj == null || !(obj instanceof KeySplitStep)) {
return false;
}
SplitStep ss = (SplitStep) obj;
KeySplitStep ss = (KeySplitStep) obj;
if ((this.kas.equals(ss.kas)) && (this.splitID.equals(ss.splitID))){
return true;
}
Expand Down Expand Up @@ -287,7 +294,7 @@ public KeyAccessGrant byAttribute(AttributeValueFQN fqn) {
}


public List<SplitStep> plan(List<String> defaultKas, Supplier<String> genSplitID) throws AutoConfigureException {
public List<KeySplitStep> plan(List<String> defaultKas, Supplier<String> genSplitID) throws AutoConfigureException {
AttributeBooleanExpression b = constructAttributeBoolean();
BooleanKeyExpression k = insertKeysForAttribute(b);
if (k == null) {
Expand All @@ -301,21 +308,21 @@ public List<SplitStep> plan(List<String> defaultKas, Supplier<String> genSplitID
if (defaultKas.isEmpty()) {
throw new AutoConfigureException("no default KAS specified; required for grantless plans");
} else if (defaultKas.size() == 1) {
return Collections.singletonList(new SplitStep(defaultKas.get(0), ""));
return Collections.singletonList(new KeySplitStep(defaultKas.get(0), ""));
} else {
List<SplitStep> result = new ArrayList<>();
List<KeySplitStep> result = new ArrayList<>();
for (String kas : defaultKas) {
result.add(new SplitStep(kas, genSplitID.get()));
result.add(new KeySplitStep(kas, genSplitID.get()));
}
return result;
}
}

List<SplitStep> steps = new ArrayList<>();
List<KeySplitStep> steps = new ArrayList<>();
for (KeyClause v : k.values) {
String splitID = (l > 1) ? genSplitID.get() : "";
for (PublicKeyInfo o : v.values) {
steps.add(new SplitStep(o.kas, splitID));
steps.add(new KeySplitStep(o.kas, splitID));
}
}
return steps;
Expand Down Expand Up @@ -668,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(AttributesServiceFutureStub as, AttributeValueFQN... fqns) throws AutoConfigureException, InterruptedException, ExecutionException {
public static Granter newGranterFromService(AttributesServiceFutureStub as, KASKeyCache keyCache, AttributeValueFQN... fqns) throws AutoConfigureException, InterruptedException, ExecutionException {
String[] fqnsStr = new String[fqns.length];
for (int i = 0; i < fqns.length; i++) {
fqnsStr[i] = fqns[i].toString();
Expand Down Expand Up @@ -699,9 +706,11 @@ public static Granter newGranterFromService(AttributesServiceFutureStub as, Attr
Value v = pair.getValue();
if (v != null && !v.getGrantsList().isEmpty()) {
grants.addAllGrants(fqn, v.getGrantsList(), def);
storeKeysToCache(v.getGrantsList(), keyCache);
} else {
if (def != null) {
grants.addAllGrants(fqn, def.getGrantsList(), def);
storeKeysToCache(def.getGrantsList(), keyCache);
}
}
}
Expand Down Expand Up @@ -738,4 +747,34 @@ public static Granter newGranterFromAttributes(Value... attrs) throws AutoConfig
return grants;
}

static void storeKeysToCache(List<KeyAccessServer> kases, KASKeyCache keyCache) {
for (KeyAccessServer kas : kases) {
List<KasPublicKey> keys = kas.getPublicKey().getCached().getKeysList();
if (keys.isEmpty()) {
logger.debug("No cached key in policy service for KAS: " + kas.getUri());
continue;
}
for (KasPublicKey ki : keys) {
Config.KASInfo kasInfo = new Config.KASInfo();
kasInfo.URL = kas.getUri();
kasInfo.KID = ki.getKid();
kasInfo.Algorithm = algProto2String(ki.getAlg());
kasInfo.PublicKey = ki.getPem();
keyCache.store(kasInfo);
}
}
}

private static String algProto2String(KasPublicKeyAlgEnum e) {
switch (e) {
case KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1:
return "ec:secp256r1";
case KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048:
return "rsa:2048";
case KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED:
default:
return "";
}
}

}
5 changes: 3 additions & 2 deletions sdk/src/main/java/io/opentdf/platform/sdk/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static class KASInfo {
public String PublicKey;
public String KID;
public Boolean Default;
public String Algorithm;
}


Expand Down Expand Up @@ -88,7 +89,7 @@ public static class TDFConfig {
public List<KASInfo> kasInfoList;
public List<io.opentdf.platform.sdk.AssertionConfig> assertionConfigList;
public String mimeType;
public List<Autoconfigure.SplitStep> splitPlan;
public List<Autoconfigure.KeySplitStep> splitPlan;

public TDFConfig() {
this.autoconfigure = true;
Expand Down Expand Up @@ -163,7 +164,7 @@ public static Consumer<TDFConfig> withKasInformation(KASInfo... kasInfoList) {
};
}

public static Consumer<TDFConfig> withSplitPlan(Autoconfigure.SplitStep... p) {
public static Consumer<TDFConfig> withSplitPlan(Autoconfigure.KeySplitStep... p) {
return (TDFConfig config) -> {
config.splitPlan = new ArrayList<>(Arrays.asList(p));
config.autoconfigure = false;
Expand Down
31 changes: 22 additions & 9 deletions sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.opentdf.platform.sdk;

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
Expand All @@ -12,6 +11,7 @@
import io.grpc.ManagedChannel;
import io.opentdf.platform.kas.AccessServiceGrpc;
import io.opentdf.platform.kas.PublicKeyRequest;
import io.opentdf.platform.kas.PublicKeyResponse;
import io.opentdf.platform.kas.RewrapRequest;
import io.opentdf.platform.sdk.nanotdf.ECKeyPair;
import io.opentdf.platform.sdk.nanotdf.NanoTDFType;
Expand All @@ -36,6 +36,8 @@ public class KASClient implements SDK.KAS, AutoCloseable {
private final AsymDecryption decryptor;
private final String publicKeyPEM;

private KASKeyCache kasKeyCache;

/***
* A client that communicates with KAS
* @param channelFactory A function that produces channels that can be used to communicate
Expand All @@ -51,6 +53,8 @@ public KASClient(Function <String, ManagedChannel> channelFactory, RSAKey dpopKe
var encryptionKeypair = CryptoUtils.generateRSAKeypair();
decryptor = new AsymDecryption(encryptionKeypair.getPrivate());
publicKeyPEM = CryptoUtils.getRSAPublicKeyPEM(encryptionKeypair.getPublic());

this.kasKeyCache = new KASKeyCache();
}

@Override
Expand All @@ -61,17 +65,26 @@ public String getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve)
}

@Override
public String getPublicKey(Config.KASInfo kasInfo) {
return getStub(kasInfo.URL)
.publicKey(PublicKeyRequest.getDefaultInstance())
.getPublicKey();
public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) {
Config.KASInfo cachedValue = this.kasKeyCache.get(kasInfo.URL, kasInfo.Algorithm);
if (cachedValue != null) {
return cachedValue;
}
PublicKeyResponse resp = getStub(kasInfo.URL).publicKey(PublicKeyRequest.getDefaultInstance());

var kiCopy = new Config.KASInfo();
kiCopy.KID = resp.getKid();
kiCopy.PublicKey = resp.getPublicKey();
kiCopy.URL = kasInfo.URL;
kiCopy.Algorithm = kasInfo.Algorithm;

this.kasKeyCache.store(kiCopy);
return kiCopy;
}

@Override
public String getKid(Config.KASInfo kasInfo) {
return getStub(kasInfo.URL)
.publicKey(PublicKeyRequest.getDefaultInstance())
.getKid();
public KASKeyCache getKeyCache(){
return this.kasKeyCache;
}

private String normalizeAddress(String urlString) {
Expand Down
82 changes: 82 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/KASKeyCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.opentdf.platform.sdk;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;

public class KASKeyCache {
Map<KASKeyRequest, TimeStampedKASInfo> cache;

public KASKeyCache() {
this.cache = new HashMap<>();
}

public void clear() {
this.cache = new HashMap<>();
}

public Config.KASInfo get(String url, String algorithm) {
KASKeyRequest cacheKey = new KASKeyRequest(url, algorithm);
LocalDateTime now = LocalDateTime.now();
TimeStampedKASInfo cachedValue = cache.get(cacheKey);

if (cachedValue == null) {
return null;
}

LocalDateTime aMinAgo = now.minus(5, ChronoUnit.MINUTES);
if (aMinAgo.isAfter(cachedValue.timestamp)) {
cache.remove(cacheKey);
return null;
}

return cachedValue.kasInfo;
}

public void store(Config.KASInfo kasInfo) {
KASKeyRequest cacheKey = new KASKeyRequest(kasInfo.URL, kasInfo.Algorithm);
cache.put(cacheKey, new TimeStampedKASInfo(kasInfo, LocalDateTime.now()));
}
}

class TimeStampedKASInfo {
Config.KASInfo kasInfo;
LocalDateTime timestamp;

public TimeStampedKASInfo(Config.KASInfo kasInfo, LocalDateTime timestamp) {
this.kasInfo = kasInfo;
this.timestamp = timestamp;
}
}

class KASKeyRequest {
private String url;
private String algorithm;

public KASKeyRequest(String url, String algorithm) {
this.url = url;
this.algorithm = algorithm;
}

// Override equals and hashCode to ensure proper functioning of the HashMap
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof KASKeyRequest)) return false;
KASKeyRequest that = (KASKeyRequest) o;
if (algorithm == null){
return url.equals(that.url);
}
return url.equals(that.url) && algorithm.equals(that.algorithm);
}

@Override
public int hashCode() {
int result = 31 * url.hashCode();
if (algorithm != null) {
result = result + algorithm.hashCode();
}
return result;
}
}
4 changes: 2 additions & 2 deletions sdk/src/main/java/io/opentdf/platform/sdk/SDK.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ public void close() throws Exception {
}

public interface KAS extends AutoCloseable {
String getPublicKey(Config.KASInfo kasInfo);
String getKid(Config.KASInfo kasInfo);
Config.KASInfo getPublicKey(Config.KASInfo kasInfo);
String getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve);
byte[] unwrap(Manifest.KeyAccess keyAccess, String policy);
byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL);
KASKeyCache getKeyCache();
}

// TODO: add KAS
Expand Down
Loading