Skip to content

feat(sdk)!: add base key and support for key grants in ZTDF #271

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a2d8b83
minor refactor
mkleene Jun 18, 2025
4b6a10e
now at least we are adding things from grants
mkleene Jun 26, 2025
075b4ac
just saving
mkleene Jun 30, 2025
744220e
just saving
mkleene Jun 30, 2025
c8b458f
saving
mkleene Jun 30, 2025
9fb0bd6
get tests passing again
mkleene Jun 30, 2025
b71f57e
pull the planning inside of autoconfigure
mkleene Jul 1, 2025
2d4f3e6
cleanup
mkleene Jul 1, 2025
65a4987
debug
mkleene Jul 1, 2025
2cfd3ec
only plan when we mean to
mkleene Jul 1, 2025
98653b9
restructure
mkleene Jul 1, 2025
e900df3
add some tests
mkleene Jul 2, 2025
78031d8
one more test
mkleene Jul 2, 2025
8c0bcf4
add test for resolving keys
mkleene Jul 2, 2025
c8370e7
added a split
mkleene Jul 2, 2025
41f3d6a
fix up tests
mkleene Jul 3, 2025
491cbf3
a couple more tests
mkleene Jul 3, 2025
26016f3
remove condition we do not need
mkleene Jul 3, 2025
1ba4cd5
add test for default kases
mkleene Jul 3, 2025
95232ad
add more tests
mkleene Jul 3, 2025
7f5a2d4
more tests
mkleene Jul 3, 2025
76085d3
sonarcloud
mkleene Jul 3, 2025
d34056e
more sonar
mkleene Jul 3, 2025
dfc3638
one more tiny one
mkleene Jul 3, 2025
93910da
more sonar
mkleene Jul 3, 2025
f94c1df
gemini suggestions
mkleene Jul 3, 2025
4e6be46
more gemini
mkleene Jul 3, 2025
f1a3244
even more (incorrect) gemini
mkleene Jul 3, 2025
f11faf9
Merge branch 'main' into base-key
mkleene Jul 10, 2025
bd64b41
Merge branch 'main' into base-key
mkleene Jul 21, 2025
a5f0c64
Merge remote-tracking branch 'origin/main' into base-key
mkleene Jul 21, 2025
aaebc79
clarify
mkleene Jul 21, 2025
f8aff1b
thread the algorithm through
mkleene Jul 22, 2025
e233de4
clarify a bit
mkleene Jul 22, 2025
cb94c03
add a key
mkleene Jul 22, 2025
98b95d9
test errors
mkleene Jul 22, 2025
e20ad25
remove unused methods
mkleene Jul 22, 2025
c442ff8
pump those coverage numbers
mkleene Jul 22, 2025
4e3b907
fix test
mkleene Jul 22, 2025
9124da8
Update KeyType.java
mkleene Jul 22, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import io.opentdf.platform.sdk.*;

import java.util.Collections;
import java.util.concurrent.ExecutionException;

import java.util.List;

Expand Down
519 changes: 333 additions & 186 deletions sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java

Large diffs are not rendered by default.

43 changes: 41 additions & 2 deletions sdk/src/main/java/io/opentdf/platform/sdk/Config.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package io.opentdf.platform.sdk;

import io.opentdf.platform.policy.KeyAccessServer;
import io.opentdf.platform.policy.SimpleKasKey;
import io.opentdf.platform.policy.Value;
import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Configuration class for setting various configurations related to TDF.
Expand All @@ -22,6 +27,7 @@ public class Config {
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 {
JSONFormat,
Expand All @@ -33,8 +39,6 @@ public enum IntegrityAlgorithm {
GMAC
}

public static final int K_HTTP_OK = 200;

public static class KASInfo implements Cloneable {
public String URL;
public String PublicKey;
Expand Down Expand Up @@ -71,6 +75,36 @@ public String toString() {
}
return sb.append("}").toString();
}

public static List<KASInfo> fromKeyAccessServer(KeyAccessServer kas) {
var keys = kas.getPublicKey().getCached().getKeysList();
if (keys.isEmpty()) {
logger.warn("Invalid KAS key mapping for kas [{}]: publicKey is empty", kas.getUri());
return Collections.emptyList();
}
return keys.stream().flatMap(ki -> {
if (ki.getPem().isEmpty()) {
logger.warn("Invalid KAS key mapping for kas [{}]: publicKey PEM is empty", kas.getUri());
return Stream.empty();
}
Config.KASInfo kasInfo = new Config.KASInfo();
kasInfo.URL = kas.getUri();
kasInfo.KID = ki.getKid();
kasInfo.Algorithm = KeyType.fromPublicKeyAlgorithm(ki.getAlg()).toString();
kasInfo.PublicKey = ki.getPem();
return Stream.of(kasInfo);
}).collect(Collectors.toList());
}

public static KASInfo fromSimpleKasKey(SimpleKasKey ki) {
Config.KASInfo kasInfo = new Config.KASInfo();
kasInfo.URL = ki.getKasUri();
kasInfo.KID = ki.getPublicKey().getKid();
kasInfo.Algorithm = KeyType.fromAlgorithm(ki.getPublicKey().getAlgorithm()).toString();
kasInfo.PublicKey = ki.getPublicKey().getPem();

return kasInfo;
}
}

public static class AssertionVerificationKeys {
Expand Down Expand Up @@ -239,6 +273,11 @@ public static Consumer<TDFConfig> withKasInformation(KASInfo... kasInfoList) {
};
}

/**
* Deprecated since 9.1.0, will be removed. To produce key shares use
* the key mapping feature
*/
@Deprecated(since = "9.1.0", forRemoval = true)
public static Consumer<TDFConfig> withSplitPlan(Autoconfigure.KeySplitStep... p) {
return (TDFConfig config) -> {
config.splitPlan = new ArrayList<>(Arrays.asList(p));
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve)

@Override
public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) {
Config.KASInfo cachedValue = this.kasKeyCache.get(kasInfo.URL, kasInfo.Algorithm);
Config.KASInfo cachedValue = this.kasKeyCache.get(kasInfo.URL, kasInfo.Algorithm, kasInfo.KID);
if (cachedValue != null) {
return cachedValue;
}
Expand Down
41 changes: 23 additions & 18 deletions sdk/src/main/java/io/opentdf/platform/sdk/KASKeyCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
* Class representing a cache for KAS (Key Access Server) information.
Expand All @@ -24,14 +25,14 @@ public void clear() {
this.cache = new HashMap<>();
}

public Config.KASInfo get(String url, String algorithm) {
log.debug("retrieving kasinfo for url = [{}], algorithm = [{}]", url, algorithm);
KASKeyRequest cacheKey = new KASKeyRequest(url, algorithm);
public Config.KASInfo get(String url, String algorithm, String kid) {
log.debug("retrieving kasinfo for url = [{}], algorithm = [{}], kid = [{}]", url, algorithm, kid);
KASKeyRequest cacheKey = new KASKeyRequest(url, algorithm, kid);
LocalDateTime now = LocalDateTime.now();
TimeStampedKASInfo cachedValue = cache.get(cacheKey);

if (cachedValue == null) {
log.debug("didn't find kasinfo for url = [{}], algorithm = [{}]", url, algorithm);
log.debug("didn't find kasinfo for key= [{}]", cacheKey);
return null;
}

Expand All @@ -49,7 +50,7 @@ public Config.KASInfo get(String url, String algorithm) {

public void store(Config.KASInfo kasInfo) {
log.debug("storing kasInfo into the cache {}", kasInfo);
KASKeyRequest cacheKey = new KASKeyRequest(kasInfo.URL, kasInfo.Algorithm);
KASKeyRequest cacheKey = new KASKeyRequest(kasInfo.URL, kasInfo.Algorithm, kasInfo.KID);
cache.put(cacheKey, new TimeStampedKASInfo(kasInfo, LocalDateTime.now()));
}
}
Expand Down Expand Up @@ -85,30 +86,34 @@ public TimeStampedKASInfo(Config.KASInfo kasInfo, LocalDateTime timestamp) {
class KASKeyRequest {
private String url;
private String algorithm;
private String kid;

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

// 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;
if (o == null || getClass() != o.getClass()) return false;
KASKeyRequest that = (KASKeyRequest) o;
if (algorithm == null){
return url.equals(that.url);
}
return url.equals(that.url) && algorithm.equals(that.algorithm);
return Objects.equals(url, that.url) && Objects.equals(algorithm, that.algorithm) && Objects.equals(kid, that.kid);
}

@Override
public int hashCode() {
int result = 31 * url.hashCode();
if (algorithm != null) {
result = result + algorithm.hashCode();
}
return result;
return Objects.hash(url, algorithm, kid);
}

@Override
public String toString() {
return "KASKeyRequest{" +
"url='" + url + '\'' +
", algorithm='" + algorithm + '\'' +
", kid='" + kid + '\'' +
'}';
}

// Override equals and hashCode to ensure proper functioning of the HashMap
}
41 changes: 40 additions & 1 deletion sdk/src/main/java/io/opentdf/platform/sdk/KeyType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.opentdf.platform.sdk;

import io.opentdf.platform.policy.Algorithm;
import io.opentdf.platform.policy.KasPublicKeyAlgEnum;

import javax.annotation.Nonnull;

import static io.opentdf.platform.sdk.NanoTDFType.ECCurve.SECP256R1;
Expand Down Expand Up @@ -46,7 +49,43 @@ public static KeyType fromString(String keyType) {
throw new IllegalArgumentException("No enum constant for key type: " + keyType);
}

public static KeyType fromAlgorithm(Algorithm algorithm) {
if (algorithm == null) {
throw new IllegalArgumentException("Algorithm cannot be null");
}
switch (algorithm) {
case ALGORITHM_RSA_2048:
return KeyType.RSA2048Key;
case ALGORITHM_EC_P256:
return KeyType.EC256Key;
case ALGORITHM_EC_P384:
return KeyType.EC384Key;
case ALGORITHM_EC_P521:
return KeyType.EC521Key;
default:
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
}

public static KeyType fromPublicKeyAlgorithm(KasPublicKeyAlgEnum algorithm) {
if (algorithm == null) {
throw new IllegalArgumentException("Algorithm cannot be null");
}
switch (algorithm) {
case KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048:
return KeyType.RSA2048Key;
case KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1:
return KeyType.EC256Key;
case KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1:
return KeyType.EC384Key;
case KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1:
return KeyType.EC521Key;
default:
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
}

public boolean isEc() {
return this.curve != null;
}
}
}
Loading