Skip to content

Commit

Permalink
fix: policy-binding new structure (#95)
Browse files Browse the repository at this point in the history
Co-authored-by: Morgan Kleene <mkleene@virtru.com>
Co-authored-by: Elizabeth Healy <35498075+elizabethhealy@users.noreply.github.com>
Co-authored-by: Paul Flynn <43211074+pflynn-virtru@users.noreply.github.com>
  • Loading branch information
4 people committed Jul 25, 2024
1 parent 9579c42 commit b10a61e
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 33 deletions.
154 changes: 153 additions & 1 deletion sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,87 @@
package io.opentdf.platform.sdk;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class Manifest {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Manifest manifest = (Manifest) o;
return Objects.equals(encryptionInformation, manifest.encryptionInformation) && Objects.equals(payload, manifest.payload) && Objects.equals(assertions, manifest.assertions);
}

@Override
public int hashCode() {
return Objects.hash(encryptionInformation, payload, assertions);
}

private static class PolicyBindingSerializer implements JsonDeserializer<Object>, JsonSerializer<Object> {
@Override
public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject()) {
return context.deserialize(json, Manifest.PolicyBinding.class);
} else if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) {
return json.getAsString();
} else {
throw new JsonParseException("Unexpected type for policyBinding");
}
}

@Override
public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src, typeOfSrc);
}
}
static public class Segment {
public String hash;
public long segmentSize;
public long encryptedSegmentSize;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Segment segment = (Segment) o;
return segmentSize == segment.segmentSize && encryptedSegmentSize == segment.encryptedSegmentSize && Objects.equals(hash, segment.hash);
}

@Override
public int hashCode() {
return Objects.hash(hash, segmentSize, encryptedSegmentSize);
}
}

static public class RootSignature {
@SerializedName(value = "alg")
public String algorithm;
@SerializedName(value = "sig")
public String signature;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RootSignature that = (RootSignature) o;
return Objects.equals(algorithm, that.algorithm) && Objects.equals(signature, that.signature);
}

@Override
public int hashCode() {
return Objects.hash(algorithm, signature);
}
}

static public class IntegrityInformation {
Expand All @@ -25,6 +90,37 @@ static public class IntegrityInformation {
public int segmentSizeDefault;
public int encryptedSegmentSizeDefault;
public List<Segment> segments;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IntegrityInformation that = (IntegrityInformation) o;
return segmentSizeDefault == that.segmentSizeDefault && encryptedSegmentSizeDefault == that.encryptedSegmentSizeDefault && Objects.equals(rootSignature, that.rootSignature) && Objects.equals(segmentHashAlg, that.segmentHashAlg) && Objects.equals(segments, that.segments);
}

@Override
public int hashCode() {
return Objects.hash(rootSignature, segmentHashAlg, segmentSizeDefault, encryptedSegmentSizeDefault, segments);
}
}

static public class PolicyBinding {
public String alg;
public String hash;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PolicyBinding that = (PolicyBinding) o;
return Objects.equals(alg, that.alg) && Objects.equals(hash, that.hash);
}

@Override
public int hashCode() {
return Objects.hash(alg, hash);
}
}

static public class KeyAccess {
Expand All @@ -33,17 +129,47 @@ static public class KeyAccess {
public String url;
public String protocol;
public String wrappedKey;
public String policyBinding;
@JsonAdapter(PolicyBindingSerializer.class)
public Object policyBinding;

public String encryptedMetadata;
public String kid;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
KeyAccess keyAccess = (KeyAccess) o;
return Objects.equals(keyType, keyAccess.keyType) && Objects.equals(url, keyAccess.url) && Objects.equals(protocol, keyAccess.protocol) && Objects.equals(wrappedKey, keyAccess.wrappedKey) && Objects.equals(policyBinding, keyAccess.policyBinding) && Objects.equals(encryptedMetadata, keyAccess.encryptedMetadata) && Objects.equals(kid, keyAccess.kid);
}

@Override
public int hashCode() {
return Objects.hash(keyType, url, protocol, wrappedKey, policyBinding, encryptedMetadata, kid);
}
}

static public class Method {
public String algorithm;
public String iv;
public Boolean IsStreamable;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Method method = (Method) o;
return Objects.equals(algorithm, method.algorithm) && Objects.equals(iv, method.iv) && Objects.equals(IsStreamable, method.IsStreamable);
}

@Override
public int hashCode() {
return Objects.hash(algorithm, iv, IsStreamable);
}
}



static public class EncryptionInformation {
@SerializedName(value = "type")
public String keyAccessType;
Expand All @@ -53,6 +179,19 @@ static public class EncryptionInformation {
public List<KeyAccess> keyAccessObj;
public Method method;
public IntegrityInformation integrityInformation;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EncryptionInformation that = (EncryptionInformation) o;
return Objects.equals(keyAccessType, that.keyAccessType) && Objects.equals(policy, that.policy) && Objects.equals(keyAccessObj, that.keyAccessObj) && Objects.equals(method, that.method) && Objects.equals(integrityInformation, that.integrityInformation);
}

@Override
public int hashCode() {
return Objects.hash(keyAccessType, policy, keyAccessObj, method, integrityInformation);
}
}

static public class Payload {
Expand All @@ -61,6 +200,19 @@ static public class Payload {
public String protocol;
public String mimeType;
public Boolean isEncrypted;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Payload payload = (Payload) o;
return Objects.equals(type, payload.type) && Objects.equals(url, payload.url) && Objects.equals(protocol, payload.protocol) && Objects.equals(mimeType, payload.mimeType) && Objects.equals(isEncrypted, payload.isEncrypted);
}

@Override
public int hashCode() {
return Objects.hash(type, url, protocol, mimeType, isEncrypted);
}
}

public EncryptionInformation encryptionInformation;
Expand Down
29 changes: 15 additions & 14 deletions sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,34 @@


import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.jwk.RSAKey;
import io.opentdf.platform.kas.RewrapRequest;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.pqc.crypto.lms.HSSSigner;
import org.erdtman.jcs.JsonCanonicalizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

public class TDF {
Expand Down Expand Up @@ -68,7 +66,8 @@ public TDF() {
private static final String kAssertionHash = "assertionHash";

private static final SecureRandom sRandom = new SecureRandom();
private static final Gson gson = new GsonBuilder().create();

private static final Gson gson = new Gson();

public static class DataSizeNotSupported extends RuntimeException {
public DataSizeNotSupported(String errorMessage) {
Expand Down Expand Up @@ -191,7 +190,10 @@ private void prepareManifest(Config.TDFConfig tdfConfig) {

// Add policyBinding
var hexBinding = Hex.encodeHexString(CryptoUtils.CalculateSHA256Hmac(symKey, base64PolicyObject.getBytes(StandardCharsets.UTF_8)));
keyAccess.policyBinding = encoder.encodeToString(hexBinding.getBytes(StandardCharsets.UTF_8));
var policyBinding = new Manifest.PolicyBinding();
policyBinding.alg = kHmacIntegrityAlgorithm;
policyBinding.hash = encoder.encodeToString(hexBinding.getBytes(StandardCharsets.UTF_8));
keyAccess.policyBinding = policyBinding;

// Wrap the key with kas public key
AsymEncryption asymmetricEncrypt = new AsymEncryption(kasInfo.PublicKey);
Expand All @@ -206,7 +208,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig) {

EncryptedMetadata encryptedMetadata = new EncryptedMetadata();
encryptedMetadata.iv = encoder.encodeToString(encrypted.getIv());
encryptedMetadata.ciphertext = encoder.encodeToString(encrypted.getCiphertext());
encryptedMetadata.ciphertext = encoder.encodeToString(encrypted.asBytes());

var metadata = gson.toJson(encryptedMetadata);
keyAccess.encryptedMetadata = encoder.encodeToString(metadata.getBytes(StandardCharsets.UTF_8));
Expand Down Expand Up @@ -514,7 +516,6 @@ public Reader loadTDF(SeekableByteChannel tdf, Config.AssertionConfig assertionC
EncryptedMetadata encryptedMetadata = gson.fromJson(decodedMetadata, EncryptedMetadata.class);

var encryptedData = new AesGcm.Encrypted(
decoder.decode(encryptedMetadata.iv),
decoder.decode(encryptedMetadata.ciphertext)
);

Expand Down Expand Up @@ -604,4 +605,4 @@ public Reader loadTDF(SeekableByteChannel tdf, Config.AssertionConfig assertionC

return new Reader(tdfReader, manifest, payloadKey, unencryptedMetadata);
}
}
}
31 changes: 15 additions & 16 deletions sdk/src/test/java/io/opentdf/platform/sdk/ManifestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class ManifestTest {
@Test
void testManifestMarshalAndUnMarshal() {
void testManifestMarshalAndUnMarshal() {
String kManifestJsonFromTDF = "{\n" +
" \"encryptionInformation\": {\n" +
" \"integrityInformation\": {\n" +
Expand All @@ -31,7 +34,10 @@ void testManifestMarshalAndUnMarshal() {
" },\n" +
" \"keyAccess\": [\n" +
" {\n" +
" \"policyBinding\": \"YTgzNThhNzc5NWRhMjdjYThlYjk4ZmNmODliNzc2Y2E5ZmZiZDExZDQ3OTM5ODFjZTRjNmE3MmVjOTUzZTFlMA==\",\n" +
" \"policyBinding\": {\n" +
" \"alg\": \"HS256\",\n" +
" \"hash\": \"YTgzNThhNzc5NWRhMjdjYThlYjk4ZmNmODliNzc2Y2E5ZmZiZDExZDQ3OTM5ODFjZTRjNmE3MmVjOTUzZTFlMA==\"\n" +
" },\n" +
" \"protocol\": \"kas\",\n" +
" \"type\": \"wrapped\",\n" +
" \"url\": \"http://localhost:65432/kas\",\n" +
Expand Down Expand Up @@ -70,25 +76,18 @@ void testManifestMarshalAndUnMarshal() {
List<Manifest.KeyAccess> keyAccess = manifest.encryptionInformation.keyAccessObj;
assertEquals(keyAccess.get(0).keyType, "wrapped");
assertEquals(keyAccess.get(0).protocol, "kas");

assertEquals(Manifest.PolicyBinding.class, keyAccess.get(0).policyBinding.getClass());
var policyBinding = (Manifest.PolicyBinding) keyAccess.get(0).policyBinding;
assertEquals(policyBinding.alg, "HS256");
assertEquals(policyBinding.hash, "YTgzNThhNzc5NWRhMjdjYThlYjk4ZmNmODliNzc2Y2E5ZmZiZDExZDQ3OTM5ODFjZTRjNmE3MmVjOTUzZTFlMA==");
assertEquals(manifest.encryptionInformation.method.algorithm, "AES-256-GCM");
assertEquals(manifest.encryptionInformation.integrityInformation.rootSignature.algorithm, "HS256");
assertEquals(manifest.encryptionInformation.integrityInformation.segmentHashAlg, "GMAC");
assertEquals(manifest.encryptionInformation.integrityInformation.segments.get(0).segmentSize, 1048576);

System.out.println(gson.toJson(manifest));


Manifest.Payload payload = new Manifest.Payload();
payload.protocol = "zip";

Manifest.EncryptionInformation encryptionInformation = manifest.encryptionInformation;
encryptionInformation.policy = "updated policy";

Manifest newManifest = new Manifest();
newManifest.payload = payload;
newManifest.encryptionInformation = encryptionInformation;
var serialized = gson.toJson(manifest);
var deserializedAgain = gson.fromJson(serialized, Manifest.class);

System.out.println(gson.toJson(newManifest));
assertEquals(manifest, deserializedAgain, "something changed when we deserialized -> serialized -> deserialized");
}
}
7 changes: 5 additions & 2 deletions sdk/src/test/java/io/opentdf/platform/sdk/TDFWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class TDFWriterTest {
@Test
void simpleTDFCreate() throws IOException {

String kManifestJsonFromTDF = "{\n" +
String kManifestJsonFromTDF = "{\n" +
" \"encryptionInformation\": {\n" +
" \"integrityInformation\": {\n" +
" \"encryptedSegmentSizeDefault\": 1048604,\n" +
Expand All @@ -36,7 +36,10 @@ void simpleTDFCreate() throws IOException {
" },\n" +
" \"keyAccess\": [\n" +
" {\n" +
" \"policyBinding\": \"YTgzNThhNzc5NWRhMjdjYThlYjk4ZmNmODliNzc2Y2E5ZmZiZDExZDQ3OTM5ODFjZTRjNmE3MmVjOTUzZTFlMA==\",\n" +
" \"policyBinding\": {\n" +
" \"alg\": \"HS256\",\n" +
" \"hash\": \"YTgzNThhNzc5NWRhMjdjYThlYjk4ZmNmODliNzc2Y2E5ZmZiZDExZDQ3OTM5ODFjZTRjNmE3MmVjOTUzZTFlMA==\"\n" +
" },\n" +
" \"protocol\": \"kas\",\n" +
" \"type\": \"wrapped\",\n" +
" \"url\": \"http://localhost:65432/kas\",\n" +
Expand Down

0 comments on commit b10a61e

Please sign in to comment.