Skip to content

Commit 9af87d6

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/sdk-encrypt
2 parents f2a9b2c + af51404 commit 9af87d6

File tree

16 files changed

+1160
-644
lines changed

16 files changed

+1160
-644
lines changed

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CODEOWNERS
22

3-
* @opentdf/java-sdk
3+
* @opentdf/java-sdk @opentdf/architecture
44

55
## High Security Area
66

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
# java-sdk
22

33
OpenTDF Java SDK
4+
5+
### Logging
6+
We use [slf4j](https://www.slf4j.org/), without providing a backend. We use log4j2 in our tests.

pom.xml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,13 @@
3737
<version>5.10.1</version>
3838
<type>pom</type>
3939
<scope>import</scope>
40-
</dependency>
41-
<dependency>
42-
<groupId>org.apache.logging.log4j</groupId>
43-
<artifactId>log4j-api</artifactId>
44-
<version>${log4j.version}</version>
4540
</dependency>
4641
<dependency>
4742
<groupId>org.apache.logging.log4j</groupId>
48-
<artifactId>log4j-core</artifactId>
49-
<version>${log4j.version}</version>
50-
</dependency>
51-
<dependency>
52-
<groupId>org.apache.logging.log4j</groupId>
53-
<artifactId>log4j-slf4j2-impl</artifactId>
54-
<version>${log4j.version}</version>
43+
<artifactId>log4j-bom</artifactId>
44+
<version>2.23.1</version>
45+
<type>pom</type>
46+
<scope>import</scope>
5547
</dependency>
5648
<dependency>
5749
<groupId>org.projectlombok</groupId>

protocol/pom.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@
5757
<exec executable="buf" dir="../">
5858
<arg value="generate" />
5959
<arg value="https://github.com/opentdf/platform.git#branch=main,subdir=service" />
60-
<arg value="--exclude-path"/>
61-
<arg value="authorization/idp_plugin.proto"/>
6260
</exec>
6361
<exec executable="buf" dir="../">
6462
<arg value="generate" />

sdk/pom.xml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@
2929
<artifactId>protocol</artifactId>
3030
<version>0.1.0-SNAPSHOT</version><!-- {x-version-update:java-sdk:current} -->
3131
</dependency>
32+
<dependency>
33+
<groupId>org.slf4j</groupId>
34+
<artifactId>slf4j-api</artifactId>
35+
<version>2.0.13</version>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.apache.logging.log4j</groupId>
39+
<artifactId>log4j-slf4j2-impl</artifactId>
40+
<scope>test</scope>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.apache.logging.log4j</groupId>
44+
<artifactId>log4j-core</artifactId>
45+
<scope>test</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.apache.logging.log4j</groupId>
49+
<artifactId>log4j-api</artifactId>
50+
<scope>test</scope>
51+
</dependency>
3252
<dependency>
3353
<groupId>org.junit.jupiter</groupId>
3454
<artifactId>junit-jupiter</artifactId>
@@ -81,14 +101,10 @@
81101
<version>2.10.1</version>
82102
</dependency>
83103
<dependency>
84-
<groupId>commons-codec</groupId>
85-
<artifactId>commons-codec</artifactId>
86-
<version>1.17.0</version>
87-
</dependency>
88-
<dependency>
89-
<groupId>commons-io</groupId>
90-
<artifactId>commons-io</artifactId>
91-
<version>2.11.0</version>
104+
<groupId>org.apache.commons</groupId>
105+
<artifactId>commons-compress</artifactId>
106+
<version>1.26.1</version>
107+
<scope>test</scope>
92108
</dependency>
93109
</dependencies>
94110
</project>

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
1616
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
1717
import com.nimbusds.oauth2.sdk.token.AccessToken;
18-
import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant;
1918
import io.grpc.CallOptions;
2019
import io.grpc.Channel;
2120
import io.grpc.ClientCall;
2221
import io.grpc.ClientInterceptor;
2322
import io.grpc.ForwardingClientCall;
2423
import io.grpc.Metadata;
2524
import io.grpc.MethodDescriptor;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
2627

2728
import java.net.URI;
2829
import java.net.URISyntaxException;
@@ -40,6 +41,9 @@ class GRPCAuthInterceptor implements ClientInterceptor {
4041
private final RSAKey rsaKey;
4142
private final URI tokenEndpointURI;
4243

44+
private static final Logger logger = LoggerFactory.getLogger(GRPCAuthInterceptor.class);
45+
46+
4347
/**
4448
* Constructs a new GRPCAuthInterceptor with the specified client authentication and RSA key.
4549
*
@@ -101,6 +105,8 @@ private synchronized AccessToken getToken() {
101105
// If the token is expired or initially null, get a new token
102106
if (token == null || isTokenExpired()) {
103107

108+
logger.trace("The current access token is expired or empty, getting a new one");
109+
104110
// Construct the client credentials grant
105111
AuthorizationGrant clientGrant = new ClientCredentialsGrant();
106112

@@ -124,9 +130,17 @@ private synchronized AccessToken getToken() {
124130
throw new RuntimeException("Token request failed: " + error);
125131
}
126132

127-
this.token = tokenResponse.toSuccessResponse().getTokens().getAccessToken();
128-
// DPoPAccessToken dPoPAccessToken = tokens.getDPoPAccessToken();
129133

134+
var tokens = tokenResponse.toSuccessResponse().getTokens();
135+
if (tokens.getDPoPAccessToken() != null) {
136+
logger.trace("retrieved a new DPoP access token");
137+
} else if (tokens.getAccessToken() != null) {
138+
logger.trace("retrieved a new access token");
139+
} else {
140+
logger.trace("got an access token of unknown type");
141+
}
142+
143+
this.token = tokens.getAccessToken();
130144

131145
if (token.getLifetime() != 0) {
132146
// Need some type of leeway but not sure whats best
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.opentdf.platform.sdk;
2+
3+
import io.grpc.Channel;
4+
import io.opentdf.platform.kas.AccessServiceGrpc;
5+
import io.opentdf.platform.kas.PublicKeyRequest;
6+
import io.opentdf.platform.kas.RewrapRequest;
7+
8+
import java.util.HashMap;
9+
import java.util.function.Function;
10+
11+
public class KASClient implements SDK.KAS {
12+
13+
private final Function<SDK.KASInfo, Channel> channelFactory;
14+
15+
public KASClient(Function <SDK.KASInfo, Channel> channelFactory) {
16+
this.channelFactory = channelFactory;
17+
}
18+
19+
@Override
20+
public String getPublicKey(SDK.KASInfo kasInfo) {
21+
return getStub(kasInfo).publicKey(PublicKeyRequest.getDefaultInstance()).getPublicKey();
22+
}
23+
24+
@Override
25+
public byte[] unwrap(SDK.KASInfo kasInfo, SDK.Policy policy) {
26+
// this is obviously wrong. we still have to generate a correct request and decrypt the payload
27+
return getStub(kasInfo).rewrap(RewrapRequest.getDefaultInstance()).getEntityWrappedKey().toByteArray();
28+
}
29+
30+
private final HashMap<SDK.KASInfo, AccessServiceGrpc.AccessServiceBlockingStub> stubs = new HashMap<>();
31+
32+
private synchronized AccessServiceGrpc.AccessServiceBlockingStub getStub(SDK.KASInfo kasInfo) {
33+
if (!stubs.containsKey(kasInfo)) {
34+
var channel = channelFactory.apply(kasInfo);
35+
var stub = AccessServiceGrpc.newBlockingStub(channel);
36+
stubs.put(kasInfo, stub);
37+
}
38+
39+
return stubs.get(kasInfo);
40+
}
41+
}

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,25 @@
1717
public class SDK {
1818
private final Services services;
1919

20+
public interface KASInfo{
21+
String getAddress();
22+
}
23+
public interface Policy{}
24+
25+
interface KAS {
26+
String getPublicKey(KASInfo kasInfo);
27+
byte[] unwrap(KASInfo kasInfo, Policy policy);
28+
}
29+
2030
// TODO: add KAS
21-
public interface Services {
31+
interface Services {
2232
AttributesServiceFutureStub attributes();
2333
NamespaceServiceFutureStub namespaces();
2434
SubjectMappingServiceFutureStub subjectMappings();
2535
ResourceMappingServiceFutureStub resourceMappings();
36+
KAS kas();
2637

27-
static Services newServices(Channel channel) {
38+
static Services newServices(Channel channel, KAS kas) {
2839
var attributeService = AttributesServiceGrpc.newFutureStub(channel);
2940
var namespaceService = NamespaceServiceGrpc.newFutureStub(channel);
3041
var subjectMappingService = SubjectMappingServiceGrpc.newFutureStub(channel);
@@ -50,11 +61,16 @@ public SubjectMappingServiceFutureStub subjectMappings() {
5061
public ResourceMappingServiceFutureStub resourceMappings() {
5162
return resourceMappingService;
5263
}
64+
65+
@Override
66+
public KAS kas() {
67+
return kas;
68+
}
5369
};
5470
}
5571
}
5672

57-
public SDK(Services services) {
73+
SDK(Services services) {
5874
this.services = services;
5975
}
6076
}

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

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,20 @@
1111
import com.nimbusds.oauth2.sdk.id.ClientID;
1212
import com.nimbusds.oauth2.sdk.id.Issuer;
1313
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
14+
import io.grpc.Channel;
1415
import io.grpc.ManagedChannel;
1516
import io.grpc.ManagedChannelBuilder;
1617
import io.grpc.Status;
18+
import io.grpc.StatusRuntimeException;
1719
import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest;
1820
import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse;
1921
import io.opentdf.platform.wellknownconfiguration.WellKnownServiceGrpc;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2024

2125
import java.io.IOException;
2226
import java.util.UUID;
27+
import java.util.function.Function;
2328

2429
/**
2530
* A builder class for creating instances of the SDK class.
@@ -30,9 +35,13 @@ public class SDKBuilder {
3035
private ClientAuthentication clientAuth = null;
3136
private Boolean usePlainText;
3237

38+
private static final Logger logger = LoggerFactory.getLogger(SDKBuilder.class);
39+
3340
public static SDKBuilder newBuilder() {
3441
SDKBuilder builder = new SDKBuilder();
3542
builder.usePlainText = false;
43+
builder.clientAuth = null;
44+
builder.platformEndpoint = null;
3645

3746
return builder;
3847
}
@@ -54,8 +63,16 @@ public SDKBuilder useInsecurePlaintextConnection(Boolean usePlainText) {
5463
return this;
5564
}
5665

57-
// this is not exposed publicly so that it can be tested
58-
ManagedChannel buildChannel() {
66+
private GRPCAuthInterceptor getGrpcAuthInterceptor() {
67+
if (platformEndpoint == null) {
68+
throw new SDKException("cannot build an SDK without specifying the platform endpoint");
69+
}
70+
71+
if (clientAuth == null) {
72+
// this simplifies things for now, if we need to support this case we can revisit
73+
throw new SDKException("cannot build an SDK without specifying OAuth credentials");
74+
}
75+
5976
// we don't add the auth listener to this channel since it is only used to call the
6077
// well known endpoint
6178
ManagedChannel bootstrapChannel = null;
@@ -65,7 +82,7 @@ ManagedChannel buildChannel() {
6582
var stub = WellKnownServiceGrpc.newBlockingStub(bootstrapChannel);
6683
try {
6784
config = stub.getWellKnownConfiguration(GetWellKnownConfigurationRequest.getDefaultInstance());
68-
} catch (Exception e) {
85+
} catch (StatusRuntimeException e) {
6986
Status status = Status.fromThrowable(e);
7087
throw new SDKException(String.format("Got grpc status [%s] when getting configuration", status), e);
7188
}
@@ -82,7 +99,7 @@ ManagedChannel buildChannel() {
8299
.getFieldsOrThrow(PLATFORM_ISSUER)
83100
.getStringValue();
84101

85-
} catch (Exception e) {
102+
} catch (StatusRuntimeException e) {
86103
throw new SDKException("Error getting the issuer from the platform", e);
87104
}
88105

@@ -104,24 +121,39 @@ ManagedChannel buildChannel() {
104121
throw new SDKException("Error generating DPoP key", e);
105122
}
106123

107-
GRPCAuthInterceptor interceptor = new GRPCAuthInterceptor(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI());
124+
return new GRPCAuthInterceptor(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI());
125+
}
108126

109-
return getManagedChannelBuilder()
110-
.intercept(interceptor)
111-
.build();
127+
SDK.Services buildServices() {
128+
var authInterceptor = getGrpcAuthInterceptor();
129+
var channel = getManagedChannelBuilder().intercept(authInterceptor).build();
130+
var client = new KASClient(getChannelFactory(authInterceptor));
131+
return SDK.Services.newServices(channel, client);
112132
}
113133

114134
public SDK build() {
115-
return new SDK(SDK.Services.newServices(buildChannel()));
135+
return new SDK(buildServices());
116136
}
117137

118138
private ManagedChannelBuilder<?> getManagedChannelBuilder() {
119-
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder
120-
.forTarget(platformEndpoint);
139+
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forTarget(platformEndpoint);
121140

122141
if (usePlainText) {
123142
channelBuilder = channelBuilder.usePlaintext();
124143
}
125144
return channelBuilder;
126145
}
146+
147+
Function<SDK.KASInfo, Channel> getChannelFactory(GRPCAuthInterceptor authInterceptor) {
148+
var pt = usePlainText; // no need to have the builder be able to influence things from beyond the grave
149+
return (SDK.KASInfo kasInfo) -> {
150+
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder
151+
.forTarget(kasInfo.getAddress())
152+
.intercept(authInterceptor);
153+
if (pt) {
154+
channelBuilder = channelBuilder.usePlaintext();
155+
}
156+
return channelBuilder.build();
157+
};
158+
}
127159
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ public class SDKException extends RuntimeException {
44
public SDKException(String message, Exception reason) {
55
super(message, reason);
66
}
7+
8+
public SDKException(String message) {
9+
super(message);
10+
}
711
}

0 commit comments

Comments
 (0)