Skip to content

Commit c3b79db

Browse files
authored
feat(sdk)!: add base key and support for key grants in ZTDF (#271)
Introduces the `Planner` class, which encapsulates the policy about how to create a split plan, basing its decision on `TDFConfig.autoconfigure`, whether KASes were provided in config, and whether a split plan was manually provided in config. Modifies the `Autoconfigure.Granter` class to consume key grants. When a key is specified using a KID the KID is used during key assignment. When there are no grants or mapped keys present `Autoconfigure.Granter` reaches out to the `wellknown` endpoint to download a base key and constructs a single element split plan. * update visibility of some internal classes * get rid of a bit of duplication * include a `WellknownServiceClientInterface` in the `SDK.Services` interface. Not sure if this is a good idea or not but it doesn't seem dangerous. * update some test classes
1 parent 03d5d41 commit c3b79db

File tree

18 files changed

+1391
-442
lines changed

18 files changed

+1391
-442
lines changed

examples/src/main/java/io/opentdf/platform/GetEntitlements.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import io.opentdf.platform.sdk.*;
88

99
import java.util.Collections;
10-
import java.util.concurrent.ExecutionException;
1110

1211
import java.util.List;
1312

sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java

Lines changed: 333 additions & 186 deletions
Large diffs are not rendered by default.

sdk/src/main/java/io/opentdf/platform/sdk/Config.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package io.opentdf.platform.sdk;
22

3+
import io.opentdf.platform.policy.KeyAccessServer;
4+
import io.opentdf.platform.policy.SimpleKasKey;
35
import io.opentdf.platform.policy.Value;
46
import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
59

610
import java.net.URI;
711
import java.net.URISyntaxException;
812
import java.util.*;
913
import java.util.function.Consumer;
1014
import java.util.stream.Collectors;
15+
import java.util.stream.Stream;
1116

1217
/**
1318
* Configuration class for setting various configurations related to TDF.
@@ -22,6 +27,7 @@ public class Config {
2227
public static final String KAS_PUBLIC_KEY_PATH = "/kas_public_key";
2328
public static final String DEFAULT_MIME_TYPE = "application/octet-stream";
2429
public static final int MAX_COLLECTION_ITERATION = (1 << 24) - 1;
30+
private static Logger logger = LoggerFactory.getLogger(Config.class);
2531

2632
public enum TDFFormat {
2733
JSONFormat,
@@ -33,8 +39,6 @@ public enum IntegrityAlgorithm {
3339
GMAC
3440
}
3541

36-
public static final int K_HTTP_OK = 200;
37-
3842
public static class KASInfo implements Cloneable {
3943
public String URL;
4044
public String PublicKey;
@@ -71,6 +75,36 @@ public String toString() {
7175
}
7276
return sb.append("}").toString();
7377
}
78+
79+
public static List<KASInfo> fromKeyAccessServer(KeyAccessServer kas) {
80+
var keys = kas.getPublicKey().getCached().getKeysList();
81+
if (keys.isEmpty()) {
82+
logger.warn("Invalid KAS key mapping for kas [{}]: publicKey is empty", kas.getUri());
83+
return Collections.emptyList();
84+
}
85+
return keys.stream().flatMap(ki -> {
86+
if (ki.getPem().isEmpty()) {
87+
logger.warn("Invalid KAS key mapping for kas [{}]: publicKey PEM is empty", kas.getUri());
88+
return Stream.empty();
89+
}
90+
Config.KASInfo kasInfo = new Config.KASInfo();
91+
kasInfo.URL = kas.getUri();
92+
kasInfo.KID = ki.getKid();
93+
kasInfo.Algorithm = KeyType.fromPublicKeyAlgorithm(ki.getAlg()).toString();
94+
kasInfo.PublicKey = ki.getPem();
95+
return Stream.of(kasInfo);
96+
}).collect(Collectors.toList());
97+
}
98+
99+
public static KASInfo fromSimpleKasKey(SimpleKasKey ki) {
100+
Config.KASInfo kasInfo = new Config.KASInfo();
101+
kasInfo.URL = ki.getKasUri();
102+
kasInfo.KID = ki.getPublicKey().getKid();
103+
kasInfo.Algorithm = KeyType.fromAlgorithm(ki.getPublicKey().getAlgorithm()).toString();
104+
kasInfo.PublicKey = ki.getPublicKey().getPem();
105+
106+
return kasInfo;
107+
}
74108
}
75109

76110
public static class AssertionVerificationKeys {
@@ -239,6 +273,11 @@ public static Consumer<TDFConfig> withKasInformation(KASInfo... kasInfoList) {
239273
};
240274
}
241275

276+
/**
277+
* Deprecated since 9.1.0, will be removed. To produce key shares use
278+
* the key mapping feature
279+
*/
280+
@Deprecated(since = "9.1.0", forRemoval = true)
242281
public static Consumer<TDFConfig> withSplitPlan(Autoconfigure.KeySplitStep... p) {
243282
return (TDFConfig config) -> {
244283
config.splitPlan = new ArrayList<>(Arrays.asList(p));

sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve)
9191

9292
@Override
9393
public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) {
94-
Config.KASInfo cachedValue = this.kasKeyCache.get(kasInfo.URL, kasInfo.Algorithm);
94+
Config.KASInfo cachedValue = this.kasKeyCache.get(kasInfo.URL, kasInfo.Algorithm, kasInfo.KID);
9595
if (cachedValue != null) {
9696
return cachedValue;
9797
}

sdk/src/main/java/io/opentdf/platform/sdk/KASKeyCache.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.time.temporal.ChronoUnit;
88
import java.util.HashMap;
99
import java.util.Map;
10+
import java.util.Objects;
1011

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

27-
public Config.KASInfo get(String url, String algorithm) {
28-
log.debug("retrieving kasinfo for url = [{}], algorithm = [{}]", url, algorithm);
29-
KASKeyRequest cacheKey = new KASKeyRequest(url, algorithm);
28+
public Config.KASInfo get(String url, String algorithm, String kid) {
29+
log.debug("retrieving kasinfo for url = [{}], algorithm = [{}], kid = [{}]", url, algorithm, kid);
30+
KASKeyRequest cacheKey = new KASKeyRequest(url, algorithm, kid);
3031
LocalDateTime now = LocalDateTime.now();
3132
TimeStampedKASInfo cachedValue = cache.get(cacheKey);
3233

3334
if (cachedValue == null) {
34-
log.debug("didn't find kasinfo for url = [{}], algorithm = [{}]", url, algorithm);
35+
log.debug("didn't find kasinfo for key= [{}]", cacheKey);
3536
return null;
3637
}
3738

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

5051
public void store(Config.KASInfo kasInfo) {
5152
log.debug("storing kasInfo into the cache {}", kasInfo);
52-
KASKeyRequest cacheKey = new KASKeyRequest(kasInfo.URL, kasInfo.Algorithm);
53+
KASKeyRequest cacheKey = new KASKeyRequest(kasInfo.URL, kasInfo.Algorithm, kasInfo.KID);
5354
cache.put(cacheKey, new TimeStampedKASInfo(kasInfo, LocalDateTime.now()));
5455
}
5556
}
@@ -85,30 +86,34 @@ public TimeStampedKASInfo(Config.KASInfo kasInfo, LocalDateTime timestamp) {
8586
class KASKeyRequest {
8687
private String url;
8788
private String algorithm;
89+
private String kid;
8890

89-
public KASKeyRequest(String url, String algorithm) {
91+
public KASKeyRequest(String url, String algorithm, String kid) {
9092
this.url = url;
9193
this.algorithm = algorithm;
94+
this.kid = kid;
9295
}
9396

94-
// Override equals and hashCode to ensure proper functioning of the HashMap
9597
@Override
9698
public boolean equals(Object o) {
97-
if (this == o) return true;
98-
if (o == null || !(o instanceof KASKeyRequest)) return false;
99+
if (o == null || getClass() != o.getClass()) return false;
99100
KASKeyRequest that = (KASKeyRequest) o;
100-
if (algorithm == null){
101-
return url.equals(that.url);
102-
}
103-
return url.equals(that.url) && algorithm.equals(that.algorithm);
101+
return Objects.equals(url, that.url) && Objects.equals(algorithm, that.algorithm) && Objects.equals(kid, that.kid);
104102
}
105103

106104
@Override
107105
public int hashCode() {
108-
int result = 31 * url.hashCode();
109-
if (algorithm != null) {
110-
result = result + algorithm.hashCode();
111-
}
112-
return result;
106+
return Objects.hash(url, algorithm, kid);
107+
}
108+
109+
@Override
110+
public String toString() {
111+
return "KASKeyRequest{" +
112+
"url='" + url + '\'' +
113+
", algorithm='" + algorithm + '\'' +
114+
", kid='" + kid + '\'' +
115+
'}';
113116
}
117+
118+
// Override equals and hashCode to ensure proper functioning of the HashMap
114119
}

sdk/src/main/java/io/opentdf/platform/sdk/KeyType.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.opentdf.platform.sdk;
22

3+
import io.opentdf.platform.policy.Algorithm;
4+
import io.opentdf.platform.policy.KasPublicKeyAlgEnum;
5+
36
import javax.annotation.Nonnull;
47

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

52+
public static KeyType fromAlgorithm(Algorithm algorithm) {
53+
if (algorithm == null) {
54+
throw new IllegalArgumentException("Algorithm cannot be null");
55+
}
56+
switch (algorithm) {
57+
case ALGORITHM_RSA_2048:
58+
return KeyType.RSA2048Key;
59+
case ALGORITHM_EC_P256:
60+
return KeyType.EC256Key;
61+
case ALGORITHM_EC_P384:
62+
return KeyType.EC384Key;
63+
case ALGORITHM_EC_P521:
64+
return KeyType.EC521Key;
65+
default:
66+
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
67+
}
68+
}
69+
70+
public static KeyType fromPublicKeyAlgorithm(KasPublicKeyAlgEnum algorithm) {
71+
if (algorithm == null) {
72+
throw new IllegalArgumentException("Algorithm cannot be null");
73+
}
74+
switch (algorithm) {
75+
case KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048:
76+
return KeyType.RSA2048Key;
77+
case KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1:
78+
return KeyType.EC256Key;
79+
case KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1:
80+
return KeyType.EC384Key;
81+
case KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1:
82+
return KeyType.EC521Key;
83+
default:
84+
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
85+
}
86+
}
87+
4988
public boolean isEc() {
5089
return this.curve != null;
5190
}
52-
}
91+
}

0 commit comments

Comments
 (0)