Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
Expand Down
37 changes: 29 additions & 8 deletions examples/src/main/java/io/opentdf/platform/DecryptExample.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
package io.opentdf.platform;

import io.opentdf.platform.sdk.*;
import java.nio.file.StandardOpenOption;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

import com.nimbusds.jose.JOSEException;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.commons.cli.*;
import org.apache.commons.codec.DecoderException;


public class DecryptExample {
public static void main(String[] args) throws IOException,
InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, TDF.FailedToCreateGMAC,
JOSEException, ParseException, NoSuchAlgorithmException, DecoderException {
InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, TDF.FailedToCreateGMAC,
JOSEException, ParseException, NoSuchAlgorithmException, DecoderException, org.apache.commons.cli.ParseException {

// Create Options object
Options options = new Options();

// Add rewrap encapsulation algorithm option
options.addOption(Option.builder("A")
.longOpt("rewrap-encapsulation-algorithm")
.hasArg()
.desc("Key wrap response algorithm algorithm:parameters")
.build());

// Parse command line arguments
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);

// Get the rewrap encapsulation algorithm
String rewrapEncapsulationAlgorithm = cmd.getOptionValue("rewrap-encapsulation-algorithm", "rsa:2048");
var sessionKeyType = KeyType.fromString(rewrapEncapsulationAlgorithm.toLowerCase());


String clientId = "opentdf";
String clientSecret = "secret";
Expand All @@ -35,8 +53,11 @@ public static void main(String[] args) throws IOException,

Path path = Paths.get("my.ciphertext");
try (var in = FileChannel.open(path, StandardOpenOption.READ)) {
var reader = new TDF().loadTDF(in, sdk.getServices().kas());
var reader = new TDF().loadTDF(in, sdk.getServices().kas(), Config.newTDFReaderConfig(Config.WithSessionKeyType(sessionKeyType)));
reader.readPayload(System.out);
}

// Print the rewrap encapsulation algorithm
System.out.println("Rewrap Encapsulation Algorithm: " + rewrapEncapsulationAlgorithm);
}
}
}
39 changes: 29 additions & 10 deletions examples/src/main/java/io/opentdf/platform/EncryptExample.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
package io.opentdf.platform;

import io.opentdf.platform.sdk.*;
import java.io.ByteArrayInputStream;
import java.io.BufferedOutputStream;
import java.nio.charset.StandardCharsets;
import java.io.FileOutputStream;

import com.nimbusds.jose.JOSEException;
import org.apache.commons.cli.*;
import org.apache.commons.codec.DecoderException;

import java.io.IOException;
import java.util.concurrent.ExecutionException;

public class EncryptExample {
public static void main(String[] args) throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException, DecoderException {
public static void main(String[] args) throws IOException, JOSEException, AutoConfigureException,
InterruptedException, ExecutionException, DecoderException, ParseException {
// Create Options object
Options options = new Options();

// Add key encapsulation algorithm option
options.addOption(Option.builder("A")
.longOpt("key-encapsulation-algorithm")
.hasArg()
.desc("Key wrap algorithm algorithm:parameters")
.build());

// Parse command line arguments
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);

// Get the key encapsulation algorithm
String keyEncapsulationAlgorithm = cmd.getOptionValue("key-encapsulation-algorithm", "rsa:2048");

String clientId = "opentdf";
String clientSecret = "secret";
String platformEndpoint = "localhost:8080";
Expand All @@ -25,17 +42,19 @@ public static void main(String[] args) throws IOException, JOSEException, AutoCo
var kasInfo = new Config.KASInfo();
kasInfo.URL = "http://localhost:8080/kas";

var tdfConfig = Config.newTDFConfig(Config.withKasInformation(kasInfo), Config.withDataAttributes("https://example.com/attr/color/value/red"));

var wrappingKeyType = KeyType.fromString(keyEncapsulationAlgorithm.toLowerCase());
var tdfConfig = Config.newTDFConfig(Config.withKasInformation(kasInfo),
Config.withDataAttributes("https://example.com/attr/color/value/red"),
Config.WithWrappingKeyAlg(wrappingKeyType));
String str = "Hello, World!";

// Convert String to InputStream
var in = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));

FileOutputStream fos = new FileOutputStream("my.ciphertext");

new TDF().createTDF(in, fos, tdfConfig,
sdk.getServices().kas(),
sdk.getServices().attributes());
sdk.getServices().kas(),
sdk.getServices().attributes());
}
}
}
13 changes: 11 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 @@ -7,10 +7,8 @@
import io.opentdf.platform.sdk.nanotdf.SymmetricAndPayloadConfig;

import io.opentdf.platform.policy.Value;
import org.bouncycastle.oer.its.ieee1609dot2.HeaderInfo;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
Expand Down Expand Up @@ -99,12 +97,14 @@ public static class TDFReaderConfig {
// Optional Map of Assertion Verification Keys
AssertionVerificationKeys assertionVerificationKeys = new AssertionVerificationKeys();
boolean disableAssertionVerification;
KeyType sessionKeyType;
}

@SafeVarargs
public static TDFReaderConfig newTDFReaderConfig(Consumer<TDFReaderConfig>... options) {
TDFReaderConfig config = new TDFReaderConfig();
config.disableAssertionVerification = false;
config.sessionKeyType = KeyType.RSA2048Key;
for (Consumer<TDFReaderConfig> option : options) {
option.accept(config);
}
Expand All @@ -120,6 +120,9 @@ public static Consumer<TDFReaderConfig> withDisableAssertionVerification(boolean
return (TDFReaderConfig config) -> config.disableAssertionVerification = disable;
}

public static Consumer<TDFReaderConfig> WithSessionKeyType(KeyType keyType) {
return (TDFReaderConfig config) -> config.sessionKeyType = keyType;
}
public static class TDFConfig {
public Boolean autoconfigure;
public int defaultSegmentSize;
Expand All @@ -136,6 +139,7 @@ public static class TDFConfig {
public List<io.opentdf.platform.sdk.AssertionConfig> assertionConfigList;
public String mimeType;
public List<Autoconfigure.KeySplitStep> splitPlan;
public KeyType wrappingKeyType;

public TDFConfig() {
this.autoconfigure = true;
Expand All @@ -149,6 +153,7 @@ public TDFConfig() {
this.assertionConfigList = new ArrayList<>();
this.mimeType = DEFAULT_MIME_TYPE;
this.splitPlan = new ArrayList<>();
this.wrappingKeyType = KeyType.RSA2048Key;
}
}

Expand Down Expand Up @@ -246,6 +251,10 @@ public static Consumer<TDFConfig> withAutoconfigure(boolean enable) {
};
}

public static Consumer<TDFConfig> WithWrappingKeyAlg(KeyType keyType) {
return (TDFConfig config) -> config.wrappingKeyType = keyType;
}

// public static Consumer<TDFConfig> withDisableEncryption() {
// return (TDFConfig config) -> config.enableEncryption = false;
// }
Expand Down
25 changes: 25 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/CryptoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;

/**
Expand Down Expand Up @@ -39,6 +40,30 @@ public static KeyPair generateRSAKeypair() {
return kpg.generateKeyPair();
}

public static KeyPair generateECKeypair(String curveName) {
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec(curveName);
kpg.initialize(ecSpec);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
throw new SDKException("error creating EC keypair", e);
}
return kpg.generateKeyPair();
}

public static String getPublicKeyPEM(PublicKey publicKey) {
return "-----BEGIN PUBLIC KEY-----\r\n" +
Base64.getMimeEncoder().encodeToString(publicKey.getEncoded()) +
"\r\n-----END PUBLIC KEY-----";
}

public static String getPrivateKeyPEM(PrivateKey privateKey) {
return "-----BEGIN PRIVATE KEY-----\r\n" +
Base64.getMimeEncoder().encodeToString(privateKey.getEncoded()) +
"\r\n-----END PRIVATE KEY-----";
}

public static String getRSAPublicKeyPEM(PublicKey publicKey) {
if (!"RSA".equals(publicKey.getAlgorithm())) {
throw new IllegalArgumentException("can't get public key PEM for algorithm [" + publicKey.getAlgorithm() + "]");
Expand Down
58 changes: 46 additions & 12 deletions sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.opentdf.platform.sdk.nanotdf.NanoTDFType;
import io.opentdf.platform.sdk.TDF.KasBadRequestException;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.net.MalformedURLException;
Expand All @@ -32,6 +33,7 @@
import java.util.HashMap;
import java.util.function.Function;

import static io.opentdf.platform.sdk.TDF.GLOBAL_KEY_SALT;
import static java.lang.String.format;

/**
Expand All @@ -43,9 +45,8 @@ public class KASClient implements SDK.KAS {

private final Function<String, ManagedChannel> channelFactory;
private final RSASSASigner signer;
private final AsymDecryption decryptor;
private final String publicKeyPEM;

private AsymDecryption decryptor;
private String clientPublicKey;
private KASKeyCache kasKeyCache;

/***
Expand All @@ -62,10 +63,6 @@ public KASClient(Function<String, ManagedChannel> channelFactory, RSAKey dpopKey
} catch (JOSEException e) {
throw new SDKException("error creating dpop signer", e);
}
var encryptionKeypair = CryptoUtils.generateRSAKeypair();
decryptor = new AsymDecryption(encryptionKeypair.getPrivate());
publicKeyPEM = CryptoUtils.getRSAPublicKeyPEM(encryptionKeypair.getPublic());

this.kasKeyCache = new KASKeyCache();
}

Expand All @@ -86,7 +83,12 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) {
if (cachedValue != null) {
return cachedValue;
}
PublicKeyResponse resp = getStub(kasInfo.URL).publicKey(PublicKeyRequest.getDefaultInstance());

PublicKeyRequest request = (kasInfo.Algorithm == null || kasInfo.Algorithm.isEmpty())
? PublicKeyRequest.getDefaultInstance()
: PublicKeyRequest.newBuilder().setAlgorithm(kasInfo.Algorithm).build();

PublicKeyResponse resp = getStub(kasInfo.URL).publicKey(request);

var kiCopy = new Config.KASInfo();
kiCopy.KID = resp.getKid();
Expand Down Expand Up @@ -161,13 +163,28 @@ static class NanoTDFRewrapRequestBody {
private static final Gson gson = new Gson();

@Override
public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy) {
public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessionKeyType) {
ECKeyPair ecKeyPair = null;

if (sessionKeyType.isEc()) {
var curveName = sessionKeyType.getCurveName();
ecKeyPair = new ECKeyPair(curveName, ECKeyPair.ECAlgorithm.ECDH);
clientPublicKey = ecKeyPair.publicKeyInPEMFormat();
} else {
// Initialize the RSA key pair only once and reuse it for future unwrap operations
if (decryptor == null) {
var encryptionKeypair = CryptoUtils.generateRSAKeypair();
decryptor = new AsymDecryption(encryptionKeypair.getPrivate());
clientPublicKey = CryptoUtils.getRSAPublicKeyPEM(encryptionKeypair.getPublic());
}
}

RewrapRequestBody body = new RewrapRequestBody();
body.policy = policy;
body.clientPublicKey = publicKeyPEM;
body.clientPublicKey = clientPublicKey;
body.keyAccess = keyAccess;
var requestBody = gson.toJson(body);

var requestBody = gson.toJson(body);
var claims = new JWTClaimsSet.Builder()
.claim("requestBody", requestBody)
.issueTime(Date.from(Instant.now()))
Expand All @@ -190,7 +207,24 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy) {
try {
response = getStub(keyAccess.url).rewrap(request);
var wrappedKey = response.getEntityWrappedKey().toByteArray();
return decryptor.decrypt(wrappedKey);
if (sessionKeyType != KeyType.RSA2048Key) {

if (ecKeyPair == null) {
throw new SDKException("ECKeyPair is null. Unable to proceed with the unwrap operation.");
}

var kasEphemeralPublicKey = response.getSessionPublicKey();
var publicKey = ECKeyPair.publicKeyFromPem(kasEphemeralPublicKey);
byte[] symKey = ECKeyPair.computeECDHKey(publicKey, ecKeyPair.getPrivateKey());

var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey);

AesGcm gcm = new AesGcm(sessionKey);
AesGcm.Encrypted encrypted = new AesGcm.Encrypted(wrappedKey);
return gcm.decrypt(encrypted);
} else {
return decryptor.decrypt(wrappedKey);
}
} catch (StatusRuntimeException e) {
if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) {
// 400 Bad Request
Expand Down
Loading