Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feature/sdk-encrypt
Browse files Browse the repository at this point in the history
  • Loading branch information
mkleene committed May 28, 2024
2 parents f2a9b2c + af51404 commit 028ead2
Show file tree
Hide file tree
Showing 20 changed files with 1,256 additions and 749 deletions.
2 changes: 1 addition & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CODEOWNERS

* @opentdf/java-sdk
* @opentdf/java-sdk @opentdf/architecture

## High Security Area

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# java-sdk

OpenTDF Java SDK

### Logging
We use [slf4j](https://www.slf4j.org/), without providing a backend. We use log4j2 in our tests.
16 changes: 4 additions & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,13 @@
<version>5.10.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j.version}</version>
<artifactId>log4j-bom</artifactId>
<version>2.23.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
Expand Down
2 changes: 0 additions & 2 deletions protocol/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@
<exec executable="buf" dir="../">
<arg value="generate" />
<arg value="https://github.com/opentdf/platform.git#branch=main,subdir=service" />
<arg value="--exclude-path"/>
<arg value="authorization/idp_plugin.proto"/>
</exec>
<exec executable="buf" dir="../">
<arg value="generate" />
Expand Down
31 changes: 26 additions & 5 deletions sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>15</source>
<target>15</target>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
Expand All @@ -29,6 +29,26 @@
<artifactId>protocol</artifactId>
<version>0.1.0-SNAPSHOT</version><!-- {x-version-update:java-sdk:current} -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down Expand Up @@ -86,9 +106,10 @@
<version>1.17.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.26.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
20 changes: 17 additions & 3 deletions sdk/src/main/java/io/opentdf/platform/sdk/GRPCAuthInterceptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
Expand All @@ -40,6 +41,9 @@ class GRPCAuthInterceptor implements ClientInterceptor {
private final RSAKey rsaKey;
private final URI tokenEndpointURI;

private static final Logger logger = LoggerFactory.getLogger(GRPCAuthInterceptor.class);


/**
* Constructs a new GRPCAuthInterceptor with the specified client authentication and RSA key.
*
Expand Down Expand Up @@ -101,6 +105,8 @@ private synchronized AccessToken getToken() {
// If the token is expired or initially null, get a new token
if (token == null || isTokenExpired()) {

logger.trace("The current access token is expired or empty, getting a new one");

// Construct the client credentials grant
AuthorizationGrant clientGrant = new ClientCredentialsGrant();

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

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

var tokens = tokenResponse.toSuccessResponse().getTokens();
if (tokens.getDPoPAccessToken() != null) {
logger.trace("retrieved a new DPoP access token");
} else if (tokens.getAccessToken() != null) {
logger.trace("retrieved a new access token");
} else {
logger.trace("got an access token of unknown type");
}

this.token = tokens.getAccessToken();

if (token.getLifetime() != 0) {
// Need some type of leeway but not sure whats best
Expand Down
41 changes: 41 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.opentdf.platform.sdk;

import io.grpc.Channel;
import io.opentdf.platform.kas.AccessServiceGrpc;
import io.opentdf.platform.kas.PublicKeyRequest;
import io.opentdf.platform.kas.RewrapRequest;

import java.util.HashMap;
import java.util.function.Function;

public class KASClient implements SDK.KAS {

private final Function<SDK.KASInfo, Channel> channelFactory;

public KASClient(Function <SDK.KASInfo, Channel> channelFactory) {
this.channelFactory = channelFactory;
}

@Override
public String getPublicKey(SDK.KASInfo kasInfo) {
return getStub(kasInfo).publicKey(PublicKeyRequest.getDefaultInstance()).getPublicKey();
}

@Override
public byte[] unwrap(SDK.KASInfo kasInfo, SDK.Policy policy) {
// this is obviously wrong. we still have to generate a correct request and decrypt the payload
return getStub(kasInfo).rewrap(RewrapRequest.getDefaultInstance()).getEntityWrappedKey().toByteArray();
}

private final HashMap<SDK.KASInfo, AccessServiceGrpc.AccessServiceBlockingStub> stubs = new HashMap<>();

private synchronized AccessServiceGrpc.AccessServiceBlockingStub getStub(SDK.KASInfo kasInfo) {
if (!stubs.containsKey(kasInfo)) {
var channel = channelFactory.apply(kasInfo);
var stub = AccessServiceGrpc.newBlockingStub(channel);
stubs.put(kasInfo, stub);
}

return stubs.get(kasInfo);
}
}
22 changes: 19 additions & 3 deletions sdk/src/main/java/io/opentdf/platform/sdk/SDK.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@
public class SDK {
private final Services services;

public interface KASInfo{
String getAddress();
}
public interface Policy{}

interface KAS {
String getPublicKey(KASInfo kasInfo);
byte[] unwrap(KASInfo kasInfo, Policy policy);
}

// TODO: add KAS
public interface Services {
interface Services {
AttributesServiceFutureStub attributes();
NamespaceServiceFutureStub namespaces();
SubjectMappingServiceFutureStub subjectMappings();
ResourceMappingServiceFutureStub resourceMappings();
KAS kas();

static Services newServices(Channel channel) {
static Services newServices(Channel channel, KAS kas) {
var attributeService = AttributesServiceGrpc.newFutureStub(channel);
var namespaceService = NamespaceServiceGrpc.newFutureStub(channel);
var subjectMappingService = SubjectMappingServiceGrpc.newFutureStub(channel);
Expand All @@ -50,11 +61,16 @@ public SubjectMappingServiceFutureStub subjectMappings() {
public ResourceMappingServiceFutureStub resourceMappings() {
return resourceMappingService;
}

@Override
public KAS kas() {
return kas;
}
};
}
}

public SDK(Services services) {
SDK(Services services) {
this.services = services;
}
}
54 changes: 43 additions & 11 deletions sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest;
import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse;
import io.opentdf.platform.wellknownconfiguration.WellKnownServiceGrpc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.UUID;
import java.util.function.Function;

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

private static final Logger logger = LoggerFactory.getLogger(SDKBuilder.class);

public static SDKBuilder newBuilder() {
SDKBuilder builder = new SDKBuilder();
builder.usePlainText = false;
builder.clientAuth = null;
builder.platformEndpoint = null;

return builder;
}
Expand All @@ -54,8 +63,16 @@ public SDKBuilder useInsecurePlaintextConnection(Boolean usePlainText) {
return this;
}

// this is not exposed publicly so that it can be tested
ManagedChannel buildChannel() {
private GRPCAuthInterceptor getGrpcAuthInterceptor() {
if (platformEndpoint == null) {
throw new SDKException("cannot build an SDK without specifying the platform endpoint");
}

if (clientAuth == null) {
// this simplifies things for now, if we need to support this case we can revisit
throw new SDKException("cannot build an SDK without specifying OAuth credentials");
}

// we don't add the auth listener to this channel since it is only used to call the
// well known endpoint
ManagedChannel bootstrapChannel = null;
Expand All @@ -65,7 +82,7 @@ ManagedChannel buildChannel() {
var stub = WellKnownServiceGrpc.newBlockingStub(bootstrapChannel);
try {
config = stub.getWellKnownConfiguration(GetWellKnownConfigurationRequest.getDefaultInstance());
} catch (Exception e) {
} catch (StatusRuntimeException e) {
Status status = Status.fromThrowable(e);
throw new SDKException(String.format("Got grpc status [%s] when getting configuration", status), e);
}
Expand All @@ -82,7 +99,7 @@ ManagedChannel buildChannel() {
.getFieldsOrThrow(PLATFORM_ISSUER)
.getStringValue();

} catch (Exception e) {
} catch (StatusRuntimeException e) {
throw new SDKException("Error getting the issuer from the platform", e);
}

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

GRPCAuthInterceptor interceptor = new GRPCAuthInterceptor(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI());
return new GRPCAuthInterceptor(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI());
}

return getManagedChannelBuilder()
.intercept(interceptor)
.build();
SDK.Services buildServices() {
var authInterceptor = getGrpcAuthInterceptor();
var channel = getManagedChannelBuilder().intercept(authInterceptor).build();
var client = new KASClient(getChannelFactory(authInterceptor));
return SDK.Services.newServices(channel, client);
}

public SDK build() {
return new SDK(SDK.Services.newServices(buildChannel()));
return new SDK(buildServices());
}

private ManagedChannelBuilder<?> getManagedChannelBuilder() {
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder
.forTarget(platformEndpoint);
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forTarget(platformEndpoint);

if (usePlainText) {
channelBuilder = channelBuilder.usePlaintext();
}
return channelBuilder;
}

Function<SDK.KASInfo, Channel> getChannelFactory(GRPCAuthInterceptor authInterceptor) {
var pt = usePlainText; // no need to have the builder be able to influence things from beyond the grave
return (SDK.KASInfo kasInfo) -> {
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder
.forTarget(kasInfo.getAddress())
.intercept(authInterceptor);
if (pt) {
channelBuilder = channelBuilder.usePlaintext();
}
return channelBuilder.build();
};
}
}
4 changes: 4 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/SDKException.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ public class SDKException extends RuntimeException {
public SDKException(String message, Exception reason) {
super(message, reason);
}

public SDKException(String message) {
super(message);
}
}
Loading

0 comments on commit 028ead2

Please sign in to comment.