Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: policy-binding new structure #95

Merged
merged 19 commits into from
Jul 25, 2024
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
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");
}
}
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
Loading